@strapi/cloud-cli 0.0.0-next.6785f47fadddb80404c824be0a0732c652850b45 → 0.0.0-next.7f1333f1967e625c57ab16648c057aea08c9dddb

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 (37) hide show
  1. package/dist/index.js +336 -96
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +336 -95
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/src/create-project/action.d.ts.map +1 -1
  6. package/dist/src/create-project/command.d.ts.map +1 -1
  7. package/dist/src/create-project/utils/project-questions.utils.d.ts +20 -0
  8. package/dist/src/create-project/utils/project-questions.utils.d.ts.map +1 -0
  9. package/dist/src/deploy-project/action.d.ts.map +1 -1
  10. package/dist/src/deploy-project/command.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/link/action.d.ts +4 -0
  14. package/dist/src/link/action.d.ts.map +1 -0
  15. package/dist/src/link/command.d.ts +7 -0
  16. package/dist/src/link/command.d.ts.map +1 -0
  17. package/dist/src/link/index.d.ts +7 -0
  18. package/dist/src/link/index.d.ts.map +1 -0
  19. package/dist/src/login/action.d.ts.map +1 -1
  20. package/dist/src/login/command.d.ts.map +1 -1
  21. package/dist/src/logout/action.d.ts.map +1 -1
  22. package/dist/src/logout/command.d.ts.map +1 -1
  23. package/dist/src/services/build-logs.d.ts.map +1 -1
  24. package/dist/src/services/cli-api.d.ts +29 -6
  25. package/dist/src/services/cli-api.d.ts.map +1 -1
  26. package/dist/src/services/strapi-info-save.d.ts +1 -1
  27. package/dist/src/services/strapi-info-save.d.ts.map +1 -1
  28. package/dist/src/types.d.ts +2 -1
  29. package/dist/src/types.d.ts.map +1 -1
  30. package/dist/src/utils/analytics.d.ts +4 -0
  31. package/dist/src/utils/analytics.d.ts.map +1 -0
  32. package/dist/src/utils/pkg.d.ts.map +1 -1
  33. package/dist/src/utils/tests/compress-files.test.d.ts +2 -0
  34. package/dist/src/utils/tests/compress-files.test.d.ts.map +1 -0
  35. package/package.json +8 -8
  36. package/dist/src/create-project/utils/apply-default-name.d.ts +0 -7
  37. 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.0.2-beta.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,8 +263,8 @@ 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
270
  node: ">=18.0.0 <=20.x.x",
