@strapi/cloud-cli 0.0.0-next.a64ced5364618f917adb31b8684b4e3c01c58862 → 0.0.0-next.ac654f8b8646bf964ebd39d4313c4afab0917a24
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/LICENSE +18 -3
- package/dist/bin.js +1 -0
- package/dist/bin.js.map +1 -1
- package/dist/index.js +1019 -324
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1021 -324
- package/dist/index.mjs.map +1 -1
- package/dist/src/cloud/command.d.ts +3 -0
- package/dist/src/cloud/command.d.ts.map +1 -0
- package/dist/src/create-project/action.d.ts +1 -1
- package/dist/src/create-project/action.d.ts.map +1 -1
- package/dist/src/create-project/command.d.ts.map +1 -1
- package/dist/src/create-project/utils/get-project-name-from-pkg.d.ts +3 -0
- package/dist/src/create-project/utils/get-project-name-from-pkg.d.ts.map +1 -0
- package/dist/src/create-project/utils/project-questions.utils.d.ts +20 -0
- package/dist/src/create-project/utils/project-questions.utils.d.ts.map +1 -0
- package/dist/src/deploy-project/action.d.ts +5 -1
- package/dist/src/deploy-project/action.d.ts.map +1 -1
- package/dist/src/deploy-project/command.d.ts.map +1 -1
- package/dist/src/environment/command.d.ts +3 -0
- package/dist/src/environment/command.d.ts.map +1 -0
- package/dist/src/environment/link/action.d.ts +4 -0
- package/dist/src/environment/link/action.d.ts.map +1 -0
- package/dist/src/environment/link/command.d.ts +4 -0
- package/dist/src/environment/link/command.d.ts.map +1 -0
- package/dist/src/environment/link/index.d.ts +7 -0
- package/dist/src/environment/link/index.d.ts.map +1 -0
- package/dist/src/environment/list/action.d.ts +4 -0
- package/dist/src/environment/list/action.d.ts.map +1 -0
- package/dist/src/environment/list/command.d.ts +4 -0
- package/dist/src/environment/list/command.d.ts.map +1 -0
- package/dist/src/environment/list/index.d.ts +7 -0
- package/dist/src/environment/list/index.d.ts.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/link/action.d.ts +4 -0
- package/dist/src/link/action.d.ts.map +1 -0
- package/dist/src/link/command.d.ts +7 -0
- package/dist/src/link/command.d.ts.map +1 -0
- package/dist/src/link/index.d.ts +7 -0
- package/dist/src/link/index.d.ts.map +1 -0
- package/dist/src/list-projects/action.d.ts +4 -0
- package/dist/src/list-projects/action.d.ts.map +1 -0
- package/dist/src/list-projects/command.d.ts +7 -0
- package/dist/src/list-projects/command.d.ts.map +1 -0
- package/dist/src/list-projects/index.d.ts +7 -0
- package/dist/src/list-projects/index.d.ts.map +1 -0
- package/dist/src/login/action.d.ts +2 -2
- package/dist/src/login/action.d.ts.map +1 -1
- package/dist/src/login/command.d.ts.map +1 -1
- package/dist/src/logout/action.d.ts.map +1 -1
- package/dist/src/logout/command.d.ts.map +1 -1
- package/dist/src/services/build-logs.d.ts.map +1 -1
- package/dist/src/services/cli-api.d.ts +67 -10
- package/dist/src/services/cli-api.d.ts.map +1 -1
- package/dist/src/services/strapi-info-save.d.ts +15 -2
- package/dist/src/services/strapi-info-save.d.ts.map +1 -1
- package/dist/src/services/token.d.ts +1 -1
- package/dist/src/services/token.d.ts.map +1 -1
- package/dist/src/types.d.ts +8 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/analytics.d.ts +4 -0
- package/dist/src/utils/analytics.d.ts.map +1 -0
- package/dist/src/utils/compress-files.d.ts.map +1 -1
- package/dist/src/utils/get-local-config.d.ts +6 -0
- package/dist/src/utils/get-local-config.d.ts.map +1 -0
- package/dist/src/utils/pkg.d.ts.map +1 -1
- package/package.json +11 -9
package/dist/index.mjs
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import crypto$1 from "crypto";
|
|
2
|
-
import fse from "fs-extra";
|
|
2
|
+
import * as fse from "fs-extra";
|
|
3
|
+
import fse__default from "fs-extra";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import boxen from "boxen";
|
|
3
6
|
import * as path from "path";
|
|
4
7
|
import path__default from "path";
|
|
5
8
|
import chalk from "chalk";
|
|
6
9
|
import axios, { AxiosError } from "axios";
|
|
7
10
|
import * as crypto from "node:crypto";
|
|
8
11
|
import { env } from "@strapi/utils";
|
|
9
|
-
import * as fs from "fs";
|
|
10
12
|
import * as tar from "tar";
|
|
11
13
|
import { minimatch } from "minimatch";
|
|
12
|
-
import inquirer from "inquirer";
|
|
13
14
|
import { defaults, has } from "lodash/fp";
|
|
14
15
|
import os from "os";
|
|
15
16
|
import XDGAppPaths from "xdg-app-paths";
|
|
17
|
+
import { merge } from "lodash";
|
|
16
18
|
import jwksClient from "jwks-rsa";
|
|
17
19
|
import jwt from "jsonwebtoken";
|
|
18
20
|
import stringify from "fast-safe-stringify";
|
|
19
21
|
import ora from "ora";
|
|
20
22
|
import * as cliProgress from "cli-progress";
|
|
21
|
-
import EventSource from "eventsource";
|
|
22
|
-
import fs$1 from "fs/promises";
|
|
23
23
|
import pkgUp from "pkg-up";
|
|
24
24
|
import * as yup from "yup";
|
|
25
|
+
import EventSource from "eventsource";
|
|
26
|
+
import { createCommand } from "commander";
|
|
25
27
|
const apiConfig = {
|
|
26
28
|
apiBaseUrl: env("STRAPI_CLI_CLOUD_API", "https://cloud-cli-api.strapi.io"),
|
|
27
29
|
dashboardBaseUrl: env("STRAPI_CLI_CLOUD_DASHBOARD", "https://cloud.strapi.io")
|
|
@@ -40,23 +42,6 @@ const IGNORED_PATTERNS = [
|
|
|
40
42
|
"**/.idea/**",
|
|
41
43
|
"**/.vscode/**"
|
|
42
44
|
];
|
|
43
|
-
const getFiles = (dirPath, ignorePatterns = [], arrayOfFiles = [], subfolder = "") => {
|
|
44
|
-
const entries = fs.readdirSync(path.join(dirPath, subfolder));
|
|
45
|
-
entries.forEach((entry) => {
|
|
46
|
-
const entryPathFromRoot = path.join(subfolder, entry);
|
|
47
|
-
const entryPath = path.relative(dirPath, entryPathFromRoot);
|
|
48
|
-
const isIgnored = isIgnoredFile(dirPath, entryPathFromRoot, ignorePatterns);
|
|
49
|
-
if (isIgnored) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (fs.statSync(entryPath).isDirectory()) {
|
|
53
|
-
getFiles(dirPath, ignorePatterns, arrayOfFiles, entryPathFromRoot);
|
|
54
|
-
} else {
|
|
55
|
-
arrayOfFiles.push(entryPath);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
return arrayOfFiles;
|
|
59
|
-
};
|
|
60
45
|
const isIgnoredFile = (folderPath, file, ignorePatterns) => {
|
|
61
46
|
ignorePatterns.push(...IGNORED_PATTERNS);
|
|
62
47
|
const relativeFilePath = path.join(folderPath, file);
|
|
@@ -74,16 +59,35 @@ const isIgnoredFile = (folderPath, file, ignorePatterns) => {
|
|
|
74
59
|
}
|
|
75
60
|
return isIgnored;
|
|
76
61
|
};
|
|
77
|
-
const
|
|
62
|
+
const getFiles = async (dirPath, ignorePatterns = [], subfolder = "") => {
|
|
63
|
+
const arrayOfFiles = [];
|
|
64
|
+
const entries = await fse.readdir(path.join(dirPath, subfolder));
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const entryPathFromRoot = path.join(subfolder, entry);
|
|
67
|
+
const entryPath = path.relative(dirPath, entryPathFromRoot);
|
|
68
|
+
const isIgnored = isIgnoredFile(dirPath, entryPathFromRoot, ignorePatterns);
|
|
69
|
+
if (!isIgnored) {
|
|
70
|
+
if (fse.statSync(entryPath).isDirectory()) {
|
|
71
|
+
const subFiles = await getFiles(dirPath, ignorePatterns, entryPathFromRoot);
|
|
72
|
+
arrayOfFiles.push(...subFiles);
|
|
73
|
+
} else {
|
|
74
|
+
arrayOfFiles.push(entryPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return arrayOfFiles;
|
|
79
|
+
};
|
|
80
|
+
const readGitignore = async (folderPath) => {
|
|
78
81
|
const gitignorePath = path.resolve(folderPath, ".gitignore");
|
|
79
|
-
|
|
82
|
+
const pathExist = await fse.pathExists(gitignorePath);
|
|
83
|
+
if (!pathExist)
|
|
80
84
|
return [];
|
|
81
|
-
const gitignoreContent =
|
|
85
|
+
const gitignoreContent = await fse.readFile(gitignorePath, "utf8");
|
|
82
86
|
return gitignoreContent.split(/\r?\n/).filter((line) => Boolean(line.trim()) && !line.startsWith("#"));
|
|
83
87
|
};
|
|
84
88
|
const compressFilesToTar = async (storagePath, folderToCompress, filename) => {
|
|
85
|
-
const ignorePatterns = readGitignore(folderToCompress);
|
|
86
|
-
const filesToCompress = getFiles(folderToCompress, ignorePatterns);
|
|
89
|
+
const ignorePatterns = await readGitignore(folderToCompress);
|
|
90
|
+
const filesToCompress = await getFiles(folderToCompress, ignorePatterns);
|
|
87
91
|
return tar.c(
|
|
88
92
|
{
|
|
89
93
|
gzip: true,
|
|
@@ -96,7 +100,7 @@ const APP_FOLDER_NAME = "com.strapi.cli";
|
|
|
96
100
|
const CONFIG_FILENAME = "config.json";
|
|
97
101
|
async function checkDirectoryExists(directoryPath) {
|
|
98
102
|
try {
|
|
99
|
-
const fsStat = await
|
|
103
|
+
const fsStat = await fse__default.lstat(directoryPath);
|
|
100
104
|
return fsStat.isDirectory();
|
|
101
105
|
} catch (e) {
|
|
102
106
|
return false;
|
|
@@ -104,24 +108,24 @@ async function checkDirectoryExists(directoryPath) {
|
|
|
104
108
|
}
|
|
105
109
|
async function getTmpStoragePath() {
|
|
106
110
|
const storagePath = path__default.join(os.tmpdir(), APP_FOLDER_NAME);
|
|
107
|
-
await
|
|
111
|
+
await fse__default.ensureDir(storagePath);
|
|
108
112
|
return storagePath;
|
|
109
113
|
}
|
|
110
114
|
async function getConfigPath() {
|
|
111
115
|
const configDirs = XDGAppPaths(APP_FOLDER_NAME).configDirs();
|
|
112
116
|
const configPath = configDirs.find(checkDirectoryExists);
|
|
113
117
|
if (!configPath) {
|
|
114
|
-
await
|
|
118
|
+
await fse__default.ensureDir(configDirs[0]);
|
|
115
119
|
return configDirs[0];
|
|
116
120
|
}
|
|
117
121
|
return configPath;
|
|
118
122
|
}
|
|
119
|
-
async function getLocalConfig() {
|
|
123
|
+
async function getLocalConfig$1() {
|
|
120
124
|
const configPath = await getConfigPath();
|
|
121
125
|
const configFilePath = path__default.join(configPath, CONFIG_FILENAME);
|
|
122
|
-
await
|
|
126
|
+
await fse__default.ensureFile(configFilePath);
|
|
123
127
|
try {
|
|
124
|
-
return await
|
|
128
|
+
return await fse__default.readJSON(configFilePath, { encoding: "utf8", throws: true });
|
|
125
129
|
} catch (e) {
|
|
126
130
|
return {};
|
|
127
131
|
}
|
|
@@ -129,10 +133,10 @@ async function getLocalConfig() {
|
|
|
129
133
|
async function saveLocalConfig(data) {
|
|
130
134
|
const configPath = await getConfigPath();
|
|
131
135
|
const configFilePath = path__default.join(configPath, CONFIG_FILENAME);
|
|
132
|
-
await
|
|
136
|
+
await fse__default.writeJson(configFilePath, data, { encoding: "utf8", spaces: 2, mode: 384 });
|
|
133
137
|
}
|
|
134
138
|
const name = "@strapi/cloud-cli";
|
|
135
|
-
const version = "4.
|
|
139
|
+
const version = "5.4.0";
|
|
136
140
|
const description = "Commands to interact with the Strapi Cloud";
|
|
137
141
|
const keywords = [
|
|
138
142
|
"strapi",
|
|
@@ -173,17 +177,19 @@ const scripts = {
|
|
|
173
177
|
build: "pack-up build",
|
|
174
178
|
clean: "run -T rimraf ./dist",
|
|
175
179
|
lint: "run -T eslint .",
|
|
180
|
+
"test:unit": "run -T jest",
|
|
176
181
|
watch: "pack-up watch"
|
|
177
182
|
};
|
|
178
183
|
const dependencies = {
|
|
179
|
-
"@strapi/utils": "4.
|
|
180
|
-
axios: "1.
|
|
184
|
+
"@strapi/utils": "5.4.0",
|
|
185
|
+
axios: "1.7.4",
|
|
186
|
+
boxen: "5.1.2",
|
|
181
187
|
chalk: "4.1.2",
|
|
182
188
|
"cli-progress": "3.12.0",
|
|
183
189
|
commander: "8.3.0",
|
|
184
190
|
eventsource: "2.0.2",
|
|
185
191
|
"fast-safe-stringify": "2.1.1",
|
|
186
|
-
"fs-extra": "
|
|
192
|
+
"fs-extra": "11.2.0",
|
|
187
193
|
inquirer: "8.2.5",
|
|
188
194
|
jsonwebtoken: "9.0.0",
|
|
189
195
|
"jwks-rsa": "3.1.0",
|
|
@@ -192,7 +198,7 @@ const dependencies = {
|
|
|
192
198
|
open: "8.4.0",
|
|
193
199
|
ora: "5.4.1",
|
|
194
200
|
"pkg-up": "3.1.0",
|
|
195
|
-
tar: "6.1
|
|
201
|
+
tar: "6.2.1",
|
|
196
202
|
"xdg-app-paths": "8.3.0",
|
|
197
203
|
yup: "0.32.9"
|
|
198
204
|
};
|
|
@@ -201,13 +207,14 @@ const devDependencies = {
|
|
|
201
207
|
"@types/cli-progress": "3.11.5",
|
|
202
208
|
"@types/eventsource": "1.1.15",
|
|
203
209
|
"@types/lodash": "^4.14.191",
|
|
204
|
-
"eslint-config-custom": "4.
|
|
205
|
-
tsconfig: "4.
|
|
210
|
+
"eslint-config-custom": "5.4.0",
|
|
211
|
+
tsconfig: "5.4.0"
|
|
206
212
|
};
|
|
207
213
|
const engines = {
|
|
208
|
-
node: ">=18.0.0 <=
|
|
214
|
+
node: ">=18.0.0 <=22.x.x",
|
|
209
215
|
npm: ">=6.0.0"
|
|
210
216
|
};
|
|
217
|
+
const gitHead = "7d785703f52464577d077c4618cbe68b44f8a9cd";
|
|
211
218
|
const packageJson = {
|
|
212
219
|
name,
|
|
213
220
|
version,
|
|
@@ -228,11 +235,12 @@ const packageJson = {
|
|
|
228
235
|
scripts,
|
|
229
236
|
dependencies,
|
|
230
237
|
devDependencies,
|
|
231
|
-
engines
|
|
238
|
+
engines,
|
|
239
|
+
gitHead
|
|
232
240
|
};
|
|
233
241
|
const VERSION = "v1";
|
|
234
|
-
async function cloudApiFactory(token) {
|
|
235
|
-
const localConfig = await getLocalConfig();
|
|
242
|
+
async function cloudApiFactory({ logger }, token) {
|
|
243
|
+
const localConfig = await getLocalConfig$1();
|
|
236
244
|
const customHeaders = {
|
|
237
245
|
"x-device-id": localConfig.deviceId,
|
|
238
246
|
"x-app-version": packageJson.version,
|
|
@@ -255,7 +263,7 @@ async function cloudApiFactory(token) {
|
|
|
255
263
|
deploy({ filePath, project }, { onUploadProgress }) {
|
|
256
264
|
return axiosCloudAPI.post(
|
|
257
265
|
`/deploy/${project.name}`,
|
|
258
|
-
{ file:
|
|
266
|
+
{ file: fse__default.createReadStream(filePath), targetEnvironment: project.targetEnvironment },
|
|
259
267
|
{
|
|
260
268
|
headers: {
|
|
261
269
|
"Content-Type": "multipart/form-data"
|
|
@@ -284,11 +292,89 @@ async function cloudApiFactory(token) {
|
|
|
284
292
|
getUserInfo() {
|
|
285
293
|
return axiosCloudAPI.get("/user");
|
|
286
294
|
},
|
|
287
|
-
config() {
|
|
288
|
-
|
|
295
|
+
async config() {
|
|
296
|
+
try {
|
|
297
|
+
const response = await axiosCloudAPI.get("/config");
|
|
298
|
+
if (response.status !== 200) {
|
|
299
|
+
throw new Error("Error fetching cloud CLI config from the server.");
|
|
300
|
+
}
|
|
301
|
+
return response;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
logger.debug(
|
|
304
|
+
"🥲 Oops! Couldn't retrieve the cloud CLI config from the server. Please try again."
|
|
305
|
+
);
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
async listProjects() {
|
|
310
|
+
try {
|
|
311
|
+
const response = await axiosCloudAPI.get("/projects");
|
|
312
|
+
if (response.status !== 200) {
|
|
313
|
+
throw new Error("Error fetching cloud projects from the server.");
|
|
314
|
+
}
|
|
315
|
+
return response;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
logger.debug(
|
|
318
|
+
"🥲 Oops! Couldn't retrieve your project's list from the server. Please try again."
|
|
319
|
+
);
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
async listLinkProjects() {
|
|
324
|
+
try {
|
|
325
|
+
const response = await axiosCloudAPI.get("/projects-linkable");
|
|
326
|
+
if (response.status !== 200) {
|
|
327
|
+
throw new Error("Error fetching cloud projects from the server.");
|
|
328
|
+
}
|
|
329
|
+
return response;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
logger.debug(
|
|
332
|
+
"🥲 Oops! Couldn't retrieve your project's list from the server. Please try again."
|
|
333
|
+
);
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
async listEnvironments({ name: name2 }) {
|
|
338
|
+
try {
|
|
339
|
+
const response = await axiosCloudAPI.get(`/projects/${name2}/environments`);
|
|
340
|
+
if (response.status !== 200) {
|
|
341
|
+
throw new Error("Error fetching cloud environments from the server.");
|
|
342
|
+
}
|
|
343
|
+
return response;
|
|
344
|
+
} catch (error) {
|
|
345
|
+
logger.debug(
|
|
346
|
+
"🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
|
|
347
|
+
);
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
async listLinkEnvironments({ name: name2 }) {
|
|
352
|
+
try {
|
|
353
|
+
const response = await axiosCloudAPI.get(`/projects/${name2}/environments-linkable`);
|
|
354
|
+
if (response.status !== 200) {
|
|
355
|
+
throw new Error("Error fetching cloud environments from the server.");
|
|
356
|
+
}
|
|
357
|
+
return response;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
logger.debug(
|
|
360
|
+
"🥲 Oops! Couldn't retrieve your project's environments from the server. Please try again."
|
|
361
|
+
);
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
289
364
|
},
|
|
290
|
-
|
|
291
|
-
|
|
365
|
+
async getProject({ name: name2 }) {
|
|
366
|
+
try {
|
|
367
|
+
const response = await axiosCloudAPI.get(`/projects/${name2}`);
|
|
368
|
+
if (response.status !== 200) {
|
|
369
|
+
throw new Error("Error fetching project's details.");
|
|
370
|
+
}
|
|
371
|
+
return response;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
logger.debug(
|
|
374
|
+
"🥲 Oops! There was a problem retrieving your project's details. Please try again."
|
|
375
|
+
);
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
292
378
|
},
|
|
293
379
|
track(event, payload = {}) {
|
|
294
380
|
return axiosCloudAPI.post("/track", {
|
|
@@ -299,34 +385,51 @@ async function cloudApiFactory(token) {
|
|
|
299
385
|
};
|
|
300
386
|
}
|
|
301
387
|
const LOCAL_SAVE_FILENAME = ".strapi-cloud.json";
|
|
388
|
+
const getFilePath = (directoryPath) => path__default.join(directoryPath || process.cwd(), LOCAL_SAVE_FILENAME);
|
|
302
389
|
async function save(data, { directoryPath } = {}) {
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await fse.ensureDir(path__default.dirname(pathToFile));
|
|
307
|
-
await fse.writeJson(pathToFile, storedData, { encoding: "utf8" });
|
|
390
|
+
const pathToFile = getFilePath(directoryPath);
|
|
391
|
+
await fse__default.ensureDir(path__default.dirname(pathToFile));
|
|
392
|
+
await fse__default.writeJson(pathToFile, data, { encoding: "utf8" });
|
|
308
393
|
}
|
|
309
394
|
async function retrieve({
|
|
310
395
|
directoryPath
|
|
311
396
|
} = {}) {
|
|
312
|
-
const pathToFile =
|
|
313
|
-
const pathExists = await
|
|
397
|
+
const pathToFile = getFilePath(directoryPath);
|
|
398
|
+
const pathExists = await fse__default.pathExists(pathToFile);
|
|
314
399
|
if (!pathExists) {
|
|
315
400
|
return {};
|
|
316
401
|
}
|
|
317
|
-
return
|
|
402
|
+
return fse__default.readJSON(pathToFile, { encoding: "utf8" });
|
|
403
|
+
}
|
|
404
|
+
async function patch(patchData, { directoryPath } = {}) {
|
|
405
|
+
const pathToFile = getFilePath(directoryPath);
|
|
406
|
+
const existingData = await retrieve({ directoryPath });
|
|
407
|
+
if (!existingData) {
|
|
408
|
+
throw new Error("No configuration data found to patch.");
|
|
409
|
+
}
|
|
410
|
+
const newData = merge(existingData, patchData);
|
|
411
|
+
await fse__default.writeJson(pathToFile, newData, { encoding: "utf8" });
|
|
412
|
+
}
|
|
413
|
+
async function deleteConfig({ directoryPath } = {}) {
|
|
414
|
+
const pathToFile = getFilePath(directoryPath);
|
|
415
|
+
const pathExists = await fse__default.pathExists(pathToFile);
|
|
416
|
+
if (pathExists) {
|
|
417
|
+
await fse__default.remove(pathToFile);
|
|
418
|
+
}
|
|
318
419
|
}
|
|
319
420
|
const strapiInfoSave = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
320
421
|
__proto__: null,
|
|
321
422
|
LOCAL_SAVE_FILENAME,
|
|
423
|
+
deleteConfig,
|
|
424
|
+
patch,
|
|
322
425
|
retrieve,
|
|
323
426
|
save
|
|
324
427
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
325
428
|
let cliConfig;
|
|
326
429
|
async function tokenServiceFactory({ logger }) {
|
|
327
|
-
const cloudApiService = await cloudApiFactory();
|
|
430
|
+
const cloudApiService = await cloudApiFactory({ logger });
|
|
328
431
|
async function saveToken(str) {
|
|
329
|
-
const appConfig = await getLocalConfig();
|
|
432
|
+
const appConfig = await getLocalConfig$1();
|
|
330
433
|
if (!appConfig) {
|
|
331
434
|
logger.error("There was a problem saving your token. Please try again.");
|
|
332
435
|
return;
|
|
@@ -340,7 +443,7 @@ async function tokenServiceFactory({ logger }) {
|
|
|
340
443
|
}
|
|
341
444
|
}
|
|
342
445
|
async function retrieveToken() {
|
|
343
|
-
const appConfig = await getLocalConfig();
|
|
446
|
+
const appConfig = await getLocalConfig$1();
|
|
344
447
|
if (appConfig.token) {
|
|
345
448
|
if (await isTokenValid(appConfig.token)) {
|
|
346
449
|
return appConfig.token;
|
|
@@ -373,14 +476,17 @@ async function tokenServiceFactory({ logger }) {
|
|
|
373
476
|
"There seems to be a problem with your login information. Please try logging in again."
|
|
374
477
|
);
|
|
375
478
|
}
|
|
479
|
+
return Promise.reject(new Error("Invalid token"));
|
|
376
480
|
}
|
|
377
481
|
return new Promise((resolve, reject) => {
|
|
378
482
|
jwt.verify(idToken, getKey, (err) => {
|
|
379
483
|
if (err) {
|
|
380
484
|
reject(err);
|
|
381
|
-
} else {
|
|
382
|
-
resolve();
|
|
383
485
|
}
|
|
486
|
+
if (decodedToken.payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
487
|
+
reject(new Error("Token is expired"));
|
|
488
|
+
}
|
|
489
|
+
resolve();
|
|
384
490
|
});
|
|
385
491
|
});
|
|
386
492
|
}
|
|
@@ -399,7 +505,7 @@ async function tokenServiceFactory({ logger }) {
|
|
|
399
505
|
}
|
|
400
506
|
}
|
|
401
507
|
async function eraseToken() {
|
|
402
|
-
const appConfig = await getLocalConfig();
|
|
508
|
+
const appConfig = await getLocalConfig$1();
|
|
403
509
|
if (!appConfig) {
|
|
404
510
|
return;
|
|
405
511
|
}
|
|
@@ -414,15 +520,15 @@ async function tokenServiceFactory({ logger }) {
|
|
|
414
520
|
throw e;
|
|
415
521
|
}
|
|
416
522
|
}
|
|
417
|
-
async function getValidToken() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
logger.log(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
523
|
+
async function getValidToken(ctx, loginAction2) {
|
|
524
|
+
let token = await retrieveToken();
|
|
525
|
+
while (!token || !await isTokenValid(token)) {
|
|
526
|
+
logger.log(
|
|
527
|
+
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."
|
|
528
|
+
);
|
|
529
|
+
if (!await loginAction2(ctx))
|
|
530
|
+
return null;
|
|
531
|
+
token = await retrieveToken();
|
|
426
532
|
}
|
|
427
533
|
return token;
|
|
428
534
|
}
|
|
@@ -556,17 +662,257 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
|
|
|
556
662
|
local: strapiInfoSave,
|
|
557
663
|
tokenServiceFactory
|
|
558
664
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
559
|
-
|
|
665
|
+
yup.object({
|
|
666
|
+
name: yup.string().required(),
|
|
667
|
+
exports: yup.lazy(
|
|
668
|
+
(value) => yup.object(
|
|
669
|
+
typeof value === "object" ? Object.entries(value).reduce(
|
|
670
|
+
(acc, [key, value2]) => {
|
|
671
|
+
if (typeof value2 === "object") {
|
|
672
|
+
acc[key] = yup.object({
|
|
673
|
+
types: yup.string().optional(),
|
|
674
|
+
source: yup.string().required(),
|
|
675
|
+
module: yup.string().optional(),
|
|
676
|
+
import: yup.string().required(),
|
|
677
|
+
require: yup.string().required(),
|
|
678
|
+
default: yup.string().required()
|
|
679
|
+
}).noUnknown(true);
|
|
680
|
+
} else {
|
|
681
|
+
acc[key] = yup.string().matches(/^\.\/.*\.json$/).required();
|
|
682
|
+
}
|
|
683
|
+
return acc;
|
|
684
|
+
},
|
|
685
|
+
{}
|
|
686
|
+
) : void 0
|
|
687
|
+
).optional()
|
|
688
|
+
)
|
|
689
|
+
});
|
|
690
|
+
const loadPkg = async ({ cwd, logger }) => {
|
|
691
|
+
const pkgPath = await pkgUp({ cwd });
|
|
692
|
+
if (!pkgPath) {
|
|
693
|
+
throw new Error("Could not find a package.json in the current directory");
|
|
694
|
+
}
|
|
695
|
+
const buffer = await fse.readFile(pkgPath);
|
|
696
|
+
const pkg = JSON.parse(buffer.toString());
|
|
697
|
+
logger.debug("Loaded package.json:", os.EOL, pkg);
|
|
698
|
+
return pkg;
|
|
699
|
+
};
|
|
700
|
+
async function getProjectNameFromPackageJson(ctx) {
|
|
701
|
+
try {
|
|
702
|
+
const packageJson2 = await loadPkg(ctx);
|
|
703
|
+
return packageJson2.name || "my-strapi-project";
|
|
704
|
+
} catch (e) {
|
|
705
|
+
return "my-strapi-project";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const trackEvent = async (ctx, cloudApiService, eventName, eventData) => {
|
|
709
|
+
try {
|
|
710
|
+
await cloudApiService.track(eventName, eventData);
|
|
711
|
+
} catch (e) {
|
|
712
|
+
ctx.logger.debug(`Failed to track ${eventName}`, e);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
const openModule$1 = import("open");
|
|
716
|
+
async function promptLogin(ctx) {
|
|
717
|
+
const response = await inquirer.prompt([
|
|
718
|
+
{
|
|
719
|
+
type: "confirm",
|
|
720
|
+
name: "login",
|
|
721
|
+
message: "Would you like to login?"
|
|
722
|
+
}
|
|
723
|
+
]);
|
|
724
|
+
if (response.login) {
|
|
725
|
+
const loginSuccessful = await loginAction(ctx);
|
|
726
|
+
return loginSuccessful;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
async function loginAction(ctx) {
|
|
731
|
+
const { logger } = ctx;
|
|
560
732
|
const tokenService = await tokenServiceFactory(ctx);
|
|
733
|
+
const existingToken = await tokenService.retrieveToken();
|
|
734
|
+
const cloudApiService = await cloudApiFactory(ctx, existingToken || void 0);
|
|
735
|
+
if (existingToken) {
|
|
736
|
+
const isTokenValid = await tokenService.isTokenValid(existingToken);
|
|
737
|
+
if (isTokenValid) {
|
|
738
|
+
try {
|
|
739
|
+
const userInfo = await cloudApiService.getUserInfo();
|
|
740
|
+
const { email } = userInfo.data.data;
|
|
741
|
+
if (email) {
|
|
742
|
+
logger.log(`You are already logged into your account (${email}).`);
|
|
743
|
+
} else {
|
|
744
|
+
logger.log("You are already logged in.");
|
|
745
|
+
}
|
|
746
|
+
logger.log(
|
|
747
|
+
"To access your dashboard, please copy and paste the following URL into your web browser:"
|
|
748
|
+
);
|
|
749
|
+
logger.log(chalk.underline(`${apiConfig.dashboardBaseUrl}/projects`));
|
|
750
|
+
return true;
|
|
751
|
+
} catch (e) {
|
|
752
|
+
logger.debug("Failed to fetch user info", e);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
let cliConfig2;
|
|
757
|
+
try {
|
|
758
|
+
logger.info("🔌 Connecting to the Strapi Cloud API...");
|
|
759
|
+
const config = await cloudApiService.config();
|
|
760
|
+
cliConfig2 = config.data;
|
|
761
|
+
} catch (e) {
|
|
762
|
+
logger.error("🥲 Oops! Something went wrong while logging you in. Please try again.");
|
|
763
|
+
logger.debug(e);
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
await trackEvent(ctx, cloudApiService, "willLoginAttempt", {});
|
|
767
|
+
logger.debug("🔐 Creating device authentication request...", {
|
|
768
|
+
client_id: cliConfig2.clientId,
|
|
769
|
+
scope: cliConfig2.scope,
|
|
770
|
+
audience: cliConfig2.audience
|
|
771
|
+
});
|
|
772
|
+
const deviceAuthResponse = await axios.post(cliConfig2.deviceCodeAuthUrl, {
|
|
773
|
+
client_id: cliConfig2.clientId,
|
|
774
|
+
scope: cliConfig2.scope,
|
|
775
|
+
audience: cliConfig2.audience
|
|
776
|
+
}).catch((e) => {
|
|
777
|
+
logger.error("There was an issue with the authentication process. Please try again.");
|
|
778
|
+
if (e.message) {
|
|
779
|
+
logger.debug(e.message, e);
|
|
780
|
+
} else {
|
|
781
|
+
logger.debug(e);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
openModule$1.then((open) => {
|
|
785
|
+
open.default(deviceAuthResponse.data.verification_uri_complete).catch((e) => {
|
|
786
|
+
logger.error("We encountered an issue opening the browser. Please try again later.");
|
|
787
|
+
logger.debug(e.message, e);
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
logger.log("If a browser tab does not open automatically, please follow the next steps:");
|
|
791
|
+
logger.log(
|
|
792
|
+
`1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
|
|
793
|
+
);
|
|
794
|
+
logger.log(
|
|
795
|
+
`2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
|
|
796
|
+
`
|
|
797
|
+
);
|
|
798
|
+
const tokenPayload = {
|
|
799
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
800
|
+
device_code: deviceAuthResponse.data.device_code,
|
|
801
|
+
client_id: cliConfig2.clientId
|
|
802
|
+
};
|
|
803
|
+
let isAuthenticated = false;
|
|
804
|
+
const authenticate = async () => {
|
|
805
|
+
const spinner = logger.spinner("Waiting for authentication");
|
|
806
|
+
spinner.start();
|
|
807
|
+
const spinnerFail = () => spinner.fail("Authentication failed!");
|
|
808
|
+
while (!isAuthenticated) {
|
|
809
|
+
try {
|
|
810
|
+
const tokenResponse = await axios.post(cliConfig2.tokenUrl, tokenPayload);
|
|
811
|
+
const authTokenData = tokenResponse.data;
|
|
812
|
+
if (tokenResponse.status === 200) {
|
|
813
|
+
try {
|
|
814
|
+
logger.debug("🔐 Validating token...");
|
|
815
|
+
await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
|
|
816
|
+
logger.debug("🔐 Token validation successful!");
|
|
817
|
+
} catch (e) {
|
|
818
|
+
logger.debug(e);
|
|
819
|
+
spinnerFail();
|
|
820
|
+
throw new Error("Unable to proceed: Token validation failed");
|
|
821
|
+
}
|
|
822
|
+
logger.debug("🔍 Fetching user information...");
|
|
823
|
+
const cloudApiServiceWithToken = await cloudApiFactory(ctx, authTokenData.access_token);
|
|
824
|
+
await cloudApiServiceWithToken.getUserInfo();
|
|
825
|
+
logger.debug("🔍 User information fetched successfully!");
|
|
826
|
+
try {
|
|
827
|
+
logger.debug("📝 Saving login information...");
|
|
828
|
+
await tokenService.saveToken(authTokenData.access_token);
|
|
829
|
+
logger.debug("📝 Login information saved successfully!");
|
|
830
|
+
isAuthenticated = true;
|
|
831
|
+
} catch (e) {
|
|
832
|
+
logger.error(
|
|
833
|
+
"There was a problem saving your login information. Please try logging in again."
|
|
834
|
+
);
|
|
835
|
+
logger.debug(e);
|
|
836
|
+
spinnerFail();
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} catch (e) {
|
|
841
|
+
if (e.message === "Unable to proceed: Token validation failed") {
|
|
842
|
+
logger.error(
|
|
843
|
+
"There seems to be a problem with your login information. Please try logging in again."
|
|
844
|
+
);
|
|
845
|
+
spinnerFail();
|
|
846
|
+
await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
|
|
850
|
+
logger.debug(e);
|
|
851
|
+
spinnerFail();
|
|
852
|
+
await trackEvent(ctx, cloudApiService, "didNotLogin", { loginMethod: "cli" });
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
await new Promise((resolve) => {
|
|
856
|
+
setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
spinner.succeed("Authentication successful!");
|
|
861
|
+
logger.log("You are now logged into Strapi Cloud.");
|
|
862
|
+
logger.log(
|
|
863
|
+
"To access your dashboard, please copy and paste the following URL into your web browser:"
|
|
864
|
+
);
|
|
865
|
+
logger.log(chalk.underline(`${apiConfig.dashboardBaseUrl}/projects`));
|
|
866
|
+
await trackEvent(ctx, cloudApiService, "didLogin", { loginMethod: "cli" });
|
|
867
|
+
};
|
|
868
|
+
await authenticate();
|
|
869
|
+
return isAuthenticated;
|
|
870
|
+
}
|
|
871
|
+
function questionDefaultValuesMapper(questionsMap) {
|
|
872
|
+
return (questions) => {
|
|
873
|
+
return questions.map((question) => {
|
|
874
|
+
const questionName = question.name;
|
|
875
|
+
if (questionName in questionsMap) {
|
|
876
|
+
const questionDefault = questionsMap[questionName];
|
|
877
|
+
if (typeof questionDefault === "function") {
|
|
878
|
+
return {
|
|
879
|
+
...question,
|
|
880
|
+
default: questionDefault(question)
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
...question,
|
|
885
|
+
default: questionDefault
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
return question;
|
|
889
|
+
});
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function getDefaultsFromQuestions(questions) {
|
|
893
|
+
return questions.reduce((acc, question) => {
|
|
894
|
+
if (question.default && question.name) {
|
|
895
|
+
return { ...acc, [question.name]: question.default };
|
|
896
|
+
}
|
|
897
|
+
return acc;
|
|
898
|
+
}, {});
|
|
899
|
+
}
|
|
900
|
+
function getProjectNodeVersionDefault(question) {
|
|
901
|
+
const currentNodeVersion = process.versions.node.split(".")[0];
|
|
902
|
+
if (question.type === "list" && Array.isArray(question.choices)) {
|
|
903
|
+
const choice = question.choices.find((choice2) => choice2.value === currentNodeVersion);
|
|
904
|
+
if (choice) {
|
|
905
|
+
return choice.value;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return question.default;
|
|
909
|
+
}
|
|
910
|
+
async function handleError(ctx, error) {
|
|
561
911
|
const { logger } = ctx;
|
|
562
912
|
logger.debug(error);
|
|
563
913
|
if (error instanceof AxiosError) {
|
|
564
914
|
const errorMessage = typeof error.response?.data === "string" ? error.response.data : null;
|
|
565
915
|
switch (error.response?.status) {
|
|
566
|
-
case 401:
|
|
567
|
-
logger.error("Your session has expired. Please log in again.");
|
|
568
|
-
await tokenService.eraseToken();
|
|
569
|
-
return;
|
|
570
916
|
case 403:
|
|
571
917
|
logger.error(
|
|
572
918
|
errorMessage || "You do not have permission to create a project. Please contact support for assistance."
|
|
@@ -592,19 +938,8 @@ async function handleError(ctx, error) {
|
|
|
592
938
|
"We encountered an issue while creating your project. Please try again in a moment. If the problem persists, contact support for assistance."
|
|
593
939
|
);
|
|
594
940
|
}
|
|
595
|
-
|
|
941
|
+
async function createProject$1(ctx, cloudApi, projectInput) {
|
|
596
942
|
const { logger } = ctx;
|
|
597
|
-
const { getValidToken } = await tokenServiceFactory(ctx);
|
|
598
|
-
const token = await getValidToken();
|
|
599
|
-
if (!token) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const cloudApi = await cloudApiFactory(token);
|
|
603
|
-
const { data: config } = await cloudApi.config();
|
|
604
|
-
const { questions, defaults: defaultValues } = config.projectCreation;
|
|
605
|
-
const projectAnswersDefaulted = defaults(defaultValues);
|
|
606
|
-
const projectAnswers = await inquirer.prompt(questions);
|
|
607
|
-
const projectInput = projectAnswersDefaulted(projectAnswers);
|
|
608
943
|
const spinner = logger.spinner("Setting up your project...").start();
|
|
609
944
|
try {
|
|
610
945
|
const { data } = await cloudApi.createProject(projectInput);
|
|
@@ -612,17 +947,53 @@ const action$3 = async (ctx) => {
|
|
|
612
947
|
spinner.succeed("Project created successfully!");
|
|
613
948
|
return data;
|
|
614
949
|
} catch (e) {
|
|
615
|
-
spinner.fail("
|
|
616
|
-
|
|
950
|
+
spinner.fail("An error occurred while creating the project on Strapi Cloud.");
|
|
951
|
+
throw e;
|
|
617
952
|
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
953
|
+
}
|
|
954
|
+
const action$6 = async (ctx) => {
|
|
955
|
+
const { logger } = ctx;
|
|
956
|
+
const { getValidToken, eraseToken } = await tokenServiceFactory(ctx);
|
|
957
|
+
const token = await getValidToken(ctx, promptLogin);
|
|
958
|
+
if (!token) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const cloudApi = await cloudApiFactory(ctx, token);
|
|
962
|
+
const { data: config } = await cloudApi.config();
|
|
963
|
+
const projectName = await getProjectNameFromPackageJson(ctx);
|
|
964
|
+
const defaultAnswersMapper = questionDefaultValuesMapper({
|
|
965
|
+
name: projectName,
|
|
966
|
+
nodeVersion: getProjectNodeVersionDefault
|
|
967
|
+
});
|
|
968
|
+
const questions = defaultAnswersMapper(config.projectCreation.questions);
|
|
969
|
+
const defaultValues = {
|
|
970
|
+
...config.projectCreation.defaults,
|
|
971
|
+
...getDefaultsFromQuestions(questions)
|
|
972
|
+
};
|
|
973
|
+
const projectAnswersDefaulted = defaults(defaultValues);
|
|
974
|
+
const projectAnswers = await inquirer.prompt(questions);
|
|
975
|
+
const projectInput = projectAnswersDefaulted(projectAnswers);
|
|
976
|
+
try {
|
|
977
|
+
return await createProject$1(ctx, cloudApi, projectInput);
|
|
978
|
+
} catch (e) {
|
|
979
|
+
if (e instanceof AxiosError && e.response?.status === 401) {
|
|
980
|
+
logger.warn("Oops! Your session has expired. Please log in again to retry.");
|
|
981
|
+
await eraseToken();
|
|
982
|
+
if (await promptLogin(ctx)) {
|
|
983
|
+
return await createProject$1(ctx, cloudApi, projectInput);
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
await handleError(ctx, e);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
function notificationServiceFactory({ logger }) {
|
|
991
|
+
return (url, token, cliConfig2) => {
|
|
992
|
+
const CONN_TIMEOUT = Number(cliConfig2.notificationsConnectionTimeout);
|
|
993
|
+
const es = new EventSource(url, {
|
|
994
|
+
headers: {
|
|
995
|
+
Authorization: `Bearer ${token}`
|
|
996
|
+
}
|
|
626
997
|
});
|
|
627
998
|
let timeoutId;
|
|
628
999
|
const resetTimeout = () => {
|
|
@@ -647,38 +1018,6 @@ function notificationServiceFactory({ logger }) {
|
|
|
647
1018
|
};
|
|
648
1019
|
};
|
|
649
1020
|
}
|
|
650
|
-
yup.object({
|
|
651
|
-
name: yup.string().required(),
|
|
652
|
-
exports: yup.lazy(
|
|
653
|
-
(value) => yup.object(
|
|
654
|
-
typeof value === "object" ? Object.entries(value).reduce((acc, [key, value2]) => {
|
|
655
|
-
if (typeof value2 === "object") {
|
|
656
|
-
acc[key] = yup.object({
|
|
657
|
-
types: yup.string().optional(),
|
|
658
|
-
source: yup.string().required(),
|
|
659
|
-
module: yup.string().optional(),
|
|
660
|
-
import: yup.string().required(),
|
|
661
|
-
require: yup.string().required(),
|
|
662
|
-
default: yup.string().required()
|
|
663
|
-
}).noUnknown(true);
|
|
664
|
-
} else {
|
|
665
|
-
acc[key] = yup.string().matches(/^\.\/.*\.json$/).required();
|
|
666
|
-
}
|
|
667
|
-
return acc;
|
|
668
|
-
}, {}) : void 0
|
|
669
|
-
).optional()
|
|
670
|
-
)
|
|
671
|
-
});
|
|
672
|
-
const loadPkg = async ({ cwd, logger }) => {
|
|
673
|
-
const pkgPath = await pkgUp({ cwd });
|
|
674
|
-
if (!pkgPath) {
|
|
675
|
-
throw new Error("Could not find a package.json in the current directory");
|
|
676
|
-
}
|
|
677
|
-
const buffer = await fs$1.readFile(pkgPath);
|
|
678
|
-
const pkg = JSON.parse(buffer.toString());
|
|
679
|
-
logger.debug("Loaded package.json:", os.EOL, pkg);
|
|
680
|
-
return pkg;
|
|
681
|
-
};
|
|
682
1021
|
const buildLogsServiceFactory = ({ logger }) => {
|
|
683
1022
|
return async (url, token, cliConfig2) => {
|
|
684
1023
|
const CONN_TIMEOUT = Number(cliConfig2.buildLogsConnectionTimeout);
|
|
@@ -732,6 +1071,7 @@ const buildLogsServiceFactory = ({ logger }) => {
|
|
|
732
1071
|
if (retries > MAX_RETRIES) {
|
|
733
1072
|
spinner.fail("We were unable to connect to the server to get build logs at this time.");
|
|
734
1073
|
es.close();
|
|
1074
|
+
clearExistingTimeout();
|
|
735
1075
|
reject(new Error("Max retries reached"));
|
|
736
1076
|
}
|
|
737
1077
|
};
|
|
@@ -740,8 +1080,31 @@ const buildLogsServiceFactory = ({ logger }) => {
|
|
|
740
1080
|
});
|
|
741
1081
|
};
|
|
742
1082
|
};
|
|
1083
|
+
const boxenOptions = {
|
|
1084
|
+
padding: 1,
|
|
1085
|
+
margin: 1,
|
|
1086
|
+
align: "center",
|
|
1087
|
+
borderColor: "yellow",
|
|
1088
|
+
borderStyle: "round"
|
|
1089
|
+
};
|
|
1090
|
+
const QUIT_OPTION$2 = "Quit";
|
|
1091
|
+
async function promptForEnvironment(environments) {
|
|
1092
|
+
const choices = environments.map((env2) => ({ name: env2, value: env2 }));
|
|
1093
|
+
const { selectedEnvironment } = await inquirer.prompt([
|
|
1094
|
+
{
|
|
1095
|
+
type: "list",
|
|
1096
|
+
name: "selectedEnvironment",
|
|
1097
|
+
message: "Select the environment to deploy:",
|
|
1098
|
+
choices: [...choices, { name: chalk.grey(`(${QUIT_OPTION$2})`), value: null }]
|
|
1099
|
+
}
|
|
1100
|
+
]);
|
|
1101
|
+
if (selectedEnvironment === null) {
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
return selectedEnvironment;
|
|
1105
|
+
}
|
|
743
1106
|
async function upload(ctx, project, token, maxProjectFileSize) {
|
|
744
|
-
const cloudApi = await cloudApiFactory(token);
|
|
1107
|
+
const cloudApi = await cloudApiFactory(ctx, token);
|
|
745
1108
|
try {
|
|
746
1109
|
const storagePath = await getTmpStoragePath();
|
|
747
1110
|
const projectFolder = path__default.resolve(process.cwd());
|
|
@@ -774,13 +1137,13 @@ async function upload(ctx, project, token, maxProjectFileSize) {
|
|
|
774
1137
|
process.exit(1);
|
|
775
1138
|
}
|
|
776
1139
|
const tarFilePath = path__default.resolve(storagePath, compressedFilename);
|
|
777
|
-
const fileStats = await
|
|
1140
|
+
const fileStats = await fse__default.stat(tarFilePath);
|
|
778
1141
|
if (fileStats.size > maxProjectFileSize) {
|
|
779
1142
|
ctx.logger.log(
|
|
780
1143
|
"Unable to proceed: Your project is too big to be transferred, please use a git repo instead."
|
|
781
1144
|
);
|
|
782
1145
|
try {
|
|
783
|
-
await
|
|
1146
|
+
await fse__default.remove(tarFilePath);
|
|
784
1147
|
} catch (e) {
|
|
785
1148
|
ctx.logger.log("Unable to remove file: ", tarFilePath);
|
|
786
1149
|
ctx.logger.debug(e);
|
|
@@ -806,20 +1169,10 @@ async function upload(ctx, project, token, maxProjectFileSize) {
|
|
|
806
1169
|
return data.build_id;
|
|
807
1170
|
} catch (e) {
|
|
808
1171
|
progressBar.stop();
|
|
809
|
-
|
|
810
|
-
if (e.response.status === 404) {
|
|
811
|
-
ctx.logger.error(
|
|
812
|
-
`The project does not exist. Remove the ${LOCAL_SAVE_FILENAME} file and try again.`
|
|
813
|
-
);
|
|
814
|
-
} else {
|
|
815
|
-
ctx.logger.error(e.response.data);
|
|
816
|
-
}
|
|
817
|
-
} else {
|
|
818
|
-
ctx.logger.error("An error occurred while deploying the project. Please try again later.");
|
|
819
|
-
}
|
|
1172
|
+
ctx.logger.error("An error occurred while deploying the project. Please try again later.");
|
|
820
1173
|
ctx.logger.debug(e);
|
|
821
1174
|
} finally {
|
|
822
|
-
await
|
|
1175
|
+
await fse__default.remove(tarFilePath);
|
|
823
1176
|
}
|
|
824
1177
|
process.exit(0);
|
|
825
1178
|
} catch (e) {
|
|
@@ -832,7 +1185,7 @@ async function getProject(ctx) {
|
|
|
832
1185
|
const { project } = await retrieve();
|
|
833
1186
|
if (!project) {
|
|
834
1187
|
try {
|
|
835
|
-
return await action$
|
|
1188
|
+
return await action$6(ctx);
|
|
836
1189
|
} catch (e) {
|
|
837
1190
|
ctx.logger.error("An error occurred while deploying the project. Please try again later.");
|
|
838
1191
|
ctx.logger.debug(e);
|
|
@@ -841,10 +1194,47 @@ async function getProject(ctx) {
|
|
|
841
1194
|
}
|
|
842
1195
|
return project;
|
|
843
1196
|
}
|
|
844
|
-
|
|
1197
|
+
async function getConfig({
|
|
1198
|
+
ctx,
|
|
1199
|
+
cloudApiService
|
|
1200
|
+
}) {
|
|
1201
|
+
try {
|
|
1202
|
+
const { data: cliConfig2 } = await cloudApiService.config();
|
|
1203
|
+
return cliConfig2;
|
|
1204
|
+
} catch (e) {
|
|
1205
|
+
ctx.logger.debug("Failed to get cli config", e);
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function validateEnvironment(ctx, environment, environments) {
|
|
1210
|
+
if (!environments.includes(environment)) {
|
|
1211
|
+
ctx.logger.error(`Environment ${environment} does not exist.`);
|
|
1212
|
+
process.exit(1);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
async function getTargetEnvironment(ctx, opts, project, environments) {
|
|
1216
|
+
if (opts.env) {
|
|
1217
|
+
validateEnvironment(ctx, opts.env, environments);
|
|
1218
|
+
return opts.env;
|
|
1219
|
+
}
|
|
1220
|
+
if (project.targetEnvironment) {
|
|
1221
|
+
return project.targetEnvironment;
|
|
1222
|
+
}
|
|
1223
|
+
if (environments.length > 1) {
|
|
1224
|
+
return promptForEnvironment(environments);
|
|
1225
|
+
}
|
|
1226
|
+
return environments[0];
|
|
1227
|
+
}
|
|
1228
|
+
function hasPendingOrLiveDeployment(environments, targetEnvironment) {
|
|
1229
|
+
const environment = environments.find((env2) => env2.name === targetEnvironment);
|
|
1230
|
+
if (!environment) {
|
|
1231
|
+
throw new Error(`Environment details ${targetEnvironment} not found.`);
|
|
1232
|
+
}
|
|
1233
|
+
return environment.hasPendingDeployment || environment.hasLiveDeployment || false;
|
|
1234
|
+
}
|
|
1235
|
+
const action$5 = async (ctx, opts) => {
|
|
845
1236
|
const { getValidToken } = await tokenServiceFactory(ctx);
|
|
846
|
-
const
|
|
847
|
-
const token = await getValidToken();
|
|
1237
|
+
const token = await getValidToken(ctx, promptLogin);
|
|
848
1238
|
if (!token) {
|
|
849
1239
|
return;
|
|
850
1240
|
}
|
|
@@ -852,14 +1242,57 @@ const action$2 = async (ctx) => {
|
|
|
852
1242
|
if (!project) {
|
|
853
1243
|
return;
|
|
854
1244
|
}
|
|
1245
|
+
const cloudApiService = await cloudApiFactory(ctx, token);
|
|
1246
|
+
let projectData;
|
|
1247
|
+
let environments;
|
|
1248
|
+
let environmentsDetails;
|
|
855
1249
|
try {
|
|
856
|
-
|
|
1250
|
+
const {
|
|
1251
|
+
data: { data, metadata }
|
|
1252
|
+
} = await cloudApiService.getProject({ name: project.name });
|
|
1253
|
+
projectData = data;
|
|
1254
|
+
environments = projectData.environments;
|
|
1255
|
+
environmentsDetails = projectData.environmentsDetails;
|
|
1256
|
+
const isProjectSuspended = projectData.suspendedAt;
|
|
1257
|
+
if (isProjectSuspended) {
|
|
1258
|
+
ctx.logger.log(
|
|
1259
|
+
"\n Oops! This project has been suspended. \n\n Please reactivate it from the dashboard to continue deploying: "
|
|
1260
|
+
);
|
|
1261
|
+
ctx.logger.log(chalk.underline(`${metadata.dashboardUrls.project}`));
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
857
1264
|
} catch (e) {
|
|
858
|
-
|
|
1265
|
+
if (e instanceof AxiosError && e.response?.data) {
|
|
1266
|
+
if (e.response.status === 404) {
|
|
1267
|
+
ctx.logger.warn(
|
|
1268
|
+
`The project associated with this folder does not exist in Strapi Cloud.
|
|
1269
|
+
Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
|
1270
|
+
"link"
|
|
1271
|
+
)} command before deploying.`
|
|
1272
|
+
);
|
|
1273
|
+
} else {
|
|
1274
|
+
ctx.logger.error(e.response.data);
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
ctx.logger.error(
|
|
1278
|
+
"An error occurred while retrieving the project's information. Please try again later."
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
ctx.logger.debug(e);
|
|
1282
|
+
return;
|
|
859
1283
|
}
|
|
1284
|
+
await trackEvent(ctx, cloudApiService, "willDeployWithCLI", {
|
|
1285
|
+
projectInternalName: project.name
|
|
1286
|
+
});
|
|
860
1287
|
const notificationService = notificationServiceFactory(ctx);
|
|
861
1288
|
const buildLogsService = buildLogsServiceFactory(ctx);
|
|
862
|
-
const
|
|
1289
|
+
const cliConfig2 = await getConfig({ ctx, cloudApiService });
|
|
1290
|
+
if (!cliConfig2) {
|
|
1291
|
+
ctx.logger.error(
|
|
1292
|
+
"An error occurred while retrieving data from Strapi Cloud. Please check your network or try again later."
|
|
1293
|
+
);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
863
1296
|
let maxSize = parseInt(cliConfig2.maxProjectFileSize, 10);
|
|
864
1297
|
if (Number.isNaN(maxSize)) {
|
|
865
1298
|
ctx.logger.debug(
|
|
@@ -867,11 +1300,34 @@ const action$2 = async (ctx) => {
|
|
|
867
1300
|
);
|
|
868
1301
|
maxSize = 1e8;
|
|
869
1302
|
}
|
|
1303
|
+
project.targetEnvironment = await getTargetEnvironment(ctx, opts, project, environments);
|
|
1304
|
+
if (!opts.force) {
|
|
1305
|
+
const shouldDisplayWarning = hasPendingOrLiveDeployment(
|
|
1306
|
+
environmentsDetails,
|
|
1307
|
+
project.targetEnvironment
|
|
1308
|
+
);
|
|
1309
|
+
if (shouldDisplayWarning) {
|
|
1310
|
+
ctx.logger.log(boxen(cliConfig2.projectDeployment.confirmationText, boxenOptions));
|
|
1311
|
+
const { confirm } = await inquirer.prompt([
|
|
1312
|
+
{
|
|
1313
|
+
type: "confirm",
|
|
1314
|
+
name: "confirm",
|
|
1315
|
+
message: `Do you want to proceed with deployment to ${chalk.cyan(projectData.displayName)} on ${chalk.cyan(project.targetEnvironment)} environment?`
|
|
1316
|
+
}
|
|
1317
|
+
]);
|
|
1318
|
+
if (!confirm) {
|
|
1319
|
+
process.exit(1);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
870
1323
|
const buildId = await upload(ctx, project, token, maxSize);
|
|
871
1324
|
if (!buildId) {
|
|
872
1325
|
return;
|
|
873
1326
|
}
|
|
874
1327
|
try {
|
|
1328
|
+
ctx.logger.log(
|
|
1329
|
+
`🚀 Deploying project to ${chalk.cyan(project.targetEnvironment ?? `production`)} environment...`
|
|
1330
|
+
);
|
|
875
1331
|
notificationService(`${apiConfig.apiBaseUrl}/notifications`, token, cliConfig2);
|
|
876
1332
|
await buildLogsService(`${apiConfig.apiBaseUrl}/v1/logs/${buildId}`, token, cliConfig2);
|
|
877
1333
|
ctx.logger.log(
|
|
@@ -881,10 +1337,11 @@ const action$2 = async (ctx) => {
|
|
|
881
1337
|
chalk.underline(`${apiConfig.dashboardBaseUrl}/projects/${project.name}/deployments`)
|
|
882
1338
|
);
|
|
883
1339
|
} catch (e) {
|
|
1340
|
+
ctx.logger.debug(e);
|
|
884
1341
|
if (e instanceof Error) {
|
|
885
1342
|
ctx.logger.error(e.message);
|
|
886
1343
|
} else {
|
|
887
|
-
|
|
1344
|
+
ctx.logger.error("An error occurred while deploying the project. Please try again later.");
|
|
888
1345
|
}
|
|
889
1346
|
}
|
|
890
1347
|
};
|
|
@@ -915,185 +1372,191 @@ const runAction = (name2, action2) => (...args) => {
|
|
|
915
1372
|
process.exit(1);
|
|
916
1373
|
});
|
|
917
1374
|
};
|
|
918
|
-
const command$
|
|
919
|
-
|
|
1375
|
+
const command$7 = ({ ctx }) => {
|
|
1376
|
+
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("-f, --force", "Skip confirmation to deploy").option("-e, --env <name>", "Specify the environment to deploy").action((opts) => runAction("deploy", action$5)(ctx, opts));
|
|
920
1377
|
};
|
|
921
1378
|
const deployProject = {
|
|
922
1379
|
name: "deploy-project",
|
|
923
1380
|
description: "Deploy a Strapi Cloud project",
|
|
924
|
-
action: action$
|
|
925
|
-
command: command$
|
|
1381
|
+
action: action$5,
|
|
1382
|
+
command: command$7
|
|
926
1383
|
};
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1384
|
+
async function getLocalConfig(ctx) {
|
|
1385
|
+
try {
|
|
1386
|
+
return await retrieve();
|
|
1387
|
+
} catch (e) {
|
|
1388
|
+
ctx.logger.debug("Failed to get project config", e);
|
|
1389
|
+
ctx.logger.error("An error occurred while retrieving config data from your local project.");
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async function getLocalProject(ctx) {
|
|
1394
|
+
const localConfig = await getLocalConfig(ctx);
|
|
1395
|
+
if (!localConfig || !localConfig.project) {
|
|
1396
|
+
ctx.logger.warn(
|
|
1397
|
+
`
|
|
1398
|
+
We couldn't find a valid local project config.
|
|
1399
|
+
Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
|
1400
|
+
"link"
|
|
1401
|
+
)} command.`
|
|
1402
|
+
);
|
|
1403
|
+
process.exit(1);
|
|
1404
|
+
}
|
|
1405
|
+
return localConfig.project;
|
|
1406
|
+
}
|
|
1407
|
+
const QUIT_OPTION$1 = "Quit";
|
|
1408
|
+
async function promptForRelink(ctx, cloudApiService, existingConfig) {
|
|
1409
|
+
if (existingConfig && existingConfig.project) {
|
|
1410
|
+
const { shouldRelink } = await inquirer.prompt([
|
|
1411
|
+
{
|
|
1412
|
+
type: "confirm",
|
|
1413
|
+
name: "shouldRelink",
|
|
1414
|
+
message: `A project named ${chalk.cyan(
|
|
1415
|
+
existingConfig.project.displayName ? existingConfig.project.displayName : existingConfig.project.name
|
|
1416
|
+
)} is already linked to this local folder. Do you want to update the link?`,
|
|
1417
|
+
default: false
|
|
958
1418
|
}
|
|
1419
|
+
]);
|
|
1420
|
+
if (!shouldRelink) {
|
|
1421
|
+
await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
|
|
1422
|
+
currentProjectName: existingConfig.project?.name
|
|
1423
|
+
});
|
|
1424
|
+
return false;
|
|
959
1425
|
}
|
|
960
1426
|
}
|
|
961
|
-
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
async function getProjectsList(ctx, cloudApiService, existingConfig) {
|
|
1430
|
+
const spinner = ctx.logger.spinner("Fetching your projects...\n").start();
|
|
962
1431
|
try {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1432
|
+
const {
|
|
1433
|
+
data: { data: projectList }
|
|
1434
|
+
} = await cloudApiService.listLinkProjects();
|
|
1435
|
+
spinner.succeed();
|
|
1436
|
+
if (!Array.isArray(projectList)) {
|
|
1437
|
+
ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud.");
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const projects = projectList.filter(
|
|
1441
|
+
(project) => !(project.isMaintainer || project.name === existingConfig?.project?.name)
|
|
1442
|
+
).map((project) => {
|
|
1443
|
+
return {
|
|
1444
|
+
name: project.displayName,
|
|
1445
|
+
value: { name: project.name, displayName: project.displayName }
|
|
1446
|
+
};
|
|
1447
|
+
});
|
|
1448
|
+
if (projects.length === 0) {
|
|
1449
|
+
ctx.logger.log("We couldn't find any projects available for linking in Strapi Cloud.");
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
return projects;
|
|
966
1453
|
} catch (e) {
|
|
967
|
-
|
|
968
|
-
logger.debug(e);
|
|
969
|
-
return
|
|
1454
|
+
spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
|
|
1455
|
+
ctx.logger.debug("Failed to list projects", e);
|
|
1456
|
+
return null;
|
|
970
1457
|
}
|
|
1458
|
+
}
|
|
1459
|
+
async function getUserSelection(ctx, projects) {
|
|
1460
|
+
const { logger } = ctx;
|
|
971
1461
|
try {
|
|
972
|
-
await
|
|
1462
|
+
const answer = await inquirer.prompt([
|
|
1463
|
+
{
|
|
1464
|
+
type: "list",
|
|
1465
|
+
name: "linkProject",
|
|
1466
|
+
message: "Which project do you want to link?",
|
|
1467
|
+
choices: [...projects, { name: chalk.grey(`(${QUIT_OPTION$1})`), value: null }]
|
|
1468
|
+
}
|
|
1469
|
+
]);
|
|
1470
|
+
if (!answer.linkProject) {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
return answer;
|
|
973
1474
|
} catch (e) {
|
|
974
|
-
logger.debug("Failed to
|
|
1475
|
+
logger.debug("Failed to get user input", e);
|
|
1476
|
+
logger.error("An error occurred while trying to get your input.");
|
|
1477
|
+
return null;
|
|
975
1478
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
logger.error("We encountered an issue opening the browser. Please try again later.");
|
|
996
|
-
logger.debug(e.message, e);
|
|
997
|
-
});
|
|
998
|
-
});
|
|
999
|
-
logger.log("If a browser tab does not open automatically, please follow the next steps:");
|
|
1000
|
-
logger.log(
|
|
1001
|
-
`1. Open this url in your device: ${deviceAuthResponse.data.verification_uri_complete}`
|
|
1002
|
-
);
|
|
1003
|
-
logger.log(
|
|
1004
|
-
`2. Enter the following code: ${deviceAuthResponse.data.user_code} and confirm to login.
|
|
1005
|
-
`
|
|
1479
|
+
}
|
|
1480
|
+
const action$4 = async (ctx) => {
|
|
1481
|
+
const { getValidToken } = await tokenServiceFactory(ctx);
|
|
1482
|
+
const token = await getValidToken(ctx, promptLogin);
|
|
1483
|
+
const { logger } = ctx;
|
|
1484
|
+
if (!token) {
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const cloudApiService = await cloudApiFactory(ctx, token);
|
|
1488
|
+
const existingConfig = await getLocalConfig(ctx);
|
|
1489
|
+
const shouldRelink = await promptForRelink(ctx, cloudApiService, existingConfig);
|
|
1490
|
+
if (!shouldRelink) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
await trackEvent(ctx, cloudApiService, "willLinkProject", {});
|
|
1494
|
+
const projects = await getProjectsList(
|
|
1495
|
+
ctx,
|
|
1496
|
+
cloudApiService,
|
|
1497
|
+
existingConfig
|
|
1006
1498
|
);
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
if (tokenResponse.status === 200) {
|
|
1022
|
-
try {
|
|
1023
|
-
logger.debug("🔐 Validating token...");
|
|
1024
|
-
await tokenService.validateToken(authTokenData.id_token, cliConfig2.jwksUrl);
|
|
1025
|
-
logger.debug("🔐 Token validation successful!");
|
|
1026
|
-
} catch (e) {
|
|
1027
|
-
logger.debug(e);
|
|
1028
|
-
spinnerFail();
|
|
1029
|
-
throw new Error("Unable to proceed: Token validation failed");
|
|
1030
|
-
}
|
|
1031
|
-
logger.debug("🔍 Fetching user information...");
|
|
1032
|
-
const cloudApiServiceWithToken = await cloudApiFactory(authTokenData.access_token);
|
|
1033
|
-
await cloudApiServiceWithToken.getUserInfo();
|
|
1034
|
-
logger.debug("🔍 User information fetched successfully!");
|
|
1035
|
-
try {
|
|
1036
|
-
logger.debug("📝 Saving login information...");
|
|
1037
|
-
await tokenService.saveToken(authTokenData.access_token);
|
|
1038
|
-
logger.debug("📝 Login information saved successfully!");
|
|
1039
|
-
isAuthenticated = true;
|
|
1040
|
-
} catch (e) {
|
|
1041
|
-
logger.error(
|
|
1042
|
-
"There was a problem saving your login information. Please try logging in again."
|
|
1043
|
-
);
|
|
1044
|
-
logger.debug(e);
|
|
1045
|
-
spinnerFail();
|
|
1046
|
-
return false;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
} catch (e) {
|
|
1050
|
-
if (e.message === "Unable to proceed: Token validation failed") {
|
|
1051
|
-
logger.error(
|
|
1052
|
-
"There seems to be a problem with your login information. Please try logging in again."
|
|
1053
|
-
);
|
|
1054
|
-
spinnerFail();
|
|
1055
|
-
await trackFailedLogin();
|
|
1056
|
-
return false;
|
|
1057
|
-
}
|
|
1058
|
-
if (e.response?.data.error && !["authorization_pending", "slow_down"].includes(e.response.data.error)) {
|
|
1059
|
-
logger.debug(e);
|
|
1060
|
-
spinnerFail();
|
|
1061
|
-
await trackFailedLogin();
|
|
1062
|
-
return false;
|
|
1063
|
-
}
|
|
1064
|
-
await new Promise((resolve) => {
|
|
1065
|
-
setTimeout(resolve, deviceAuthResponse.data.interval * 1e3);
|
|
1066
|
-
});
|
|
1499
|
+
if (!projects) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const answer = await getUserSelection(ctx, projects);
|
|
1503
|
+
if (!answer) {
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
try {
|
|
1507
|
+
const { confirmAction } = await inquirer.prompt([
|
|
1508
|
+
{
|
|
1509
|
+
type: "confirm",
|
|
1510
|
+
name: "confirmAction",
|
|
1511
|
+
message: "Warning: Once linked, deploying from CLI will replace the existing project and its data. Confirm to proceed:",
|
|
1512
|
+
default: false
|
|
1067
1513
|
}
|
|
1514
|
+
]);
|
|
1515
|
+
if (!confirmAction) {
|
|
1516
|
+
await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
|
|
1517
|
+
cancelledProjectName: answer.linkProject.name,
|
|
1518
|
+
currentProjectName: existingConfig ? existingConfig.project?.name : null
|
|
1519
|
+
});
|
|
1520
|
+
return;
|
|
1068
1521
|
}
|
|
1069
|
-
|
|
1070
|
-
logger.log("You are now logged into Strapi Cloud.");
|
|
1522
|
+
await save({ project: answer.linkProject });
|
|
1071
1523
|
logger.log(
|
|
1072
|
-
|
|
1524
|
+
` You have successfully linked your project to ${chalk.cyan(answer.linkProject.displayName)}. You are now able to deploy your project.`
|
|
1073
1525
|
);
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1526
|
+
await trackEvent(ctx, cloudApiService, "didLinkProject", {
|
|
1527
|
+
projectInternalName: answer.linkProject
|
|
1528
|
+
});
|
|
1529
|
+
} catch (e) {
|
|
1530
|
+
logger.debug("Failed to link project", e);
|
|
1531
|
+
logger.error("An error occurred while linking the project.");
|
|
1532
|
+
await trackEvent(ctx, cloudApiService, "didNotLinkProject", {
|
|
1533
|
+
projectInternalName: answer.linkProject
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1083
1536
|
};
|
|
1084
|
-
const command$
|
|
1085
|
-
command2.command("cloud:
|
|
1537
|
+
const command$6 = ({ command: command2, ctx }) => {
|
|
1538
|
+
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));
|
|
1539
|
+
};
|
|
1540
|
+
const link = {
|
|
1541
|
+
name: "link-project",
|
|
1542
|
+
description: "Link a local directory to a Strapi Cloud project",
|
|
1543
|
+
action: action$4,
|
|
1544
|
+
command: command$6
|
|
1545
|
+
};
|
|
1546
|
+
const command$5 = ({ ctx }) => {
|
|
1547
|
+
return createCommand("cloud:login").alias("login").description("Strapi Cloud Login").addHelpText(
|
|
1086
1548
|
"after",
|
|
1087
1549
|
"\nAfter running this command, you will be prompted to enter your authentication information."
|
|
1088
|
-
).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login",
|
|
1550
|
+
).option("-d, --debug", "Enable debugging mode with verbose logs").option("-s, --silent", "Don't log anything").action(() => runAction("login", loginAction)(ctx));
|
|
1089
1551
|
};
|
|
1090
1552
|
const login = {
|
|
1091
1553
|
name: "login",
|
|
1092
1554
|
description: "Strapi Cloud Login",
|
|
1093
|
-
action:
|
|
1094
|
-
command: command$
|
|
1555
|
+
action: loginAction,
|
|
1556
|
+
command: command$5
|
|
1095
1557
|
};
|
|
1096
|
-
const
|
|
1558
|
+
const openModule = import("open");
|
|
1559
|
+
const action$3 = async (ctx) => {
|
|
1097
1560
|
const { logger } = ctx;
|
|
1098
1561
|
const { retrieveToken, eraseToken } = await tokenServiceFactory(ctx);
|
|
1099
1562
|
const token = await retrieveToken();
|
|
@@ -1101,9 +1564,21 @@ const action = async (ctx) => {
|
|
|
1101
1564
|
logger.log("You're already logged out.");
|
|
1102
1565
|
return;
|
|
1103
1566
|
}
|
|
1104
|
-
const cloudApiService = await cloudApiFactory(token);
|
|
1567
|
+
const cloudApiService = await cloudApiFactory(ctx, token);
|
|
1568
|
+
const config = await cloudApiService.config();
|
|
1569
|
+
const cliConfig2 = config.data;
|
|
1105
1570
|
try {
|
|
1106
1571
|
await eraseToken();
|
|
1572
|
+
openModule.then((open) => {
|
|
1573
|
+
open.default(
|
|
1574
|
+
`${cliConfig2.baseUrl}/oidc/logout?client_id=${encodeURIComponent(
|
|
1575
|
+
cliConfig2.clientId
|
|
1576
|
+
)}&logout_hint=${encodeURIComponent(token)}
|
|
1577
|
+
`
|
|
1578
|
+
).catch((e) => {
|
|
1579
|
+
logger.debug(e.message, e);
|
|
1580
|
+
});
|
|
1581
|
+
});
|
|
1107
1582
|
logger.log(
|
|
1108
1583
|
"🔌 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."
|
|
1109
1584
|
);
|
|
@@ -1111,39 +1586,258 @@ const action = async (ctx) => {
|
|
|
1111
1586
|
logger.error("🥲 Oops! Something went wrong while logging you out. Please try again.");
|
|
1112
1587
|
logger.debug(e);
|
|
1113
1588
|
}
|
|
1114
|
-
|
|
1115
|
-
await cloudApiService.track("didLogout", { loginMethod: "cli" });
|
|
1116
|
-
} catch (e) {
|
|
1117
|
-
logger.debug("Failed to track logout event", e);
|
|
1118
|
-
}
|
|
1589
|
+
await trackEvent(ctx, cloudApiService, "didLogout", { loginMethod: "cli" });
|
|
1119
1590
|
};
|
|
1120
|
-
const command$
|
|
1121
|
-
|
|
1591
|
+
const command$4 = ({ ctx }) => {
|
|
1592
|
+
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));
|
|
1122
1593
|
};
|
|
1123
1594
|
const logout = {
|
|
1124
1595
|
name: "logout",
|
|
1125
1596
|
description: "Strapi Cloud Logout",
|
|
1126
|
-
action,
|
|
1127
|
-
command: command$
|
|
1597
|
+
action: action$3,
|
|
1598
|
+
command: command$4
|
|
1128
1599
|
};
|
|
1129
|
-
const command = ({
|
|
1130
|
-
|
|
1600
|
+
const command$3 = ({ ctx }) => {
|
|
1601
|
+
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));
|
|
1131
1602
|
};
|
|
1132
1603
|
const createProject = {
|
|
1133
1604
|
name: "create-project",
|
|
1134
1605
|
description: "Create a new project",
|
|
1135
|
-
action: action$
|
|
1606
|
+
action: action$6,
|
|
1607
|
+
command: command$3
|
|
1608
|
+
};
|
|
1609
|
+
const action$2 = 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 cloudApiService = await cloudApiFactory(ctx, token);
|
|
1617
|
+
const spinner = logger.spinner("Fetching your projects...").start();
|
|
1618
|
+
try {
|
|
1619
|
+
const {
|
|
1620
|
+
data: { data: projectList }
|
|
1621
|
+
} = await cloudApiService.listProjects();
|
|
1622
|
+
spinner.succeed();
|
|
1623
|
+
logger.log(projectList);
|
|
1624
|
+
} catch (e) {
|
|
1625
|
+
ctx.logger.debug("Failed to list projects", e);
|
|
1626
|
+
spinner.fail("An error occurred while fetching your projects from Strapi Cloud.");
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
const command$2 = ({ command: command2, ctx }) => {
|
|
1630
|
+
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));
|
|
1631
|
+
};
|
|
1632
|
+
const listProjects = {
|
|
1633
|
+
name: "list-projects",
|
|
1634
|
+
description: "List Strapi Cloud projects",
|
|
1635
|
+
action: action$2,
|
|
1636
|
+
command: command$2
|
|
1637
|
+
};
|
|
1638
|
+
const action$1 = async (ctx) => {
|
|
1639
|
+
const { getValidToken } = await tokenServiceFactory(ctx);
|
|
1640
|
+
const token = await getValidToken(ctx, promptLogin);
|
|
1641
|
+
const { logger } = ctx;
|
|
1642
|
+
if (!token) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const project = await getLocalProject(ctx);
|
|
1646
|
+
if (!project) {
|
|
1647
|
+
ctx.logger.debug(`No valid local project configuration was found.`);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const cloudApiService = await cloudApiFactory(ctx, token);
|
|
1651
|
+
const spinner = logger.spinner("Fetching environments...").start();
|
|
1652
|
+
await trackEvent(ctx, cloudApiService, "willListEnvironment", {
|
|
1653
|
+
projectInternalName: project.name
|
|
1654
|
+
});
|
|
1655
|
+
try {
|
|
1656
|
+
const {
|
|
1657
|
+
data: { data: environmentsList }
|
|
1658
|
+
} = await cloudApiService.listEnvironments({ name: project.name });
|
|
1659
|
+
spinner.succeed();
|
|
1660
|
+
logger.log(environmentsList);
|
|
1661
|
+
await trackEvent(ctx, cloudApiService, "didListEnvironment", {
|
|
1662
|
+
projectInternalName: project.name
|
|
1663
|
+
});
|
|
1664
|
+
} catch (e) {
|
|
1665
|
+
if (e.response && e.response.status === 404) {
|
|
1666
|
+
spinner.succeed();
|
|
1667
|
+
logger.warn(
|
|
1668
|
+
`
|
|
1669
|
+
The project associated with this folder does not exist in Strapi Cloud.
|
|
1670
|
+
Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
|
1671
|
+
"link"
|
|
1672
|
+
)} command`
|
|
1673
|
+
);
|
|
1674
|
+
} else {
|
|
1675
|
+
spinner.fail("An error occurred while fetching environments data from Strapi Cloud.");
|
|
1676
|
+
logger.debug("Failed to list environments", e);
|
|
1677
|
+
}
|
|
1678
|
+
await trackEvent(ctx, cloudApiService, "didNotListEnvironment", {
|
|
1679
|
+
projectInternalName: project.name
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
function defineCloudNamespace(command2, ctx) {
|
|
1684
|
+
const cloud = command2.command("cloud").description("Manage Strapi Cloud projects");
|
|
1685
|
+
cloud.command("environments").description("Alias for cloud environment list").action(() => runAction("list", action$1)(ctx));
|
|
1686
|
+
return cloud;
|
|
1687
|
+
}
|
|
1688
|
+
let environmentCmd = null;
|
|
1689
|
+
const initializeEnvironmentCommand = (command2, ctx) => {
|
|
1690
|
+
if (!environmentCmd) {
|
|
1691
|
+
const cloud = defineCloudNamespace(command2, ctx);
|
|
1692
|
+
environmentCmd = cloud.command("environment").description("Manage environments");
|
|
1693
|
+
}
|
|
1694
|
+
return environmentCmd;
|
|
1695
|
+
};
|
|
1696
|
+
const command$1 = ({ command: command2, ctx }) => {
|
|
1697
|
+
const environmentCmd2 = initializeEnvironmentCommand(command2, ctx);
|
|
1698
|
+
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));
|
|
1699
|
+
};
|
|
1700
|
+
const listEnvironments = {
|
|
1701
|
+
name: "list-environments",
|
|
1702
|
+
description: "List Strapi Cloud environments",
|
|
1703
|
+
action: action$1,
|
|
1704
|
+
command: command$1
|
|
1705
|
+
};
|
|
1706
|
+
const QUIT_OPTION = "Quit";
|
|
1707
|
+
const action = async (ctx) => {
|
|
1708
|
+
const { getValidToken } = await tokenServiceFactory(ctx);
|
|
1709
|
+
const token = await getValidToken(ctx, promptLogin);
|
|
1710
|
+
const { logger } = ctx;
|
|
1711
|
+
if (!token) {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const project = await getLocalProject(ctx);
|
|
1715
|
+
if (!project) {
|
|
1716
|
+
logger.debug(`No valid local project configuration was found.`);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
const cloudApiService = await cloudApiFactory(ctx, token);
|
|
1720
|
+
const environments = await getEnvironmentsList(ctx, cloudApiService, project);
|
|
1721
|
+
if (!environments) {
|
|
1722
|
+
logger.debug(`Fetching environments failed.`);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (environments.length === 0) {
|
|
1726
|
+
logger.log(
|
|
1727
|
+
`The only available environment is already linked. You can add a new one from your project settings on the Strapi Cloud dashboard.`
|
|
1728
|
+
);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
const answer = await promptUserForEnvironment(ctx, environments);
|
|
1732
|
+
if (!answer) {
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
await trackEvent(ctx, cloudApiService, "willLinkEnvironment", {
|
|
1736
|
+
projectName: project.name,
|
|
1737
|
+
environmentName: answer.targetEnvironment
|
|
1738
|
+
});
|
|
1739
|
+
try {
|
|
1740
|
+
await patch({ project: { targetEnvironment: answer.targetEnvironment } });
|
|
1741
|
+
} catch (e) {
|
|
1742
|
+
await trackEvent(ctx, cloudApiService, "didNotLinkEnvironment", {
|
|
1743
|
+
projectName: project.name,
|
|
1744
|
+
environmentName: answer.targetEnvironment
|
|
1745
|
+
});
|
|
1746
|
+
logger.debug("Failed to link environment", e);
|
|
1747
|
+
logger.error(
|
|
1748
|
+
"Failed to link the environment. If this issue persists, try re-linking your project or contact support."
|
|
1749
|
+
);
|
|
1750
|
+
process.exit(1);
|
|
1751
|
+
}
|
|
1752
|
+
logger.log(
|
|
1753
|
+
` You have successfully linked your project to ${chalk.cyan(answer.targetEnvironment)}, on ${chalk.cyan(project.displayName)}. You are now able to deploy your project.`
|
|
1754
|
+
);
|
|
1755
|
+
await trackEvent(ctx, cloudApiService, "didLinkEnvironment", {
|
|
1756
|
+
projectName: project.name,
|
|
1757
|
+
environmentName: answer.targetEnvironment
|
|
1758
|
+
});
|
|
1759
|
+
};
|
|
1760
|
+
async function promptUserForEnvironment(ctx, environments) {
|
|
1761
|
+
const { logger } = ctx;
|
|
1762
|
+
try {
|
|
1763
|
+
const answer = await inquirer.prompt([
|
|
1764
|
+
{
|
|
1765
|
+
type: "list",
|
|
1766
|
+
name: "targetEnvironment",
|
|
1767
|
+
message: "Which environment do you want to link?",
|
|
1768
|
+
choices: [...environments, { name: chalk.grey(`(${QUIT_OPTION})`), value: null }]
|
|
1769
|
+
}
|
|
1770
|
+
]);
|
|
1771
|
+
if (!answer.targetEnvironment) {
|
|
1772
|
+
return null;
|
|
1773
|
+
}
|
|
1774
|
+
return answer;
|
|
1775
|
+
} catch (e) {
|
|
1776
|
+
logger.debug("Failed to get user input", e);
|
|
1777
|
+
logger.error("An error occurred while trying to get your environment selection.");
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
async function getEnvironmentsList(ctx, cloudApiService, project) {
|
|
1782
|
+
const spinner = ctx.logger.spinner("Fetching environments...\n").start();
|
|
1783
|
+
try {
|
|
1784
|
+
const {
|
|
1785
|
+
data: { data: environmentsList }
|
|
1786
|
+
} = await cloudApiService.listLinkEnvironments({ name: project.name });
|
|
1787
|
+
if (!Array.isArray(environmentsList) || environmentsList.length === 0) {
|
|
1788
|
+
throw new Error("Environments not found in server response");
|
|
1789
|
+
}
|
|
1790
|
+
spinner.succeed();
|
|
1791
|
+
return environmentsList.filter(
|
|
1792
|
+
(environment) => environment.name !== project.targetEnvironment
|
|
1793
|
+
);
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
if (e.response && e.response.status === 404) {
|
|
1796
|
+
spinner.succeed();
|
|
1797
|
+
ctx.logger.warn(
|
|
1798
|
+
`
|
|
1799
|
+
The project associated with this folder does not exist in Strapi Cloud.
|
|
1800
|
+
Please link your local project to an existing Strapi Cloud project using the ${chalk.cyan(
|
|
1801
|
+
"link"
|
|
1802
|
+
)} command.`
|
|
1803
|
+
);
|
|
1804
|
+
} else {
|
|
1805
|
+
spinner.fail("An error occurred while fetching environments data from Strapi Cloud.");
|
|
1806
|
+
ctx.logger.debug("Failed to list environments", e);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const command = ({ command: command2, ctx }) => {
|
|
1811
|
+
const environmentCmd2 = initializeEnvironmentCommand(command2, ctx);
|
|
1812
|
+
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));
|
|
1813
|
+
};
|
|
1814
|
+
const linkEnvironment = {
|
|
1815
|
+
name: "link-environment",
|
|
1816
|
+
description: "Link Strapi Cloud environment to a local project",
|
|
1817
|
+
action,
|
|
1136
1818
|
command
|
|
1137
1819
|
};
|
|
1138
1820
|
const cli = {
|
|
1139
1821
|
deployProject,
|
|
1822
|
+
link,
|
|
1140
1823
|
login,
|
|
1141
1824
|
logout,
|
|
1142
|
-
createProject
|
|
1825
|
+
createProject,
|
|
1826
|
+
linkEnvironment,
|
|
1827
|
+
listProjects,
|
|
1828
|
+
listEnvironments
|
|
1143
1829
|
};
|
|
1144
|
-
const cloudCommands = [
|
|
1830
|
+
const cloudCommands = [
|
|
1831
|
+
deployProject,
|
|
1832
|
+
link,
|
|
1833
|
+
login,
|
|
1834
|
+
logout,
|
|
1835
|
+
linkEnvironment,
|
|
1836
|
+
listProjects,
|
|
1837
|
+
listEnvironments
|
|
1838
|
+
];
|
|
1145
1839
|
async function initCloudCLIConfig() {
|
|
1146
|
-
const localConfig = await getLocalConfig();
|
|
1840
|
+
const localConfig = await getLocalConfig$1();
|
|
1147
1841
|
if (!localConfig.deviceId) {
|
|
1148
1842
|
localConfig.deviceId = crypto$1.randomUUID();
|
|
1149
1843
|
}
|
|
@@ -1157,7 +1851,10 @@ async function buildStrapiCloudCommands({
|
|
|
1157
1851
|
await initCloudCLIConfig();
|
|
1158
1852
|
for (const cloudCommand of cloudCommands) {
|
|
1159
1853
|
try {
|
|
1160
|
-
await cloudCommand.command({ command: command2, ctx, argv });
|
|
1854
|
+
const subCommand = await cloudCommand.command({ command: command2, ctx, argv });
|
|
1855
|
+
if (subCommand) {
|
|
1856
|
+
command2.addCommand(subCommand);
|
|
1857
|
+
}
|
|
1161
1858
|
} catch (e) {
|
|
1162
1859
|
console.error(`Failed to load command ${cloudCommand.name}`, e);
|
|
1163
1860
|
}
|