@strapi/cloud-cli 0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b → 0.0.0-next.ec9b1b708d4d319f2b8b39d9397bd752d250d541

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 (43) hide show
  1. package/dist/bin.js +1 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/index.js +388 -74
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +387 -73
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/src/cloud/command.d.ts +3 -0
  8. package/dist/src/cloud/command.d.ts.map +1 -0
  9. package/dist/src/create-project/command.d.ts.map +1 -1
  10. package/dist/src/deploy-project/action.d.ts +4 -1
  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/link/action.d.ts +4 -0
  16. package/dist/src/environment/link/action.d.ts.map +1 -0
  17. package/dist/src/environment/link/command.d.ts +4 -0
  18. package/dist/src/environment/link/command.d.ts.map +1 -0
  19. package/dist/src/environment/link/index.d.ts +7 -0
  20. package/dist/src/environment/link/index.d.ts.map +1 -0
  21. package/dist/src/environment/list/action.d.ts +4 -0
  22. package/dist/src/environment/list/action.d.ts.map +1 -0
  23. package/dist/src/environment/list/command.d.ts +4 -0
  24. package/dist/src/environment/list/command.d.ts.map +1 -0
  25. package/dist/src/environment/list/index.d.ts +7 -0
  26. package/dist/src/environment/list/index.d.ts.map +1 -0
  27. package/dist/src/index.d.ts +2 -0
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/link/action.d.ts.map +1 -1
  30. package/dist/src/login/command.d.ts.map +1 -1
  31. package/dist/src/logout/command.d.ts.map +1 -1
  32. package/dist/src/services/cli-api.d.ts +23 -3
  33. package/dist/src/services/cli-api.d.ts.map +1 -1
  34. package/dist/src/services/strapi-info-save.d.ts +15 -2
  35. package/dist/src/services/strapi-info-save.d.ts.map +1 -1
  36. package/dist/src/types.d.ts +4 -1
  37. package/dist/src/types.d.ts.map +1 -1
  38. package/dist/src/utils/get-local-config.d.ts +6 -0
  39. package/dist/src/utils/get-local-config.d.ts.map +1 -0
  40. package/dist/src/utils/pkg.d.ts.map +1 -1
  41. package/dist/src/utils/tests/compress-files.test.d.ts +2 -0
  42. package/dist/src/utils/tests/compress-files.test.d.ts.map +1 -0
  43. package/package.json +8 -8
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import crypto$1 from "crypto";
2
2
  import * as fse from "fs-extra";
3
3
  import fse__default from "fs-extra";
4
+ import inquirer from "inquirer";
4
5
  import * as path from "path";
5
6
  import path__default from "path";
6
7
  import chalk from "chalk";
@@ -9,10 +10,10 @@ import * as crypto from "node:crypto";
9
10
  import { env } from "@strapi/utils";
10
11
  import * as tar from "tar";
11
12
  import { minimatch } from "minimatch";
12
- import inquirer from "inquirer";
13
13
  import { defaults, has } from "lodash/fp";
14
14
  import os from "os";
15
15
  import XDGAppPaths from "xdg-app-paths";
16
+ import { merge } from "lodash";
16
17
  import jwksClient from "jwks-rsa";
17
18
  import jwt from "jsonwebtoken";
18
19
  import stringify from "fast-safe-stringify";
@@ -21,6 +22,7 @@ import * as cliProgress from "cli-progress";
21
22
  import pkgUp from "pkg-up";
22
23
  import * as yup from "yup";
23
24
  import EventSource from "eventsource";
25
+ import { createCommand } from "commander";
24
26
  const apiConfig = {
25
27
  apiBaseUrl: env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
26
28
  dashboardBaseUrl: env("STRAPI_CLI_CLOUD_DASHBOARD", "https://cloud.strapi.io")
@@ -117,7 +119,7 @@ async function getConfigPath() {
117
119
  }
118
120
  return configPath;
119
121
  }