@@ -375,6 +374,34 @@ 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 getProject({ name: name2 }) {
392
+ try {
393
+ const response = await axiosCloudAPI.get(`/projects/${name2}`);
394
+ if (response.status !== 200) {
395
+ throw new Error("Error fetching project's details.");
396
+ }
397
+ return response;
398
+ } catch (error) {
399
+ logger.debug(
400
+ "🥲 Oops! There was a problem retrieving your project's details. Please try again."
401
+ );
402
+ throw error;
403
+ }
404
+ },
378
405
  track(event, payload = {}) {
379
406
  return axiosCloudAPI.post("/track", {
380
407
  event,
@@ -648,21 +675,24 @@ yup__namespace.object({
648
675
  name: yup__namespace.string().required(),
649
676
  exports: yup__namespace.lazy(
650
677
  (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
678
+ typeof value === "object" ? Object.entries(value).reduce(
679
+ (acc, [key, value2]) => {
680
+ if (typeof value2 === "object") {
681
+ acc[key] = yup__namespace.object({
682
+ types: yup__namespace.string().optional(),
683
+ source: yup__namespace.string().required(),
684
+ module: yup__namespace.string().optional(),
685
+ import: yup__namespace.string().required(),
686
+ require: yup__namespace.string().required(),
687
+ default: yup__namespace.string().required()
688
+ }).noUnknown(true);
689
+ } else {
690
+ acc[key] = yup__namespace.string().matches(/^\.\/.*\.json$/).required();
691
+ }
692
+ return acc;
693
+ },
694
+ {}
695
+ ) : void 0
666
696
  ).optional()
667
697
  )
668
698
  });
@@ -684,18 +714,13 @@ async function getProjectNameFromPackageJson(ctx) {
684
714
  return "my-strapi-project";
685
715
  }
686
716
  }
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
- }
717
+ const trackEvent = async (ctx, cloudApiService, eventName, eventData) => {
718
+ try {
719
+ await cloudApiService.track(eventName, eventData);
720
+ } catch (e) {
721
+ ctx.logger.debug(`Failed to track ${eventName}`, e);
722
+ }
723
+ };
699
724
  const openModule$1 = import("open");
700
725
  async function promptLogin(ctx) {
701
726
  const response = await inquirer__default.default.prompt([
@@ -716,13 +741,6 @@ async function loginAction(ctx) {
716
741
  const tokenService = await tokenServiceFactory(ctx);
717
742
  const existingToken = await tokenService.retrieveToken();
718
743
  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
744
  if (existingToken) {
727
745
  const isTokenValid = await tokenService.isTokenValid(existingToken);
728
746
  if (isTokenValid) {
@@ -754,11 +772,7 @@ async function loginAction(ctx) {
754
772
  logger.debug(e);
755
773
  return false;
756
774
  }
757
- try {
758
- await cloudApiService.track("willLoginAttempt", {});
759
- } catch (e) {
760
- logger.debug("Failed to track login attempt", e);
761
- }
775
+ await trackEvent(ctx, cloudApiService, "willLoginAttempt", {});
762
776
  logger.debug("🔐 Creating device authentication request...", {
763
777
  client_id: cliConfig2.clientId,
764
778
  scope: cliConfig2.scope,
@@ -838,13 +852,13 @@ async function loginAction(ctx) {
838
852
  "There seems to be a problem with your login information. Please try logging in again."
839
853
  );
840
854
  spinnerFail();
841
- await trackFailedLogin();
855
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
842
856
  return false;
843
857
  }
844
858
  if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
845
859
  logger.debug(e);
846
860
  spinnerFail();
847
- await trackFailedLogin();
861
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
848
862
  return false;
849
863
  }
850
864
  await new Promise((resolve) => {
@@ -858,15 +872,50 @@ async function loginAction(ctx) {
858
872
  "To access your dashboard, please copy and paste the following URL into your web browser:"
859
873
  );
860
874
  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
- }
875
+ await trackEvent(ctx, cloudApiService, "didLogin", { loginMethod: "cli" });
866
876
  };
867
877
  await authenticate();
868
878
  return isAuthenticated;
869
879
  }
880
+ function questionDefaultValuesMapper(questionsMap) {
881
+ return (questions) => {
882
+ return questions.map((question) => {
883
+ const questionName = question.name;
884
+ if (questionName in questionsMap) {
885
+ const questionDefault = questionsMap[questionName];
886
+ if (typeof questionDefault === "function") {
887
+ return {
888
+ ...question,
889
+ default: questionDefault(question)
890
+ };
891
+ }
892
+ return {
893
+ ...question,
894
+ default: questionDefault
895
+ };
896
+ }
897
+ return question;
898
+ });
899
+ };
900
+ }
901
+ function getDefaultsFromQuestions(questions) {
902
+ return questions.reduce((acc, question) => {
903
+ if (question.default && question.name) {
904
+ return { ...acc, [question.name]: question.default };
905
+ }
906
+ return acc;
907
+ }, {});
908
+ }
909
+ function getProjectNodeVersionDefault(question) {
910
+ const currentNodeVersion = process.versions.node.split(".")[0];
911
+ if (question.type === "list" && Array.isArray(question.choices)) {
912
+ const choice = question.choices.find((choice2) => choice2.value === currentNodeVersion);
913
+ if (choice) {
914
+ return choice.value;
915
+ }
916
+ }
917
+ return question.default;
918
+ }
870
919
  async function handleError(ctx, error) {
871
920
  const { logger } = ctx;
872
921
  logger.debug(error);
@@ -911,7 +960,7 @@ async function createProject$1(ctx, cloudApi, projectInput) {
911
960
  throw e;
912
961
  }
913
962
  }
914
- const action$3 = async (ctx) => {
963
+ const action$4 = async (ctx) => {
915
964
  const { logger } = ctx;
916
965
  const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
917
966
  const token = await getValidToken(ctx, promptLogin);
@@ -920,11 +969,16 @@ const action$3 = async (ctx) => {
920
969
  }
921
970
  const cloudApi = await cloudApiFactory(ctx, token);
922
971
  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
- );
972
+ const projectName = await getProjectNameFromPackageJson(ctx);
973
+ const defaultAnswersMapper = questionDefaultValuesMapper({
974
+ name: projectName,
975
+ nodeVersion: getProjectNodeVersionDefault
976
+ });
977
+ const questions = defaultAnswersMapper(config.projectCreation.questions);
978
+ const defaultValues = {
979
+ ...config.projectCreation.defaults,
980
+ ...getDefaultsFromQuestions(questions)
981
+ };
928
982
  const projectAnswersDefaulted = fp.defaults(defaultValues);
929
983
  const projectAnswers = await inquirer__default.default.prompt(questions);
930
984
  const projectInput = projectAnswersDefaulted(projectAnswers);
@@ -1026,6 +1080,7 @@ const buildLogsServiceFactory = ({ logger }) => {
1026
1080
  if (retries > MAX_RETRIES) {
1027
1081
  spinner.fail("We were unable to connect to the server to get build logs at this time.");
1028
1082
  es.close();
1083
+ clearExistingTimeout();
1029
1084
  reject(new Error("Max retries reached"));
1030
1085
  }
1031
1086
  };
@@ -1100,17 +1155,7 @@ async function upload(ctx, project, token, maxProjectFileSize) {
1100
1155
  return data.build_id;
1101
1156
  } catch (e) {
1102
1157
  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
- }
1158
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1114
1159
  ctx.logger.debug(e);
1115
1160
  } finally {
1116
1161
  await fse__namespace.default.remove(tarFilePath);
@@ -1126,7 +1171,7 @@ async function getProject(ctx) {
1126
1171
  const { project } = await retrieve();
1127
1172
  if (!project) {
1128
1173
  try {
1129
- return await action$3(ctx);
1174
+ return await action$4(ctx);
1130
1175
  } catch (e) {
1131
1176
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1132
1177
  ctx.logger.debug(e);
@@ -1135,7 +1180,19 @@ async function getProject(ctx) {
1135
1180
  }
1136
1181
  return project;
1137
1182
  }
1138
- const action$2 = async (ctx) => {
1183
+ async function getConfig({
1184
+ ctx,
1185
+ cloudApiService
1186
+ }) {
1187
+ try {
1188
+ const { data: cliConfig2 } = await cloudApiService.config();
1189
+ return cliConfig2;
1190
+ } catch (e) {
1191
+ ctx.logger.debug("Failed to get cli config", e);
1192
+ return null;
1193
+ }
1194
+ }
1195
+ const action$3 = async (ctx) => {
1139
1196
  const { getValidToken } = await tokenServiceFactory(ctx);
1140
1197
  const token = await getValidToken(ctx, promptLogin);
1141
1198
  if (!token) {
@@ -1145,15 +1202,51 @@ const action$2 = async (ctx) => {
1145
1202
  if (!project) {
1146
1203
  return;
1147
1204
  }
1148
- const cloudApiService = await cloudApiFactory(ctx);
1205
+ const cloudApiService = await cloudApiFactory(ctx, token);
1149
1206
  try {
1150
- await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1207
+ const {
1208
+ data: { data: projectData, metadata }
1209
+ } = await cloudApiService.getProject({ name: project.name });
1210
+ const isProjectSuspended = projectData.suspendedAt;
1211
+ if (isProjectSuspended) {
1212
+ ctx.logger.log(
1213
+ "\n Oops! This project has been suspended. \n\n Please reactivate it from the dashboard to continue deploying: "
1214
+ );
1215
+ ctx.logger.log(chalk__default.default.underline(`${metadata.dashboardUrls.project}`));
1216
+ return;
1217
+ }
1151
1218
  } catch (e) {
1152
- ctx.logger.debug("Failed to track willDeploy", e);
1219
+ if (e instanceof axios.AxiosError && e.response?.data) {
1220
+ if (e.response.status === 404) {
1221
+ ctx.logger.warn(
1222
+ `The project associated with this folder does not exist in Strapi Cloud.
1223
+ Please link your local project to an existing Strapi Cloud project using the ${chalk__default.default.cyan(
1224
+ "link"
1225
+ )} command before deploying.`
1226
+ );
1227
+ } else {
1228
+ ctx.logger.error(e.response.data);
1229
+ }
1230
+ } else {
1231
+ ctx.logger.error(
1232
+ "An error occurred while retrieving the project's information. Please try again later."
1233
+ );
1234
+ }
1235
+ ctx.logger.debug(e);
1236
+ return;
1153
1237
  }
1238
+ await trackEvent(ctx, cloudApiService, "willDeployWithCLI", {
1239
+ projectInternalName: project.name
1240
+ });
1154
1241
  const notificationService = notificationServiceFactory(ctx);
1155
1242
  const buildLogsService = buildLogsServiceFactory(ctx);
1156
- const { data: cliConfig2 } = await cloudApiService.config();
1243
+ const cliConfig2 = await getConfig({ ctx, cloudApiService });
1244
+ if (!cliConfig2) {
1245
+ ctx.logger.error(
1246
+ "An error occurred while retrieving data from Strapi Cloud. Please check your network or try again later."
1247
+ );
1248
+ return;
1249
+ }
1157
1250
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
1158
1251
  if (Number.isNaN(maxSize)) {
1159
1252
  ctx.logger.debug(
@@ -1175,10 +1268,11 @@ const action$2 = async (ctx) => {
1175
1268
  chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1176
1269
  );
1177
1270
  } catch (e) {
1271
+ ctx.logger.debug(e);
1178
1272
  if (e instanceof Error) {
1179
1273
  ctx.logger.error(e.message);
1180
1274
  } else {
1181
- throw e;
1275
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1182
1276
  }
1183
1277
  }
1184
1278
  };
@@ -1209,17 +1303,163 @@ const runAction = (name2, action2) => (...args) => {
1209
1303
  process.exit(1);
1210
1304
  });
1211
1305
  };
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));
1306
+ const command$5 = ({ ctx }) => {
1307
+ 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$3)(ctx));
1214
1308
  };
1215
1309
  const deployProject = {
1216
1310
  name: "deploy-project",
1217
1311
  description: "Deploy a Strapi Cloud project",
1312
+ action: action$3,
1313
+ command: command$5
1314
+ };
1315
+ const QUIT_OPTION = "Quit";
1316
+ async function getExistingConfig(ctx) {
1317
+ try {
1318
+ return await retrieve();
1319
+ } catch (e) {
1320
+ ctx.logger.debug("Failed to get project config", e);
1321
+ ctx.logger.error("An error occurred while retrieving config data from your local project.");
1322
+ return null;
1323
+ }
1324
+ }
1325
+ async function promptForRelink(ctx, cloudApiService, existingConfig) {
1326
+ if (existingConfig && existingConfig.project) {
1327
+ const { shouldRelink } = await inquirer__default.default.prompt([
1328
+ {
1329
+ type: "confirm",
1330
+ name: "shouldRelink",
1331
+ message: `A project named ${chalk__default.default.cyan(
1332
+ existingConfig.project.displayName ? existingConfig.project.displayName : existingConfig.project.name
1333
+ )} is already linked to this local folder. Do you want to update the link?`,
1334
+ default: false
1335
+ }
1336
+ ]);
1337
+ if (!shouldRelink) {
1338
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1339
+ currentProjectName: existingConfig.project?.name
1340
+ });
1341
+ return false;
1342
+ }
1343
+ }
1344
+ return true;
1345
+ }
1346
+ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1347
+ const spinner = ctx.logger.spinner("Fetching your projects...\n").start();
1348
+ try {
1349
+ const {
1350
+ data: { data: projectList }
1351
+ } = await cloudApiService.listLinkProjects();
1352
+ spinner.succeed();
1353
+ if (!Array.isArray(projectList)) {
1354
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1355
+ return null;
1356
+ }
1357
+ const projects = projectList.filter(
1358
+ (project) => !(project.isMaintainer || project.name === existingConfig?.project?.name)
1359
+ ).map((project) => {
1360
+ return {
1361
+ name: project.displayName,
1362
+ value: { name: project.name, displayName: project.displayName }
1363
+ };
1364
+ });
1365
+ if (projects.length === 0) {
1366
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1367
+ return null;
1368
+ }
1369
+ return projects;
1370
+ } catch (e) {
1371
+ spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1372
+ ctx.logger.debug("Failed to list projects", e);
1373
+ return null;
1374
+ }
1375
+ }
1376
+ async function getUserSelection(ctx, projects) {
1377
+ const { logger } = ctx;
1378
+ try {
1379
+ const answer = await inquirer__default.default.prompt([
1380
+ {
1381
+ type: "list",
1382
+ name: "linkProject",
1383
+ message: "Which project do you want to link?",
1384
+ choices: [...projects, { name: chalk__default.default.grey(`(${QUIT_OPTION})`), value: null }]
1385
+ }
1386
+ ]);
1387
+ if (!answer.linkProject) {
1388
+ return null;
1389
+ }
1390
+ return answer;
1391
+ } catch (e) {
1392
+ logger.debug("Failed to get user input", e);
1393
+ logger.error("An error occurred while trying to get your input.");
1394
+ return null;
1395
+ }
1396
+ }
1397
+ const action$2 = async (ctx) => {
1398
+ const { getValidToken } = await tokenServiceFactory(ctx);
1399
+ const token = await getValidToken(ctx, promptLogin);
1400
+ const { logger } = ctx;
1401
+ if (!token) {
1402
+ return;
1403
+ }
1404
+ const cloudApiService = await cloudApiFactory(ctx, token);
1405
+ const existingConfig = await getExistingConfig(ctx);
1406
+ const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
1407
+ if (!shouldRelink) {
1408
+ return;
1409
+ }
1410
+ await trackEvent(ctx, cloudApiService, "willLinkProject", {});
1411
+ const projects = await getProjectsList(
1412
+ ctx,
1413
+ cloudApiService,
1414
+ existingConfig
1415
+ );
1416
+ if (!projects) {
1417
+ return;
1418
+ }
1419
+ const answer = await getUserSelection(ctx, projects);
1420
+ if (!answer) {
1421
+ return;
1422
+ }
1423
+ try {
1424
+ const { confirmAction } = await inquirer__default.default.prompt([
1425
+ {
1426
+ type: "confirm",
1427
+ name: "confirmAction",
1428
+ message: "Warning: Once linked, deploying from CLI will replace the existing project and its data. Confirm to proceed:",
1429
+ default: false
1430
+ }
1431
+ ]);
1432
+ if (!confirmAction) {
1433
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1434
+ cancelledProjectName: answer.linkProject.name,
1435
+ currentProjectName: existingConfig ? existingConfig.project?.name : null
1436
+ });
1437
+ return;
1438
+ }
1439
+ await save({ project: answer.linkProject });
1440
+ logger.log(`Project ${chalk__default.default.cyan(answer.linkProject.displayName)} linked successfully.`);
1441
+ await trackEvent(ctx, cloudApiService, "didLinkProject", {
1442
+ projectInternalName: answer.linkProject
1443
+ });
1444
+ } catch (e) {
1445
+ logger.debug("Failed to link project", e);
1446
+ logger.error("An error occurred while linking the project.");
1447
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1448
+ projectInternalName: answer.linkProject
1449
+ });
1450
+ }
1451
+ };
1452
+ const command$4 = ({ command: command2, ctx }) => {
1453
+ 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));
1454
+ };
1455
+ const link = {
1456
+ name: "link-project",
1457
+ description: "Link a local directory to a Strapi Cloud project",
1218
1458
  action: action$2,
1219
1459
  command: command$4
1220
1460
  };
