mirror of
https://github.com/strapi/strapi.git
synced 2025-08-15 12:18:38 +00:00
chore: backmerge develop into v5 main (#20678)
* Fix typo of query key from 'providers' to 'get-providers' * Closing #19644 fix filters type * fix patreon oauth 400 error from lack of user-agent header * test: backport cli tests (#20433) * fix(chore): increase specificity of the Lightning icon color (#20467) * test: temporarily disable broken edit ctb tests on CI (#20481) * feat(cli): cloud cli commands (v4) (#20119) * feat(cli): add cloud commands Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com> Co-authored-by: nathan-pichon <nathan.pichon@strapi.io> Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com> * v4.25.0 (#20500) * NPS: Update frequency of the NPS (#20492) * enhancement(admin): change postFirstDismissal and display delays * enhancement(admin): change the display delay * fix: prevent use of local ips on webhooks (#20487) * chore: mask error on webhook manual trigger * feat: prevent using local ips * feat: display webhook edit error * chore: pr suggestion * chore: api tests * chore: allow local ips on development * chore: only run check on production * feat: include internationalized urls * fix: prettier * Add: Strapi deploy command to README files * v4.25.1 * Update @aws-sdk/client-s3 package * feat(cli): add browser logout step (#20502) * feat(cli): add browser log out step * handle error whiile fetching the config * Update LICENSE packing for packages for v4 (#20576) * feat(create-strapi-app): remove the cloud project creation part (#20561) * feat(create-strapi-app): remove the cloud project creation part * fix(create-strapi-app): adding new parameter to cloud service instantiation * Update Yarn to 4.3.1 Signed-off-by: Sora Morimoto <sora@morimoto.io> * feat(cli): trigger login sequence when token is missing or invalid (#20572) * feat(cli): launch login when auth fails * fix(cli): abstract create project fn * fix(cli): guidelines * fix(cli): rebase --------- Co-authored-by: Gonzalo Andres Garcia <nouvellegon@gmail.com> * fix clone entity, #20509 (#20531) * chore: bump glob from 7.2.0 to 9.0.0 * chore: bump glob to v10.4.2 in core/strapi * update yarn.lock * removed redundant packages/core/strapi/src/load/glob.ts file * v4.25.2 (#20675) * fix: support string array * v4.25.2 --------- Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com> * Merge branch 'develop' into v5/main * fix: ts errors * fix: typescript issues * fix: remove unused import * Update packages/cli/create-strapi-app/src/create-strapi-app.ts Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/cli/create-strapi-app/src/create-strapi-app.ts Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/generators/app/src/resources/files/js/README.md Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/generators/app/src/resources/files/ts/README.md Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * feat(cloud-cli): enable cloud-cli --------- Signed-off-by: Sora Morimoto <sora@morimoto.io> Co-authored-by: smoothdvd <madfxgao@gmail.com> Co-authored-by: Micah Riggan <micahriggan@gmail.com> Co-authored-by: Tewson Seeoun <tewson.seeoun@gmail.com> Co-authored-by: Ben Irvin <ben.irvin@strapi.io> Co-authored-by: Simone <startae14@gmail.com> Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com> Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com> Co-authored-by: Maxime Castres <mcastres@student.42.fr> Co-authored-by: Convly <jean-sebastien.herbaux@epitech.eu> Co-authored-by: Alex Supkay <asupkay1124@gmail.com> Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com> Co-authored-by: Sora Morimoto <sora@morimoto.io> Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Kirill Verevkin <kira795@yandex.ru> Co-authored-by: chrismuiruriz <chrismuiruri007@gmail.com>
This commit is contained in:
parent
ef0648bf9b
commit
c3f078328f
893
.yarn/releases/yarn-4.0.1.cjs
vendored
893
.yarn/releases/yarn-4.0.1.cjs
vendored
File diff suppressed because one or more lines are too long
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -1,11 +1,7 @@
|
|||||||
compressionLevel: mixed
|
|
||||||
|
|
||||||
defaultSemverRangePrefix: ''
|
defaultSemverRangePrefix: ''
|
||||||
|
|
||||||
enableGlobalCache: true
|
|
||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
preferInteractive: true
|
preferInteractive: true
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.0.1.cjs
|
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||||
|
@ -153,7 +153,7 @@
|
|||||||
"yalc": "1.0.0-pre.53",
|
"yalc": "1.0.0-pre.53",
|
||||||
"yargs": "17.7.2"
|
"yargs": "17.7.2"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.0.1",
|
"packageManager": "yarn@4.3.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <=20.x.x",
|
"node": ">=18.0.0 <=20.x.x",
|
||||||
"npm": ">=6.0.0"
|
"npm": ">=6.0.0"
|
||||||
|
@ -3,19 +3,14 @@ import { AxiosError } from 'axios';
|
|||||||
import { defaults } from 'lodash/fp';
|
import { defaults } from 'lodash/fp';
|
||||||
import type { CLIContext, ProjectAnswers, ProjectInput } from '../types';
|
import type { CLIContext, ProjectAnswers, ProjectInput } from '../types';
|
||||||
import { tokenServiceFactory, cloudApiFactory, local } from '../services';
|
import { tokenServiceFactory, cloudApiFactory, local } from '../services';
|
||||||
|
import { promptLogin } from '../login/action';
|
||||||
|
|
||||||
async function handleError(ctx: CLIContext, error: Error) {
|
async function handleError(ctx: CLIContext, error: Error) {
|
||||||
const tokenService = await tokenServiceFactory(ctx);
|
|
||||||
const { logger } = ctx;
|
const { logger } = ctx;
|
||||||
|
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
const errorMessage = typeof error.response?.data === 'string' ? error.response.data : null;
|
const errorMessage = typeof error.response?.data === 'string' ? error.response.data : null;
|
||||||
switch (error.response?.status) {
|
switch (error.response?.status) {
|
||||||
case 401:
|
|
||||||
logger.error('Your session has expired. Please log in again.');
|
|
||||||
await tokenService.eraseToken();
|
|
||||||
return;
|
|
||||||
case 403:
|
case 403:
|
||||||
logger.error(
|
logger.error(
|
||||||
errorMessage ||
|
errorMessage ||
|
||||||
@ -43,15 +38,30 @@ async function handleError(ctx: CLIContext, error: Error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createProject(ctx: CLIContext, cloudApi: any, projectInput: ProjectInput) {
|
||||||
|
const { logger } = ctx;
|
||||||
|
const spinner = logger.spinner('Setting up your project...').start();
|
||||||
|
try {
|
||||||
|
const { data } = await cloudApi.createProject(projectInput);
|
||||||
|
await local.save({ project: data });
|
||||||
|
spinner.succeed('Project created successfully!');
|
||||||
|
return data;
|
||||||
|
} catch (e: Error | unknown) {
|
||||||
|
spinner.fail('An error occurred while creating the project on Strapi Cloud.');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async (ctx: CLIContext) => {
|
export default async (ctx: CLIContext) => {
|
||||||
const { logger } = ctx;
|
const { logger } = ctx;
|
||||||
const { getValidToken } = await tokenServiceFactory(ctx);
|
const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
|
||||||
|
|
||||||
const token = await getValidToken();
|
const token = await getValidToken(ctx, promptLogin);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cloudApi = await cloudApiFactory(token);
|
|
||||||
|
const cloudApi = await cloudApiFactory(ctx, token);
|
||||||
const { data: config } = await cloudApi.config();
|
const { data: config } = await cloudApi.config();
|
||||||
const { questions, defaults: defaultValues } = config.projectCreation;
|
const { questions, defaults: defaultValues } = config.projectCreation;
|
||||||
|
|
||||||
@ -60,14 +70,17 @@ export default async (ctx: CLIContext) => {
|
|||||||
|
|
||||||
const projectInput: ProjectInput = projectAnswersDefaulted(projectAnswers);
|
const projectInput: ProjectInput = projectAnswersDefaulted(projectAnswers);
|
||||||
|
|
||||||
const spinner = logger.spinner('Setting up your project...').start();
|
|
||||||
try {
|
try {
|
||||||
const { data } = await cloudApi.createProject(projectInput);
|
return await createProject(ctx, cloudApi, projectInput);
|
||||||
await local.save({ project: data });
|
|
||||||
spinner.succeed('Project created successfully!');
|
|
||||||
return data;
|
|
||||||
} catch (e: Error | unknown) {
|
} catch (e: Error | unknown) {
|
||||||
spinner.fail('Failed to create project on Strapi Cloud.');
|
if (e instanceof AxiosError && e.response?.status === 401) {
|
||||||
await handleError(ctx, e as Error);
|
logger.warn('Oops! Your session has expired. Please log in again to retry.');
|
||||||
|
await eraseToken();
|
||||||
|
if (await promptLogin(ctx)) {
|
||||||
|
return await createProject(ctx, cloudApi, projectInput);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await handleError(ctx, e as Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,7 @@ import { cloudApiFactory, tokenServiceFactory, local } from '../services';
|
|||||||
import { notificationServiceFactory } from '../services/notification';
|
import { notificationServiceFactory } from '../services/notification';
|
||||||
import { loadPkg } from '../utils/pkg';
|
import { loadPkg } from '../utils/pkg';
|
||||||
import { buildLogsServiceFactory } from '../services/build-logs';
|
import { buildLogsServiceFactory } from '../services/build-logs';
|
||||||
|
import { promptLogin } from '../login/action';
|
||||||
|
|
||||||
type PackageJson = {
|
type PackageJson = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -26,7 +27,7 @@ async function upload(
|
|||||||
token: string,
|
token: string,
|
||||||
maxProjectFileSize: number
|
maxProjectFileSize: number
|
||||||
) {
|
) {
|
||||||
const cloudApi = await cloudApiFactory(token);
|
const cloudApi = await cloudApiFactory(ctx, token);
|
||||||
// * Upload project
|
// * Upload project
|
||||||
try {
|
try {
|
||||||
const storagePath = await getTmpStoragePath();
|
const storagePath = await getTmpStoragePath();
|
||||||
@ -138,19 +139,17 @@ async function getProject(ctx: CLIContext) {
|
|||||||
|
|
||||||
export default async (ctx: CLIContext) => {
|
export default async (ctx: CLIContext) => {
|
||||||
const { getValidToken } = await tokenServiceFactory(ctx);
|
const { getValidToken } = await tokenServiceFactory(ctx);
|
||||||
const cloudApiService = await cloudApiFactory();
|
const token = await getValidToken(ctx, promptLogin);
|
||||||
const token = await getValidToken();
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await getProject(ctx);
|
const project = await getProject(ctx);
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cloudApiService = await cloudApiFactory(ctx);
|
||||||
try {
|
try {
|
||||||
await cloudApiService.track('willDeployWithCLI', { projectInternalName: project.name });
|
await cloudApiService.track('willDeployWithCLI', { projectInternalName: project.name });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,16 +1,33 @@
|
|||||||
import axios, { AxiosResponse, AxiosError } from 'axios';
|
import axios, { AxiosResponse, AxiosError } from 'axios';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import inquirer from 'inquirer';
|
||||||
import { tokenServiceFactory, cloudApiFactory } from '../services';
|
import { tokenServiceFactory, cloudApiFactory } from '../services';
|
||||||
import type { CloudCliConfig, CLIContext } from '../types';
|
import type { CloudCliConfig, CLIContext } from '../types';
|
||||||
import { apiConfig } from '../config/api';
|
import { apiConfig } from '../config/api';
|
||||||
|
|
||||||
const openModule = import('open');
|
const openModule = import('open');
|
||||||
|
|
||||||
export default async (ctx: CLIContext): Promise<boolean> => {
|
export async function promptLogin(ctx: CLIContext) {
|
||||||
|
const response = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'login',
|
||||||
|
message: 'Would you like to login?',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (response.login) {
|
||||||
|
const loginSuccessful = await loginAction(ctx);
|
||||||
|
return loginSuccessful;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function loginAction(ctx: CLIContext): Promise<boolean> {
|
||||||
const { logger } = ctx;
|
const { logger } = ctx;
|
||||||
const tokenService = await tokenServiceFactory(ctx);
|
const tokenService = await tokenServiceFactory(ctx);
|
||||||
const existingToken = await tokenService.retrieveToken();
|
const existingToken = await tokenService.retrieveToken();
|
||||||
const cloudApiService = await cloudApiFactory(existingToken || undefined);
|
const cloudApiService = await cloudApiFactory(ctx, existingToken || undefined);
|
||||||
|
|
||||||
const trackFailedLogin = async () => {
|
const trackFailedLogin = async () => {
|
||||||
try {
|
try {
|
||||||
@ -38,7 +55,6 @@ export default async (ctx: CLIContext): Promise<boolean> => {
|
|||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Failed to fetch user info', e);
|
logger.debug('Failed to fetch user info', e);
|
||||||
// If the token is invalid and request failed, we should proceed with the login process
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,7 +142,7 @@ export default async (ctx: CLIContext): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('🔍 Fetching user information...');
|
logger.debug('🔍 Fetching user information...');
|
||||||
const cloudApiServiceWithToken = await cloudApiFactory(authTokenData.access_token);
|
const cloudApiServiceWithToken = await cloudApiFactory(ctx, authTokenData.access_token);
|
||||||
// Call to get user info to create the user in DB if not exists
|
// Call to get user info to create the user in DB if not exists
|
||||||
await cloudApiServiceWithToken.getUserInfo();
|
await cloudApiServiceWithToken.getUserInfo();
|
||||||
logger.debug('🔍 User information fetched successfully!');
|
logger.debug('🔍 User information fetched successfully!');
|
||||||
@ -184,4 +200,4 @@ export default async (ctx: CLIContext): Promise<boolean> => {
|
|||||||
|
|
||||||
await authenticate();
|
await authenticate();
|
||||||
return isAuthenticated;
|
return isAuthenticated;
|
||||||
};
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { CLIContext } from '../types';
|
import type { CLIContext } from '../types';
|
||||||
import { tokenServiceFactory, cloudApiFactory } from '../services';
|
import { tokenServiceFactory, cloudApiFactory } from '../services';
|
||||||
|
|
||||||
|
const openModule = import('open');
|
||||||
|
|
||||||
export default async (ctx: CLIContext) => {
|
export default async (ctx: CLIContext) => {
|
||||||
const { logger } = ctx;
|
const { logger } = ctx;
|
||||||
const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
|
const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
|
||||||
@ -10,10 +12,27 @@ export default async (ctx: CLIContext) => {
|
|||||||
logger.log("You're already logged out.");
|
logger.log("You're already logged out.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cloudApiService = await cloudApiFactory(token);
|
const cloudApiService = await cloudApiFactory(ctx, token);
|
||||||
|
const config = await cloudApiService.config();
|
||||||
|
const cliConfig = config.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// we might want also to perform extra actions like logging out from the auth0 tenant
|
|
||||||
await eraseToken();
|
await eraseToken();
|
||||||
|
|
||||||
|
openModule.then((open) => {
|
||||||
|
open
|
||||||
|
.default(
|
||||||
|
`${cliConfig.baseUrl}/oidc/logout?client_id=${encodeURIComponent(
|
||||||
|
cliConfig.clientId
|
||||||
|
)}&logout_hint=${encodeURIComponent(token)}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.catch((e: Error) => {
|
||||||
|
// Failing to open the logout URL is not a critical error, so we just log it
|
||||||
|
logger.debug(e.message, e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
'🔌 You have been logged out from the CLI. If you are on a shared computer, please make sure to log out from the Strapi Cloud Dashboard as well.'
|
'🔌 You have been logged out from the CLI. If you are on a shared computer, please make sure to log out from the Strapi Cloud Dashboard as well.'
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import axios, { type AxiosResponse } from 'axios';
|
|||||||
import fse from 'fs-extra';
|
import fse from 'fs-extra';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { apiConfig } from '../config/api';
|
import { apiConfig } from '../config/api';
|
||||||
import type { CloudCliConfig } from '../types';
|
import type { CLIContext, CloudCliConfig } from '../types';
|
||||||
import { getLocalConfig } from '../config/local';
|
import { getLocalConfig } from '../config/local';
|
||||||
|
|
||||||
import packageJson from '../../package.json';
|
import packageJson from '../../package.json';
|
||||||
@ -52,7 +52,10 @@ export interface CloudApiService {
|
|||||||
track(event: string, payload?: TrackPayload): Promise<AxiosResponse<void>>;
|
track(event: string, payload?: TrackPayload): Promise<AxiosResponse<void>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cloudApiFactory(token?: string): Promise<CloudApiService> {
|
export async function cloudApiFactory(
|
||||||
|
{ logger }: { logger: CLIContext['logger'] },
|
||||||
|
token?: string
|
||||||
|
): Promise<CloudApiService> {
|
||||||
const localConfig = await getLocalConfig();
|
const localConfig = await getLocalConfig();
|
||||||
const customHeaders = {
|
const customHeaders = {
|
||||||
'x-device-id': localConfig.deviceId,
|
'x-device-id': localConfig.deviceId,
|
||||||
@ -111,8 +114,22 @@ export async function cloudApiFactory(token?: string): Promise<CloudApiService>
|
|||||||
return axiosCloudAPI.get('/user');
|
return axiosCloudAPI.get('/user');
|
||||||
},
|
},
|
||||||
|
|
||||||
config(): Promise<AxiosResponse<CloudCliConfig>> {
|
async config(): Promise<AxiosResponse<CloudCliConfig>> {
|
||||||
return axiosCloudAPI.get('/config');
|
try {
|
||||||
|
const response = await axiosCloudAPI.get('/config');
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Error fetching cloud CLI config from the server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(
|
||||||
|
"🥲 Oops! Couldn't retrieve the cloud CLI config from the server. Please try again."
|
||||||
|
);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
listProjects() {
|
listProjects() {
|
||||||
|
@ -12,7 +12,7 @@ interface DecodedToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function tokenServiceFactory({ logger }: { logger: CLIContext['logger'] }) {
|
export async function tokenServiceFactory({ logger }: { logger: CLIContext['logger'] }) {
|
||||||
const cloudApiService = await cloudApiFactory();
|
const cloudApiService = await cloudApiFactory({ logger });
|
||||||
|
|
||||||
async function saveToken(str: string) {
|
async function saveToken(str: string) {
|
||||||
const appConfig = await getLocalConfig();
|
const appConfig = await getLocalConfig();
|
||||||
@ -62,7 +62,6 @@ export async function tokenServiceFactory({ logger }: { logger: CLIContext['logg
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decode the JWT token to get the header and payload
|
|
||||||
const decodedToken = jwt.decode(idToken, { complete: true }) as DecodedToken;
|
const decodedToken = jwt.decode(idToken, { complete: true }) as DecodedToken;
|
||||||
if (!decodedToken) {
|
if (!decodedToken) {
|
||||||
if (typeof idToken === 'undefined' || idToken === '') {
|
if (typeof idToken === 'undefined' || idToken === '') {
|
||||||
@ -72,6 +71,7 @@ export async function tokenServiceFactory({ logger }: { logger: CLIContext['logg
|
|||||||
'There seems to be a problem with your login information. Please try logging in again.'
|
'There seems to be a problem with your login information. Please try logging in again.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return Promise.reject(new Error('Invalid token'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the JWT token signature using the JWKS Key
|
// Verify the JWT token signature using the JWKS Key
|
||||||
@ -79,9 +79,11 @@ export async function tokenServiceFactory({ logger }: { logger: CLIContext['logg
|
|||||||
jwt.verify(idToken, getKey, (err: VerifyErrors | null) => {
|
jwt.verify(idToken, getKey, (err: VerifyErrors | null) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
}
|
||||||
|
if (decodedToken.payload.exp < Math.floor(Date.now() / 1000)) {
|
||||||
|
reject(new Error('Token is expired'));
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -121,17 +123,22 @@ export async function tokenServiceFactory({ logger }: { logger: CLIContext['logg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getValidToken() {
|
async function getValidToken(
|
||||||
const token = await retrieveToken();
|
ctx: CLIContext,
|
||||||
if (!token) {
|
loginAction: (ctx: CLIContext) => Promise<boolean>
|
||||||
logger.log('No token found. Please login first.');
|
) {
|
||||||
return null;
|
let token = await retrieveToken();
|
||||||
|
|
||||||
|
while (!token || !(await isTokenValid(token))) {
|
||||||
|
logger.log(
|
||||||
|
token
|
||||||
|
? 'Oops! Your token seems expired or invalid. Please login again.'
|
||||||
|
: "We couldn't find a valid token. You need to be logged in to use this feature."
|
||||||
|
);
|
||||||
|
if (!(await loginAction(ctx))) return null;
|
||||||
|
token = await retrieveToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await isTokenValid(token))) {
|
|
||||||
logger.log('Unable to proceed: Token is expired or not valid. Please login again.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import { resolve } from 'node:path';
|
|
||||||
import { cli as cloudCli, services as cloudServices } from '@strapi/cloud-cli';
|
import { cli as cloudCli, services as cloudServices } from '@strapi/cloud-cli';
|
||||||
import parseToChalk from './utils/parse-to-chalk';
|
import parseToChalk from './utils/parse-to-chalk';
|
||||||
|
|
||||||
@ -16,13 +15,13 @@ function assertCloudError(e: unknown): asserts e is CloudError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCloudProject(projectName: string): Promise<void> {
|
export async function handleCloudLogin(): Promise<void> {
|
||||||
const logger = cloudServices.createLogger({
|
const logger = cloudServices.createLogger({
|
||||||
silent: false,
|
silent: false,
|
||||||
debug: process.argv.includes('--debug'),
|
debug: process.argv.includes('--debug'),
|
||||||
timestamp: false,
|
timestamp: false,
|
||||||
});
|
});
|
||||||
let cloudApiService = await cloudServices.cloudApiFactory();
|
const cloudApiService = await cloudServices.cloudApiFactory({ logger });
|
||||||
const defaultErrorMessage =
|
const defaultErrorMessage =
|
||||||
'An error occurred while trying to interact with Strapi Cloud. Use strapi deploy command once the project is generated.';
|
'An error occurred while trying to interact with Strapi Cloud. Use strapi deploy command once the project is generated.';
|
||||||
|
|
||||||
@ -48,36 +47,9 @@ export async function handleCloudProject(projectName: string): Promise<void> {
|
|||||||
logger,
|
logger,
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
};
|
};
|
||||||
const projectCreationSpinner = logger.spinner('Creating project on Strapi Cloud');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tokenService = await cloudServices.tokenServiceFactory(cliContext);
|
await cloudCli.login.action(cliContext);
|
||||||
const loginSuccess = await cloudCli.login.action(cliContext);
|
|
||||||
if (!loginSuccess) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug('Retrieving token');
|
|
||||||
const token = await tokenService.retrieveToken();
|
|
||||||
|
|
||||||
cloudApiService = await cloudServices.cloudApiFactory(token);
|
|
||||||
|
|
||||||
logger.debug('Retrieving config');
|
|
||||||
const { data: config } = await cloudApiService.config();
|
|
||||||
logger.debug('config', config);
|
|
||||||
const defaultProjectValues = config.projectCreation?.defaults || {};
|
|
||||||
logger.debug('default project values', defaultProjectValues);
|
|
||||||
projectCreationSpinner.start();
|
|
||||||
const { data: project } = await cloudApiService.createProject({
|
|
||||||
nodeVersion: process.versions?.node?.slice(1, 3) || '20',
|
|
||||||
region: 'NYC',
|
|
||||||
plan: 'trial',
|
|
||||||
...defaultProjectValues,
|
|
||||||
name: projectName,
|
|
||||||
});
|
|
||||||
projectCreationSpinner.succeed('Project created on Strapi Cloud');
|
|
||||||
const projectPath = resolve(projectName);
|
|
||||||
logger.debug(project, projectPath);
|
|
||||||
await cloudServices.local.save({ project }, { directoryPath: projectPath });
|
|
||||||
} catch (e: Error | CloudError | unknown) {
|
} catch (e: Error | CloudError | unknown) {
|
||||||
logger.debug(e);
|
logger.debug(e);
|
||||||
try {
|
try {
|
||||||
@ -86,22 +58,14 @@ export async function handleCloudProject(projectName: string): Promise<void> {
|
|||||||
const message =
|
const message =
|
||||||
typeof e.response.data === 'string'
|
typeof e.response.data === 'string'
|
||||||
? e.response.data
|
? e.response.data
|
||||||
: 'We are sorry, but we are not able to create a Strapi Cloud project for you at the moment.';
|
: 'We are sorry, but we are not able to log you into Strapi Cloud at the moment.';
|
||||||
if (projectCreationSpinner.isSpinning) {
|
logger.warn(message);
|
||||||
projectCreationSpinner.fail(message);
|
|
||||||
} else {
|
|
||||||
logger.warn(message);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
if (projectCreationSpinner.isSpinning) {
|
logger.error(defaultErrorMessage);
|
||||||
projectCreationSpinner.fail(defaultErrorMessage);
|
|
||||||
} else {
|
|
||||||
logger.error(defaultErrorMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import * as prompts from './prompts';
|
|||||||
import type { Options } from './types';
|
import type { Options } from './types';
|
||||||
import { detectPackageManager } from './package-manager';
|
import { detectPackageManager } from './package-manager';
|
||||||
import * as database from './database';
|
import * as database from './database';
|
||||||
// import { handleCloudProject } from './cloud';
|
import { handleCloudLogin } from './cloud';
|
||||||
|
|
||||||
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
|
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
|
||||||
|
|
||||||
@ -30,9 +30,10 @@ command
|
|||||||
.option('--use-yarn', 'Use yarn as the project package manager')
|
.option('--use-yarn', 'Use yarn as the project package manager')
|
||||||
.option('--use-pnpm', 'Use pnpm as the project package manager')
|
.option('--use-pnpm', 'Use pnpm as the project package manager')
|
||||||
|
|
||||||
|
// Cloud options
|
||||||
|
.option('--skip-cloud', 'Skip cloud login and project creation')
|
||||||
|
|
||||||
// Database options
|
// Database options
|
||||||
// TODO V5: Uncomment when cloud-cli is ready
|
|
||||||
// .option('--skip-cloud', 'Skip cloud login and project creation')
|
|
||||||
.option('--dbclient <dbclient>', 'Database client')
|
.option('--dbclient <dbclient>', 'Database client')
|
||||||
.option('--dbhost <dbhost>', 'Database host')
|
.option('--dbhost <dbhost>', 'Database host')
|
||||||
.option('--dbport <dbport>', 'Database port')
|
.option('--dbport <dbport>', 'Database port')
|
||||||
@ -60,11 +61,9 @@ async function createStrapiApp(directory: string | undefined, options: Options)
|
|||||||
|
|
||||||
const appDirectory = directory || (await prompts.directory());
|
const appDirectory = directory || (await prompts.directory());
|
||||||
|
|
||||||
// TODO V5: Uncomment when cloud-cli is ready
|
if (!options.skipCloud) {
|
||||||
// if (!options.skipCloud) {
|
await handleCloudLogin();
|
||||||
// checkRequirements();
|
}
|
||||||
// await handleCloudProject(projectName);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const appOptions = {
|
const appOptions = {
|
||||||
directory: appDirectory,
|
directory: appDirectory,
|
||||||
|
@ -2,7 +2,21 @@ Copyright (c) 2015-present Strapi Solutions SAS
|
|||||||
|
|
||||||
Portions of the Strapi software are licensed as follows:
|
Portions of the Strapi software are licensed as follows:
|
||||||
|
|
||||||
* All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined in "ee/LICENSE".
|
* All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined below.
|
||||||
|
|
||||||
|
Enterprise License
|
||||||
|
|
||||||
|
If you or the company you represent has entered into a written agreement referencing the Enterprise Edition of the Strapi source code available at
|
||||||
|
https://github.com/strapi/strapi, then such agreement applies to your use of the Enterprise Edition of the Strapi Software. If you or the company you
|
||||||
|
represent is using the Enterprise Edition of the Strapi Software in connection with a subscription to our cloud offering, then the agreement you have
|
||||||
|
agreed to with respect to our cloud offering and the licenses included in such agreement apply to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
Otherwise, the Strapi Enterprise Software License Agreement (found here https://strapi.io/enterprise-terms) applies to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
|
||||||
|
BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.
|
||||||
|
IF YOU ARE NOT AUTHORIZED TO ACCEPT THESE TERMS ON BEHALF OF THE COMPANY YOU REPRESENT OR IF YOU DO NOT AGREE TO ALL OF THE RELEVANT TERMS AND CONDITIONS REFERENCED AND YOU
|
||||||
|
HAVE NOT OTHERWISE EXECUTED A WRITTEN AGREEMENT WITH STRAPI, YOU ARE NOT AUTHORIZED TO ACCESS OR USE OR ALLOW ANY USER TO ACCESS OR USE ANY PART OF
|
||||||
|
THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE. YOUR ACCESS RIGHTS ARE CONDITIONAL ON YOUR CONSENT TO THE RELEVANT REFERENCED TERMS TO THE EXCLUSION OF ALL OTHER TERMS;
|
||||||
|
IF THE RELEVANT REFERENCED TERMS ARE CONSIDERED AN OFFER BY YOU, ACCEPTANCE IS EXPRESSLY LIMITED TO THE RELEVANT REFERENCED TERMS.
|
||||||
|
|
||||||
* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below.
|
* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below.
|
||||||
|
|
||||||
@ -18,5 +32,6 @@ furnished to do so, subject to the following conditions:
|
|||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
SOFTWARE.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
@ -54,6 +54,11 @@ export const cors: Core.MiddlewareFactory<Config> = (config) => {
|
|||||||
return originList.includes(ctx.get('Origin')) ? ctx.get('Origin') : '';
|
return originList.includes(ctx.get('Origin')) ? ctx.get('Origin') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedOrigin = originList.split(',').map((origin) => origin.trim());
|
||||||
|
if (parsedOrigin.length > 1) {
|
||||||
|
return parsedOrigin.includes(ctx.get('Origin')) ? ctx.get('Origin') : '';
|
||||||
|
}
|
||||||
|
|
||||||
return originList;
|
return originList;
|
||||||
},
|
},
|
||||||
exposeHeaders: expose,
|
exposeHeaders: expose,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// import { buildStrapiCloudCommands as cloudCommands } from '@strapi/cloud-cli';
|
import { buildStrapiCloudCommands as cloudCommands } from '@strapi/cloud-cli';
|
||||||
|
|
||||||
import { command as createAdminUser } from './admin/create-user';
|
import { command as createAdminUser } from './admin/create-user';
|
||||||
import { command as resetAdminUserPassword } from './admin/reset-user-password';
|
import { command as resetAdminUserPassword } from './admin/reset-user-password';
|
||||||
@ -59,6 +59,5 @@ export const commands: StrapiCommand[] = [
|
|||||||
/**
|
/**
|
||||||
* Cloud
|
* Cloud
|
||||||
*/
|
*/
|
||||||
// TODO V5: Uncomment when cloud-cli is ready
|
cloudCommands,
|
||||||
// cloudCommands,
|
|
||||||
];
|
];
|
||||||
|
@ -36,6 +36,10 @@ yarn build
|
|||||||
|
|
||||||
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn strapi deploy
|
||||||
|
```
|
||||||
|
|
||||||
## 📚 Learn more
|
## 📚 Learn more
|
||||||
|
|
||||||
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
||||||
|
@ -36,6 +36,10 @@ yarn build
|
|||||||
|
|
||||||
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn strapi deploy
|
||||||
|
```
|
||||||
|
|
||||||
## 📚 Learn more
|
## 📚 Learn more
|
||||||
|
|
||||||
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
||||||
|
@ -89,6 +89,7 @@ const params: OpenAPIV3.ParameterObject[] = [
|
|||||||
required: false,
|
required: false,
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
additionalProperties: true,
|
||||||
},
|
},
|
||||||
style: 'deepObject',
|
style: 'deepObject',
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"watch": "pack-up watch"
|
"watch": "pack-up watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.433.0",
|
"@aws-sdk/client-s3": "3.600.0",
|
||||||
"@aws-sdk/lib-storage": "3.433.0",
|
"@aws-sdk/lib-storage": "3.433.0",
|
||||||
"@aws-sdk/s3-request-presigner": "3.433.0",
|
"@aws-sdk/s3-request-presigner": "3.433.0",
|
||||||
"@aws-sdk/types": "3.433.0",
|
"@aws-sdk/types": "3.433.0",
|
||||||
|
@ -138,6 +138,7 @@ export default {
|
|||||||
const fileKey = getFileKey(file);
|
const fileKey = getFileKey(file);
|
||||||
|
|
||||||
const url = await getSignedUrl(
|
const url = await getSignedUrl(
|
||||||
|
// @ts-expect-error - TODO fix client type
|
||||||
s3Client,
|
s3Client,
|
||||||
new GetObjectCommand({
|
new GetObjectCommand({
|
||||||
Bucket: config.params.Bucket,
|
Bucket: config.params.Bucket,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user