120
- async function getLocalConfig() {
122
+ async function getLocalConfig$1() {
121
123
  const configPath = await getConfigPath();
122
124
  const configFilePath = path__default.join(configPath, CONFIG_FILENAME);
123
125
  await fse__default.ensureFile(configFilePath);
@@ -133,7 +135,7 @@ async function saveLocalConfig(data) {
133
135
  await fse__default.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
134
136
  }
135
137
  const name = "@strapi/cloud-cli";
136
- const version = "4.25.9";
138
+ const version = "5.4.0";
137
139
  const description = "Commands to interact with the Strapi Cloud";
138
140
  const keywords = [
139
141
  "strapi",
@@ -178,14 +180,14 @@ const scripts = {
178
180
  watch: "pack-up watch"
179
181
  };
180
182
  const dependencies = {
181
- "@strapi/utils": "4.25.9",
183
+ "@strapi/utils": "5.4.0",
182
184
  axios: "1.7.4",
183
185
  chalk: "4.1.2",
184
186
  "cli-progress": "3.12.0",
185
187
  commander: "8.3.0",
186
188
  eventsource: "2.0.2",
187
189
  "fast-safe-stringify": "2.1.1",
188
- "fs-extra": "10.0.0",
190
+ "fs-extra": "11.2.0",
189
191
  inquirer: "8.2.5",
190
192
  jsonwebtoken: "9.0.0",
191
193
  "jwks-rsa": "3.1.0",
@@ -194,7 +196,7 @@ const dependencies = {
194
196
  open: "8.4.0",
195
197
  ora: "5.4.1",
196
198
  "pkg-up": "3.1.0",
197
- tar: "6.1.13",
199
+ tar: "6.2.1",
198
200
  "xdg-app-paths": "8.3.0",
199
201
  yup: "0.32.9"
200
202
  };
@@ -203,13 +205,14 @@ const devDependencies = {
203
205
  "@types/cli-progress": "3.11.5",
204
206
  "@types/eventsource": "1.1.15",
205
207
  "@types/lodash": "^4.14.191",
206
- "eslint-config-custom": "4.25.9",
207
- tsconfig: "4.25.9"
208
+ "eslint-config-custom": "5.4.0",
209
+ tsconfig: "5.4.0"
208
210
  };
209
211
  const engines = {
210
- node: ">=18.0.0 <=20.x.x",
212
+ node: ">=18.0.0 <=22.x.x",
211
213
  npm: ">=6.0.0"
212
214
  };
215
+ const gitHead = "7d785703f52464577d077c4618cbe68b44f8a9cd";
213
216
  const packageJson = {
214
217
  name,
215
218
  version,
@@ -230,11 +233,12 @@ const packageJson = {
230
233
  scripts,
231
234
  dependencies,
232
235
  devDependencies,
233
- engines
236
+ engines,
237
+ gitHead
234
238
  };
235
239
  const VERSION = "v1";
236
240
  async function cloudApiFactory({ logger }, token) {
237
- const localConfig = await getLocalConfig();
241
+ const localConfig = await getLocalConfig$1();
238
242
  const customHeaders = {
239
243
  "x-device-id": localConfig.deviceId,
240
244
  "x-app-version": packageJson.version,
@@ -257,7 +261,7 @@ async function cloudApiFactory({ logger }, token) {
257
261
  deploy({ filePath, project }, { onUploadProgress }) {
258
262
  return axiosCloudAPI.post(
259
263
  `/deploy/${project.name}`,
260
- { file: fse__default.createReadStream(filePath) },
264
+ { file: fse__default.createReadStream(filePath), targetEnvironment: project.targetEnvironment },
261
265
  {
262
266
  headers: {
263
267
  "Content-Type": "multipart/form-data"
@@ -328,6 +332,34 @@ async function cloudApiFactory({ logger }, token) {
328
332
  throw error;
329
333
  }
330
334
  },
335
+ async listEnvironments({ name: name2 }) {
336
+ try {
337
+ const response = await axiosCloudAPI.get(`/projects/${name2}/environments`);
338
+ if (response.status !== 200) {
339
+ throw new Error("Error fetching cloud environments from the server.");
340
+ }
341
+ return response;
342
+ } catch (error) {
343
+ logger.debug(
344
+ "🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
345
+ );
346
+ throw error;
347
+ }
348
+ },
349
+ async listLinkEnvironments({ name: name2 }) {
350
+ try {
351
+ const response = await axiosCloudAPI.get(`/projects/${name2}/environments-linkable`);
352
+ if (response.status !== 200) {
353
+ throw new Error("Error fetching cloud environments from the server.");
354
+ }
355
+ return response;
356
+ } catch (error) {
357
+ logger.debug(
358
+ "🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
359
+ );
360
+ throw error;
361
+ }
362
+ },
331
363
  async getProject({ name: name2 }) {
332
364
  try {
333
365
  const response = await axiosCloudAPI.get(`/projects/${name2}`);
@@ -351,26 +383,43 @@ async function cloudApiFactory({ logger }, token) {
351
383
  };
352
384
  }
353
385
  const LOCAL_SAVE_FILENAME = ".strapi-cloud.json";
386
+ const getFilePath = (directoryPath) => path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
354
387
  async function save(data, { directoryPath } = {}) {
355
- const alreadyInFileData = await retrieve({ directoryPath });
356
- const storedData = { ...alreadyInFileData, ...data };
357
- const pathToFile = path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
388
+ const pathToFile = getFilePath(directoryPath);
358
389
  await fse__default.ensureDir(path__default.dirname(pathToFile));
359
- await fse__default.writeJson(pathToFile, storedData, { encoding: "utf8" });
390
+ await fse__default.writeJson(pathToFile, data, { encoding: "utf8" });
360
391
  }
361
392
  async function retrieve({
362
393
  directoryPath
363
394
  } = {}) {
364
- const pathToFile = path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
395
+ const pathToFile = getFilePath(directoryPath);
365
396
  const pathExists = await fse__default.pathExists(pathToFile);
366
397
  if (!pathExists) {
367
398
  return {};
368
399
  }
369
400
  return fse__default.readJSON(pathToFile, { encoding: "utf8" });
370
401
  }
402
+ async function patch(patchData, { directoryPath } = {}) {
403
+ const pathToFile = getFilePath(directoryPath);
404
+ const existingData = await retrieve({ directoryPath });
405
+ if (!existingData) {
406
+ throw new Error("No configuration data found to patch.");
407
+ }
408
+ const newData = merge(existingData, patchData);
409
+ await fse__default.writeJson(pathToFile, newData, { encoding: "utf8" });
410
+ }
411
+ async function deleteConfig({ directoryPath } = {}) {
412
+ const pathToFile = getFilePath(directoryPath);
413
+ const pathExists = await fse__default.pathExists(pathToFile);
414
+ if (pathExists) {
415
+ await fse__default.remove(pathToFile);
416
+ }
417
+ }
371
418
  const strapiInfoSave = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
372
419
  __proto__: null,
373
420
  LOCAL_SAVE_FILENAME,
421
+ deleteConfig,
422
+ patch,
374
423
  retrieve,
375
424
  save
376
425
  }, Symbol.toStringTag, { value: "Module" }));
@@ -378,7 +427,7 @@ let cliConfig;
378
427
  async function tokenServiceFactory({ logger }) {
379
428
  const cloudApiService = await cloudApiFactory({ logger });
380
429
  async function saveToken(str) {
381
- const appConfig = await getLocalConfig();
430
+ const appConfig = await getLocalConfig$1();
382
431
  if (!appConfig) {
383
432
  logger.error("There was a problem saving your token. Please try again.");
384
433
  return;
@@ -392,7 +441,7 @@ async function tokenServiceFactory({ logger }) {
392
441
  }
393
442
  }
394
443
  async function retrieveToken() {
395
- const appConfig = await getLocalConfig();
444
+ const appConfig = await getLocalConfig$1();
396
445
  if (appConfig.token) {
397
446
  if (await isTokenValid(appConfig.token)) {
398
447
  return appConfig.token;
@@ -454,7 +503,7 @@ async function tokenServiceFactory({ logger }) {
454
503
  }
455
504
  }
456
505
  async function eraseToken() {
457
- const appConfig = await getLocalConfig();
506
+ const appConfig = await getLocalConfig$1();
458
507
  if (!appConfig) {
459
508
  return;
460
509
  }
@@ -615,21 +664,24 @@ yup.object({
615
664
  name: yup.string().required(),
616
665
  exports: yup.lazy(
617
666
  (value) => yup.object(
618
- typeof value === "object" ? Object.entries(value).reduce((acc, [key, value2]) => {
619
- if (typeof value2 === "object") {
620
- acc[key] = yup.object({
621
- types: yup.string().optional(),
622
- source: yup.string().required(),
623
- module: yup.string().optional(),
624
- import: yup.string().required(),
625
- require: yup.string().required(),
626
- default: yup.string().required()
627
- }).noUnknown(true);
628
- } else {
629
- acc[key] = yup.string().matches(/^\.\/.*\.json$/).required();
630
- }
631
- return acc;
632
- }, {}) : void 0
667
+ typeof value === "object" ? Object.entries(value).reduce(
668
+ (acc, [key, value2]) => {
669
+ if (typeof value2 === "object") {
670
+ acc[key] = yup.object({
671
+ types: yup.string().optional(),
672
+ source: yup.string().required(),
673
+ module: yup.string().optional(),
674
+ import: yup.string().required(),
675
+ require: yup.string().required(),
676
+ default: yup.string().required()
677
+ }).noUnknown(true);
678
+ } else {
679
+ acc[key] = yup.string().matches(/^\.\/.*\.json$/).required();
680
+ }
681
+ return acc;
682
+ },
683
+ {}
684
+ ) : void 0
633
685
  ).optional()
634
686
  )
635
687
  });
@@ -897,7 +949,7 @@ async function createProject$1(ctx, cloudApi, projectInput) {
897
949
  throw e;
898
950
  }
899
951
  }
900
- const action$4 = async (ctx) => {
952
+ const action$6 = async (ctx) => {
901
953
  const { logger } = ctx;
902
954
  const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
903
955
  const token = await getValidToken(ctx, promptLogin);
@@ -1026,6 +1078,32 @@ const buildLogsServiceFactory = ({ logger }) => {
1026
1078
  });
1027
1079
  };
1028
1080
  };
1081
+ const QUIT_OPTION$2 = "Quit";
1082
+ async function promptForEnvironment(environments) {
1083
+ const choices = environments.map((env2) => ({ name: env2, value: env2 }));
1084
+ const { selectedEnvironment } = await inquirer.prompt([
1085
+ {
1086
+ type: "list",
1087
+ name: "selectedEnvironment",
1088
+ message: "Select the environment to deploy:",
1089
+ choices: [...choices, { name: chalk.grey(`(${QUIT_OPTION$2})`), value: null }]
1090
+ }
1091
+ ]);
1092
+ if (selectedEnvironment === null) {
1093
+ process.exit(1);
1094
+ }
1095
+ const { confirm } = await inquirer.prompt([
1096
+ {
1097
+ type: "confirm",
1098
+ name: "confirm",
1099
+ message: `Do you want to proceed with deployment to ${chalk.cyan(selectedEnvironment)}?`
1100
+ }
1101
+ ]);
1102
+ if (!confirm) {
1103
+ process.exit(1);
1104
+ }
1105
+ return selectedEnvironment;
1106
+ }
1029
1107
  async function upload(ctx, project, token, maxProjectFileSize) {
1030
1108
  const cloudApi = await cloudApiFactory(ctx, token);
1031
1109
  try {
@@ -1108,7 +1186,7 @@ async function getProject(ctx) {
1108
1186
  const { project } = await retrieve();
1109
1187
  if (!project) {
1110
1188
  try {
1111
- return await action$4(ctx);
1189
+ return await action$6(ctx);
1112
1190
  } catch (e) {
1113
1191
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
1114
1192
  ctx.logger.debug(e);
@@ -1129,7 +1207,26 @@ async function getConfig({
1129
1207
  return null;
1130
1208
  }
1131
1209
  }
1132
- const action$3 = async (ctx) => {
1210
+ function validateEnvironment(ctx, environment, environments) {
1211
+ if (!environments.includes(environment)) {
1212
+ ctx.logger.error(`Environment ${environment} does not exist.`);
1213
+ process.exit(1);
1214
+ }
1215
+ }
1216
+ async function getTargetEnvironment(ctx, opts, project, environments) {
1217
+ if (opts.env) {
1218
+ validateEnvironment(ctx, opts.env, environments);
1219
+ return opts.env;
1220
+ }
1221
+ if (project.targetEnvironment) {
1222
+ return project.targetEnvironment;
1223
+ }
1224
+ if (environments.length > 1) {
1225
+ return promptForEnvironment(environments);
1226
+ }
1227
+ return environments[0];
1228
+ }
1229
+ const action$5 = async (ctx, opts) => {
1133
1230
  const { getValidToken } = await tokenServiceFactory(ctx);
1134
1231
  const token = await getValidToken(ctx, promptLogin);
1135
1232
  if (!token) {
@@ -1140,11 +1237,13 @@ const action$3 = async (ctx) => {
1140
1237
  return;
1141
1238
  }
1142
1239
  const cloudApiService = await cloudApiFactory(ctx, token);
1240
+ let environments;
1143
1241
  try {
1144
1242
  const {
1145
1243
  data: { data: projectData, metadata }
1146
1244
  } = await cloudApiService.getProject({ name: project.name });
1147
1245
  const isProjectSuspended = projectData.suspendedAt;
1246
+ environments = projectData.environments;
1148
1247
  if (isProjectSuspended) {
1149
1248
  ctx.logger.log(
1150
1249
  "\n Oops! This project has been suspended. \n\n Please reactivate it from the dashboard to continue deploying: "
@@ -1191,11 +1290,15 @@ Please link your local project to an existing Strapi Cloud project using the ${c
1191
1290
  );
1192
1291
  maxSize = 1e8;
1193
1292
  }
1293
+ project.targetEnvironment = await getTargetEnvironment(ctx, opts, project, environments);
1194
1294
  const buildId = await upload(ctx, project, token, maxSize);
1195
1295
  if (!buildId) {
1196
1296
  return;
1197
1297
  }
1198
1298
  try {
1299
+ ctx.logger.log(
1300
+ `🚀 Deploying project to ${chalk.cyan(project.targetEnvironment ?? `production`)} environment...`
1301
+ );
1199
1302
  notificationService(`${apiConfig.apiBaseUrl}/notifications`, token, cliConfig2);
1200
1303
  await buildLogsService(`${apiConfig.apiBaseUrl}/v1/logs/${buildId}`, token, cliConfig2);
1201
1304
  ctx.logger.log(
@@ -1240,17 +1343,16 @@ const runAction = (name2, action2) => (...args) => {
1240
1343
  process.exit(1);
1241
1344
  });
1242
1345
  };
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));
1346
+ const command$7 = ({ ctx }) => {
1347
+ return 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").option("-e, --env <name>", "Specify the environment to deploy").action((opts) => runAction("deploy", action$5)(ctx, opts));
1245
1348
  };
1246
1349
  const deployProject = {
1247
1350
  name: "deploy-project",
1248
1351
  description: "Deploy a Strapi Cloud project",
1249
- action: action$3,
1250
- command: command$5
1352
+ action: action$5,
1353
+ command: command$7
1251
1354
  };
1252
- const QUIT_OPTION = "Quit";
1253
- async function getExistingConfig(ctx) {
1355
+ async function getLocalConfig(ctx) {
1254
1356
  try {
1255
1357
  return await retrieve();
1256
1358
  } catch (e) {
@@ -1259,6 +1361,21 @@ async function getExistingConfig(ctx) {
1259
1361
  return null;
1260
1362
  }
1261
1363
  }
1364
+ async function getLocalProject(ctx) {
1365
+ const localConfig = await getLocalConfig(ctx);
1366
+ if (!localConfig || !localConfig.project) {
1367
+ ctx.logger.warn(
1368
+ `
1369
+ We couldn't find a valid local project config.
1370
+ Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
1371
+ "link"
1372
+ )} command.`
1373
+ );
1374
+ process.exit(1);
1375
+ }
1376
+ return localConfig.project;
1377
+ }
1378
+ const QUIT_OPTION$1 = "Quit";
1262
1379
  async function promptForRelink(ctx, cloudApiService, existingConfig) {
1263
1380
  if (existingConfig && existingConfig.project) {
1264
1381
  const { shouldRelink } = await inquirer.prompt([
@@ -1288,7 +1405,7 @@ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1288
1405
  } = await cloudApiService.listLinkProjects();
1289
1406
  spinner.succeed();
1290
1407
  if (!Array.isArray(projectList)) {
1291
- ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1408
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud.");
1292
1409
  return null;
1293
1410
  }
1294
1411
  const projects = projectList.filter(
@@ -1300,7 +1417,7 @@ async function getProjectsList(ctx, cloudApiService, existingConfig) {
1300
1417
  };
1301
1418
  });
1302
1419
  if (projects.length === 0) {
1303
- ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud");
1420
+ ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud.");
1304
1421
  return null;
1305
1422
  }
1306
1423
  return projects;
@@ -1318,7 +1435,7 @@ async function getUserSelection(ctx, projects) {
1318
1435
  type: "list",
1319
1436
  name: "linkProject",
1320
1437
  message: "Which project do you want to link?",
1321
- choices: [...projects, { name: chalk.grey(`(${QUIT_OPTION})`), value: null }]
1438
+ choices: [...projects, { name: chalk.grey(`(${QUIT_OPTION$1})`), value: null }]
1322
1439
  }
1323
1440
  ]);
1324
1441
  if (!answer.linkProject) {
@@ -1331,7 +1448,7 @@ async function getUserSelection(ctx, projects) {
1331
1448
  return null;
1332
1449
  }
1333
1450
  }
1334
- const action$2 = async (ctx) => {
1451
+ const action$4 = async (ctx) => {
1335
1452
  const { getValidToken } = await tokenServiceFactory(ctx);
1336
1453
  const token = await getValidToken(ctx, promptLogin);
1337
1454
  const { logger } = ctx;
@@ -1339,7 +1456,7 @@ const action$2 = async (ctx) => {
1339
1456
  return;
1340
1457
  }
1341
1458
  const cloudApiService = await cloudApiFactory(ctx, token);
1342
- const existingConfig = await getExistingConfig(ctx);
1459
+ const existingConfig = await getLocalConfig(ctx);
1343
1460
  const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
1344
1461
  if (!shouldRelink) {
1345
1462
  return;
@@ -1374,7 +1491,9 @@ const action$2 = async (ctx) => {
1374
1491
  return;
1375
1492
  }
1376
1493
  await save({ project: answer.linkProject });
1377
- logger.log(`Project ${chalk.cyan(answer.linkProject.displayName)} linked successfully.`);
1494
+ logger.log(
1495
+ ` You have successfully linked your project to ${chalk.cyan(answer.linkProject.displayName)}. You are now able to deploy your project.`
1496
+ );
1378
1497
  await trackEvent(ctx, cloudApiService, "didLinkProject", {
1379
1498
  projectInternalName: answer.linkProject
1380
1499
  });
@@ -1386,17 +1505,17 @@ const action$2 = async (ctx) => {
1386
1505
  });
1387
1506
  }
1388
1507
  };
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));
1508
+ const command$6 = ({ command: command2, ctx }) => {
1509
+ 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$4)(ctx));
1391
1510
  };
1392
1511
  const link = {
1393
1512
  name: "link-project",
1394
1513
  description: "Link a local directory to a Strapi Cloud project",
1395
- action: action$2,
1396
- command: command$4
1514
+ action: action$4,
1515
+ command: command$6
1397
1516
  };
1398
- const command$3 = ({ command: command2, ctx }) => {
1399
- command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1517
+ const command$5 = ({ ctx }) => {
1518
+ return createCommand("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1400
1519
  "after",
1401
1520
  "\nAfter running this command, you will be prompted to enter your authentication information."
1402
1521
  ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
@@ -1405,10 +1524,10 @@ const login = {
1405
1524
  name: "login",
1406
1525
  description: "Strapi Cloud Login",
1407
1526
  action: loginAction,
1408
- command: command$3
1527
+ command: command$5
1409
1528
  };
1410
1529
  const openModule = import("open");
1411
- const action$1 = async (ctx) => {
1530
+ const action$3 = async (ctx) => {
1412
1531
  const { logger } = ctx;
1413
1532
  const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
1414
1533
  const token = await retrieveToken();
@@ -1440,25 +1559,25 @@ const action$1 = async (ctx) => {
1440
1559
  }
1441
1560
  await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
1442
1561
  };
1443
- const command$2 = ({ command: command2, ctx }) => {
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));
1562
+ const command$4 = ({ ctx }) => {
1563
+ return 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$3)(ctx));
1445
1564
  };
1446
1565
  const logout = {
1447
1566
  name: "logout",
1448
1567
  description: "Strapi Cloud Logout",
1449
- action: action$1,
1450
- command: command$2
1568
+ action: action$3,
1569
+ command: command$4
1451
1570
  };
1452
- const command$1 = ({ command: command2, 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));
1571
+ const command$3 = ({ ctx }) => {
1572
+ return 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$6)(ctx));
1454
1573
  };
1455
1574
  const createProject = {
1456
1575
  name: "create-project",
1457
1576
  description: "Create a new project",
1458
- action: action$4,
1459
- command: command$1
1577
+ action: action$6,
1578
+ command: command$3
1460
1579
  };
1461
- const action = async (ctx) => {
1580
+ const action$2 = async (ctx) => {
1462
1581
  const { getValidToken } = await tokenServiceFactory(ctx);
1463
1582
  const token = await getValidToken(ctx, promptLogin);
1464
1583
  const { logger } = ctx;
@@ -1478,12 +1597,194 @@ const action = async (ctx) => {
1478
1597
  spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
1479
1598
  }
1480
1599
  };
1481
- const command = ({ command: command2, 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));
1600
+ const command$2 = ({ command: command2, ctx }) => {
1601
+ 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$2)(ctx));
1483
1602
  };
1484
1603
  const listProjects = {
1485
1604
  name: "list-projects",
1486
1605
  description: "List Strapi Cloud projects",
1606
+ action: action$2,
1607
+ command: command$2
1608
+ };
1609
+ const action$1 = async (ctx) => {
1610
+ const { getValidToken } = await tokenServiceFactory(ctx);
1611
+ const token = await getValidToken(ctx, promptLogin);
1612
+ const { logger } = ctx;
1613
+ if (!token) {
1614
+ return;
1615
+ }
1616
+ const project = await getLocalProject(ctx);
1617
+ if (!project) {
1618
+ ctx.logger.debug(`No valid local project configuration was found.`);
1619
+ return;
1620
+ }
1621
+ const cloudApiService = await cloudApiFactory(ctx, token);
1622
+ const spinner = logger.spinner("Fetching environments...").start();
1623
+ await trackEvent(ctx, cloudApiService, "willListEnvironment", {
1624
+ projectInternalName: project.name
1625
+ });
1626
+ try {
1627
+ const {
1628
+ data: { data: environmentsList }
1629
+ } = await cloudApiService.listEnvironments({ name: project.name });
1630
+ spinner.succeed();
1631
+ logger.log(environmentsList);
1632
+ await trackEvent(ctx, cloudApiService, "didListEnvironment", {
1633
+ projectInternalName: project.name
1634
+ });
1635
+ } catch (e) {
1636
+ if (e.response && e.response.status === 404) {
1637
+ spinner.succeed();
1638
+ logger.warn(
1639
+ `
1640
+ The project associated with this folder does not exist in Strapi Cloud.
1641
+ Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
1642
+ "link"
1643
+ )} command`
1644
+ );
1645
+ } else {
1646
+ spinner.fail("An error occurred while fetching environments data from Strapi Cloud.");
1647
+ logger.debug("Failed to list environments", e);
1648
+ }
1649
+ await trackEvent(ctx, cloudApiService, "didNotListEnvironment", {
1650
+ projectInternalName: project.name
1651
+ });
1652
+ }
1653
+ };
1654
+ function defineCloudNamespace(command2, ctx) {
1655
+ const cloud = command2.command("cloud").description("Manage Strapi Cloud projects");
1656
+ cloud.command("environments").description("Alias for cloud environment list").action(() => runAction("list", action$1)(ctx));
1657
+ return cloud;
1658
+ }
1659
+ let environmentCmd = null;
1660
+ const initializeEnvironmentCommand = (command2, ctx) => {
1661
+ if (!environmentCmd) {
1662
+ const cloud = defineCloudNamespace(command2, ctx);
1663
+ environmentCmd = cloud.command("environment").description("Manage environments");
1664
+ }
1665
+ return environmentCmd;
1666
+ };
1667
+ const command$1 = ({ command: command2, ctx }) => {
1668
+ const environmentCmd2 = initializeEnvironmentCommand(command2, ctx);
1669
+ environmentCmd2.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$1)(ctx));
1670
+ };
1671
+ const listEnvironments = {
1672
+ name: "list-environments",
1673
+ description: "List Strapi Cloud environments",
1674
+ action: action$1,
1675
+ command: command$1
1676
+ };
1677
+ const QUIT_OPTION = "Quit";
1678
+ const action = async (ctx) => {
1679
+ const { getValidToken } = await tokenServiceFactory(ctx);
1680
+ const token = await getValidToken(ctx, promptLogin);
1681
+ const { logger } = ctx;
1682
+ if (!token) {
1683
+ return;
1684
+ }
1685
+ const project = await getLocalProject(ctx);
1686
+ if (!project) {
1687
+ logger.debug(`No valid local project configuration was found.`);
1688
+ return;
1689
+ }
1690
+ const cloudApiService = await cloudApiFactory(ctx, token);
1691
+ const environments = await getEnvironmentsList(ctx, cloudApiService, project);
1692
+ if (!environments) {
1693
+ logger.debug(`Fetching environments failed.`);
1694
+ return;
1695
+ }
1696
+ if (environments.length === 0) {
1697
+ logger.log(
1698
+ `The only available environment is already linked. You can add a new one from your project settings on the Strapi Cloud dashboard.`
1699
+ );
1700
+ return;
1701
+ }
1702
+ const answer = await promptUserForEnvironment(ctx, environments);
1703
+ if (!answer) {
1704
+ return;
1705
+ }
1706
+ await trackEvent(ctx, cloudApiService, "willLinkEnvironment", {
1707
+ projectName: project.name,
1708
+ environmentName: answer.targetEnvironment
1709
+ });
1710
+ try {
1711
+ await patch({ project: { targetEnvironment: answer.targetEnvironment } });
1712
+ } catch (e) {
1713
+ await trackEvent(ctx, cloudApiService, "didNotLinkEnvironment", {
1714
+ projectName: project.name,
1715
+ environmentName: answer.targetEnvironment
1716
+ });
1717
+ logger.debug("Failed to link environment", e);
1718
+ logger.error(
1719
+ "Failed to link the environment. If this issue persists, try re-linking your project or contact support."
1720
+ );
1721
+ process.exit(1);
1722
+ }
1723
+ logger.log(
1724
+ ` You have successfully linked your project to ${chalk.cyan(answer.targetEnvironment)}, on ${chalk.cyan(project.displayName)}. You are now able to deploy your project.`
1725
+ );
1726
+ await trackEvent(ctx, cloudApiService, "didLinkEnvironment", {
1727
+ projectName: project.name,
1728
+ environmentName: answer.targetEnvironment
1729
+ });
1730
+ };
1731
+ async function promptUserForEnvironment(ctx, environments) {
1732
+ const { logger } = ctx;
1733
+ try {
1734
+ const answer = await inquirer.prompt([
1735
+ {
1736
+ type: "list",
1737
+ name: "targetEnvironment",
1738
+ message: "Which environment do you want to link?",
1739
+ choices: [...environments, { name: chalk.grey(`(${QUIT_OPTION})`), value: null }]
1740
+ }
1741
+ ]);
1742
+ if (!answer.targetEnvironment) {
1743
+ return null;
1744
+ }
1745
+ return answer;
1746
+ } catch (e) {
1747
+ logger.debug("Failed to get user input", e);
1748
+ logger.error("An error occurred while trying to get your environment selection.");
1749
+ return null;
1750
+ }
1751
+ }
1752
+ async function getEnvironmentsList(ctx, cloudApiService, project) {
1753
+ const spinner = ctx.logger.spinner("Fetching environments...\n").start();
1754
+ try {
1755
+ const {
1756
+ data: { data: environmentsList }
1757
+ } = await cloudApiService.listLinkEnvironments({ name: project.name });
1758
+ if (!Array.isArray(environmentsList) || environmentsList.length === 0) {
1759
+ throw new Error("Environments not found in server response");
1760
+ }
1761
+ spinner.succeed();
1762
+ return environmentsList.filter(
1763
+ (environment) => environment.name !== project.targetEnvironment
1764
+ );
1765
+ } catch (e) {
1766
+ if (e.response && e.response.status === 404) {
1767
+ spinner.succeed();
1768
+ ctx.logger.warn(
1769
+ `
1770
+ The project associated with this folder does not exist in Strapi Cloud.
1771
+ Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
1772
+ "link"
1773
+ )} command.`
1774
+ );
1775
+ } else {
1776
+ spinner.fail("An error occurred while fetching environments data from Strapi Cloud.");
1777
+ ctx.logger.debug("Failed to list environments", e);
1778
+ }
1779
+ }
1780
+ }
1781
+ const command = ({ command: command2, ctx }) => {
1782
+ const environmentCmd2 = initializeEnvironmentCommand(command2, ctx);
1783
+ environmentCmd2.command("link").description("Link project to a specific Strapi Cloud project environment").option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("link", action)(ctx));
1784
+ };
1785
+ const linkEnvironment = {
1786
+ name: "link-environment",
1787
+ description: "Link Strapi Cloud environment to a local project",
1487
1788
  action,
1488
1789
  command
1489
1790
  };
@@ -1493,11 +1794,21 @@ const cli = {
1493
1794
  login,
1494
1795
  logout,
1495
1796
  createProject,
1496
- listProjects
1797
+ linkEnvironment,
1798
+ listProjects,
1799
+ listEnvironments
1497
1800
  };
1498
- const cloudCommands = [deployProject, link, login, logout, listProjects];
1801
+ const cloudCommands = [
1802
+ deployProject,
1803
+ link,
1804
+ login,
1805
+ logout,
1806
+ linkEnvironment,
1807
+ listProjects,
1808
+ listEnvironments
1809
+ ];
1499
1810
  async function initCloudCLIConfig() {
1500
- const localConfig = await getLocalConfig();
1811
+ const localConfig = await getLocalConfig$1();
1501
1812
  if (!localConfig.deviceId) {
1502
1813
  localConfig.deviceId = crypto$1.randomUUID();
1503
1814
  }
@@ -1511,7 +1822,10 @@ async function buildStrapiCloudCommands({
1511
1822
  await initCloudCLIConfig();
1512
1823
  for (const cloudCommand of cloudCommands) {
1513
1824
  try {
1514
- await cloudCommand.command({ command: command2, ctx, argv });
1825
+ const subCommand = await cloudCommand.command({ command: command2, ctx, argv });
1826
+ if (subCommand) {
1827
+ command2.addCommand(subCommand);
1828
+ }
1515
1829
  } catch (e) {
1516
1830
  console.error(`Failed to load command ${cloudCommand.name}`, e);
1517
1831
  }