@strapi/cloud-cli 0.0.0-next.6785f47fadddb80404c824be0a0732c652850b45 → 0.0.0-next.6ed779c066310248c506ce3d2cdae97f59f700ef

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.
Files changed (47) hide show
  1. package/dist/index.js +438 -109
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +438 -108
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/src/cloud/command.d.ts +3 -0
  6. package/dist/src/cloud/command.d.ts.map +1 -0
  7. package/dist/src/create-project/action.d.ts.map +1 -1
  8. package/dist/src/create-project/command.d.ts.map +1 -1
  9. package/dist/src/create-project/utils/project-questions.utils.d.ts +20 -0
  10. package/dist/src/create-project/utils/project-questions.utils.d.ts.map +1 -0
  11. package/dist/src/deploy-project/action.d.ts.map +1 -1
  12. package/dist/src/deploy-project/command.d.ts.map +1 -1
  13. package/dist/src/environment/command.d.ts +3 -0
  14. package/dist/src/environment/command.d.ts.map +1 -0
  15. package/dist/src/environment/list/action.d.ts +4 -0
  16. package/dist/src/environment/list/action.d.ts.map +1 -0
  17. package/dist/src/environment/list/command.d.ts +4 -0
  18. package/dist/src/environment/list/command.d.ts.map +1 -0
  19. package/dist/src/environment/list/index.d.ts +7 -0
  20. package/dist/src/environment/list/index.d.ts.map +1 -0
  21. package/dist/src/index.d.ts +2 -0
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/link/action.d.ts +4 -0
  24. package/dist/src/link/action.d.ts.map +1 -0
  25. package/dist/src/link/command.d.ts +7 -0
  26. package/dist/src/link/command.d.ts.map +1 -0
  27. package/dist/src/link/index.d.ts +7 -0
  28. package/dist/src/link/index.d.ts.map +1 -0
  29. package/dist/src/login/action.d.ts.map +1 -1
  30. package/dist/src/login/command.d.ts.map +1 -1
  31. package/dist/src/logout/action.d.ts.map +1 -1
  32. package/dist/src/logout/command.d.ts.map +1 -1
  33. package/dist/src/services/build-logs.d.ts.map +1 -1
  34. package/dist/src/services/cli-api.d.ts +38 -6
  35. package/dist/src/services/cli-api.d.ts.map +1 -1
  36. package/dist/src/services/strapi-info-save.d.ts +1 -1
  37. package/dist/src/services/strapi-info-save.d.ts.map +1 -1
  38. package/dist/src/types.d.ts +5 -1
  39. package/dist/src/types.d.ts.map +1 -1
  40. package/dist/src/utils/analytics.d.ts +4 -0
  41. package/dist/src/utils/analytics.d.ts.map +1 -0
  42. package/dist/src/utils/pkg.d.ts.map +1 -1
  43. package/dist/src/utils/tests/compress-files.test.d.ts +2 -0
  44. package/dist/src/utils/tests/compress-files.test.d.ts.map +1 -0
  45. package/package.json +9 -9
  46. package/dist/src/create-project/utils/apply-default-name.d.ts +0 -7
  47. package/dist/src/create-project/utils/apply-default-name.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -42,8 +42,8 @@ const ora = require("ora");
42
42
  const cliProgress = require("cli-progress");
43
43
  const pkgUp = require("pkg-up");
44
44
  const yup = require("yup");
45
- const _ = require("lodash");
46
45
  const EventSource = require("eventsource");
