@strapi/cloud-cli 0.0.0-experimental.f77206734629a2b88793a7a8abca40388843c656 → 0.0.0-next.48485bcb4aa83c14d6d2cb99cbd47676d0d31b97

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.js CHANGED
@@ -23,11 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
25
  const crypto$1 = require("crypto");
26
- const fs = require("fs");
26
+ const fse = require("fs-extra");
27
27
  const path = require("path");
28
+ const chalk = require("chalk");
28
29
  const axios = require("axios");
29
30
  const crypto = require("node:crypto");
30
31
  const utils = require("@strapi/utils");
32
+ const fs = require("fs");
31
33
  const tar = require("tar");
32
34
  const minimatch = require("minimatch");
33
35
  const inquirer = require("inquirer");
@@ -36,7 +38,6 @@ const os = require("os");
36
38
  const XDGAppPaths = require("xdg-app-paths");
37
39
  const jwksClient = require("jwks-rsa");
38
40
  const jwt = require("jsonwebtoken");
39
- const chalk = require("chalk");
40
41
  const stringify = require("fast-safe-stringify");
41
42
  const ora = require("ora");
42
43
  const cliProgress = require("cli-progress");
@@ -64,17 +65,18 @@ function _interopNamespace(e) {
64
65
  return Object.freeze(n);
65
66
  }
66
67
  const crypto__default = /* @__PURE__ */ _interopDefault(crypto$1);
67
- const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
68
+ const fse__default = /* @__PURE__ */ _interopDefault(fse);
68
69
  const path__namespace = /* @__PURE__ */ _interopNamespace(path);
70
+ const chalk__default = /* @__PURE__ */ _interopDefault(chalk);
69
71
  const axios__default = /* @__PURE__ */ _interopDefault(axios);
70
72
  const crypto__namespace = /* @__PURE__ */ _interopNamespace(crypto);
73
+ const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
71
74
  const tar__namespace = /* @__PURE__ */ _interopNamespace(tar);
72
75
  const inquirer__default = /* @__PURE__ */ _interopDefault(inquirer);
73
76
  const os__default = /* @__PURE__ */ _interopDefault(os);
74
77
  const XDGAppPaths__default = /* @__PURE__ */ _interopDefault(XDGAppPaths);
75
78
  const jwksClient__default = /* @__PURE__ */ _interopDefault(jwksClient);
76
79
  const jwt__default = /* @__PURE__ */ _interopDefault(jwt);
77
- const chalk__default = /* @__PURE__ */ _interopDefault(chalk);
78
80
  const stringify__default = /* @__PURE__ */ _interopDefault(stringify);
79
81
  const ora__default = /* @__PURE__ */ _interopDefault(ora);
80
82
  const cliProgress__namespace = /* @__PURE__ */ _interopNamespace(cliProgress);
@@ -83,7 +85,8 @@ const fs__default = /* @__PURE__ */ _interopDefault(fs$1);
83
85
  const pkgUp__default = /* @__PURE__ */ _interopDefault(pkgUp);
84
86
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
85
87
  const apiConfig = {
86
- apiBaseUrl: utils.env("STRAPI_CLI_CLOUD_API", "https://cli.cloud.strapi.io")
88
+ apiBaseUrl: utils.env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
89
+ dashboardBaseUrl: utils.env("STRAPI_CLI_CLOUD_DASHBOARD", "https://cloud.strapi.io")
87
90
  };
