mirror of
https://github.com/strapi/strapi.git
synced 2025-09-29 02:11:45 +00:00
Merge branch 'develop' into fix/issue_21600
This commit is contained in:
commit
c7c382f99a
5
packages/cli/cloud/src/cloud/command.ts
Normal file
5
packages/cli/cloud/src/cloud/command.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
|
||||||
|
export function defineCloudNamespace(command: Command): Command {
|
||||||
|
return command.command('cloud').description('Manage Strapi Cloud projects');
|
||||||
|
}
|
7
packages/cli/cloud/src/environment/command.ts
Normal file
7
packages/cli/cloud/src/environment/command.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
import { defineCloudNamespace } from '../cloud/command';
|
||||||
|
|
||||||
|
export function createEnvironmentCommand(command: Command): Command {
|
||||||
|
const cloud = defineCloudNamespace(command);
|
||||||
|
return cloud.command('environment').description('Manage environments for a Strapi Cloud project');
|
||||||
|
}
|
66
packages/cli/cloud/src/environment/list/action.ts
Normal file
66
packages/cli/cloud/src/environment/list/action.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import type { CLIContext } from '../../types';
|
||||||
|
import { cloudApiFactory, local, tokenServiceFactory } from '../../services';
|
||||||
|
import { promptLogin } from '../../login/action';
|
||||||
|
import { trackEvent } from '../../utils/analytics';
|
||||||
|
|
||||||
|
async function getProject(ctx: CLIContext) {
|
||||||
|
const { project } = await local.retrieve();
|
||||||
|
if (!project) {
|
||||||
|
ctx.logger.warn(
|
||||||
|
`\nWe couldn't find a valid local project config.\nPlease link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
||||||
|
'link'
|
||||||
|
)} command`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (ctx: CLIContext) => {
|
||||||
|
const { getValidToken } = await tokenServiceFactory(ctx);
|
||||||
|
const token = await getValidToken(ctx, promptLogin);
|
||||||
|
const { logger } = ctx;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await getProject(ctx);
|
||||||
|
if (!project) {
|
||||||
|
ctx.logger.debug(`No valid local project configuration was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudApiService = await cloudApiFactory(ctx, token);
|
||||||
|
const spinner = logger.spinner('Fetching environments...').start();
|
||||||
|
await trackEvent(ctx, cloudApiService, 'willListEnvironment', {
|
||||||
|
projectInternalName: project.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { data: environmentsList },
|
||||||
|
} = await cloudApiService.listEnvironments({ name: project.name });
|
||||||
|
spinner.succeed();
|
||||||
|
logger.log(environmentsList);
|
||||||
|
await trackEvent(ctx, cloudApiService, 'didListEnvironment', {
|
||||||
|
projectInternalName: project.name,
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.response && e.response.status === 404) {
|
||||||
|
spinner.succeed();
|
||||||
|
logger.warn(
|
||||||
|
`\nThe project associated with this folder does not exist in Strapi Cloud. \nPlease link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
||||||
|
'link'
|
||||||
|
)} command`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
spinner.fail('An error occurred while fetching environments data from Strapi Cloud.');
|
||||||
|
logger.debug('Failed to list environments', e);
|
||||||
|
}
|
||||||
|
await trackEvent(ctx, cloudApiService, 'didNotListEnvironment', {
|
||||||
|
projectInternalName: project.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
25
packages/cli/cloud/src/environment/list/command.ts
Normal file
25
packages/cli/cloud/src/environment/list/command.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { type StrapiCloudCommand } from '../../types';
|
||||||
|
import { runAction } from '../../utils/helpers';
|
||||||
|
import action from './action';
|
||||||
|
import { defineCloudNamespace } from '../../cloud/command';
|
||||||
|
|
||||||
|
const command: StrapiCloudCommand = ({ command, ctx }) => {
|
||||||
|
const cloud = defineCloudNamespace(command);
|
||||||
|
|
||||||
|
cloud
|
||||||
|
.command('environments')
|
||||||
|
.description('Alias for cloud environment list')
|
||||||
|
.action(() => runAction('list', action)(ctx));
|
||||||
|
|
||||||
|
const environment = cloud
|
||||||
|
.command('environment')
|
||||||
|
.description('Manage environments for a Strapi Cloud project');
|
||||||
|
environment
|
||||||
|
.command('list')
|
||||||
|
.description('List Strapi Cloud project environments')
|
||||||
|
.option('-d, --debug', 'Enable debugging mode with verbose logs')
|
||||||
|
.option('-s, --silent', "Don't log anything")
|
||||||
|
.action(() => runAction('list', action)(ctx));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default command;
|
12
packages/cli/cloud/src/environment/list/index.ts
Normal file
12
packages/cli/cloud/src/environment/list/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import action from './action';
|
||||||
|
import command from './command';
|
||||||
|
import type { StrapiCloudCommandInfo } from '../../types';
|
||||||
|
|
||||||
|
export { action, command };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'list-environments',
|
||||||
|
description: 'List Strapi Cloud environments',
|
||||||
|
action,
|
||||||
|
command,
|
||||||
|
} as StrapiCloudCommandInfo;
|
@ -6,6 +6,7 @@ import login from './login';
|
|||||||
import logout from './logout';
|
import logout from './logout';
|
||||||
import createProject from './create-project';
|
import createProject from './create-project';
|
||||||
import listProjects from './list-projects';
|
import listProjects from './list-projects';
|
||||||
|
import listEnvironments from './environment/list';
|
||||||
import { CLIContext } from './types';
|
import { CLIContext } from './types';
|
||||||
import { getLocalConfig, saveLocalConfig } from './config/local';
|
import { getLocalConfig, saveLocalConfig } from './config/local';
|
||||||
|
|
||||||
@ -16,9 +17,10 @@ export const cli = {
|
|||||||
logout,
|
logout,
|
||||||
createProject,
|
createProject,
|
||||||
listProjects,
|
listProjects,
|
||||||
|
listEnvironments,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloudCommands = [deployProject, link, login, logout, listProjects];
|
const cloudCommands = [deployProject, link, login, logout, listProjects, listEnvironments];
|
||||||
|
|
||||||
async function initCloudCLIConfig() {
|
async function initCloudCLIConfig() {
|
||||||
const localConfig = await getLocalConfig();
|
const localConfig = await getLocalConfig();
|
||||||
|
@ -19,6 +19,8 @@ export type ProjectInfos = {
|
|||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EnvironmentInfo = Record<string, unknown>;
|
||||||
|
|
||||||
export type ProjectInput = Omit<ProjectInfos, 'id'>;
|
export type ProjectInput = Omit<ProjectInfos, 'id'>;
|
||||||
|
|
||||||
export type DeployResponse = {
|
export type DeployResponse = {
|
||||||
@ -32,6 +34,12 @@ export type ListProjectsResponse = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ListEnvironmentsResponse = {
|
||||||
|
data: {
|
||||||
|
data: EnvironmentInfo[] | Record<string, never>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type ListLinkProjectsResponse = {
|
export type ListLinkProjectsResponse = {
|
||||||
data: {
|
data: {
|
||||||
data: ProjectInfos[] | Record<string, never>;
|
data: ProjectInfos[] | Record<string, never>;
|
||||||
@ -78,6 +86,8 @@ export interface CloudApiService {
|
|||||||
|
|
||||||
listLinkProjects(): Promise<AxiosResponse<ListLinkProjectsResponse>>;
|
listLinkProjects(): Promise<AxiosResponse<ListLinkProjectsResponse>>;
|
||||||
|
|
||||||
|
listEnvironments(project: { name: string }): Promise<AxiosResponse<ListEnvironmentsResponse>>;
|
||||||
|
|
||||||
getProject(project: { name: string }): Promise<AxiosResponse<GetProjectResponse>>;
|
getProject(project: { name: string }): Promise<AxiosResponse<GetProjectResponse>>;
|
||||||
|
|
||||||
track(event: string, payload?: TrackPayload): Promise<AxiosResponse<void>>;
|
track(event: string, payload?: TrackPayload): Promise<AxiosResponse<void>>;
|
||||||
@ -197,6 +207,23 @@ export async function cloudApiFactory(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async listEnvironments({ name }): Promise<AxiosResponse<ListEnvironmentsResponse>> {
|
||||||
|
try {
|
||||||
|
const response = await axiosCloudAPI.get(`/projects/${name}/environments`);
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Error fetching cloud environments from the server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(
|
||||||
|
"🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async getProject({ name }): Promise<AxiosResponse<GetProjectResponse>> {
|
async getProject({ name }): Promise<AxiosResponse<GetProjectResponse>> {
|
||||||
try {
|
try {
|
||||||
const response = await axiosCloudAPI.get(`/projects/${name}`);
|
const response = await axiosCloudAPI.get(`/projects/${name}`);
|
||||||
|
@ -39,6 +39,10 @@ export type StrapiCloudCommand = (params: {
|
|||||||
ctx: CLIContext;
|
ctx: CLIContext;
|
||||||
}) => void | Command | Promise<Command | void>;
|
}) => void | Command | Promise<Command | void>;
|
||||||
|
|
||||||
|
export type StrapiCloudNamespaceCommand = (params: {
|
||||||
|
command: Command;
|
||||||
|
}) => void | Command | Promise<Command | void>;
|
||||||
|
|
||||||
export type StrapiCloudCommandInfo = {
|
export type StrapiCloudCommandInfo = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -66,7 +66,10 @@ const NpmPackageCard = ({
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
const versionRange = semver.validRange(attributes.strapiVersion);
|
const versionRange = semver.validRange(attributes.strapiVersion);
|
||||||
const isCompatible = semver.satisfies(strapiAppVersion ?? '', versionRange ?? '');
|
|
||||||
|
const isCompatible = versionRange
|
||||||
|
? semver.satisfies(strapiAppVersion ?? '', versionRange)
|
||||||
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -272,28 +275,17 @@ const CardButton = ({
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
data-testid={`tooltip-${pluginName}`}
|
data-testid={`tooltip-${pluginName}`}
|
||||||
label={
|
label={formatMessage(
|
||||||
!versionRange
|
{
|
||||||
? formatMessage(
|
id: 'admin.pages.MarketPlacePage.plugin.version',
|
||||||
{
|
defaultMessage:
|
||||||
id: 'admin.pages.MarketPlacePage.plugin.version.null',
|
'Update your Strapi version: "{strapiAppVersion}" to: "{versionRange}"',
|
||||||
defaultMessage:
|
},
|
||||||
'Unable to verify compatibility with your Strapi version: "{strapiAppVersion}"',
|
{
|
||||||
},
|
strapiAppVersion,
|
||||||
{ strapiAppVersion }
|
versionRange,
|
||||||
)
|
}
|
||||||
: formatMessage(
|
)}
|
||||||
{
|
|
||||||
id: 'admin.pages.MarketPlacePage.plugin.version',
|
|
||||||
defaultMessage:
|
|
||||||
'Update your Strapi version: "{strapiAppVersion}" to: "{versionRange}"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
strapiAppVersion,
|
|
||||||
versionRange,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<Button
|
<Button
|
||||||
|
@ -62,25 +62,17 @@ describe('Marketplace page - layout', () => {
|
|||||||
expect(button).not.toBeInTheDocument();
|
expect(button).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows compatibility tooltip message when no version provided', async () => {
|
it('Does not show (copy install command) button', async () => {
|
||||||
const { findByTestId, findAllByTestId, user } = render();
|
const { findAllByTestId } = render();
|
||||||
|
|
||||||
const alreadyInstalledCard = (await findAllByTestId('npm-package-card')).find((div) =>
|
const alreadyInstalledCard = (await findAllByTestId('npm-package-card')).find((div) =>
|
||||||
div.innerHTML.includes('Config Sync')
|
div.innerHTML.includes('Config Sync')
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
const button = within(alreadyInstalledCard)
|
const button = within(alreadyInstalledCard).queryByText(/copy install command/i);
|
||||||
.getByText(/copy install command/i)
|
|
||||||
.closest('button')!;
|
|
||||||
|
|
||||||
await user.hover(button);
|
// Assert that the button does not show
|
||||||
const tooltip = await findByTestId(`tooltip-Config Sync`);
|
expect(button).not.toBeInTheDocument();
|
||||||
|
|
||||||
expect(button).toBeEnabled();
|
|
||||||
expect(tooltip).toBeInTheDocument();
|
|
||||||
expect(tooltip).toHaveTextContent(
|
|
||||||
'Unable to verify compatibility with your Strapi version: "4.1.0"'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles production environment', async () => {
|
it('handles production environment', async () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user