@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.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  import crypto$1 from "crypto";
2
- import * as fs from "fs";
3
- import fs__default from "fs";
2
+ import fse from "fs-extra";
4
3
  import * as path from "path";
5
4
  import path__default from "path";
5
+ import chalk from "chalk";
6
6
  import axios, { AxiosError } from "axios";
7
7
  import * as crypto from "node:crypto";
8
8
  import { env } from "@strapi/utils";
9
+ import * as fs from "fs";
9
10
  import * as tar from "tar";
10
11
  import { minimatch } from "minimatch";
11
12
  import inquirer from "inquirer";
@@ -14,7 +15,6 @@ import os from "os";
14
15
  import XDGAppPaths from "xdg-app-paths";
15
16
  import jwksClient from "jwks-rsa";
16
17
  import jwt from "jsonwebtoken";
17
- import chalk from "chalk";
18
18
  import stringify from "fast-safe-stringify";
19
19
  import ora from "ora";
20
20
  import * as cliProgress from "cli-progress";
@@ -23,7 +23,8 @@ import fs$1 from "fs/promises";
23
23
  import pkgUp from "pkg-up";
24
24
  import * as yup from "yup";
25
25
  const apiConfig = {
26
- apiBaseUrl: env("STRAPI_CLI_CLOUD_API", "https://cli.cloud.strapi.io")
26
+ apiBaseUrl: env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
27
+ dashboardBaseUrl: env("STRAPI_CLI_CLOUD_DASHBOARD", "https://cloud.strapi.io")
27
28
  };