1221
- const command$3 = ({ command: command2, ctx }) => {
1222
- command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1461
+ const command$3 = ({ ctx }) => {
1462
+ return commander.createCommand("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1223
1463
  "after",
1224
1464
  "\nAfter running this command, you will be prompted to enter your authentication information."
1225
1465
  ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
@@ -1261,14 +1501,10 @@ const action$1 = async (ctx) => {
1261
1501
  logger.error("🥲 Oops! Something went wrong while logging you out. Please try again.");
1262
1502
  logger.debug(e);
1263
1503
  }
1264
- try {
1265
- await cloudApiService.track("didLogout", { loginMethod: "cli" });
1266
- } catch (e) {
1267
- logger.debug("Failed to track logout event", e);
1268
- }
1504
+ await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
1269
1505
  };
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));
1506
+ const command$2 = ({ ctx }) => {
1507
+ 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$1)(ctx));
1272
1508
  };
1273
1509
  const logout = {
1274
1510
  name: "logout",
@@ -1276,13 +1512,13 @@ const logout = {
1276
1512
  action: action$1,
1277
1513
  command: command$2
1278
1514
  };
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));
1515
+ const command$1 = ({ ctx }) => {
1516
+ 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$4)(ctx));
1281
1517
  };
1282
1518
  const createProject = {
1283
1519
  name: "create-project",
1284
1520
  description: "Create a new project",
1285
- action: action$3,
1521
+ action: action$4,
1286
1522
  command: command$1
1287
1523
  };
