@strapi/cloud-cli 0.0.0-next.ae38a9dcdeddea4b4c0978686fb024413e5c54f5 → 0.0.0-next.aff9d09e4edf4d38b8a0de5abf83d79bedb28313

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.4";
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.4",
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.4",
208
- tsconfig: "4.25.4"
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,34 @@ 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
+ },
331
+ async getProject({ name: name2 }) {
332
+ try {
333
+ const response = await axiosCloudAPI.get(`/projects/${name2}`);
334
+ if (response.status !== 200) {
335
+ throw new Error("Error fetching project's details.");
336
+ }
337
+ return response;
338
+ } catch (error) {
339
+ logger.debug(
340
+ "🥲 Oops! There was a problem retrieving your project's details. Please try again."
341
+ );
342
+ throw error;
343
+ }
344
+ },
318
345
  track(event, payload = {}) {
319
346
  return axiosCloudAPI.post("/track", {
320
347
  event,
@@ -624,18 +651,13 @@ async function getProjectNameFromPackageJson(ctx) {
624
651
  return "my-strapi-project";
625
652
  }
626
653
  }
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
- }
654
+ const trackEvent = async (ctx, cloudApiService, eventName, eventData) => {
655
+ try {
656
+ await cloudApiService.track(eventName, eventData);
657
+ } catch (e) {
658
+ ctx.logger.debug(`Failed to track ${eventName}`, e);
659
+ }
660
+ };
639
661
  const openModule$1 = import("open");
640
662
  async function promptLogin(ctx) {
641
663
  const response = await inquirer.prompt([
@@ -656,13 +678,6 @@ async function loginAction(ctx) {
656
678
  const tokenService = await tokenServiceFactory(ctx);
657
679
  const existingToken = await tokenService.retrieveToken();
658
680
  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
681
  if (existingToken) {
667
682
  const isTokenValid = await tokenService.isTokenValid(existingToken);
668
683
  if (isTokenValid) {
@@ -694,11 +709,7 @@ async function loginAction(ctx) {
694
709
  logger.debug(e);
695
710
  return false;
696
711
  }
697
- try {
698
- await cloudApiService.track("willLoginAttempt", {});
699
- } catch (e) {
700
- logger.debug("Failed to track login attempt", e);
701
- }
712
+ await trackEvent(ctx, cloudApiService, "willLoginAttempt", {});
702
713
  logger.debug("🔐 Creating device authentication request...", {
703
714
  client_id: cliConfig2.clientId,
704
715
  scope: cliConfig2.scope,
@@ -778,13 +789,13 @@ async function loginAction(ctx) {
778
789
  "There seems to be a problem with your login information. Please try logging in again."
779
790
  );
780
791
  spinnerFail();
781
- await trackFailedLogin();
792
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
782
793
  return false;
783
794
  }
784
795
  if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
785
796
  logger.debug(e);
786
797
  spinnerFail();
787
- await trackFailedLogin();
798
+ await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
788
799
  return false;
789
800
  }
790
801
  await new Promise((resolve) => {
@@ -798,15 +809,50 @@ async function loginAction(ctx) {
798
809
  "To access your dashboard, please copy and paste the following URL into your web browser:"
799
810
  );
800
811
  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
- }
812
+ await trackEvent(ctx, cloudApiService, "didLogin", { loginMethod: "cli" });
806
813
  };
807
814
  await authenticate();
808
815
  return isAuthenticated;
809
816
  }
817
+ function questionDefaultValuesMapper(questionsMap) {
818
+ return (questions) => {
819
+ return questions.map((question) => {
820
+ const questionName = question.name;
821
+ if (questionName in questionsMap) {
822
+ const questionDefault = questionsMap[questionName];
823
+ if (typeof questionDefault === "function") {
824
+ return {
825
+ ...question,
826
+ default: questionDefault(question)
827
+ };
828
+ }
829
+ return {
830
+ ...question,
831
+ default: questionDefault
832
+ };
833
+ }
834
+ return question;
835
+ });
836
+ };
837
+ }
838
+ function getDefaultsFromQuestions(questions) {
839
+ return questions.reduce((acc, question) => {
840
+ if (question.default && question.name) {
841
+ return { ...acc, [question.name]: question.default };
842
+ }
843
+ return acc;
844
+ }, {});
845
+ }
846
+ function getProjectNodeVersionDefault(question) {
847
+ const currentNodeVersion = process.versions.node.split(".")[0];
848
+ if (question.type === "list" && Array.isArray(question.choices)) {
849
+ const choice = question.choices.find((choice2) => choice2.value === currentNodeVersion);
850
+ if (choice) {
851
+ return choice.value;
852
+ }
853
+ }
854
+ return question.default;
855
+ }
810
856
  async function handleError(ctx, error) {
811
857
  const { logger } = ctx;
812
858
  logger.debug(error);
@@ -851,7 +897,7 @@ async function createProject$1(ctx, cloudApi, projectInput) {
851
897
  throw e;
852
898
  }
853
899
  }
854
- const action$3 = async (ctx) => {
900
+ const action$4 = async (ctx) => {
855
901
  const { logger } = ctx;
856
902
  const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
857
903
  const token = await getValidToken(ctx, promptLogin);
@@ -860,11 +906,16 @@ const action$3 = async (ctx) => {
860
906
  }
861
907
  const cloudApi = await cloudApiFactory(ctx, token);
862
908
  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
- );
909
+ const projectName = await getProjectNameFromPackageJson(ctx);
910
+ const defaultAnswersMapper = questionDefaultValuesMapper({
911
+ name: projectName,
912
+ nodeVersion: getProjectNodeVersionDefault
913
+ });
914
+ const questions = defaultAnswersMapper(config.projectCreation.questions);
915
+ const defaultValues = {
916
+ ...config.projectCreation.defaults,
917
+ ...getDefaultsFromQuestions(questions)
918
+ };
868
919
  const projectAnswersDefaulted = defaults(defaultValues);
869
920
  const projectAnswers = await inquirer.prompt(questions);
870
921
  const projectInput = projectAnswersDefaulted(projectAnswers);
@@ -966,6 +1017,7 @@ const buildLogsServiceFactory = ({ logger }) => {
966
1017
  if (retries > MAX_RETRIES) {
967
1018
  spinner.fail("We were unable to connect to the server to get build logs at this time.");
968
1019
  es.close();
1020
+ clearExistingTimeout();
969
1021
  reject(new Error("Max retries reached"));
970
1022
  }
971
1023
  };
@@ -1040,17 +1092,7 @@ async function upload(ctx, project, token, maxProjectFileSize) {
1040
1092
  return data.build_id;
1041
1093
  } catch (e) {
1042
1094
  progressBar.stop();
1043
- if (e instanceof AxiosError && e.response?.data) {
1044
- if (e.response.status === 404) {
1045
- ctx.logger.error(
1046
- `The project does not exist. Remove the ${LOCAL_SAVE_FILENAME} file and try again.`
1047
- );
1048
- } else {
1049
- ctx.logger.error(e.response.data);
1050
- }
1051
- } else {
1052
- ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1053
- }
1095
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1054
1096
  ctx.logger.debug(e);
1055
1097
  } finally {
1056
1098
  await fse__default.remove(tarFilePath);
@@ -1066,7 +1108,7 @@ async function getProject(ctx) {
1066
1108
  const { project } = await retrieve();
1067
1109
  if (!project) {
1068
1110
  try {
1069
- return await action$3(ctx);
1111
+ return await action$4(ctx);
1070
1112
  } catch (e) {
1071
1113
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1072
1114
  ctx.logger.debug(e);
@@ -1075,7 +1117,19 @@ async function getProject(ctx) {
1075
1117
  }
1076
1118
  return project;
1077
1119
  }
1078
- const action$2 = async (ctx) => {
1120
+ async function getConfig({
1121
+ ctx,
1122
+ cloudApiService
1123
+ }) {
1124
+ try {
1125
+ const { data: cliConfig2 } = await cloudApiService.config();
1126
+ return cliConfig2;
1127
+ } catch (e) {
1128
+ ctx.logger.debug("Failed to get cli config", e);
1129
+ return null;
1130
+ }
1131
+ }
1132
+ const action$3 = async (ctx) => {
1079
1133
  const { getValidToken } = await tokenServiceFactory(ctx);
1080
1134
  const token = await getValidToken(ctx, promptLogin);
1081
1135
  if (!token) {
@@ -1085,15 +1139,51 @@ const action$2 = async (ctx) => {
1085
1139
  if (!project) {
1086
1140
  return;
1087
1141
  }
1088
- const cloudApiService = await cloudApiFactory(ctx);
1142
+ const cloudApiService = await cloudApiFactory(ctx, token);
1089
1143
  try {
1090
- await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1144
+ const {
1145
+ data: { data: projectData, metadata }
1146
+ } = await cloudApiService.getProject({ name: project.name });
1147
+ const isProjectSuspended = projectData.suspendedAt;
1148
+ if (isProjectSuspended) {
1149
+ ctx.logger.log(
1150
+ "\n Oops! This project has been suspended. \n\n Please reactivate it from the dashboard to continue deploying: "
1151
+ );
1152
+ ctx.logger.log(chalk.underline(`${metadata.dashboardUrls.project}`));
1153
+ return;
1154
+ }
1091
1155
  } catch (e) {
1092
- ctx.logger.debug("Failed to track willDeploy", e);
1156
+ if (e instanceof AxiosError && e.response?.data) {
1157
+ if (e.response.status === 404) {
1158
+ ctx.logger.warn(
1159
+ `The project associated with this folder does not exist in Strapi Cloud.
1160
+ Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
1161
+ "link"
1162
+ )} command before deploying.`
1163
+ );
1164
+ } else {
1165
+ ctx.logger.error(e.response.data);
1166
+ }
1167
+ } else {
1168
+ ctx.logger.error(
1169
+ "An error occurred while retrieving the project's information. Please try again later."
1170
+ );
1171
+ }
1172
+ ctx.logger.debug(e);
1173
+ return;
1093
1174
  }
1175
+ await trackEvent(ctx, cloudApiService, "willDeployWithCLI", {
1176
+ projectInternalName: project.name
1177
+ });
1094
1178
  const notificationService = notificationServiceFactory(ctx);
1095
1179
  const buildLogsService = buildLogsServiceFactory(ctx);
1096
- const { data: cliConfig2 } = await cloudApiService.config();
1180
+ const cliConfig2 = await getConfig({ ctx, cloudApiService });
1181
+ if (!cliConfig2) {
1182
+ ctx.logger.error(
1183
+ "An error occurred while retrieving data from Strapi Cloud. Please check your network or try again later."
1184
+ );
1185
+ return;
1186
+ }
1097
1187
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
1098
1188
  if (Number.isNaN(maxSize)) {
1099
1189
  ctx.logger.debug(
@@ -1115,10 +1205,11 @@ const action$2 = async (ctx) => {
1115
1205
  chalk.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1116
1206
  );
1117
1207
  } catch (e) {
1208
+ ctx.logger.debug(e);
1118
1209
  if (e instanceof Error) {
1119
1210
  ctx.logger.error(e.message);
1120
1211
  } else {
1121
- throw e;
1212
+ ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1122
1213
  }
1123
1214
  }
1124
1215
  };
@@ -1149,12 +1240,158 @@ const runAction = (name2, action2) => (...args) => {
1149
1240
  process.exit(1);
1150
1241
  });
1151
1242
  };
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));
1243
+ const command$5 = ({ command: command2, ctx }) => {
1244
+ 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
1245
  };
1155
1246
  const deployProject = {
1156
1247
  name: "deploy-project",
1157
1248
  description: "Deploy a Strapi Cloud project",
1249
+ action: action$3,
1250
+ command: command$5
1251
+ };
1252
+ const QUIT_OPTION = "Quit";
1253
+ async function getExistingConfig(ctx) {
1254
+ try {
1255
+ return await retrieve();
1256
+ } catch (e) {
1257
+ ctx.logger.debug("Failed to get project config", e);
1258
+ ctx.logger.error("An error occurred while retrieving config data from your local project.");
1259
+ return null;
1260
+ }
1261
+ }
1262
+ async function promptForRelink(ctx, cloudApiService, existingConfig) {
1263
+ if (existingConfig && existingConfig.project) {
1264
+ const { shouldRelink } = await inquirer.prompt([
1265
+ {
1266
+ type: "confirm",
1267
+ name: "shouldRelink",
1268
+ message: `A project named ${chalk.cyan(
1269
+ existingConfig.project.displayName ? existingConfig.project.displayName : existingConfig.project.name
1270
+ )} is already linked to this local folder. Do you want to update the link?`,
1271
+ default: false
1272
+ }
1273
+ ]);
1274
+ if (!shouldRelink) {
1275
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1276
+ currentProjectName: existingConfig.project?.name
1277
+ });
1278
+ return false;
1279
+ }
1280
+ }
1281
+ return true;
1282
+ }
1283
+ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1284
+ const spinner = ctx.logger.spinner("Fetching your projects...\n").start();
1285
+ try {
1286
+ const {
1287
+ data: { data: projectList }
1288
+ } = await cloudApiService.listLinkProjects();
1289
+ spinner.succeed();
1290
+ if (!Array.isArray(projectList)) {
1291
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1292
+ return null;
1293
+ }
1294
+ const projects = projectList.filter(
1295
+ (project) => !(project.isMaintainer || project.name === existingConfig?.project?.name)
1296
+ ).map((project) => {
1297
+ return {
1298
+ name: project.displayName,
1299
+ value: { name: project.name, displayName: project.displayName }
1300
+ };
1301
+ });
1302
+ if (projects.length === 0) {
1303
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1304
+ return null;
1305
+ }
1306
+ return projects;
1307
+ } catch (e) {
1308
+ spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1309
+ ctx.logger.debug("Failed to list projects", e);
1310
+ return null;
1311
+ }
1312
+ }
1313
+ async function getUserSelection(ctx, projects) {
1314
+ const { logger } = ctx;
1315
+ try {
1316
+ const answer = await inquirer.prompt([
1317
+ {
1318
+ type: "list",
1319
+ name: "linkProject",
1320
+ message: "Which project do you want to link?",
1321
+ choices: [...projects, { name: chalk.grey(`(${QUIT_OPTION})`), value: null }]
1322
+ }
1323
+ ]);
1324
+ if (!answer.linkProject) {
1325
+ return null;
1326
+ }
1327
+ return answer;
1328
+ } catch (e) {
1329
+ logger.debug("Failed to get user input", e);
1330
+ logger.error("An error occurred while trying to get your input.");
1331
+ return null;
1332
+ }
1333
+ }
1334
+ const action$2 = async (ctx) => {
1335
+ const { getValidToken } = await tokenServiceFactory(ctx);
1336
+ const token = await getValidToken(ctx, promptLogin);
1337
+ const { logger } = ctx;
1338
+ if (!token) {
1339
+ return;
1340
+ }
1341
+ const cloudApiService = await cloudApiFactory(ctx, token);
1342
+ const existingConfig = await getExistingConfig(ctx);
1343
+ const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
1344
+ if (!shouldRelink) {
1345
+ return;
1346
+ }
1347
+ await trackEvent(ctx, cloudApiService, "willLinkProject", {});
1348
+ const projects = await getProjectsList(
1349
+ ctx,
1350
+ cloudApiService,
1351
+ existingConfig
1352
+ );
1353
+ if (!projects) {
1354
+ return;
1355
+ }
1356
+ const answer = await getUserSelection(ctx, projects);
1357
+ if (!answer) {
1358
+ return;
1359
+ }
1360
+ try {
1361
+ const { confirmAction } = await inquirer.prompt([
1362
+ {
1363
+ type: "confirm",
1364
+ name: "confirmAction",
1365
+ message: "Warning: Once linked, deploying from CLI will replace the existing project and its data. Confirm to proceed:",
1366
+ default: false
1367
+ }
1368
+ ]);
1369
+ if (!confirmAction) {
1370
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1371
+ cancelledProjectName: answer.linkProject.name,
1372
+ currentProjectName: existingConfig ? existingConfig.project?.name : null
1373
+ });
1374
+ return;
1375
+ }
1376
+ await save({ project: answer.linkProject });
1377
+ logger.log(`Project ${chalk.cyan(answer.linkProject.displayName)} linked successfully.`);
1378
+ await trackEvent(ctx, cloudApiService, "didLinkProject", {
1379
+ projectInternalName: answer.linkProject
1380
+ });
1381
+ } catch (e) {
1382
+ logger.debug("Failed to link project", e);
1383
+ logger.error("An error occurred while linking the project.");
1384
+ await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
1385
+ projectInternalName: answer.linkProject
1386
+ });
1387
+ }
1388
+ };
1389
+ const command$4 = ({ command: command2, ctx }) => {
1390
+ 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));
1391
+ };
1392
+ const link = {
1393
+ name: "link-project",
1394
+ description: "Link a local directory to a Strapi Cloud project",
1158
1395
  action: action$2,
1159
1396
  command: command$4
1160
1397
  };
@@ -1201,11 +1438,7 @@ const action$1 = async (ctx) => {
1201
1438
  logger.error("🥲 Oops! Something went wrong while logging you out. Please try again.");
1202
1439
  logger.debug(e);
1203
1440
  }
1204
- try {
1205
- await cloudApiService.track("didLogout", { loginMethod: "cli" });
1206
- } catch (e) {
1207
- logger.debug("Failed to track logout event", e);
1208
- }
1441
+ await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
1209
1442
  };