28
29
  const IGNORED_PATTERNS = [
29
30
  "**/.git/**",
@@ -78,7 +79,7 @@ const readGitignore = (folderPath) => {
78
79
  if (!fs.existsSync(gitignorePath))
79
80
  return [];
80
81
  const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
81
- return gitignoreContent.split("\n").filter((line) => Boolean(line.trim()) && !line.startsWith("#"));
82
+ return gitignoreContent.split(/\r?\n/).filter((line) => Boolean(line.trim()) && !line.startsWith("#"));
82
83
  };
83
84
  const compressFilesToTar = async (storagePath, folderToCompress, filename) => {
84
85
  const ignorePatterns = readGitignore(folderToCompress);
@@ -93,48 +94,45 @@ const compressFilesToTar = async (storagePath, folderToCompress, filename) => {
93
94
  };
94
95
  const APP_FOLDER_NAME = "com.strapi.cli";
95
96
  const CONFIG_FILENAME = "config.json";
96
- function checkDirectoryExists(directoryPath) {
97
+ async function checkDirectoryExists(directoryPath) {
97
98
  try {
98
- return fs__default.lstatSync(directoryPath).isDirectory();
99
+ const fsStat = await fse.lstat(directoryPath);
100
+ return fsStat.isDirectory();
99
101
  } catch (e) {
100
102
  return false;
101
103
  }
102
104
  }
103
- function getTmpStoragePath() {
105
+ async function getTmpStoragePath() {
104
106
  const storagePath = path__default.join(os.tmpdir(), APP_FOLDER_NAME);
105
- if (!checkDirectoryExists(storagePath)) {
106
- fs__default.mkdirSync(storagePath, { recursive: true });
107
- }
107
+ await fse.ensureDir(storagePath);
108
108
  return storagePath;
109
109
  }
110
- function getConfigPath() {
110
+ async function getConfigPath() {
111
111
  const configDirs = XDGAppPaths(APP_FOLDER_NAME).configDirs();
112
112
  const configPath = configDirs.find(checkDirectoryExists);
113
113
  if (!configPath) {
114
- fs__default.mkdirSync(configDirs[0], { recursive: true });
114
+ await fse.ensureDir(configDirs[0]);
115
115
  return configDirs[0];
116
116
  }
117
117
  return configPath;
118
118
  }
119
- function getLocalConfig() {
120
- const configPath = getConfigPath();
119
+ async function getLocalConfig() {
120
+ const configPath = await getConfigPath();
121
121
  const configFilePath = path__default.join(configPath, CONFIG_FILENAME);
122
- if (!fs__default.existsSync(configFilePath)) {
123
- return {};
124
- }
122
+ await fse.ensureFile(configFilePath);
125
123
  try {
126
- return JSON.parse(fs__default.readFileSync(configFilePath, "utf8"));
124
+ return await fse.readJSON(configFilePath, { encoding: "utf8", throws: true });
127
125
  } catch (e) {
128
126
  return {};
129
127
  }
130
128
  }
131
- function saveLocalConfig(data) {
132
- const configPath = getConfigPath();
129
+ async function saveLocalConfig(data) {
130
+ const configPath = await getConfigPath();
133
131
  const configFilePath = path__default.join(configPath, CONFIG_FILENAME);
134
- fs__default.writeFileSync(configFilePath, JSON.stringify(data), { encoding: "utf8", mode: 384 });
132
+ await fse.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
135
133
  }
136
134
  const name = "@strapi/cloud-cli";
137
- const version = "4.24.3";
135
+ const version = "4.25.1";
138
136
  const description = "Commands to interact with the Strapi Cloud";
139
137
  const keywords = [
140
138
  "strapi",
@@ -178,13 +176,14 @@ const scripts = {
178
176
  watch: "pack-up watch"
179
177
  };
180
178
  const dependencies = {
181
- "@strapi/utils": "4.24.3",
179
+ "@strapi/utils": "4.25.1",
182
180
  axios: "1.6.0",
183
181
  chalk: "4.1.2",
184
182
  "cli-progress": "3.12.0",
185
183
  commander: "8.3.0",
186
184
  eventsource: "2.0.2",
187
185
  "fast-safe-stringify": "2.1.1",
186
+ "fs-extra": "10.0.0",
188
187
  inquirer: "8.2.5",
189
188
  jsonwebtoken: "9.0.0",
190
189
  "jwks-rsa": "3.1.0",
@@ -202,8 +201,8 @@ const devDependencies = {
202
201
  "@types/cli-progress": "3.11.5",
203
202
  "@types/eventsource": "1.1.15",
204
203
  "@types/lodash": "^4.14.191",
205
- "eslint-config-custom": "4.24.3",
206
- tsconfig: "4.24.3"
204
+ "eslint-config-custom": "4.25.1",
205
+ tsconfig: "4.25.1"
207
206
  };
208
207
  const engines = {
209
208
  node: ">=18.0.0 <=20.x.x",
@@ -232,8 +231,8 @@ const packageJson = {
232
231
  engines
233
232
  };
234
233
  const VERSION = "v1";
235
- function cloudApiFactory(token) {
236
- const localConfig = getLocalConfig();
234
+ async function cloudApiFactory({ logger }, token) {
235
+ const localConfig = await getLocalConfig();
237
236
  const customHeaders = {
238
237
  "x-device-id": localConfig.deviceId,
239
238
  "x-app-version": packageJson.version,
@@ -256,7 +255,7 @@ function cloudApiFactory(token) {
256
255
  deploy({ filePath, project }, { onUploadProgress }) {
257
256
  return axiosCloudAPI.post(
258
257
  `/deploy/${project.name}`,
259
- { file: fs.createReadStream(filePath) },
258
+ { file: fse.createReadStream(filePath) },
260
259
  {
261
260
  headers: {
262
261
  "Content-Type": "multipart/form-data"
@@ -285,8 +284,19 @@ function cloudApiFactory(token) {
285
284
  getUserInfo() {
286
285
  return axiosCloudAPI.get("/user");
287
286
  },
288
- config() {
289
- return axiosCloudAPI.get("/config");
287
+ async config() {
288
+ try {
289
+ const response = await axiosCloudAPI.get("/config");
290
+ if (response.status !== 200) {
291
+ throw new Error("Error fetching cloud CLI config from the server.");
292
+ }
293
+ return response;
294
+ } catch (error) {
295
+ logger.debug(
296
+ "🥲 Oops! Couldn't retrieve the cloud CLI config from the server. Please try again."
297
+ );
298
+ throw error;
299
+ }
290
300
  },
291
301
  listProjects() {
292
302
  return axiosCloudAPI.get("/projects");
@@ -300,53 +310,48 @@ function cloudApiFactory(token) {
300
310
  };
301
311
  }
302
312
  const LOCAL_SAVE_FILENAME = ".strapi-cloud.json";
303
- function save(data, { directoryPath } = {}) {
304
- const storedData = { ...retrieve(), ...data };
313
+ async function save(data, { directoryPath } = {}) {
314
+ const alreadyInFileData = await retrieve({ directoryPath });
315
+ const storedData = { ...alreadyInFileData, ...data };
305
316
  const pathToFile = path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
306
- if (!fs__default.existsSync(path__default.dirname(pathToFile))) {
307
- fs__default.mkdirSync(path__default.dirname(pathToFile), { recursive: true });
308
- }
309
- fs__default.writeFileSync(pathToFile, JSON.stringify(storedData), "utf8");
317
+ await fse.ensureDir(path__default.dirname(pathToFile));
318
+ await fse.writeJson(pathToFile, storedData, { encoding: "utf8" });
310
319
  }
311
- function retrieve({ directoryPath } = {}) {
320
+ async function retrieve({
321
+ directoryPath
322
+ } = {}) {
312
323
  const pathToFile = path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
313
- if (!fs__default.existsSync(pathToFile)) {
324
+ const pathExists = await fse.pathExists(pathToFile);
325
+ if (!pathExists) {
314
326
  return {};
315
327
  }
316
- return JSON.parse(fs__default.readFileSync(pathToFile, "utf8"));
317
- }
318
- function erase({ directoryPath } = {}) {
319
- const pathToFile = path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
320
- if (fs__default.existsSync(pathToFile)) {
321
- fs__default.unlinkSync(pathToFile);
322
- }
328
+ return fse.readJSON(pathToFile, { encoding: "utf8" });
323
329
  }
324
330
  const strapiInfoSave = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
325
331
  __proto__: null,
326
332
  LOCAL_SAVE_FILENAME,
327
- erase,
328
333
  retrieve,
329
334
  save
330
335
  }, Symbol.toStringTag, { value: "Module" }));
331
- const cloudApiService = cloudApiFactory();
332
336
  let cliConfig;
333
- function tokenServiceFactory({ logger }) {
334
- function saveToken(str) {
335
- const appConfig = getLocalConfig();
337
+ async function tokenServiceFactory({ logger }) {
338
+ const cloudApiService = await cloudApiFactory({ logger });
339
+ async function saveToken(str) {
340
+ const appConfig = await getLocalConfig();
336
341
  if (!appConfig) {
337
342
  logger.error("There was a problem saving your token. Please try again.");
338
343
  return;
339
344
  }
340
345
  appConfig.token = str;
341
346
  try {
342
- saveLocalConfig(appConfig);
343
- } catch (error) {
344
- logger.debug(error);
347
+ await saveLocalConfig(appConfig);
348
+ } catch (e) {
349
+ logger.debug(e);
345
350
  logger.error("There was a problem saving your token. Please try again.");
346
351
  }
347
352
  }
348
353
  async function retrieveToken() {
349
- const appConfig = getLocalConfig();
354
+ const appConfig = await getLocalConfig();
350
355
  if (appConfig.token) {
351
356
  if (await isTokenValid(appConfig.token)) {
352
357
  return appConfig.token;
@@ -359,9 +364,9 @@ function tokenServiceFactory({ logger }) {
359
364
  jwksUri: jwksUrl
360
365
  });
361
366
  const getKey = (header, callback) => {
362
- client.getSigningKey(header.kid, (err, key) => {
363
- if (err) {
364
- callback(err);
367
+ client.getSigningKey(header.kid, (e, key) => {
368
+ if (e) {
369
+ callback(e);
365
370
  } else if (key) {
366
371
  const publicKey = "publicKey" in key ? key.publicKey : key.rsaPublicKey;
367
372
  callback(null, publicKey);
@@ -379,14 +384,17 @@ function tokenServiceFactory({ logger }) {
379
384
  "There seems to be a problem with your login information. Please try logging in again."
380
385
  );
381
386
  }
387
+ return Promise.reject(new Error("Invalid token"));
382
388
  }
383
389
  return new Promise((resolve, reject) => {
384
390
  jwt.verify(idToken, getKey, (err) => {
385
391
  if (err) {
386
392
  reject(err);
387
- } else {
388
- resolve();
389
393
  }
394
+ if (decodedToken.payload.exp < Math.floor(Date.now() / 1e3)) {
395
+ reject(new Error("Token is expired"));
396
+ }
397
+ resolve();
390
398
  });
391
399
  });
392
400
  }
@@ -399,35 +407,36 @@ function tokenServiceFactory({ logger }) {
399
407
  return true;
400
408
  }
401
409
  return false;
402
- } catch (error) {
403
- logger.debug(error);
410
+ } catch (e) {
411
+ logger.debug(e);
404
412
  return false;
405
413
  }
406
414
  }
407
- function eraseToken() {
408
- const appConfig = getLocalConfig();
415
+ async function eraseToken() {
416
+ const appConfig = await getLocalConfig();
409
417
  if (!appConfig) {
410
418
  return;
411
419
  }
412
420
  delete appConfig.token;
413
421
  try {
414
- saveLocalConfig(appConfig);
415
- } catch (error) {
416
- logger.debug(error);
422
+ await saveLocalConfig(appConfig);
423
+ } catch (e) {
424
+ logger.debug(e);
417
425
  logger.error(
418
426
  "There was an issue removing your login information. Please try logging out again."
419
427
  );
428
+ throw e;
420
429
  }
421
430
  }
422
- async function getValidToken() {
423
- const token = await retrieveToken();
424
- if (!token) {
425
- logger.log("No token found. Please login first.");
426
- return null;
427
- }
428
- if (!await isTokenValid(token)) {
429
- logger.log("Unable to proceed: Token is expired or not valid. Please login again.");
430
- return null;
431
+ async function getValidToken(ctx, loginAction2) {
432
+ let token = await retrieveToken();
433
+ while (!token || !await isTokenValid(token)) {
434
+ logger.log(
435
+ 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."
436
+ );
437
+ if (!await loginAction2(ctx))
438
+ return null;
439
+ token = await retrieveToken();
431
440
  }
432
441
  return token;
433
442
  }
@@ -561,57 +570,246 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
561
570
  local: strapiInfoSave,
562
571
  tokenServiceFactory
563
572
  }, Symbol.toStringTag, { value: "Module" }));
564
- function handleError(ctx, error) {
565
- const tokenService = tokenServiceFactory(ctx);
573
+ const openModule$1 = import("open");
574
+ async function promptLogin(ctx) {
575
+ const response = await inquirer.prompt([
576
+ {
577
+ type: "confirm",
578
+ name: "login",
579
+ message: "Would you like to login?"
580
+ }
581
+ ]);
582
+ if (response.login) {
583
+ const loginSuccessful = await loginAction(ctx);
584
+ return loginSuccessful;
585
+ }
586
+ return false;
587
+ }
588
+ async function loginAction(ctx) {
589
+ const { logger } = ctx;
590
+ const tokenService = await tokenServiceFactory(ctx);
591
+ const existingToken = await tokenService.retrieveToken();
592
+ const cloudApiService = await cloudApiFactory(ctx, existingToken || void 0);
593
+ const trackFailedLogin = async () => {
594
+ try {
595
+ await cloudApiService.track("didNotLogin", { loginMethod: "cli" });
596
+ } catch (e) {
597
+ logger.debug("Failed to track failed login", e);
598
+ }
599
+ };
600
+ if (existingToken) {
601
+ const isTokenValid = await tokenService.isTokenValid(existingToken);
602
+ if (isTokenValid) {
603
+ try {
604
+ const userInfo = await cloudApiService.getUserInfo();
605
+ const { email } = userInfo.data.data;
606
+ if (email) {
607
+ logger.log(`You are already logged into your account (${email}).`);
608
+ } else {
609
+ logger.log("You are already logged in.");
610
+ }
611
+ logger.log(
612
+ "To access your dashboard, please copy and paste the following URL into your web browser:"
613
+ );
614
+ logger.log(chalk.underline(`${apiConfig.dashboardBaseUrl}/projects`));
615
+ return true;
616
+ } catch (e) {
617
+ logger.debug("Failed to fetch user info", e);
618
+ }
619
+ }
620
+ }
621
+ let cliConfig2;
622
+ try {
623
+ logger.info("🔌 Connecting to the Strapi Cloud API...");
624
+ const config = await cloudApiService.config();
625
+ cliConfig2 = config.data;
626
+ } catch (e) {
627
+ logger.error("🥲 Oops! Something went wrong while logging you in. Please try again.");
628
+ logger.debug(e);
629
+ return false;
630
+ }
631
+ try {
632
+ await cloudApiService.track("willLoginAttempt", {});
633
+ } catch (e) {
634
+ logger.debug("Failed to track login attempt", e);
635
+ }
636
+ logger.debug("🔐 Creating device authentication request...", {
637
+ client_id: cliConfig2.clientId,
638
+ scope: cliConfig2.scope,
639
+ audience: cliConfig2.audience
640
+ });
641
+ const deviceAuthResponse = await axios.post(cliConfig2.deviceCodeAuthUrl, {
642
+ client_id: cliConfig2.clientId,
643
+ scope: cliConfig2.scope,
644
+ audience: cliConfig2.audience
645
+ }).catch((e) => {
646
+ logger.error("There was an issue with the authentication process. Please try again.");
647
+ if (e.message) {
648
+ logger.debug(e.message, e);
649
+ } else {
650
+ logger.debug(e);
651
+ }
652
+ });
653
+ openModule$1.then((open) => {
654
+ open.default(deviceAuthResponse.data.verification_uri_complete).catch((e) => {
655
+ logger.error("We encountered an issue opening the browser. Please try again later.");
656
+ logger.debug(e.message, e);
657
+ });
658
+ });
659
+ logger.log("If a browser tab does not open automatically, please follow the next steps:");
660
+ logger.log(
661
+ `1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
662
+ );
663
+ logger.log(
664
+ `2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
665
+ `
666
+ );
667
+ const tokenPayload = {
668
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
669
+ device_code: deviceAuthResponse.data.device_code,
670
+ client_id: cliConfig2.clientId
671
+ };
672
+ let isAuthenticated = false;
673
+ const authenticate = async () => {
674
+ const spinner = logger.spinner("Waiting for authentication");
675
+ spinner.start();
676
+ const spinnerFail = () => spinner.fail("Authentication failed!");
677
+ while (!isAuthenticated) {
678
+ try {
679
+ const tokenResponse = await axios.post(cliConfig2.tokenUrl, tokenPayload);
680
+ const authTokenData = tokenResponse.data;
681
+ if (tokenResponse.status === 200) {
682
+ try {
683
+ logger.debug("🔐 Validating token...");
684
+ await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
685
+ logger.debug("🔐 Token validation successful!");
686
+ } catch (e) {
687
+ logger.debug(e);
688
+ spinnerFail();
689
+ throw new Error("Unable to proceed: Token validation failed");
690
+ }
691
+ logger.debug("🔍 Fetching user information...");
692
+ const cloudApiServiceWithToken = await cloudApiFactory(ctx, authTokenData.access_token);
693
+ await cloudApiServiceWithToken.getUserInfo();
694
+ logger.debug("🔍 User information fetched successfully!");
695
+ try {
696
+ logger.debug("📝 Saving login information...");
697
+ await tokenService.saveToken(authTokenData.access_token);
698
+ logger.debug("📝 Login information saved successfully!");
699
+ isAuthenticated = true;
700
+ } catch (e) {
701
+ logger.error(
702
+ "There was a problem saving your login information. Please try logging in again."
703
+ );
704
+ logger.debug(e);
705
+ spinnerFail();
706
+ return false;
707
+ }
708
+ }
709
+ } catch (e) {
710
+ if (e.message === "Unable to proceed: Token validation failed") {
711
+ logger.error(
712
+ "There seems to be a problem with your login information. Please try logging in again."
713
+ );
714
+ spinnerFail();
715
+ await trackFailedLogin();
716
+ return false;
717
+ }
718
+ if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
719
+ logger.debug(e);
720
+ spinnerFail();
721
+ await trackFailedLogin();
722
+ return false;
723
+ }
724
+ await new Promise((resolve) => {
725
+ setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
726
+ });
727
+ }
728
+ }
729
+ spinner.succeed("Authentication successful!");
730
+ logger.log("You are now logged into Strapi Cloud.");
731
+ logger.log(
732
+ "To access your dashboard, please copy and paste the following URL into your web browser:"
733
+ );
734
+ logger.log(chalk.underline(`${apiConfig.dashboardBaseUrl}/projects`));
735
+ try {
736
+ await cloudApiService.track("didLogin", { loginMethod: "cli" });
737
+ } catch (e) {
738
+ logger.debug("Failed to track login", e);
739
+ }
740
+ };
741
+ await authenticate();
742
+ return isAuthenticated;
743
+ }
744
+ async function handleError(ctx, error) {
566
745
  const { logger } = ctx;
567
746
  logger.debug(error);
568
747
  if (error instanceof AxiosError) {
748
+ const errorMessage = typeof error.response?.data === "string" ? error.response.data : null;
569
749
  switch (error.response?.status) {
570
- case 401:
571
- logger.error("Your session has expired. Please log in again.");
572
- tokenService.eraseToken();
573
- return;
574
750
  case 403:
575
751
  logger.error(
576
- error.response.data || "You do not have permission to create a project. Please contact support for assistance."
752
+ errorMessage || "You do not have permission to create a project. Please contact support for assistance."
577
753
  );
578
754
  return;
579
755
  case 400:
580
- logger.error("Invalid input. Please check your inputs and try again.");
756
+ logger.error(errorMessage || "Invalid input. Please check your inputs and try again.");
581
757
  return;
582
758
  case 503:
583
759
  logger.error(
584
760
  "Strapi Cloud project creation is currently unavailable. Please try again later."
585
761
  );
586
762
  return;
763
+ default:
764
+ if (errorMessage) {
765
+ logger.error(errorMessage);
766
+ return;
767
+ }
768
+ break;
587
769
  }
588
770
  }
589
771
  logger.error(
590
772
  "We encountered an issue while creating your project. Please try again in a moment. If the problem persists, contact support for assistance."
591
773
  );
592
774
  }
593
- const action$3 = async (ctx) => {
775
+ async function createProject$1(ctx, cloudApi, projectInput) {
594
776
  const { logger } = ctx;
595
- const { getValidToken } = tokenServiceFactory(ctx);
596
- const token = await getValidToken();
777
+ const spinner = logger.spinner("Setting up your project...").start();
778
+ try {
779
+ const { data } = await cloudApi.createProject(projectInput);
780
+ await save({ project: data });
781
+ spinner.succeed("Project created successfully!");
782
+ return data;
783
+ } catch (e) {
784
+ spinner.fail("An error occurred while creating the project on Strapi Cloud.");
785
+ throw e;
786
+ }
787
+ }
788
+ const action$2 = async (ctx) => {
789
+ const { logger } = ctx;
790
+ const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
791
+ const token = await getValidToken(ctx, promptLogin);
597
792
  if (!token) {
598
793
  return;
599
794
  }
600
- const cloudApi = cloudApiFactory(token);
795
+ const cloudApi = await cloudApiFactory(ctx, token);
601
796
  const { data: config } = await cloudApi.config();
602
797
  const { questions, defaults: defaultValues } = config.projectCreation;
603
798
  const projectAnswersDefaulted = defaults(defaultValues);
604
799
  const projectAnswers = await inquirer.prompt(questions);
605
800
  const projectInput = projectAnswersDefaulted(projectAnswers);
606
- const spinner = logger.spinner("Setting up your project...").start();
607
801
  try {
608
- const { data } = await cloudApi.createProject(projectInput);
609
- save({ project: data });
610
- spinner.succeed("Project created successfully!");
611
- return data;
802
+ return await createProject$1(ctx, cloudApi, projectInput);
612
803
  } catch (e) {
613
- spinner.fail("Failed to create project on Strapi Cloud.");
614
- handleError(ctx, e);
804
+ if (e instanceof AxiosError && e.response?.status === 401) {
805
+ logger.warn("Oops! Your session has expired. Please log in again to retry.");
806
+ await eraseToken();
807
+ if (await promptLogin(ctx)) {
808
+ return await createProject$1(ctx, cloudApi, projectInput);
809
+ }
810
+ } else {
811
+ await handleError(ctx, e);
812
+ }
615
813
  }
616
814
  };
617
815
  function notificationServiceFactory({ logger }) {
@@ -739,9 +937,9 @@ const buildLogsServiceFactory = ({ logger }) => {
739
937
  };
740
938
  };
741
939
  async function upload(ctx, project, token, maxProjectFileSize) {
742
- const cloudApi = cloudApiFactory(token);
940
+ const cloudApi = await cloudApiFactory(ctx, token);
743
941
  try {
744
- const storagePath = getTmpStoragePath();
942
+ const storagePath = await getTmpStoragePath();
745
943
  const projectFolder = path__default.resolve(process.cwd());
746
944
  const packageJson2 = await loadPkg(ctx);
747
945
  if (!packageJson2) {
@@ -772,11 +970,18 @@ async function upload(ctx, project, token, maxProjectFileSize) {
772
970
  process.exit(1);
773
971
  }
774
972
  const tarFilePath = path__default.resolve(storagePath, compressedFilename);
775
- const fileStats = fs__default.statSync(tarFilePath);
973
+ const fileStats = await fse.stat(tarFilePath);
776
974
  if (fileStats.size > maxProjectFileSize) {
777
- return ctx.logger.log(
975
+ ctx.logger.log(
778
976
  "Unable to proceed: Your project is too big to be transferred, please use a git repo instead."
779
977
  );
978
+ try {
979
+ await fse.remove(tarFilePath);
980
+ } catch (e) {
981
+ ctx.logger.log("Unable to remove file: ", tarFilePath);
982
+ ctx.logger.debug(e);
983
+ }
984
+ return;
780
985
  }
781
986
  ctx.logger.info("🚀 Uploading project...");
782
987
  const progressBar = ctx.logger.progressBar(100, "Upload Progress");
@@ -810,7 +1015,7 @@ async function upload(ctx, project, token, maxProjectFileSize) {
810
1015
  }
811
1016
  ctx.logger.debug(e);
812
1017
  } finally {
813
- fs__default.rmSync(tarFilePath, { force: true });
1018
+ await fse.remove(tarFilePath);
814
1019
  }
815
1020
  process.exit(0);
816
1021
  } catch (e) {
@@ -820,10 +1025,10 @@ async function upload(ctx, project, token, maxProjectFileSize) {
820
1025
  }
821
1026
  }
822
1027
  async function getProject(ctx) {
823
- const { project } = retrieve();
1028
+ const { project } = await retrieve();
824
1029
  if (!project) {
825
1030
  try {
826
- return await action$3(ctx);
1031
+ return await action$2(ctx);
827
1032
  } catch (e) {
828
1033
  ctx.logger.error("An error occurred while deploying the project. Please try again later.");
829
1034
  ctx.logger.debug(e);
@@ -832,9 +1037,9 @@ async function getProject(ctx) {
832
1037
  }
833
1038
  return project;
834
1039
  }
835
- const action$2 = async (ctx) => {
836
- const { getValidToken } = tokenServiceFactory(ctx);
837
- const token = await getValidToken();
1040
+ const action$1 = async (ctx) => {
1041
+ const { getValidToken } = await tokenServiceFactory(ctx);
1042
+ const token = await getValidToken(ctx, promptLogin);
838
1043
  if (!token) {
839
1044
  return;
840
1045
  }
@@ -842,10 +1047,15 @@ const action$2 = async (ctx) => {
842
1047
  if (!project) {
843
1048
  return;
844
1049
  }
1050
+ const cloudApiService = await cloudApiFactory(ctx);
1051
+ try {
1052
+ await cloudApiService.track("willDeployWithCLI", { projectInternalName: project.name });
1053
+ } catch (e) {
1054
+ ctx.logger.debug("Failed to track willDeploy", e);
1055
+ }
845
1056
  const notificationService = notificationServiceFactory(ctx);
846
1057
  const buildLogsService = buildLogsServiceFactory(ctx);
847
- const cloudApiService2 = cloudApiFactory();
848
- const { data: cliConfig2 } = await cloudApiService2.config();
1058
+ const { data: cliConfig2 } = await cloudApiService.config();
849
1059
  let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
850
1060
  if (Number.isNaN(maxSize)) {
851
1061
  ctx.logger.debug(
@@ -854,9 +1064,18 @@ const action$2 = async (ctx) => {
854
1064
  maxSize = 1e8;
855
1065
  }
856
1066
  const buildId = await upload(ctx, project, token, maxSize);
1067
+ if (!buildId) {
1068
+ return;
1069
+ }
857
1070
  try {
858
1071
  notificationService(`${apiConfig.apiBaseUrl}/notifications`, token, cliConfig2);
859
1072
  await buildLogsService(`${apiConfig.apiBaseUrl}/v1/logs/${buildId}`, token, cliConfig2);
1073
+ ctx.logger.log(
1074
+ "Visit the following URL for deployment logs. Your deployment will be available here shortly."
1075
+ );
1076
+ ctx.logger.log(
1077
+ chalk.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
1078
+ );
860
1079
  } catch (e) {
861
1080
  if (e instanceof Error) {
862
1081
  ctx.logger.error(e.message);
@@ -893,185 +1112,50 @@ const runAction = (name2, action2) => (...args) => {
893
1112
  });
894
1113
  };
895
1114
  const command$3 = ({ command: command2, ctx }) => {
896
- 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));
1115
+ 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));
897
1116
  };
898
1117
  const deployProject = {
899
1118
  name: "deploy-project",
900
1119
  description: "Deploy a Strapi Cloud project",
901
- action: action$2,
1120
+ action: action$1,
902
1121
  command: command$3
903
1122
  };
904
- const openModule = import("open");
905
- const action$1 = async (ctx) => {
906
- const { logger } = ctx;
907
- const tokenService = tokenServiceFactory(ctx);
908
- const existingToken = await tokenService.retrieveToken();
909
- const cloudApiService2 = cloudApiFactory(existingToken || void 0);
910
- const trackFailedLogin = async () => {
911
- try {
912
- await cloudApiService2.track("didNotLogin", { loginMethod: "cli" });
913
- } catch (e) {
914
- logger.debug("Failed to track failed login", e);
915
- }
916
- };
917
- if (existingToken) {
918
- const isTokenValid = await tokenService.isTokenValid(existingToken);
919
- if (isTokenValid) {
920
- try {
921
- const userInfo = await cloudApiService2.getUserInfo();
922
- const { email } = userInfo.data.data;
923
- if (email) {
924
- logger.log(`You are already logged into your account (${email}).`);
925
- } else {
926
- logger.log("You are already logged in.");
927
- }
928
- return;
929
- } catch (e) {
930
- logger.debug("Failed to fetch user info", e);
931
- }
932
- }
933
- }
934
- let cliConfig2;
935
- try {
936
- logger.info("🔌 Connecting to the Strapi Cloud API...");
937
- const config = await cloudApiService2.config();
938
- cliConfig2 = config.data;
939
- } catch (e) {
940
- logger.error("🥲 Oops! Something went wrong while logging you in. Please try again.");
941
- logger.debug(e);
942
- return;
943
- }
944
- try {
945
- await cloudApiService2.track("willLoginAttempt", {});
946
- } catch (e) {
947
- logger.debug("Failed to track login attempt", e);
948
- }
949
- logger.debug("🔐 Creating device authentication request...", {
950
- client_id: cliConfig2.clientId,
951
- scope: cliConfig2.scope,
952
- audience: cliConfig2.audience
953
- });
954
- const deviceAuthResponse = await axios.post(cliConfig2.deviceCodeAuthUrl, {
955
- client_id: cliConfig2.clientId,
956
- scope: cliConfig2.scope,
957
- audience: cliConfig2.audience
958
- }).catch((e) => {
959
- logger.error("There was an issue with the authentication process. Please try again.");
960
- if (e.message) {
961
- logger.debug(e.message, e);
962
- } else {
963
- logger.debug(e);
964
- }
965
- });
966
- openModule.then((open) => {
967
- open.default(deviceAuthResponse.data.verification_uri_complete).catch((e) => {
968
- logger.error("We encountered an issue opening the browser. Please try again later.");
969
- logger.debug(e.message, e);
970
- });
971
- });
972
- logger.log("If a browser tab does not open automatically, please follow the next steps:");
973
- logger.log(
974
- `1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
975
- );
976
- logger.log(
977
- `2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
978
- `
979
- );
980
- const tokenPayload = {
981
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
982
- device_code: deviceAuthResponse.data.device_code,
983
- client_id: cliConfig2.clientId
984
- };
985
- let isAuthenticated = false;
986
- const authenticate = async () => {
987
- const spinner = logger.spinner("Waiting for authentication");
988
- spinner.start();
989
- const spinnerFail = () => spinner.fail("Authentication failed!");
990
- while (!isAuthenticated) {
991
- try {
992
- const tokenResponse = await axios.post(cliConfig2.tokenUrl, tokenPayload);
993
- const authTokenData = tokenResponse.data;
994
- if (tokenResponse.status === 200) {
995
- try {
996
- logger.debug("🔐 Validating token...");
997
- await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
998
- logger.debug("🔐 Token validation successful!");
999
- } catch (e) {
1000
- logger.debug(e);
1001
- spinnerFail();
1002
- throw new Error("Unable to proceed: Token validation failed");
1003
- }
1004
- const cloudApiService22 = cloudApiFactory(authTokenData.access_token);
1005
- logger.debug("🔍 Fetching user information...");
1006
- await cloudApiService22.getUserInfo();
1007
- logger.debug("🔍 User information fetched successfully!");
1008
- try {
1009
- logger.debug("📝 Saving login information...");
1010
- await tokenService.saveToken(authTokenData.access_token);
1011
- logger.debug("📝 Login information saved successfully!");
1012
- isAuthenticated = true;
1013
- } catch (e) {
1014
- logger.error(
1015
- "There was a problem saving your login information. Please try logging in again."
1016
- );
1017
- logger.debug(e);
1018
- spinnerFail();
1019
- return;
1020
- }
1021
- }
1022
- } catch (e) {
1023
- if (e.message === "Unable to proceed: Token validation failed") {
1024
- logger.error(
1025
- "There seems to be a problem with your login information. Please try logging in again."
1026
- );
1027
- spinnerFail();
1028
- await trackFailedLogin();
1029
- return;
1030
- }
1031
- if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
1032
- logger.debug(e);
1033
- spinnerFail();
1034
- await trackFailedLogin();
1035
- return;
1036
- }
1037
- await new Promise((resolve) => {
1038
- setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
1039
- });
1040
- }
1041
- }
1042
- spinner.succeed("Authentication successful!");
1043
- logger.log("You are now logged into Strapi Cloud.");
1044
- try {
1045
- await cloudApiService2.track("didLogin", { loginMethod: "cli" });
1046
- } catch (e) {
1047
- logger.debug("Failed to track login", e);
1048
- }
1049
- };
1050
- await authenticate();
1051
- };
1052
1123
  const command$2 = ({ command: command2, ctx }) => {
1053
- return command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1124
+ command2.command("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
1054
1125
  "after",
1055
1126
  "\nAfter running this command, you will be prompted to enter your authentication information."
1056
- ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", action$1)(ctx));
1127
+ ).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
1057
1128
  };
1058
1129
  const login = {
1059
1130
  name: "login",
1060
1131
  description: "Strapi Cloud Login",
1061
- action: action$1,
1132
+ action: loginAction,
1062
1133
  command: command$2
1063
1134
  };
1135
+ const openModule = import("open");
1064
1136
  const action = async (ctx) => {
1065
1137
  const { logger } = ctx;
1066
- const { retrieveToken, eraseToken } = tokenServiceFactory(ctx);
1138
+ const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
1067
1139
  const token = await retrieveToken();
1068
1140
  if (!token) {
1069
1141
  logger.log("You're already logged out.");
1070
1142
  return;
1071
1143
  }
1072
- const cloudApiService2 = cloudApiFactory(token);
1144
+ const cloudApiService = await cloudApiFactory(ctx, token);
1145
+ const config = await cloudApiService.config();
1146
+ const cliConfig2 = config.data;
1073
1147
  try {
1074
1148
  await eraseToken();
1149
+ openModule.then((open) => {
1150
+ open.default(
1151
+ `${cliConfig2.baseUrl}/oidc/logout?client_id=${encodeURIComponent(
1152
+ cliConfig2.clientId
1153
+ )}&logout_hint=${encodeURIComponent(token)}
1154
+ `
1155
+ ).catch((e) => {
1156
+ logger.debug(e.message, e);
1157
+ });
1158
+ });
1075
1159
  logger.log(
1076
1160
  "🔌 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."
1077
1161
  );
@@ -1080,13 +1164,13 @@ const action = async (ctx) => {
1080
1164
  logger.debug(e);
1081
1165
  }
1082
1166
  try {
1083
- await cloudApiService2.track("didLogout", { loginMethod: "cli" });
1167
+ await cloudApiService.track("didLogout", { loginMethod: "cli" });
1084
1168
  } catch (e) {
1085
1169
  logger.debug("Failed to track logout event", e);
1086
1170
  }
1087
1171
  };
1088
1172
  const command$1 = ({ command: command2, ctx }) => {
1089
- 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));
1173
+ 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));
1090
1174
  };
1091
1175
  const logout = {
1092
1176
  name: "logout",
@@ -1095,12 +1179,12 @@ const logout = {
1095
1179
  command: command$1
1096
1180
  };
1097
1181
  const command = ({ command: command2, ctx }) => {
1098
- 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));
1182
+ 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));
1099
1183
  };
1100
1184
  const createProject = {
1101
1185
  name: "create-project",
1102
1186
  description: "Create a new project",
1103
- action: action$3,
1187
+ action: action$2,
1104
1188
  command
1105
1189
  };
1106
1190
  const cli = {
@@ -1111,25 +1195,25 @@ const cli = {
1111
1195
  };
1112
1196
  const cloudCommands = [deployProject, login, logout];
1113
1197
  async function initCloudCLIConfig() {
1114
- const localConfig = getLocalConfig();
1198
+ const localConfig = await getLocalConfig();
1115
1199
  if (!localConfig.deviceId) {
1116
1200
  localConfig.deviceId = crypto$1.randomUUID();
1117
1201
  }
1118
- saveLocalConfig(localConfig);
1202
+ await saveLocalConfig(localConfig);
1119
1203
  }
1120
- function buildStrapiCloudCommands({
1204
+ async function buildStrapiCloudCommands({
1121
1205
  command: command2,
1122
1206
  ctx,
1123
1207
  argv
1124
1208
  }) {
1125
- initCloudCLIConfig();
1126
- cloudCommands.forEach((cloudCommand) => {
1209
+ await initCloudCLIConfig();
1210
+ for (const cloudCommand of cloudCommands) {
1127
1211
  try {
1128
- cloudCommand.command({ command: command2, ctx, argv });
1212
+ await cloudCommand.command({ command: command2, ctx, argv });
1129
1213
  } catch (e) {
1130
1214
  console.error(`Failed to load command ${cloudCommand.name}`, e);
1131
1215
  }
1132
- });
1216
+ }
1133
1217
  }
1134
1218
  export {
1135
1219
  buildStrapiCloudCommands,