1288
1524
  const action = async (ctx) => {
@@ -1306,7 +1542,7 @@ const action = async (ctx) => {
1306
1542
  }
1307
1543
  };
1308
1544
  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));
1545
+ 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));
1310
1546
  };
1311
1547
  const listProjects = {
1312
1548
  name: "list-projects",
@@ -1316,12 +1552,13 @@ const listProjects = {
1316
1552
  };
1317
1553
  const cli = {
1318
1554
  deployProject,
1555
+ link,
1319
1556
  login,
1320
1557
  logout,
1321
1558
  createProject,
1322
1559
  listProjects
1323
1560
  };
1324
- const cloudCommands = [deployProject, login, logout, listProjects];
1561
+ const cloudCommands = [deployProject, link, login, logout, listProjects];
1325
1562
  async function initCloudCLIConfig() {
1326
1563
  const localConfig = await getLocalConfig();
1327
1564
  if (!localConfig.deviceId) {
@@ -1337,7 +1574,10 @@ async function buildStrapiCloudCommands({
1337
1574
  await initCloudCLIConfig();
1338
1575
  for (const cloudCommand of cloudCommands) {
1339
1576
  try {
1340
- await cloudCommand.command({ command: command2, ctx, argv });
1577
+ const subCommand = await cloudCommand.command({ command: command2, ctx, argv });
1578
+ if (subCommand) {
1579
+ command2.addCommand(subCommand);
1580
+ }
1341
1581
  } catch (e) {
1342
1582
  console.error(`Failed to load command ${cloudCommand.name}`, e);
1343
1583
  }