@strapi/cloud-cli 0.0.0-next.9b0c43700e0ad92b33a4c97f1507f9f3243f2b6a → 0.0.0-next.a237dfc081ee4788ee5abb71a735161aebec0ee5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -20,7 +20,6 @@ import ora from "ora";
20
20
  import * as cliProgress from "cli-progress";
21
21
  import pkgUp from "pkg-up";
22
22
  import * as yup from "yup";
23
- import _ from "lodash";
24
23
  import EventSource from "eventsource";
25
24
  const apiConfig = {
26
25
  apiBaseUrl: env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
@@ -134,7 +133,7 @@ async function saveLocalConfig(data) {
134
133
  await fse__default.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
135
134
  }
136
135
  const name = "@strapi/cloud-cli";
137
- const version = "4.25.2";
136
+ const version = "4.25.8";
138
137
  const description = "Commands to interact with the Strapi Cloud";
139
138
  const keywords = [
140
139
  "strapi",
@@ -179,7 +178,7 @@ const scripts = {
179
178
  watch: "pack-up watch"
180
179
  };
181
180
  const dependencies = {
182
- "@strapi/utils": "4.25.2",
181
+ "@strapi/utils": "4.25.8",
183
182
  axios: "1.6.0",
184
183
  chalk: "4.1.2",
185
184
  "cli-progress": "3.12.0",
@@ -204,8 +203,8 @@ const devDependencies = {
204
203
  "@types/cli-progress": "3.11.5",
205
204
  "@types/eventsource": "1.1.15",
206
205
  "@types/lodash": "^4.14.191",
207
- "eslint-config-custom": "4.25.2",
208
- tsconfig: "4.25.2"
206
+ "eslint-config-custom": "4.25.8",
207
+ tsconfig: "4.25.8"
209
208
  };
210
209
  const engines = {
211
210
  node: ">=18.0.0 <=20.x.x",
@@ -315,6 +314,20 @@ async function cloudApiFactory({ logger }, token) {
315
314
  throw error;
316
315
  }
317
316
  },
317
+ async listLinkProjects() {
318
+ try {
319
+ const response = await axiosCloudAPI.get("/projects/linkable");
320
+ if (response.status !== 200) {
321
+ throw new Error("Error fetching cloud projects from the server.");
322
+ }
323
+ return response;
324
+ } catch (error) {
325
+ logger.debug(
326
+ "🥲 Oops! Couldn't retrieve your project's list from the server. Please try again."
327
+ );
328
+ throw error;
329
+ }
330
+ },
318
331
  track(event, payload = {}) {
319
332
  return axiosCloudAPI.post("/track", {
320
333
  event,
@@ -624,18 +637,13 @@ async function getProjectNameFromPackageJson(ctx) {
624
637
  return "my-strapi-project";
625
638
  }
626
639
  }
627
- function applyDefaultName(newDefaultName, questions, defaultValues) {
628
- const newDefaultValues = _.cloneDeep(defaultValues);
629
- newDefaultValues.name = newDefaultName;
630
- const newQuestions = questions.map((question) => {
631
- const questionCopy = _.cloneDeep(question);
632
- if (questionCopy.name === "name") {
633
- questionCopy.default = newDefaultName;
634
- }
635
- return questionCopy;
636
- });
637
- return { newQuestions, newDefaultValues };
638
- }
640
+ const trackEvent = async (ctx, cloudApiService, eventName, eventData) => {
641
+ try {
642
+ await cloudApiService.track(eventName, eventData);
643
+ } catch (e) {
644
+ ctx.logger.debug(`Failed to track ${eventName}`, e);
645
+ }
646
+ };
639
647
  const openModule$1 = import("open");
640
648
  async function promptLogin(ctx) {
641
649
  const response = await inquirer.prompt([
@@ -656,13 +664,6 @@ async function loginAction(ctx) {
656
664
  const tokenService = await tokenServiceFactory(ctx);
657
665
  const existingToken = await tokenService.retrieveToken();
658
666
  const cloudApiService = await cloudApiFactory(ctx, existingToken || void 0);
659
- const trackFailedLogin = async () => {
660
- try {
661
- await cloudApiService.track("didNotLogin", { loginMethod: "cli" });
662
- } catch (e) {
663
- logger.debug("Failed to track failed login", e);
664
- }
665
- };
666
667
  if (existingToken) {
667
668
  const isTokenValid = await tokenService.isTokenValid(existingToken);
668
669
  if (isTokenValid) {
@@ -694,11 +695,7 @@ async function loginAction(ctx) {
694
695
  logger.debug(e);
695
696
  return false;
696
697
  }
697
- try {
698
- await cloudApiService.track("willLoginAttempt", {});
699
- } catch (e) {
700
- logger.debug("Failed to track login attempt", e);
701
- }
698
+ await trackEvent(ctx, cloudApiService, "willLoginAttempt", {});
702
699
  logger.debug("🔐 Creating device authentication request...", {
703
700
  client_id: cliConfig2.clientId,
704
701
  scope: cliConfig2.scope,
@@ -778,13 +775,13 @@ async function loginAction(ctx) {
778
775
  "There seems to be a problem with your login information. Please try logging in again."
779
776
  );
780
777
  spinnerFail();
781
- await trackFailedLogin();
778
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
782
779
  return false;
783
780
  }
784
781
  if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
785
782
  logger.debug(e);
786
783
  spinnerFail();
787
- await trackFailedLogin();
784
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
788
785
  return false;
789
786
  }
790
787
  await new Promise((resolve) => {
@@ -798,15 +795,50 @@ async function loginAction(ctx) {
798
795
  "To access your dashboard, please copy and paste the following URL into your web browser:"
799
796
  );
800
797
  logger.log(chalk.underline(`${apiConfig.dashboardBaseUrl}/projects`));
801
- try {
802
- await cloudApiService.track("didLogin", { loginMethod: "cli" });
803
- } catch (e) {
804
- logger.debug("Failed to track login", e);
805
- }
798
+ await trackEvent(ctx, cloudApiService, "didLogin", { loginMethod: "cli" });
806
799
  };
807
800
  await authenticate();
808
801
  return isAuthenticated;
809
802
  }
803
+ function questionDefaultValuesMapper(questionsMap) {
804
+ return (questions) => {
805
+ return questions.map((question) => {
806
+ const questionName = question.name;
807
+ if (questionName in questionsMap) {
808
+ const questionDefault = questionsMap[questionName];
809
+ if (typeof questionDefault === "function") {
810
+ return {
811
+ ...question,
812
+ default: questionDefault(question)
813
+ };
814
+ }
815
+ return {
816
+ ...question,
817
+ default: questionDefault
818
+ };
819
+ }
820
+ return question;
821
+ });
822
+ };
823
+ }
824
+ function getDefaultsFromQuestions(questions) {
825
+ return questions.reduce((acc, question) => {
826
+ if (question.default && question.name) {
827
+ return { ...acc, [question.name]: question.default };
828
+ }
829
+ return acc;
830
+ }, {});
831
+ }
832
+ function getProjectNodeVersionDefault(question) {
833
+ const currentNodeVersion = process.versions.node.split(".")[0];
834
+ if (question.type === "list" && Array.isArray(question.choices)) {
835
+ const choice = question.choices.find((choice2) => choice2.value === currentNodeVersion);
836
+ if (choice) {
837
+ return choice.value;
838
+ }
839
+ }
840
+ return question.default;
841
+ }
810
842
  async function handleError(ctx, error) {
811
843
  const { logger } = ctx;
812
844
  logger.debug(error);
@@ -851,7 +883,7 @@ async function createProject$1(ctx, cloudApi, projectInput) {
851
883
  throw e;
852
884
  }
853
885
  }
854
- const action$3 = async (ctx) => {
886
+ const action$4 = async (ctx) => {
855
887
  const { logger } = ctx;
856
888
  const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
857
889
  const token = await getValidToken(ctx, promptLogin);
@@ -860,11 +892,16 @@ const action$3 = async (ctx) => {
860
892
  }
861
893
  const cloudApi = await cloudApiFactory(ctx, token);
862
894
  const { data: config } = await cloudApi.config();
863
- const { newQuestions: questions, newDefaultValues: defaultValues } = applyDefaultName(
864
- await getProjectNameFromPackageJson(ctx),
865
- config.projectCreation.questions,
866
- config.projectCreation.defaults
867
- );
895
+ const projectName = await getProjectNameFromPackageJson(ctx);
896
+ const defaultAnswersMapper = questionDefaultValuesMapper({
897
+ name: projectName,
898
+ nodeVersion: getProjectNodeVersionDefault
899
+ });
900
+ const questions = defaultAnswersMapper(config.projectCreation.questions);
901
+ const defaultValues = {
902
+ ...config.projectCreation.defaults,
903
+ ...getDefaultsFromQuestions(questions)
904
+ };
868
905
  const projectAnswersDefaulted = defaults(defaultValues);
869
906
  const projectAnswers = await inquirer.prompt(questions);
870
907
  const projectInput = projectAnswersDefaulted(projectAnswers);
@@ -966,6 +1003,7 @@ const buildLogsServiceFactory = ({ logger }) => {
966
1003
  if (retries > MAX_RETRIES) {
967
1004
  spinner.fail("We were unable to connect to the server to get build logs at this time.");
968
1005
  es.close();
1006
+ clearExistingTimeout();
969
1007
  reject(new Error("Max retries reached"));
970
1008
  }
971
1009
  };
@@ -1042,8 +1080,8 @@ async function upload(ctx, project, token, maxProjectFileSize) {
1042
1080
  progressBar.stop();
1043
1081
  if (e instanceof AxiosError && e.response?.data) {
1044
1082
  if (e.response.status === 404) {
1045
- ctx.logger.error(
1046
- `The project does not exist. Remove the ${LOCAL_SAVE_FILENAME} file and try again.`
1083
+ ctx.logger.warn(
1084
+ `The project does not exist. Please link your local project to a Strapi Cloud project using the link command.`
1047
1085
  );
1048
1086
  } else {
1049
1087
  ctx.logger.error(e.response.data);
@@ -1066,7 +1104,7 @@ async function getProject(ctx) {
1066
1104
  const { project } = await retrieve();
1067
1105
  if (!project) {
1068
1106
  try {
1069
- return await action$3(ctx);
1107
+ return await action$4(ctx);
1070
1108
  } catch (e) {
1071
1109
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1072
1110
  ctx.logger.debug(e);
@@ -1075,7 +1113,19 @@ async function getProject(ctx) {
1075
1113
  }
1076
1114
  return project;
1077
1115
  }
1078
- const action$2 = async (ctx) => {
1116
+ async function getConfig({
1117
+ ctx,
1118
+ cloudApiService
1119
+ }) {
1120
+ try {
1121
+ const { data: cliConfig2 } = await cloudApiService.config();
1122
+ return cliConfig2;
1123
+ } catch (e) {
1124
+ ctx.logger.debug("Failed to get cli config", e);
1125
+ return null;
1126
+ }
1127
+ }
1128
+ const action$3 = async (ctx) => {
1079
1129
  const { getValidToken } = await tokenServiceFactory(ctx);
1080
1130
  const token = await getValidToken(ctx, promptLogin);
1081
1131
  if (!token) {
@@ -1086,14 +1136,18 @@ const action$2 = async (ctx) => {
1086
1136
  return;
1087
1137
  }
1088
1138
  const cloudApiService = await cloudApiFactory(ctx);
1089
- try {
1090
- await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1091
- } catch (e) {
1092
- ctx.logger.debug("Failed to track willDeploy", e);
1093
- }
1139
+ await trackEvent(ctx, cloudApiService, "willDeployWithCLI", {
1140
+ projectInternalName: project.name
1141
+ });
1094
1142
  const notificationService = notificationServiceFactory(ctx);
1095
1143
  const buildLogsService = buildLogsServiceFactory(ctx);
1096
- const { data: cliConfig2 } = await cloudApiService.config();
1144
+ const cliConfig2 = await getConfig({ ctx, cloudApiService });
1145
+ if (!cliConfig2) {
1146
+ ctx.logger.error(
1147
+ "An error occurred while retrieving data from Strapi Cloud. Please try check your network or again later."
1148
+ );
1149
+ return;
1150
+ }
1097
1151
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
1098
1152
  if (Number.isNaN(maxSize)) {
1099
1153
  ctx.logger.debug(
@@ -1115,10 +1169,11 @@ const action$2 = async (ctx) => {
1115
1169
  chalk.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1116
1170
  );
1117
1171
  } catch (e) {
1172
+ ctx.logger.debug(e);
1118
1173
  if (e instanceof Error) {
1119
1174
  ctx.logger.error(e.message);
1120
1175
  } else {
1121
- throw e;
1176
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1122
1177
  }
1123
1178
  }
1124
1179
  };
@@ -1149,12 +1204,158 @@ const runAction = (name2, action2) => (...args) => {
1149
1204
  process.exit(1);
1150
1205
  });
1151
1206
  };
1152
- const command$4 = ({ command: command2, ctx }) => {
1153
- command2.command("cloud:deploy").alias("deploy").description("Deploy a Strapi Cloud project").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("deploy", action$2)(ctx));
1207
+ const command$5 = ({ command: command2, ctx }) => {
1208
+ command2.command("cloud:deploy").alias("deploy").description("Deploy a Strapi Cloud project").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("deploy", action$3)(ctx));
1154
1209
  };
1155
1210
  const deployProject = {
1156
1211
  name: "deploy-project",
1157
1212
  description: "Deploy a Strapi Cloud project",
1213
+ action: action$3,
1214
+ command: command$5
1215
+ };
1216
+ const QUIT_OPTION = "Quit";
1217
+ async function getExistingConfig(ctx) {
1218
+ try {
1219
+ return await retrieve();
1220
+ } catch (e) {
1221
+ ctx.logger.debug("Failed to get project config", e);
1222
+ ctx.logger.error("An error occurred while retrieving config data from your local project.");
1223
+ return null;
1224
+ }
1225
+ }
1226
+ async function promptForRelink(ctx, cloudApiService, existingConfig) {
1227
+ if (existingConfig && existingConfig.project) {
1228
+ const { shouldRelink } = await inquirer.prompt([
1229
+ {
1230
+ type: "confirm",
1231
+ name: "shouldRelink",
1232
+ message: `A project named ${chalk.cyan(
1233
+ existingConfig.project.displayName ? existingConfig.project.displayName : existingConfig.project.name
1234
+ )} is already linked to this local folder. Do you want to update the link?`,
1235
+ default: false
1236
+ }
1237
+ ]);
1238
+ if (!shouldRelink) {
1239
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1240
+ currentProjectName: existingConfig.project?.name
1241
+ });
1242
+ return false;
1243
+ }
1244
+ }
1245
+ return true;
1246
+ }
1247
+ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1248
+ const spinner = ctx.logger.spinner("Fetching your projects...\n").start();
1249
+ try {
1250
+ const {
1251
+ data: { data: projectList }
1252
+ } = await cloudApiService.listLinkProjects();
1253
+ spinner.succeed();
1254
+ if (!Array.isArray(projectList)) {
1255
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1256
+ return null;
1257
+ }
1258
+ const projects = projectList.filter(
1259
+ (project) => !(project.isMaintainer || project.name === existingConfig?.project?.name)
1260
+ ).map((project) => {
1261
+ return {
1262
+ name: project.displayName,
1263
+ value: { name: project.name, displayName: project.displayName }
1264
+ };
1265
+ });
1266
+ if (projects.length === 0) {
1267
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1268
+ return null;
1269
+ }
1270
+ return projects;
1271
+ } catch (e) {
1272
+ spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1273
+ ctx.logger.debug("Failed to list projects", e);
1274
+ return null;
1275
+ }
1276
+ }
1277
+ async function getUserSelection(ctx, projects) {
1278
+ const { logger } = ctx;
1279
+ try {
1280
+ const answer = await inquirer.prompt([
1281
+ {
1282
+ type: "list",
1283
+ name: "linkProject",
1284
+ message: "Which project do you want to link?",
1285
+ choices: [...projects, { name: chalk.grey(`(${QUIT_OPTION})`), value: null }]
1286
+ }
1287
+ ]);
1288
+ if (!answer.linkProject) {
1289
+ return null;
1290
+ }
1291
+ return answer;
1292
+ } catch (e) {
1293
+ logger.debug("Failed to get user input", e);
1294
+ logger.error("An error occurred while trying to get your input.");
1295
+ return null;
1296
+ }
1297
+ }
1298
+ const action$2 = async (ctx) => {
1299
+ const { getValidToken } = await tokenServiceFactory(ctx);
1300
+ const token = await getValidToken(ctx, promptLogin);
1301
+ const { logger } = ctx;
1302
+ if (!token) {
1303
+ return;
1304
+ }
1305
+ const cloudApiService = await cloudApiFactory(ctx, token);
1306
+ const existingConfig = await getExistingConfig(ctx);
1307
+ const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
1308
+ if (!shouldRelink) {
1309
+ return;
1310
+ }
1311
+ await trackEvent(ctx, cloudApiService, "willLinkProject", {});
1312
+ const projects = await getProjectsList(
1313
+ ctx,
1314
+ cloudApiService,
1315
+ existingConfig
1316
+ );
1317
+ if (!projects) {
1318
+ return;
1319
+ }
1320
+ const answer = await getUserSelection(ctx, projects);
1321
+ if (!answer) {
1322
+ return;
1323
+ }
1324
+ try {
1325
+ const { confirmAction } = await inquirer.prompt([
1326
+ {
1327
+ type: "confirm",
1328
+ name: "confirmAction",
1329
+ message: "Warning: Once linked, deploying from CLI will replace the existing project and its data. Confirm to proceed:",
1330
+ default: false
1331
+ }
1332
+ ]);
1333
+ if (!confirmAction) {
1334
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1335
+ cancelledProjectName: answer.linkProject.name,
1336
+ currentProjectName: existingConfig ? existingConfig.project?.name : null
1337
+ });
1338
+ return;
1339
+ }
1340
+ await save({ project: answer.linkProject });
1341
+ logger.log(`Project ${chalk.cyan(answer.linkProject.displayName)} linked successfully.`);
1342
+ await trackEvent(ctx, cloudApiService, "didLinkProject", {
1343
+ projectInternalName: answer.linkProject
1344
+ });
1345
+ } catch (e) {
1346
+ logger.debug("Failed to link project", e);
1347
+ logger.error("An error occurred while linking the project.");
1348
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1349
+ projectInternalName: answer.linkProject
1350
+ });
1351
+ }
1352
+ };
1353
+ const command$4 = ({ command: command2, ctx }) => {
1354
+ command2.command("cloud:link").alias("link").description("Link a local directory to a Strapi Cloud project").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("link", action$2)(ctx));
1355
+ };
1356
+ const link = {
1357
+ name: "link-project",
1358
+ description: "Link a local directory to a Strapi Cloud project",
1158
1359
  action: action$2,
1159
1360
  command: command$4
1160
1361
  };
@@ -1201,11 +1402,7 @@ const action$1 = async (ctx) => {
1201
1402
  logger.error("🥲 Oops! Something went wrong while logging you out. Please try again.");
1202
1403
  logger.debug(e);
1203
1404
  }
1204
- try {
1205
- await cloudApiService.track("didLogout", { loginMethod: "cli" });
1206
- } catch (e) {
1207
- logger.debug("Failed to track logout event", e);
1208
- }
1405
+ await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
1209
1406
  };
1210
1407
  const command$2 = ({ command: command2, ctx }) => {
1211
1408
  command2.command("cloud:logout").alias("logout").description("Strapi Cloud Logout").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("logout", action$1)(ctx));
@@ -1217,12 +1414,12 @@ const logout = {
1217
1414
  command: command$2
1218
1415
  };
1219
1416
  const command$1 = ({ command: command2, ctx }) => {
1220
- command2.command("cloud:create-project").description("Create a Strapi Cloud project").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("cloud:create-project", action$3)(ctx));
1417
+ command2.command("cloud:create-project").description("Create a Strapi Cloud project").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("cloud:create-project", action$4)(ctx));
1221
1418
  };
1222
1419
  const createProject = {
1223
1420
  name: "create-project",
1224
1421
  description: "Create a new project",
1225
- action: action$3,
1422
+ action: action$4,
1226
1423
  command: command$1
1227
1424
  };
1228
1425
  const action = async (ctx) => {
@@ -1246,7 +1443,7 @@ const action = async (ctx) => {
1246
1443
  }
1247
1444
  };
1248
1445
  const command = ({ command: command2, ctx }) => {
1249
- command2.command("cloud:projects").alias("projects").description("List Strapi Cloud projects").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("listProjects", action)(ctx));
1446
+ command2.command("cloud:projects").alias("projects").description("List Strapi Cloud projects").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("projects", action)(ctx));
1250
1447
  };
1251
1448
  const listProjects = {
1252
1449
  name: "list-projects",
@@ -1256,12 +1453,13 @@ const listProjects = {
1256
1453
  };
1257
1454
  const cli = {
1258
1455
  deployProject,
1456
+ link,
1259
1457
  login,
1260
1458
  logout,
1261
1459
  createProject,
1262
1460
  listProjects
1263
1461
  };
1264
- const cloudCommands = [deployProject, login, logout, listProjects];
1462
+ const cloudCommands = [deployProject, link, login, logout, listProjects];
1265
1463
  async function initCloudCLIConfig() {
1266
1464
  const localConfig = await getLocalConfig();
1267
1465
  if (!localConfig.deviceId) {