46
+ const commander = require("commander");
47
47
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
48
48
  function _interopNamespace(e) {
49
49
  if (e && e.__esModule)
@@ -80,7 +80,6 @@ const ora__default = /* @__PURE__ */ _interopDefault(ora);
80
80
  const cliProgress__namespace = /* @__PURE__ */ _interopNamespace(cliProgress);
81
81
  const pkgUp__default = /* @__PURE__ */ _interopDefault(pkgUp);
82
82
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
83
- const ___default = /* @__PURE__ */ _interopDefault(_);
84
83
  const EventSource__default = /* @__PURE__ */ _interopDefault(EventSource);
85
84
  const apiConfig = {
86
85
  apiBaseUrl: utils.env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
@@ -194,7 +193,7 @@ async function saveLocalConfig(data) {
194
193
  await fse__namespace.default.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
195
194
  }
196
195
  const name = "@strapi/cloud-cli";
197
- const version = "4.25.3";
196
+ const version = "5.1.0";
198
197
  const description = "Commands to interact with the Strapi Cloud";
199
198
  const keywords = [
200
199
  "strapi",
@@ -239,14 +238,14 @@ const scripts = {
239
238
  watch: "pack-up watch"
240
239
  };
241
240
  const dependencies = {
242
- "@strapi/utils": "4.25.3",
243
- axios: "1.6.0",
241
+ "@strapi/utils": "workspace:*",
242
+ axios: "1.7.4",
244
243
  chalk: "4.1.2",
245
244
  "cli-progress": "3.12.0",
246
245
  commander: "8.3.0",
247
246
  eventsource: "2.0.2",
248
247
  "fast-safe-stringify": "2.1.1",
249
- "fs-extra": "10.0.0",
248
+ "fs-extra": "11.2.0",
250
249
  inquirer: "8.2.5",
251
250
  jsonwebtoken: "9.0.0",
252
251
  "jwks-rsa": "3.1.0",
@@ -255,7 +254,7 @@ const dependencies = {
255
254
  open: "8.4.0",
256
255
  ora: "5.4.1",
257
256
  "pkg-up": "3.1.0",
258
- tar: "6.1.13",
257
+ tar: "6.2.1",
259
258
  "xdg-app-paths": "8.3.0",
260
259
  yup: "0.32.9"
261
260
  };
@@ -264,11 +263,11 @@ const devDependencies = {
264
263
  "@types/cli-progress": "3.11.5",
265
264
  "@types/eventsource": "1.1.15",
266
265
  "@types/lodash": "^4.14.191",
267
- "eslint-config-custom": "4.25.3",
268
- tsconfig: "4.25.3"
266
+ "eslint-config-custom": "workspace:*",
267
+ tsconfig: "workspace:*"
269
268
  };
270
269
  const engines = {
271
- node: ">=18.0.0 <=20.x.x",
270
+ node: ">=18.0.0 <=22.x.x",
272
271
  npm: ">=6.0.0"
273
272
  };
274
273
  const packageJson = {
@@ -375,6 +374,48 @@ async function cloudApiFactory({ logger }, token) {
375
374
  throw error;
376
375
  }
377
376
  },
377
+ async listLinkProjects() {
378
+ try {
379
+ const response = await axiosCloudAPI.get("/projects-linkable");
380
+ if (response.status !== 200) {
381
+ throw new Error("Error fetching cloud projects from the server.");
382
+ }
383
+ return response;
384
+ } catch (error) {
385
+ logger.debug(
386
+ "🥲 Oops! Couldn't retrieve your project's list from the server. Please try again."
387
+ );
388
+ throw error;
389
+ }
390
+ },
391
+ async listEnvironments({ name: name2 }) {
392
+ try {
393
+ const response = await axiosCloudAPI.get(`/projects/${name2}/environments`);
394
+ if (response.status !== 200) {
395
+ throw new Error("Error fetching cloud environments from the server.");
396
+ }
397
+ return response;
398
+ } catch (error) {
399
+ logger.debug(
400
+ "🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
401
+ );
402
+ throw error;
403
+ }
404
+ },
405
+ async getProject({ name: name2 }) {
406
+ try {
407
+ const response = await axiosCloudAPI.get(`/projects/${name2}`);
408
+ if (response.status !== 200) {
409
+ throw new Error("Error fetching project's details.");
410
+ }
411
+ return response;
412
+ } catch (error) {
413
+ logger.debug(
414
+ "🥲 Oops! There was a problem retrieving your project's details. Please try again."
415
+ );
416
+ throw error;
417
+ }
418
+ },
378
419
  track(event, payload = {}) {
379
420
  return axiosCloudAPI.post("/track", {
380
421
  event,
@@ -648,21 +689,24 @@ yup__namespace.object({
648
689
  name: yup__namespace.string().required(),
649
690
  exports: yup__namespace.lazy(
650
691
  (value) => yup__namespace.object(
651
- typeof value === "object" ? Object.entries(value).reduce((acc, [key, value2]) => {
652
- if (typeof value2 === "object") {
653
- acc[key] = yup__namespace.object({
654
- types: yup__namespace.string().optional(),
655
- source: yup__namespace.string().required(),
656
- module: yup__namespace.string().optional(),
657
- import: yup__namespace.string().required(),
658
- require: yup__namespace.string().required(),
659
- default: yup__namespace.string().required()
660
- }).noUnknown(true);
661
- } else {
662
- acc[key] = yup__namespace.string().matches(/^\.\/.*\.json$/).required();
663
- }
664
- return acc;
665
- }, {}) : void 0
692
+ typeof value === "object" ? Object.entries(value).reduce(
693
+ (acc, [key, value2]) => {
694
+ if (typeof value2 === "object") {
695
+ acc[key] = yup__namespace.object({
696
+ types: yup__namespace.string().optional(),
697
+ source: yup__namespace.string().required(),
698
+ module: yup__namespace.string().optional(),
699
+ import: yup__namespace.string().required(),
700
+ require: yup__namespace.string().required(),
701
+ default: yup__namespace.string().required()
702
+ }).noUnknown(true);
703
+ } else {
704
+ acc[key] = yup__namespace.string().matches(/^\.\/.*\.json$/).required();
705
+ }
706
+ return acc;
707
+ },
708
+ {}
709
+ ) : void 0
666
710
  ).optional()
667
711
  )
668
712
  });
@@ -684,18 +728,13 @@ async function getProjectNameFromPackageJson(ctx) {
684
728
  return "my-strapi-project";
685
729
  }
686
730
  }
687
- function applyDefaultName(newDefaultName, questions, defaultValues) {
688
- const newDefaultValues = ___default.default.cloneDeep(defaultValues);
689
- newDefaultValues.name = newDefaultName;
690
- const newQuestions = questions.map((question) => {
691
- const questionCopy = ___default.default.cloneDeep(question);
692
- if (questionCopy.name === "name") {
693
- questionCopy.default = newDefaultName;
694
- }
695
- return questionCopy;
696
- });
697
- return { newQuestions, newDefaultValues };
698
- }
731
+ const trackEvent = async (ctx, cloudApiService, eventName, eventData) => {
732
+ try {
733
+ await cloudApiService.track(eventName, eventData);
734
+ } catch (e) {
735
+ ctx.logger.debug(`Failed to track ${eventName}`, e);
736
+ }
737
+ };
699
738
  const openModule$1 = import("open");
700
739
  async function promptLogin(ctx) {
701
740
  const response = await inquirer__default.default.prompt([
@@ -716,13 +755,6 @@ async function loginAction(ctx) {
716
755
  const tokenService = await tokenServiceFactory(ctx);
717
756
  const existingToken = await tokenService.retrieveToken();
718
757
  const cloudApiService = await cloudApiFactory(ctx, existingToken || void 0);
719
- const trackFailedLogin = async () => {
720
- try {
721
- await cloudApiService.track("didNotLogin", { loginMethod: "cli" });
722
- } catch (e) {
723
- logger.debug("Failed to track failed login", e);
724
- }
725
- };
726
758
  if (existingToken) {
727
759
  const isTokenValid = await tokenService.isTokenValid(existingToken);
728
760
  if (isTokenValid) {
@@ -754,11 +786,7 @@ async function loginAction(ctx) {
754
786
  logger.debug(e);
755
787
  return false;
756
788
  }
757
- try {
758
- await cloudApiService.track("willLoginAttempt", {});
759
- } catch (e) {
760
- logger.debug("Failed to track login attempt", e);
761
- }
789
+ await trackEvent(ctx, cloudApiService, "willLoginAttempt", {});
762
790
  logger.debug("🔐 Creating device authentication request...", {
763
791
  client_id: cliConfig2.clientId,
764
792
  scope: cliConfig2.scope,
@@ -838,13 +866,13 @@ async function loginAction(ctx) {
838
866
  "There seems to be a problem with your login information. Please try logging in again."
839
867
  );
840
868
  spinnerFail();
841
- await trackFailedLogin();
869
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
842
870
  return false;
843
871
  }
844
872
  if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
845
873
  logger.debug(e);
846
874
  spinnerFail();
847
- await trackFailedLogin();
875
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
848
876
  return false;
849
877
  }
850
878
  await new Promise((resolve) => {
@@ -858,15 +886,50 @@ async function loginAction(ctx) {
858
886
  "To access your dashboard, please copy and paste the following URL into your web browser:"
859
887
  );
860
888
  logger.log(chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects`));
861
- try {
862
- await cloudApiService.track("didLogin", { loginMethod: "cli" });
863
- } catch (e) {
864
- logger.debug("Failed to track login", e);
865
- }
889
+ await trackEvent(ctx, cloudApiService, "didLogin", { loginMethod: "cli" });
866
890
  };
867
891
  await authenticate();
868
892
  return isAuthenticated;
869
893
  }
894
+ function questionDefaultValuesMapper(questionsMap) {
895
+ return (questions) => {
896
+ return questions.map((question) => {
897
+ const questionName = question.name;
898
+ if (questionName in questionsMap) {
899
+ const questionDefault = questionsMap[questionName];
900
+ if (typeof questionDefault === "function") {
901
+ return {
902
+ ...question,
903
+ default: questionDefault(question)
904
+ };
905
+ }
906
+ return {
907
+ ...question,
908
+ default: questionDefault
909
+ };
910
+ }
911
+ return question;
912
+ });
913
+ };
914
+ }
915
+ function getDefaultsFromQuestions(questions) {
916
+ return questions.reduce((acc, question) => {
917
+ if (question.default && question.name) {
918
+ return { ...acc, [question.name]: question.default };
919
+ }
920
+ return acc;
921
+ }, {});
922
+ }
923
+ function getProjectNodeVersionDefault(question) {
924
+ const currentNodeVersion = process.versions.node.split(".")[0];
925
+ if (question.type === "list" && Array.isArray(question.choices)) {
926
+ const choice = question.choices.find((choice2) => choice2.value === currentNodeVersion);
927
+ if (choice) {
928
+ return choice.value;
929
+ }
930
+ }
931
+ return question.default;
932
+ }
870
933
  async function handleError(ctx, error) {
871
934
  const { logger } = ctx;
872
935
  logger.debug(error);
@@ -911,7 +974,7 @@ async function createProject$1(ctx, cloudApi, projectInput) {
911
974
  throw e;
912
975
  }
913
976
  }
914
- const action$3 = async (ctx) => {
977
+ const action$5 = async (ctx) => {
915
978
  const { logger } = ctx;
916
979
  const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
917
980
  const token = await getValidToken(ctx, promptLogin);
@@ -920,11 +983,16 @@ const action$3 = async (ctx) => {
920
983
  }
921
984
  const cloudApi = await cloudApiFactory(ctx, token);
922
985
  const { data: config } = await cloudApi.config();
923
- const { newQuestions: questions, newDefaultValues: defaultValues } = applyDefaultName(
924
- await getProjectNameFromPackageJson(ctx),
925
- config.projectCreation.questions,
926
- config.projectCreation.defaults
927
- );
986
+ const projectName = await getProjectNameFromPackageJson(ctx);
987
+ const defaultAnswersMapper = questionDefaultValuesMapper({
988
+ name: projectName,
989
+ nodeVersion: getProjectNodeVersionDefault
990
+ });
991
+ const questions = defaultAnswersMapper(config.projectCreation.questions);
992
+ const defaultValues = {
993
+ ...config.projectCreation.defaults,
994
+ ...getDefaultsFromQuestions(questions)
995
+ };
928
996
  const projectAnswersDefaulted = fp.defaults(defaultValues);
929
997
  const projectAnswers = await inquirer__default.default.prompt(questions);
930
998
  const projectInput = projectAnswersDefaulted(projectAnswers);
@@ -1026,6 +1094,7 @@ const buildLogsServiceFactory = ({ logger }) => {
1026
1094
  if (retries > MAX_RETRIES) {
1027
1095
  spinner.fail("We were unable to connect to the server to get build logs at this time.");
1028
1096
  es.close();
1097
+ clearExistingTimeout();
1029
1098
  reject(new Error("Max retries reached"));
1030
1099
  }
1031
1100
  };
@@ -1100,17 +1169,7 @@ async function upload(ctx, project, token, maxProjectFileSize) {
1100
1169
  return data.build_id;
1101
1170
  } catch (e) {
1102
1171
  progressBar.stop();
1103
- if (e instanceof axios.AxiosError && e.response?.data) {
1104
- if (e.response.status === 404) {
1105
- ctx.logger.error(
1106
- `The project does not exist. Remove the ${LOCAL_SAVE_FILENAME} file and try again.`
1107
- );
1108
- } else {
1109
- ctx.logger.error(e.response.data);
1110
- }
1111
- } else {
1112
- ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1113
- }
1172
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1114
1173
  ctx.logger.debug(e);
1115
1174
  } finally {
1116
1175
  await fse__namespace.default.remove(tarFilePath);
@@ -1122,11 +1181,11 @@ async function upload(ctx, project, token, maxProjectFileSize) {
1122
1181
  process.exit(1);
1123
1182
  }
1124
1183
  }
1125
- async function getProject(ctx) {
1184
+ async function getProject$1(ctx) {
1126
1185
  const { project } = await retrieve();
1127
1186
  if (!project) {
1128
1187
  try {
1129
- return await action$3(ctx);
1188
+ return await action$5(ctx);
1130
1189
  } catch (e) {
1131
1190
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1132
1191
  ctx.logger.debug(e);
@@ -1135,25 +1194,73 @@ async function getProject(ctx) {
1135
1194
  }
1136
1195
  return project;
1137
1196
  }
1138
- const action$2 = async (ctx) => {
1197
+ async function getConfig({
1198
+ ctx,
1199
+ cloudApiService
1200
+ }) {
1201
+ try {
1202
+ const { data: cliConfig2 } = await cloudApiService.config();
1203
+ return cliConfig2;
1204
+ } catch (e) {
1205
+ ctx.logger.debug("Failed to get cli config", e);
1206
+ return null;
1207
+ }
1208
+ }
1209
+ const action$4 = async (ctx) => {
1139
1210
  const { getValidToken } = await tokenServiceFactory(ctx);
1140
1211
  const token = await getValidToken(ctx, promptLogin);
1141
1212
  if (!token) {
1142
1213
  return;
1143
1214
  }
1144
- const project = await getProject(ctx);
1215
+ const project = await getProject$1(ctx);
1145
1216
  if (!project) {
1146
1217
  return;
1147
1218
  }
1148
- const cloudApiService = await cloudApiFactory(ctx);
1219
+ const cloudApiService = await cloudApiFactory(ctx, token);
1149
1220
  try {
1150
- await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1221
+ const {
1222
+ data: { data: projectData, metadata }
1223
+ } = await cloudApiService.getProject({ name: project.name });
1224
+ const isProjectSuspended = projectData.suspendedAt;
1225
+ if (isProjectSuspended) {
1226
+ ctx.logger.log(
1227
+ "\n Oops! This project has been suspended. \n\n Please reactivate it from the dashboard to continue deploying: "
1228
+ );
1229
+ ctx.logger.log(chalk__default.default.underline(`${metadata.dashboardUrls.project}`));
1230
+ return;
1231
+ }
1151
1232
  } catch (e) {
1152
- ctx.logger.debug("Failed to track willDeploy", e);
1233
+ if (e instanceof axios.AxiosError && e.response?.data) {
1234
+ if (e.response.status === 404) {
1235
+ ctx.logger.warn(
1236
+ `The project associated with this folder does not exist in Strapi Cloud.
1237
+ Please link your local project to an existing Strapi Cloud project using the ${chalk__default.default.cyan(
1238
+ "link"
1239
+ )} command before deploying.`
1240
+ );
1241
+ } else {
1242
+ ctx.logger.error(e.response.data);
1243
+ }
1244
+ } else {
1245
+ ctx.logger.error(
1246
+ "An error occurred while retrieving the project's information. Please try again later."
1247
+ );
1248
+ }
1249
+ ctx.logger.debug(e);
1250
+ return;
1153
1251
  }
1252
+ await trackEvent(ctx, cloudApiService, "willDeployWithCLI", {
1253
+ projectInternalName: project.name
1254
+ });
1154
1255
  const notificationService = notificationServiceFactory(ctx);
1155
1256
  const buildLogsService = buildLogsServiceFactory(ctx);
1156
- const { data: cliConfig2 } = await cloudApiService.config();
1257
+ const cliConfig2 = await getConfig({ ctx, cloudApiService });
1258
+ if (!cliConfig2) {
1259
+ ctx.logger.error(
1260
+ "An error occurred while retrieving data from Strapi Cloud. Please check your network or try again later."
1261
+ );
1262
+ return;
1263
+ }
1157
1264
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
1158
1265
  if (Number.isNaN(maxSize)) {
1159
1266
  ctx.logger.debug(
@@ -1175,10 +1282,11 @@ const action$2 = async (ctx) => {
1175
1282
  chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1176
1283
  );
1177
1284
  } catch (e) {
1285
+ ctx.logger.debug(e);
1178
1286
  if (e instanceof Error) {
1179
1287
  ctx.logger.error(e.message);
1180
1288
  } else {
1181
- throw e;
1289
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1182
1290
  }
1183
1291
  }
1184
1292
  };
@@ -1209,17 +1317,163 @@ const runAction = (name2, action2) => (...args) => {
1209
1317
  process.exit(1);
1210
1318
  });
1211
1319
  };
1212
- const command$4 = ({ command: command2, ctx }) => {
1213
- 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));
1320
+ const command$6 = ({ ctx }) => {
1321
+ return commander.createCommand("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$4)(ctx));
1214
1322
  };
1215
1323
  const deployProject = {
1216
1324
  name: "deploy-project",
1217
1325
  description: "Deploy a Strapi Cloud project",
1218
- action: action$2,
1219
- command: command$4
1326
+ action: action$4,
1327
+ command: command$6
1220
1328
  };
1221
- const command$3 = ({ command: command2, ctx }) => {
1222
- command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1329
+ const QUIT_OPTION = "Quit";
1330
+ async function getExistingConfig(ctx) {
1331
+ try {
1332
+ return await retrieve();
1333
+ } catch (e) {
1334
+ ctx.logger.debug("Failed to get project config", e);
1335
+ ctx.logger.error("An error occurred while retrieving config data from your local project.");
1336
+ return null;
1337
+ }
1338
+ }
1339
+ async function promptForRelink(ctx, cloudApiService, existingConfig) {
1340
+ if (existingConfig && existingConfig.project) {
1341
+ const { shouldRelink } = await inquirer__default.default.prompt([
1342
+ {
1343
+ type: "confirm",
1344
+ name: "shouldRelink",
1345
+ message: `A project named ${chalk__default.default.cyan(
1346
+ existingConfig.project.displayName ? existingConfig.project.displayName : existingConfig.project.name
1347
+ )} is already linked to this local folder. Do you want to update the link?`,
1348
+ default: false
1349
+ }
1350
+ ]);
1351
+ if (!shouldRelink) {
1352
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1353
+ currentProjectName: existingConfig.project?.name
1354
+ });
1355
+ return false;
1356
+ }
1357
+ }
1358
+ return true;
1359
+ }
1360
+ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1361
+ const spinner = ctx.logger.spinner("Fetching your projects...\n").start();
1362
+ try {
1363
+ const {
1364
+ data: { data: projectList }
1365
+ } = await cloudApiService.listLinkProjects();
1366
+ spinner.succeed();
1367
+ if (!Array.isArray(projectList)) {
1368
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1369
+ return null;
1370
+ }
1371
+ const projects = projectList.filter(
1372
+ (project) => !(project.isMaintainer || project.name === existingConfig?.project?.name)
1373
+ ).map((project) => {
1374
+ return {
1375
+ name: project.displayName,
1376
+ value: { name: project.name, displayName: project.displayName }
1377
+ };
1378
+ });
1379
+ if (projects.length === 0) {
1380
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1381
+ return null;
1382
+ }
1383
+ return projects;
1384
+ } catch (e) {
1385
+ spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1386
+ ctx.logger.debug("Failed to list projects", e);
1387
+ return null;
1388
+ }
1389
+ }
1390
+ async function getUserSelection(ctx, projects) {
1391
+ const { logger } = ctx;
1392
+ try {
1393
+ const answer = await inquirer__default.default.prompt([
1394
+ {
1395
+ type: "list",
1396
+ name: "linkProject",
1397
+ message: "Which project do you want to link?",
1398
+ choices: [...projects, { name: chalk__default.default.grey(`(${QUIT_OPTION})`), value: null }]
1399
+ }
1400
+ ]);
1401
+ if (!answer.linkProject) {
1402
+ return null;
1403
+ }
1404
+ return answer;
1405
+ } catch (e) {
1406
+ logger.debug("Failed to get user input", e);
1407
+ logger.error("An error occurred while trying to get your input.");
1408
+ return null;
1409
+ }
1410
+ }
1411
+ const action$3 = async (ctx) => {
1412
+ const { getValidToken } = await tokenServiceFactory(ctx);
1413
+ const token = await getValidToken(ctx, promptLogin);
1414
+ const { logger } = ctx;
1415
+ if (!token) {
1416
+ return;
1417
+ }
1418
+ const cloudApiService = await cloudApiFactory(ctx, token);
1419
+ const existingConfig = await getExistingConfig(ctx);
1420
+ const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
1421
+ if (!shouldRelink) {
1422
+ return;
1423
+ }
1424
+ await trackEvent(ctx, cloudApiService, "willLinkProject", {});
1425
+ const projects = await getProjectsList(
1426
+ ctx,
1427
+ cloudApiService,
1428
+ existingConfig
1429
+ );
1430
+ if (!projects) {
1431
+ return;
1432
+ }
1433
+ const answer = await getUserSelection(ctx, projects);
1434
+ if (!answer) {
1435
+ return;
1436
+ }
1437
+ try {
1438
+ const { confirmAction } = await inquirer__default.default.prompt([
1439
+ {
1440
+ type: "confirm",
1441
+ name: "confirmAction",
1442
+ message: "Warning: Once linked, deploying from CLI will replace the existing project and its data. Confirm to proceed:",
1443
+ default: false
1444
+ }
1445
+ ]);
1446
+ if (!confirmAction) {
1447
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1448
+ cancelledProjectName: answer.linkProject.name,
1449
+ currentProjectName: existingConfig ? existingConfig.project?.name : null
1450
+ });
1451
+ return;
1452
+ }
1453
+ await save({ project: answer.linkProject });
1454
+ logger.log(`Project ${chalk__default.default.cyan(answer.linkProject.displayName)} linked successfully.`);
1455
+ await trackEvent(ctx, cloudApiService, "didLinkProject", {
1456
+ projectInternalName: answer.linkProject
1457
+ });
1458
+ } catch (e) {
1459
+ logger.debug("Failed to link project", e);
1460
+ logger.error("An error occurred while linking the project.");
1461
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1462
+ projectInternalName: answer.linkProject
1463
+ });
1464
+ }
1465
+ };
1466
+ const command$5 = ({ command: command2, ctx }) => {
1467
+ 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$3)(ctx));
1468
+ };
1469
+ const link = {
1470
+ name: "link-project",
1471
+ description: "Link a local directory to a Strapi Cloud project",
1472
+ action: action$3,
1473
+ command: command$5
1474
+ };
1475
+ const command$4 = ({ ctx }) => {
1476
+ return commander.createCommand("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1223
1477
  "after",
1224
1478
  "\nAfter running this command, you will be prompted to enter your authentication information."
1225
1479
  ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
@@ -1228,10 +1482,10 @@ const login = {
1228
1482
  name: "login",
1229
1483
  description: "Strapi Cloud Login",
1230
1484
  action: loginAction,
1231
- command: command$3
1485
+ command: command$4
1232
1486
  };
1233
1487
  const openModule = import("open");
1234
- const action$1 = async (ctx) => {
1488
+ const action$2 = async (ctx) => {
1235
1489
  const { logger } = ctx;
1236
1490
  const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
1237
1491
  const token = await retrieveToken();
@@ -1261,31 +1515,27 @@ const action$1 = async (ctx) => {
1261
1515
  logger.error("🥲 Oops! Something went wrong while logging you out. Please try again.");
1262
1516
  logger.debug(e);
1263
1517
  }
1264
- try {
1265
- await cloudApiService.track("didLogout", { loginMethod: "cli" });
1266
- } catch (e) {
1267
- logger.debug("Failed to track logout event", e);
1268
- }
1518
+ await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
1269
1519
  };
1270
- const command$2 = ({ command: command2, ctx }) => {
1271
- 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));
1520
+ const command$3 = ({ ctx }) => {
1521
+ return commander.createCommand("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$2)(ctx));
1272
1522
  };
1273
1523
  const logout = {
1274
1524
  name: "logout",
1275
1525
  description: "Strapi Cloud Logout",
1276
- action: action$1,
1277
- command: command$2
1526
+ action: action$2,
1527
+ command: command$3
1278
1528
  };
1279
- const command$1 = ({ command: command2, ctx }) => {
1280
- 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));
1529
+ const command$2 = ({ ctx }) => {
1530
+ return commander.createCommand("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$5)(ctx));
1281
1531
  };
1282
1532
  const createProject = {
1283
1533
  name: "create-project",
1284
1534
  description: "Create a new project",
1285
- action: action$3,
1286
- command: command$1
1535
+ action: action$5,
1536
+ command: command$2
1287
1537
  };
1288
- const action = async (ctx) => {
1538
+ const action$1 = async (ctx) => {
1289
1539
  const { getValidToken } = await tokenServiceFactory(ctx);
1290
1540
  const token = await getValidToken(ctx, promptLogin);
1291
1541
  const { logger } = ctx;
@@ -1305,23 +1555,99 @@ const action = async (ctx) => {
1305
1555
  spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1306
1556
  }
1307
1557
  };
1308
- const command = ({ command: command2, ctx }) => {
1309
- 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));
1558
+ const command$1 = ({ command: command2, ctx }) => {
1559
+ 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$1)(ctx));
1310
1560
  };
1311
1561
  const listProjects = {
1312
1562
  name: "list-projects",
1313
1563
  description: "List Strapi Cloud projects",
1564
+ action: action$1,
1565
+ command: command$1
1566
+ };
1567
+ async function getProject(ctx) {
1568
+ const { project } = await retrieve();
1569
+ if (!project) {
1570
+ ctx.logger.warn(
1571
+ `
1572
+ We couldn't find a valid local project config.
1573
+ Please link your local project to an existing Strapi Cloud project using the ${chalk__default.default.cyan(
1574
+ "link"
1575
+ )} command`
1576
+ );
1577
+ process.exit(1);
1578
+ }
1579
+ return project;
1580
+ }
1581
+ const action = async (ctx) => {
1582
+ const { getValidToken } = await tokenServiceFactory(ctx);
1583
+ const token = await getValidToken(ctx, promptLogin);
1584
+ const { logger } = ctx;
1585
+ if (!token) {
1586
+ return;
1587
+ }
1588
+ const project = await getProject(ctx);
1589
+ if (!project) {
1590
+ ctx.logger.debug(`No valid local project configuration was found.`);
1591
+ return;
1592
+ }
1593
+ const cloudApiService = await cloudApiFactory(ctx, token);
1594
+ const spinner = logger.spinner("Fetching environments...").start();
1595
+ await trackEvent(ctx, cloudApiService, "willListEnvironment", {
1596
+ projectInternalName: project.name
1597
+ });
1598
+ try {
1599
+ const {
1600
+ data: { data: environmentsList }
1601
+ } = await cloudApiService.listEnvironments({ name: project.name });
1602
+ spinner.succeed();
1603
+ logger.log(environmentsList);
1604
+ await trackEvent(ctx, cloudApiService, "didListEnvironment", {
1605
+ projectInternalName: project.name
1606
+ });
1607
+ } catch (e) {
1608
+ if (e.response && e.response.status === 404) {
1609
+ spinner.succeed();
1610
+ logger.warn(
1611
+ `
1612
+ The project associated with this folder does not exist in Strapi Cloud.
1613
+ Please link your local project to an existing Strapi Cloud project using the ${chalk__default.default.cyan(
1614
+ "link"
1615
+ )} command`
1616
+ );
1617
+ } else {
1618
+ spinner.fail("An error occurred while fetching environments data from Strapi Cloud.");
1619
+ logger.debug("Failed to list environments", e);
1620
+ }
1621
+ await trackEvent(ctx, cloudApiService, "didNotListEnvironment", {
1622
+ projectInternalName: project.name
1623
+ });
1624
+ }
1625
+ };
1626
+ function defineCloudNamespace(command2) {
1627
+ return command2.command("cloud").description("Manage Strapi Cloud projects");
1628
+ }
1629
+ const command = ({ command: command2, ctx }) => {
1630
+ const cloud = defineCloudNamespace(command2);
1631
+ cloud.command("environments").description("Alias for cloud environment list").action(() => runAction("list", action)(ctx));
1632
+ const environment = cloud.command("environment").description("Manage environments for a Strapi Cloud project");
1633
+ 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));
1634
+ };
1635
+ const listEnvironments = {
1636
+ name: "list-environments",
1637
+ description: "List Strapi Cloud environments",
1314
1638
  action,
1315
1639
  command
1316
1640
  };
1317
1641
  const cli = {
1318
1642
  deployProject,
1643
+ link,
1319
1644
  login,
1320
1645
  logout,
1321
1646
  createProject,
1322
- listProjects
1647
+ listProjects,
1648
+ listEnvironments
1323
1649
  };
1324
- const cloudCommands = [deployProject, login, logout, listProjects];
1650
+ const cloudCommands = [deployProject, link, login, logout, listProjects, listEnvironments];
1325
1651
  async function initCloudCLIConfig() {
1326
1652
  const localConfig = await getLocalConfig();
1327
1653
  if (!localConfig.deviceId) {
@@ -1337,7 +1663,10 @@ async function buildStrapiCloudCommands({
1337
1663
  await initCloudCLIConfig();
1338
1664
  for (const cloudCommand of cloudCommands) {
1339
1665
  try {
1340
- await cloudCommand.command({ command: command2, ctx, argv });
1666
+ const subCommand = await cloudCommand.command({ command: command2, ctx, argv });
1667
+ if (subCommand) {
1668
+ command2.addCommand(subCommand);
1669
+ }
1341
1670
  } catch (e) {
1342
1671
  console.error(`Failed to load command ${cloudCommand.name}`, e);
1343
1672
  }