1210
1443
  const command$2 = ({ command: command2, ctx }) => {
1211
1444
  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 +1450,12 @@ const logout = {
1217
1450
  command: command$2
1218
1451
  };
1219
1452
  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));
1453
+ 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
1454
  };
1222
1455
  const createProject = {
1223
1456
  name: "create-project",
1224
1457
  description: "Create a new project",
1225
- action: action$3,
1458
+ action: action$4,
1226
1459
  command: command$1
1227
1460
  };
1228
1461
  const action = async (ctx) => {
@@ -1246,7 +1479,7 @@ const action = async (ctx) => {
1246
1479
  }
1247
1480
  };
1248
1481
  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));
1482
+ 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
1483
  };
1251
1484
  const listProjects = {
1252
1485
  name: "list-projects",
@@ -1256,12 +1489,13 @@ const listProjects = {
1256
1489
  };
1257
1490
  const cli = {
1258
1491
  deployProject,
1492
+ link,
1259
1493
  login,
1260
1494
  logout,
1261
1495
  createProject,
1262
1496
  listProjects
1263
1497
  };
1264
- const cloudCommands = [deployProject, login, logout, listProjects];
1498
+ const cloudCommands = [deployProject, link, login, logout, listProjects];
1265
1499
  async function initCloudCLIConfig() {
1266
1500
  const localConfig = await getLocalConfig();
1267
1501
  if (!localConfig.deviceId) {