88
91
  const IGNORED_PATTERNS = [
89
92
  "**/.git/**",
@@ -138,7 +141,7 @@ const readGitignore = (folderPath) => {
138
141
  if (!fs__namespace.existsSync(gitignorePath))
139
142
  return [];
140
143
  const gitignoreContent = fs__namespace.readFileSync(gitignorePath, "utf8");
141
- return gitignoreContent.split("\n").filter((line) => Boolean(line.trim()) && !line.startsWith("#"));
144
+ return gitignoreContent.split(/\r?\n/).filter((line) => Boolean(line.trim()) && !line.startsWith("#"));
142
145
  };
143
146
  const compressFilesToTar = async (storagePath, folderToCompress, filename) => {
144
147
  const ignorePatterns = readGitignore(folderToCompress);
@@ -153,48 +156,45 @@ const compressFilesToTar = async (storagePath, folderToCompress, filename) => {
153
156
  };
154
157
  const APP_FOLDER_NAME = "com.strapi.cli";
155
158
  const CONFIG_FILENAME = "config.json";
156
- function checkDirectoryExists(directoryPath) {
159
+ async function checkDirectoryExists(directoryPath) {
157
160
  try {
158
- return fs__namespace.default.lstatSync(directoryPath).isDirectory();
161
+ const fsStat = await fse__default.default.lstat(directoryPath);
162
+ return fsStat.isDirectory();
159
163
  } catch (e) {
160
164
  return false;
161
165
  }
162
166
  }
163
- function getTmpStoragePath() {
167
+ async function getTmpStoragePath() {
164
168
  const storagePath = path__namespace.default.join(os__default.default.tmpdir(), APP_FOLDER_NAME);
165
- if (!checkDirectoryExists(storagePath)) {
166
- fs__namespace.default.mkdirSync(storagePath, { recursive: true });
167
- }
169
+ await fse__default.default.ensureDir(storagePath);
168
170
  return storagePath;
169
171
  }
170
- function getConfigPath() {
172
+ async function getConfigPath() {
171
173
  const configDirs = XDGAppPaths__default.default(APP_FOLDER_NAME).configDirs();
172
174
  const configPath = configDirs.find(checkDirectoryExists);
173
175
  if (!configPath) {
174
- fs__namespace.default.mkdirSync(configDirs[0], { recursive: true });
176
+ await fse__default.default.ensureDir(configDirs[0]);
175
177
  return configDirs[0];
176
178
  }
177
179
  return configPath;
178
180
  }
179
- function getLocalConfig() {
180
- const configPath = getConfigPath();
181
+ async function getLocalConfig() {
182
+ const configPath = await getConfigPath();
181
183
  const configFilePath = path__namespace.default.join(configPath, CONFIG_FILENAME);
182
- if (!fs__namespace.default.existsSync(configFilePath)) {
183
- return {};
184
- }
184
+ await fse__default.default.ensureFile(configFilePath);
185
185
  try {
186
- return JSON.parse(fs__namespace.default.readFileSync(configFilePath, "utf8"));
186
+ return await fse__default.default.readJSON(configFilePath, { encoding: "utf8", throws: true });
187
187
  } catch (e) {
188
188
  return {};
189
189
  }
190
190
  }
191
- function saveLocalConfig(data) {
192
- const configPath = getConfigPath();
191
+ async function saveLocalConfig(data) {
192
+ const configPath = await getConfigPath();
193
193
  const configFilePath = path__namespace.default.join(configPath, CONFIG_FILENAME);
194
- fs__namespace.default.writeFileSync(configFilePath, JSON.stringify(data), { encoding: "utf8", mode: 384 });
194
+ await fse__default.default.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
195
195
  }
196
196
  const name = "@strapi/cloud-cli";
197
- const version = "4.24.3";
197
+ const version = "4.25.1";
198
198
  const description = "Commands to interact with the Strapi Cloud";
199
199
  const keywords = [
200
200
  "strapi",
@@ -238,13 +238,14 @@ const scripts = {
238
238
  watch: "pack-up watch"
239
239
  };
240
240
  const dependencies = {
241
- "@strapi/utils": "4.24.3",
241
+ "@strapi/utils": "4.25.1",
242
242
  axios: "1.6.0",
243
243
  chalk: "4.1.2",
244
244
  "cli-progress": "3.12.0",
245
245
  commander: "8.3.0",
246
246
  eventsource: "2.0.2",
247
247
  "fast-safe-stringify": "2.1.1",
248
+ "fs-extra": "10.0.0",
248
249
  inquirer: "8.2.5",
249
250
  jsonwebtoken: "9.0.0",
250
251
  "jwks-rsa": "3.1.0",
@@ -262,8 +263,8 @@ const devDependencies = {
262
263
  "@types/cli-progress": "3.11.5",
263
264
  "@types/eventsource": "1.1.15",
264
265
  "@types/lodash": "^4.14.191",
265
- "eslint-config-custom": "4.24.3",
266
- tsconfig: "4.24.3"
266
+ "eslint-config-custom": "4.25.1",
267
+ tsconfig: "4.25.1"
267
268
  };
268
269
  const engines = {
269
270
  node: ">=18.0.0 <=20.x.x",
@@ -292,8 +293,8 @@ const packageJson = {
292
293
  engines
293
294
  };
294
295
  const VERSION = "v1";
295
- function cloudApiFactory(token) {
296
- const localConfig = getLocalConfig();
296
+ async function cloudApiFactory({ logger }, token) {
297
+ const localConfig = await getLocalConfig();
297
298
  const customHeaders = {
298
299
  "x-device-id": localConfig.deviceId,
299
300
  "x-app-version": packageJson.version,
@@ -316,7 +317,7 @@ function cloudApiFactory(token) {
316
317
  deploy({ filePath, project }, { onUploadProgress }) {
317
318
  return axiosCloudAPI.post(
318
319
  `/deploy/${project.name}`,
319
- { file: fs__namespace.createReadStream(filePath) },
320
+ { file: fse__default.default.createReadStream(filePath) },
320
321
  {
321
322
  headers: {
322
323
  "Content-Type": "multipart/form-data"
@@ -345,8 +346,19 @@ function cloudApiFactory(token) {
345
346
  getUserInfo() {
346
347
  return axiosCloudAPI.get("/user");
347
348
  },
348
- config() {
349
- return axiosCloudAPI.get("/config");
349
+ async config() {
350
+ try {
351
+ const response = await axiosCloudAPI.get("/config");
352
+ if (response.status !== 200) {
353
+ throw new Error("Error fetching cloud CLI config from the server.");
354
+ }
355
+ return response;
356
+ } catch (error) {
357
+ logger.debug(
358
+ "🥲 Oops! Couldn't retrieve the cloud CLI config from the server. Please try again."
359
+ );
360
+ throw error;
361
+ }
350
362
  },
351
363
  listProjects() {
352
364
  return axiosCloudAPI.get("/projects");
@@ -360,53 +372,48 @@ function cloudApiFactory(token) {
360
372
  };
361
373
  }
362
374
  const LOCAL_SAVE_FILENAME = ".strapi-cloud.json";
363
- function save(data, { directoryPath } = {}) {
364
- const storedData = { ...retrieve(), ...data };
375
+ async function save(data, { directoryPath } = {}) {
376
+ const alreadyInFileData = await retrieve({ directoryPath });
377
+ const storedData = { ...alreadyInFileData, ...data };
365
378
  const pathToFile = path__namespace.default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
366
- if (!fs__namespace.default.existsSync(path__namespace.default.dirname(pathToFile))) {
367
- fs__namespace.default.mkdirSync(path__namespace.default.dirname(pathToFile), { recursive: true });
368
- }
369
- fs__namespace.default.writeFileSync(pathToFile, JSON.stringify(storedData), "utf8");
379
+ await fse__default.default.ensureDir(path__namespace.default.dirname(pathToFile));
380
+ await fse__default.default.writeJson(pathToFile, storedData, { encoding: "utf8" });
370
381
  }
371
- function retrieve({ directoryPath } = {}) {
382
+ async function retrieve({
383
+ directoryPath
384
+ } = {}) {
372
385
  const pathToFile = path__namespace.default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
373
- if (!fs__namespace.default.existsSync(pathToFile)) {
386
+ const pathExists = await fse__default.default.pathExists(pathToFile);
387
+ if (!pathExists) {
374
388
  return {};
375
389
  }
376
- return JSON.parse(fs__namespace.default.readFileSync(pathToFile, "utf8"));
377
- }
378
- function erase({ directoryPath } = {}) {
379
- const pathToFile = path__namespace.default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
380
- if (fs__namespace.default.existsSync(pathToFile)) {
381
- fs__namespace.default.unlinkSync(pathToFile);
382
- }
390
+ return fse__default.default.readJSON(pathToFile, { encoding: "utf8" });
383
391
  }
384
392
  const strapiInfoSave = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
385
393
  __proto__: null,
386
394
  LOCAL_SAVE_FILENAME,
387
- erase,
388
395
  retrieve,
389
396
  save
390
397
  }, Symbol.toStringTag, { value: "Module" }));
391
- const cloudApiService = cloudApiFactory();
392
398
  let cliConfig;
393
- function tokenServiceFactory({ logger }) {
394
- function saveToken(str) {
395
- const appConfig = getLocalConfig();
399
+ async function tokenServiceFactory({ logger }) {
400
+ const cloudApiService = await cloudApiFactory({ logger });
401
+ async function saveToken(str) {
402
+ const appConfig = await getLocalConfig();
396
403
  if (!appConfig) {
397
404
  logger.error("There was a problem saving your token. Please try again.");
398
405
  return;
399
406
  }
400
407
  appConfig.token = str;
401
408
  try {
402
- saveLocalConfig(appConfig);
403
- } catch (error) {
404
- logger.debug(error);
409
+ await saveLocalConfig(appConfig);
410
+ } catch (e) {
411
+ logger.debug(e);
405
412
  logger.error("There was a problem saving your token. Please try again.");
406
413
  }
407
414
  }
408
415
  async function retrieveToken() {
409
- const appConfig = getLocalConfig();
416
+ const appConfig = await getLocalConfig();
410
417
  if (appConfig.token) {
411
418
  if (await isTokenValid(appConfig.token)) {
412
419
  return appConfig.token;
@@ -419,9 +426,9 @@ function tokenServiceFactory({ logger }) {
419
426
  jwksUri: jwksUrl
420
427
  });
421
428
  const getKey = (header, callback) => {
422
- client.getSigningKey(header.kid, (err, key) => {
423
- if (err) {
424
- callback(err);
429
+ client.getSigningKey(header.kid, (e, key) => {
430
+ if (e) {
431
+ callback(e);
425
432
  } else if (key) {
426
433
  const publicKey = "publicKey" in key ? key.publicKey : key.rsaPublicKey;
427
434
  callback(null, publicKey);
@@ -439,14 +446,17 @@ function tokenServiceFactory({ logger }) {
439
446
  "There seems to be a problem with your login information. Please try logging in again."
440
447
  );
441
448
  }
449
+ return Promise.reject(new Error("Invalid token"));
442
450
  }
443
451
  return new Promise((resolve, reject) => {
444
452
  jwt__default.default.verify(idToken, getKey, (err) => {
445
453
  if (err) {
446
454
  reject(err);
447
- } else {
448
- resolve();
449
455
  }
456
+ if (decodedToken.payload.exp < Math.floor(Date.now() / 1e3)) {
457
+ reject(new Error("Token is expired"));
458
+ }
459
+ resolve();
450
460
  });
451
461
  });
452
462
  }
@@ -459,35 +469,36 @@ function tokenServiceFactory({ logger }) {
459
469
  return true;
460
470
  }
461
471
  return false;
462
- } catch (error) {
463
- logger.debug(error);
472
+ } catch (e) {
473
+ logger.debug(e);
464
474
  return false;
465
475
  }
466
476
  }
467
- function eraseToken() {
468
- const appConfig = getLocalConfig();
477
+ async function eraseToken() {
478
+ const appConfig = await getLocalConfig();
469
479
  if (!appConfig) {
470
480
  return;
471
481
  }
472
482
  delete appConfig.token;
473
483
  try {
474
- saveLocalConfig(appConfig);
475
- } catch (error) {
476
- logger.debug(error);
484
+ await saveLocalConfig(appConfig);
485
+ } catch (e) {
486
+ logger.debug(e);
477
487
  logger.error(
478
488
  "There was an issue removing your login information. Please try logging out again."
479
489
  );
490
+ throw e;
480
491
  }
481
492
  }
482
- async function getValidToken() {
483
- const token = await retrieveToken();
484
- if (!token) {
485
- logger.log("No token found. Please login first.");
486
- return null;
487
- }
488
- if (!await isTokenValid(token)) {
489
- logger.log("Unable to proceed: Token is expired or not valid. Please login again.");
490
- return null;
493
+ async function getValidToken(ctx, loginAction2) {
494
+ let token = await retrieveToken();
495
+ while (!token || !await isTokenValid(token)) {
496
+ logger.log(
497
+ token ? "Oops! Your token seems expired or invalid. Please login again." : "We couldn't find a valid token. You need to be logged in to use this feature."
498
+ );
499
+ if (!await loginAction2(ctx))
500
+ return null;
501
+ token = await retrieveToken();
491
502
  }
492
503
  return token;
493
504
  }
@@ -621,57 +632,246 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
621
632
  local: strapiInfoSave,
622
633
  tokenServiceFactory
623
634
  }, Symbol.toStringTag, { value: "Module" }));
624
- function handleError(ctx, error) {
625
- const tokenService = tokenServiceFactory(ctx);
635
+ const openModule$1 = import("open");
636
+ async function promptLogin(ctx) {
637
+ const response = await inquirer__default.default.prompt([
638
+ {
639
+ type: "confirm",
640
+ name: "login",
641
+ message: "Would you like to login?"
642
+ }
643
+ ]);
644
+ if (response.login) {
645
+ const loginSuccessful = await loginAction(ctx);
646
+ return loginSuccessful;
647
+ }
648
+ return false;
649
+ }
650
+ async function loginAction(ctx) {
651
+ const { logger } = ctx;
652
+ const tokenService = await tokenServiceFactory(ctx);
653
+ const existingToken = await tokenService.retrieveToken();
654
+ const cloudApiService = await cloudApiFactory(ctx, existingToken || void 0);
655
+ const trackFailedLogin = async () => {
656
+ try {
657
+ await cloudApiService.track("didNotLogin", { loginMethod: "cli" });
658
+ } catch (e) {
659
+ logger.debug("Failed to track failed login", e);
660
+ }
661
+ };
662
+ if (existingToken) {
663
+ const isTokenValid = await tokenService.isTokenValid(existingToken);
664
+ if (isTokenValid) {
665
+ try {
666
+ const userInfo = await cloudApiService.getUserInfo();
667
+ const { email } = userInfo.data.data;
668
+ if (email) {
669
+ logger.log(`You are already logged into your account (${email}).`);
670
+ } else {
671
+ logger.log("You are already logged in.");
672
+ }
673
+ logger.log(
674
+ "To access your dashboard, please copy and paste the following URL into your web browser:"
675
+ );
676
+ logger.log(chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects`));
677
+ return true;
678
+ } catch (e) {
679
+ logger.debug("Failed to fetch user info", e);
680
+ }
681
+ }
682
+ }
683
+ let cliConfig2;
684
+ try {
685
+ logger.info("🔌 Connecting to the Strapi Cloud API...");
686
+ const config = await cloudApiService.config();
687
+ cliConfig2 = config.data;
688
+ } catch (e) {
689
+ logger.error("🥲 Oops! Something went wrong while logging you in. Please try again.");
690
+ logger.debug(e);
691
+ return false;
692
+ }
693
+ try {
694
+ await cloudApiService.track("willLoginAttempt", {});
695
+ } catch (e) {
696
+ logger.debug("Failed to track login attempt", e);
697
+ }
698
+ logger.debug("🔐 Creating device authentication request...", {
699
+ client_id: cliConfig2.clientId,
700
+ scope: cliConfig2.scope,
701
+ audience: cliConfig2.audience
702
+ });
703
+ const deviceAuthResponse = await axios__default.default.post(cliConfig2.deviceCodeAuthUrl, {
704
+ client_id: cliConfig2.clientId,
705
+ scope: cliConfig2.scope,
706
+ audience: cliConfig2.audience
707
+ }).catch((e) => {
708
+ logger.error("There was an issue with the authentication process. Please try again.");
709
+ if (e.message) {
710
+ logger.debug(e.message, e);
711
+ } else {
712
+ logger.debug(e);
713
+ }
714
+ });
715
+ openModule$1.then((open) => {
716
+ open.default(deviceAuthResponse.data.verification_uri_complete).catch((e) => {
717
+ logger.error("We encountered an issue opening the browser. Please try again later.");
718
+ logger.debug(e.message, e);
719
+ });
720
+ });
721
+ logger.log("If a browser tab does not open automatically, please follow the next steps:");
722
+ logger.log(
723
+ `1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
724
+ );
725
+ logger.log(
726
+ `2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
727
+ `
728
+ );
729
+ const tokenPayload = {
730
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
731
+ device_code: deviceAuthResponse.data.device_code,
732
+ client_id: cliConfig2.clientId
733
+ };
734
+ let isAuthenticated = false;
735
+ const authenticate = async () => {
736
+ const spinner = logger.spinner("Waiting for authentication");
737
+ spinner.start();
738
+ const spinnerFail = () => spinner.fail("Authentication failed!");
739
+ while (!isAuthenticated) {
740
+ try {
741
+ const tokenResponse = await axios__default.default.post(cliConfig2.tokenUrl, tokenPayload);
742
+ const authTokenData = tokenResponse.data;
743
+ if (tokenResponse.status === 200) {
744
+ try {
745
+ logger.debug("🔐 Validating token...");
746
+ await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
747
+ logger.debug("🔐 Token validation successful!");
748
+ } catch (e) {
749
+ logger.debug(e);
750
+ spinnerFail();
751
+ throw new Error("Unable to proceed: Token validation failed");
752
+ }
753
+ logger.debug("🔍 Fetching user information...");
754
+ const cloudApiServiceWithToken = await cloudApiFactory(ctx, authTokenData.access_token);
755
+ await cloudApiServiceWithToken.getUserInfo();
756
+ logger.debug("🔍 User information fetched successfully!");
757
+ try {
758
+ logger.debug("📝 Saving login information...");
759
+ await tokenService.saveToken(authTokenData.access_token);
760
+ logger.debug("📝 Login information saved successfully!");
761
+ isAuthenticated = true;
762
+ } catch (e) {
763
+ logger.error(
764
+ "There was a problem saving your login information. Please try logging in again."
765
+ );
766
+ logger.debug(e);
767
+ spinnerFail();
768
+ return false;
769
+ }
770
+ }
771
+ } catch (e) {
772
+ if (e.message === "Unable to proceed: Token validation failed") {
773
+ logger.error(
774
+ "There seems to be a problem with your login information. Please try logging in again."
775
+ );
776
+ spinnerFail();
777
+ await trackFailedLogin();
778
+ return false;
779
+ }
780
+ if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
781
+ logger.debug(e);
782
+ spinnerFail();
783
+ await trackFailedLogin();
784
+ return false;
785
+ }
786
+ await new Promise((resolve) => {
787
+ setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
788
+ });
789
+ }
790
+ }
791
+ spinner.succeed("Authentication successful!");
792
+ logger.log("You are now logged into Strapi Cloud.");
793
+ logger.log(
794
+ "To access your dashboard, please copy and paste the following URL into your web browser:"
795
+ );
796
+ logger.log(chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects`));
797
+ try {
798
+ await cloudApiService.track("didLogin", { loginMethod: "cli" });
799
+ } catch (e) {
800
+ logger.debug("Failed to track login", e);
801
+ }
802
+ };
803
+ await authenticate();
804
+ return isAuthenticated;
805
+ }
806
+ async function handleError(ctx, error) {
626
807
  const { logger } = ctx;
627
808
  logger.debug(error);
628
809
  if (error instanceof axios.AxiosError) {
810
+ const errorMessage = typeof error.response?.data === "string" ? error.response.data : null;
629
811
  switch (error.response?.status) {
630
- case 401:
631
- logger.error("Your session has expired. Please log in again.");
632
- tokenService.eraseToken();
633
- return;
634
812
  case 403:
635
813
  logger.error(
636
- error.response.data || "You do not have permission to create a project. Please contact support for assistance."
814
+ errorMessage || "You do not have permission to create a project. Please contact support for assistance."
637
815
  );
638
816
  return;
639
817
  case 400:
640
- logger.error("Invalid input. Please check your inputs and try again.");
818
+ logger.error(errorMessage || "Invalid input. Please check your inputs and try again.");
641
819
  return;
642
820
  case 503:
643
821
  logger.error(
644
822
  "Strapi Cloud project creation is currently unavailable. Please try again later."
645
823
  );
646
824
  return;
825
+ default:
826
+ if (errorMessage) {
827
+ logger.error(errorMessage);
828
+ return;
829
+ }
830
+ break;
647
831
  }
648
832
  }
649
833
  logger.error(
650
834
  "We encountered an issue while creating your project. Please try again in a moment. If the problem persists, contact support for assistance."
651
835
  );
652
836
  }
653
- const action$3 = async (ctx) => {
837
+ async function createProject$1(ctx, cloudApi, projectInput) {
654
838
  const { logger } = ctx;
655
- const { getValidToken } = tokenServiceFactory(ctx);
656
- const token = await getValidToken();
839
+ const spinner = logger.spinner("Setting up your project...").start();
840
+ try {
841
+ const { data } = await cloudApi.createProject(projectInput);
842
+ await save({ project: data });
843
+ spinner.succeed("Project created successfully!");
844
+ return data;
845
+ } catch (e) {
846
+ spinner.fail("An error occurred while creating the project on Strapi Cloud.");
847
+ throw e;
848
+ }
849
+ }
850
+ const action$2 = async (ctx) => {
851
+ const { logger } = ctx;
852
+ const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
853
+ const token = await getValidToken(ctx, promptLogin);
657
854
  if (!token) {
658
855
  return;
659
856
  }
660
- const cloudApi = cloudApiFactory(token);
857
+ const cloudApi = await cloudApiFactory(ctx, token);
661
858
  const { data: config } = await cloudApi.config();
662
859
  const { questions, defaults: defaultValues } = config.projectCreation;
663
860
  const projectAnswersDefaulted = fp.defaults(defaultValues);
664
861
  const projectAnswers = await inquirer__default.default.prompt(questions);
665
862
  const projectInput = projectAnswersDefaulted(projectAnswers);
666
- const spinner = logger.spinner("Setting up your project...").start();
667
863
  try {
668
- const { data } = await cloudApi.createProject(projectInput);
669
- save({ project: data });
670
- spinner.succeed("Project created successfully!");
671
- return data;
864
+ return await createProject$1(ctx, cloudApi, projectInput);
672
865
  } catch (e) {
673
- spinner.fail("Failed to create project on Strapi Cloud.");
674
- handleError(ctx, e);
866
+ if (e instanceof axios.AxiosError && e.response?.status === 401) {
867
+ logger.warn("Oops! Your session has expired. Please log in again to retry.");
868
+ await eraseToken();
869
+ if (await promptLogin(ctx)) {
870
+ return await createProject$1(ctx, cloudApi, projectInput);
871
+ }
872
+ } else {
873
+ await handleError(ctx, e);
874
+ }
675
875
  }
676
876
  };
677
877
  function notificationServiceFactory({ logger }) {
@@ -799,9 +999,9 @@ const buildLogsServiceFactory = ({ logger }) => {
799
999
  };
800
1000
  };
801
1001
  async function upload(ctx, project, token, maxProjectFileSize) {
802
- const cloudApi = cloudApiFactory(token);
1002
+ const cloudApi = await cloudApiFactory(ctx, token);
803
1003
  try {
804
- const storagePath = getTmpStoragePath();
1004
+ const storagePath = await getTmpStoragePath();
805
1005
  const projectFolder = path__namespace.default.resolve(process.cwd());
806
1006
  const packageJson2 = await loadPkg(ctx);
807
1007
  if (!packageJson2) {
@@ -832,11 +1032,18 @@ async function upload(ctx, project, token, maxProjectFileSize) {
832
1032
  process.exit(1);
833
1033
  }
834
1034
  const tarFilePath = path__namespace.default.resolve(storagePath, compressedFilename);
835
- const fileStats = fs__namespace.default.statSync(tarFilePath);
1035
+ const fileStats = await fse__default.default.stat(tarFilePath);
836
1036
  if (fileStats.size > maxProjectFileSize) {
837
- return ctx.logger.log(
1037
+ ctx.logger.log(
838
1038
  "Unable to proceed: Your project is too big to be transferred, please use a git repo instead."
839
1039
  );
1040
+ try {
1041
+ await fse__default.default.remove(tarFilePath);
1042
+ } catch (e) {
1043
+ ctx.logger.log("Unable to remove file: ", tarFilePath);
1044
+ ctx.logger.debug(e);
1045
+ }
1046
+ return;
840
1047
  }
841
1048
  ctx.logger.info("🚀 Uploading project...");
842
1049
  const progressBar = ctx.logger.progressBar(100, "Upload Progress");
@@ -870,7 +1077,7 @@ async function upload(ctx, project, token, maxProjectFileSize) {
870
1077
  }
871
1078
  ctx.logger.debug(e);
872
1079
  } finally {
873
- fs__namespace.default.rmSync(tarFilePath, { force: true });
1080
+ await fse__default.default.remove(tarFilePath);
874
1081
  }
875
1082
  process.exit(0);
876
1083
  } catch (e) {
@@ -880,10 +1087,10 @@ async function upload(ctx, project, token, maxProjectFileSize) {
880
1087
  }
881
1088
  }
882
1089
  async function getProject(ctx) {
883
- const { project } = retrieve();
1090
+ const { project } = await retrieve();
884
1091
  if (!project) {
885
1092
  try {
886
- return await action$3(ctx);
1093
+ return await action$2(ctx);
887
1094
  } catch (e) {
888
1095
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
889
1096
  ctx.logger.debug(e);
@@ -892,9 +1099,9 @@ async function getProject(ctx) {
892
1099
  }
893
1100
  return project;
894
1101
  }
895
- const action$2 = async (ctx) => {
896
- const { getValidToken } = tokenServiceFactory(ctx);
897
- const token = await getValidToken();
1102
+ const action$1 = async (ctx) => {
1103
+ const { getValidToken } = await tokenServiceFactory(ctx);
1104
+ const token = await getValidToken(ctx, promptLogin);
898
1105
  if (!token) {
899
1106
  return;
900
1107
  }
@@ -902,10 +1109,15 @@ const action$2 = async (ctx) => {
902
1109
  if (!project) {
903
1110
  return;
904
1111
  }
1112
+ const cloudApiService = await cloudApiFactory(ctx);
1113
+ try {
1114
+ await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1115
+ } catch (e) {
1116
+ ctx.logger.debug("Failed to track willDeploy", e);
1117
+ }
905
1118
  const notificationService = notificationServiceFactory(ctx);
906
1119
  const buildLogsService = buildLogsServiceFactory(ctx);
907
- const cloudApiService2 = cloudApiFactory();
908
- const { data: cliConfig2 } = await cloudApiService2.config();
1120
+ const { data: cliConfig2 } = await cloudApiService.config();
909
1121
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
910
1122
  if (Number.isNaN(maxSize)) {
911
1123
  ctx.logger.debug(
@@ -914,9 +1126,18 @@ const action$2 = async (ctx) => {
914
1126
  maxSize = 1e8;
915
1127
  }
916
1128
  const buildId = await upload(ctx, project, token, maxSize);
1129
+ if (!buildId) {
1130
+ return;
1131
+ }
917
1132
  try {
918
1133
  notificationService(`${apiConfig.apiBaseUrl}/notifications`, token, cliConfig2);
919
1134
  await buildLogsService(`${apiConfig.apiBaseUrl}/v1/logs/${buildId}`, token, cliConfig2);
1135
+ ctx.logger.log(
1136
+ "Visit the following URL for deployment logs. Your deployment will be available here shortly."
1137
+ );
1138
+ ctx.logger.log(
1139
+ chalk__default.default.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1140
+ );
920
1141
  } catch (e) {
921
1142
  if (e instanceof Error) {
922
1143
  ctx.logger.error(e.message);
@@ -953,185 +1174,50 @@ const runAction = (name2, action2) => (...args) => {
953
1174
  });
954
1175
  };
955
1176
  const command$3 = ({ command: command2, ctx }) => {
956
- return 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));
1177
+ 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$1)(ctx));
957
1178
  };
958
1179
  const deployProject = {
959
1180
  name: "deploy-project",
960
1181
  description: "Deploy a Strapi Cloud project",
961
- action: action$2,
1182
+ action: action$1,
962
1183
  command: command$3
963
1184
  };
964
- const openModule = import("open");
965
- const action$1 = async (ctx) => {
966
- const { logger } = ctx;
967
- const tokenService = tokenServiceFactory(ctx);
968
- const existingToken = await tokenService.retrieveToken();
969
- const cloudApiService2 = cloudApiFactory(existingToken || void 0);
970
- const trackFailedLogin = async () => {
971
- try {
972
- await cloudApiService2.track("didNotLogin", { loginMethod: "cli" });
973
- } catch (e) {
974
- logger.debug("Failed to track failed login", e);
975
- }
976
- };
977
- if (existingToken) {
978
- const isTokenValid = await tokenService.isTokenValid(existingToken);
979
- if (isTokenValid) {
980
- try {
981
- const userInfo = await cloudApiService2.getUserInfo();
982
- const { email } = userInfo.data.data;
983
- if (email) {
984
- logger.log(`You are already logged into your account (${email}).`);
985
- } else {
986
- logger.log("You are already logged in.");
987
- }
988
- return;
989
- } catch (e) {
990
- logger.debug("Failed to fetch user info", e);
991
- }
992
- }
993
- }
994
- let cliConfig2;
995
- try {
996
- logger.info("🔌 Connecting to the Strapi Cloud API...");
997
- const config = await cloudApiService2.config();
998
- cliConfig2 = config.data;
999
- } catch (e) {
1000
- logger.error("🥲 Oops! Something went wrong while logging you in. Please try again.");
1001
- logger.debug(e);
1002
- return;
1003
- }
1004
- try {
1005
- await cloudApiService2.track("willLoginAttempt", {});
1006
- } catch (e) {
1007
- logger.debug("Failed to track login attempt", e);
1008
- }
1009
- logger.debug("🔐 Creating device authentication request...", {
1010
- client_id: cliConfig2.clientId,
1011
- scope: cliConfig2.scope,
1012
- audience: cliConfig2.audience
1013
- });
1014
- const deviceAuthResponse = await axios__default.default.post(cliConfig2.deviceCodeAuthUrl, {
1015
- client_id: cliConfig2.clientId,
1016
- scope: cliConfig2.scope,
1017
- audience: cliConfig2.audience
1018
- }).catch((e) => {
1019
- logger.error("There was an issue with the authentication process. Please try again.");
1020
- if (e.message) {
1021
- logger.debug(e.message, e);
1022
- } else {
1023
- logger.debug(e);
1024
- }
1025
- });
1026
- openModule.then((open) => {
1027
- open.default(deviceAuthResponse.data.verification_uri_complete).catch((e) => {
1028
- logger.error("We encountered an issue opening the browser. Please try again later.");
1029
- logger.debug(e.message, e);
1030
- });
1031
- });
1032
- logger.log("If a browser tab does not open automatically, please follow the next steps:");
1033
- logger.log(
1034
- `1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
1035
- );
1036
- logger.log(
1037
- `2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
1038
- `
1039
- );
1040
- const tokenPayload = {
1041
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1042
- device_code: deviceAuthResponse.data.device_code,
1043
- client_id: cliConfig2.clientId
1044
- };
1045
- let isAuthenticated = false;
1046
- const authenticate = async () => {
1047
- const spinner = logger.spinner("Waiting for authentication");
1048
- spinner.start();
1049
- const spinnerFail = () => spinner.fail("Authentication failed!");
1050
- while (!isAuthenticated) {
1051
- try {
1052
- const tokenResponse = await axios__default.default.post(cliConfig2.tokenUrl, tokenPayload);
1053
- const authTokenData = tokenResponse.data;
1054
- if (tokenResponse.status === 200) {
1055
- try {
1056
- logger.debug("🔐 Validating token...");
1057
- await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
1058
- logger.debug("🔐 Token validation successful!");
1059
- } catch (e) {
1060
- logger.debug(e);
1061
- spinnerFail();
1062
- throw new Error("Unable to proceed: Token validation failed");
1063
- }
1064
- const cloudApiService22 = cloudApiFactory(authTokenData.access_token);
1065
- logger.debug("🔍 Fetching user information...");
1066
- await cloudApiService22.getUserInfo();
1067
- logger.debug("🔍 User information fetched successfully!");
1068
- try {
1069
- logger.debug("📝 Saving login information...");
1070
- await tokenService.saveToken(authTokenData.access_token);
1071
- logger.debug("📝 Login information saved successfully!");
1072
- isAuthenticated = true;
1073
- } catch (e) {
1074
- logger.error(
1075
- "There was a problem saving your login information. Please try logging in again."
1076
- );
1077
- logger.debug(e);
1078
- spinnerFail();
1079
- return;
1080
- }
1081
- }
1082
- } catch (e) {
1083
- if (e.message === "Unable to proceed: Token validation failed") {
1084
- logger.error(
1085
- "There seems to be a problem with your login information. Please try logging in again."
1086
- );
1087
- spinnerFail();
1088
- await trackFailedLogin();
1089
- return;
1090
- }
1091
- if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
1092
- logger.debug(e);
1093
- spinnerFail();
1094
- await trackFailedLogin();
1095
- return;
1096
- }
1097
- await new Promise((resolve) => {
1098
- setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
1099
- });
1100
- }
1101
- }
1102
- spinner.succeed("Authentication successful!");
1103
- logger.log("You are now logged into Strapi Cloud.");
1104
- try {
1105
- await cloudApiService2.track("didLogin", { loginMethod: "cli" });
1106
- } catch (e) {
1107
- logger.debug("Failed to track login", e);
1108
- }
1109
- };
1110
- await authenticate();
1111
- };
1112
1185
  const command$2 = ({ command: command2, ctx }) => {
1113
- return command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1186
+ command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1114
1187
  "after",
1115
1188
  "\nAfter running this command, you will be prompted to enter your authentication information."
1116
- ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", action$1)(ctx));
1189
+ ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
1117
1190
  };
1118
1191
  const login = {
1119
1192
  name: "login",
1120
1193
  description: "Strapi Cloud Login",
1121
- action: action$1,
1194
+ action: loginAction,
1122
1195
  command: command$2
1123
1196
  };
1197
+ const openModule = import("open");
1124
1198
  const action = async (ctx) => {
1125
1199
  const { logger } = ctx;
1126
- const { retrieveToken, eraseToken } = tokenServiceFactory(ctx);
1200
+ const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
1127
1201
  const token = await retrieveToken();
1128
1202
  if (!token) {
1129
1203
  logger.log("You're already logged out.");
1130
1204
  return;
1131
1205
  }
1132
- const cloudApiService2 = cloudApiFactory(token);
1206
+ const cloudApiService = await cloudApiFactory(ctx, token);
1207
+ const config = await cloudApiService.config();
1208
+ const cliConfig2 = config.data;
1133
1209
  try {
1134
1210
  await eraseToken();
1211
+ openModule.then((open) => {
1212
+ open.default(
1213
+ `${cliConfig2.baseUrl}/oidc/logout?client_id=${encodeURIComponent(
1214
+ cliConfig2.clientId
1215
+ )}&logout_hint=${encodeURIComponent(token)}
1216
+ `
1217
+ ).catch((e) => {
1218
+ logger.debug(e.message, e);
1219
+ });
1220
+ });
1135
1221
  logger.log(
1136
1222
  "🔌 You have been logged out from the CLI. If you are on a shared computer, please make sure to log out from the Strapi Cloud Dashboard as well."
1137
1223
  );
@@ -1140,13 +1226,13 @@ const action = async (ctx) => {
1140
1226
  logger.debug(e);
1141
1227
  }
1142
1228
  try {
1143
- await cloudApiService2.track("didLogout", { loginMethod: "cli" });
1229
+ await cloudApiService.track("didLogout", { loginMethod: "cli" });
1144
1230
  } catch (e) {
1145
1231
  logger.debug("Failed to track logout event", e);
1146
1232
  }
1147
1233
  };
1148
1234
  const command$1 = ({ command: command2, ctx }) => {
1149
- return 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)(ctx));
1235
+ 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)(ctx));
1150
1236
  };
1151
1237
  const logout = {
1152
1238
  name: "logout",
@@ -1155,12 +1241,12 @@ const logout = {
1155
1241
  command: command$1
1156
1242
  };
1157
1243
  const command = ({ command: command2, ctx }) => {
1158
- return 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));
1244
+ 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$2)(ctx));
1159
1245
  };
1160
1246
  const createProject = {
1161
1247
  name: "create-project",
1162
1248
  description: "Create a new project",
1163
- action: action$3,
1249
+ action: action$2,
1164
1250
  command
1165
1251
  };
1166
1252
  const cli = {
@@ -1171,25 +1257,25 @@ const cli = {
1171
1257
  };
1172
1258
  const cloudCommands = [deployProject, login, logout];
1173
1259
  async function initCloudCLIConfig() {
1174
- const localConfig = getLocalConfig();
1260
+ const localConfig = await getLocalConfig();
1175
1261
  if (!localConfig.deviceId) {
1176
1262
  localConfig.deviceId = crypto__default.default.randomUUID();
1177
1263
  }
1178
- saveLocalConfig(localConfig);
1264
+ await saveLocalConfig(localConfig);
1179
1265
  }
1180
- function buildStrapiCloudCommands({
1266
+ async function buildStrapiCloudCommands({
1181
1267
  command: command2,
1182
1268
  ctx,
1183
1269
  argv
1184
1270
  }) {
1185
- initCloudCLIConfig();
1186
- cloudCommands.forEach((cloudCommand) => {
1271
+ await initCloudCLIConfig();
1272
+ for (const cloudCommand of cloudCommands) {
1187
1273
  try {
1188
- cloudCommand.command({ command: command2, ctx, argv });
1274
+ await cloudCommand.command({ command: command2, ctx, argv });
1189
1275
  } catch (e) {
1190
1276
  console.error(`Failed to load command ${cloudCommand.name}`, e);
1191
1277
  }
1192
- });
1278
+ }
1193
1279
  }
1194
1280
  exports.buildStrapiCloudCommands = buildStrapiCloudCommands;
1195
1281
  exports.cli = cli;