@nocobase/cli 2.1.0-alpha.2 → 2.1.0-alpha.20
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.txt +107 -0
- package/README.md +165 -19
- package/bin/run.cmd +3 -0
- package/bin/run.js +95 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/build.js +51 -0
- package/dist/commands/db/start.js +22 -0
- package/dist/commands/dev.js +58 -0
- package/dist/commands/download.js +293 -0
- package/dist/commands/env/add.js +198 -0
- package/dist/commands/env/auth.js +61 -0
- package/dist/commands/env/list.js +41 -0
- package/dist/commands/env/remove.js +65 -0
- package/dist/commands/env/update.js +73 -0
- package/dist/commands/env/use.js +36 -0
- package/dist/commands/init.js +186 -0
- package/dist/commands/install.js +703 -0
- package/dist/commands/pm/disable.js +31 -0
- package/dist/commands/pm/enable.js +31 -0
- package/dist/commands/pm/list.js +21 -0
- package/dist/commands/restart.js +32 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/start.js +55 -0
- package/dist/commands/upgrade.js +35 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +20 -0
- package/dist/lib/api-client.js +199 -0
- package/dist/lib/auth-store.js +200 -0
- package/dist/lib/bootstrap.js +383 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +30 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/env-auth.js +527 -0
- package/dist/lib/generated-command.js +142 -0
- package/dist/lib/init-browser-wizard.js +431 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/resource-command.js +335 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +59 -0
- package/dist/lib/runtime-generator.js +408 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/ui.js +175 -0
- package/dist/post-processors/data-modeling.js +66 -0
- package/dist/post-processors/data-source-manager.js +114 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +287 -0
- package/package.json +52 -26
- package/LICENSE +0 -661
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/cli.js +0 -19
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/index.js +0 -17
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/index.js +0 -14
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/models/index.ts +0 -12
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import fsp from 'node:fs/promises';
|
|
10
|
+
import { Command, Flags } from '@oclif/core';
|
|
11
|
+
import * as p from '@clack/prompts';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
14
|
+
import { run } from "../lib/run-npm.js";
|
|
15
|
+
export default class Download extends Command {
|
|
16
|
+
static description = 'Scaffold or fetch NocoBase: npm (create-nocobase-app), docker (image pull), or git (shallow clone).';
|
|
17
|
+
static examples = [
|
|
18
|
+
'<%= config.bin %> <%= command.id %>',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> -y --source npm --version latest',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --source npm --version latest',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --source npm --version latest --output-dir=./app',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --source docker --version latest --docker-registry=nocobase/nocobase',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --source git --version latest --git-url=https://github.com/nocobase/nocobase.git',
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
yes: Flags.boolean({
|
|
27
|
+
char: 'y',
|
|
28
|
+
description: 'Skip interactive prompts; use flags only (non-TTY implies -y)',
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
31
|
+
source: Flags.string({
|
|
32
|
+
char: 's',
|
|
33
|
+
description: 'Distribution: npm runs create-nocobase-app, docker runs docker pull, git clones the repository',
|
|
34
|
+
options: ['docker', 'npm', 'git'],
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
version: Flags.string({
|
|
38
|
+
char: 'v',
|
|
39
|
+
description: 'npm: dist-tag or version for create-nocobase-app; docker: image tag; git: branch or tag (latest→main, beta→next, alpha→develop); default: latest',
|
|
40
|
+
}),
|
|
41
|
+
replace: Flags.boolean({
|
|
42
|
+
char: 'r',
|
|
43
|
+
description: 'npm/git: delete the target project directory if it exists, then scaffold or clone again; docker: ignored',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
'dev': Flags.boolean({
|
|
47
|
+
description: 'npm: install devDependencies in create-nocobase-app and run a non-production yarn install; git/docker: ignored',
|
|
48
|
+
default: false,
|
|
49
|
+
}),
|
|
50
|
+
'output-dir': Flags.string({
|
|
51
|
+
char: 'o',
|
|
52
|
+
description: 'npm/git: output directory (relative to cwd); default ./nocobase-<version> using the same value as --version; docker: ignored',
|
|
53
|
+
}),
|
|
54
|
+
'git-url': Flags.string({
|
|
55
|
+
description: 'git: remote URL to clone (default: https://github.com/nocobase/nocobase.git)',
|
|
56
|
+
}),
|
|
57
|
+
'docker-registry': Flags.string({
|
|
58
|
+
description: 'docker: image reference without tag (default: nocobase/nocobase); use -v for the tag, e.g. ghcr.io/nocobase/nocobase',
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
resolveOutputDir(flags) {
|
|
62
|
+
const explicit = flags['output-dir'];
|
|
63
|
+
if (explicit) {
|
|
64
|
+
return explicit;
|
|
65
|
+
}
|
|
66
|
+
const tag = flags.version || 'latest';
|
|
67
|
+
const safe = tag.replace(/[/\\]/g, '-');
|
|
68
|
+
return `./nocobase-${safe}`;
|
|
69
|
+
}
|
|
70
|
+
defaultOutputDir(versionTag) {
|
|
71
|
+
const safe = versionTag.replace(/[/\\]/g, '-');
|
|
72
|
+
return `./nocobase-${safe}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* When stdin/stdout are TTY and not `-y`, prompt for any missing download options.
|
|
76
|
+
*/
|
|
77
|
+
async resolveDownloadFlags(flags) {
|
|
78
|
+
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY && !flags.yes);
|
|
79
|
+
let source = flags.source?.trim();
|
|
80
|
+
if (source === '') {
|
|
81
|
+
source = undefined;
|
|
82
|
+
}
|
|
83
|
+
let version = flags.version?.trim() || undefined;
|
|
84
|
+
let replace = flags.replace;
|
|
85
|
+
let dev = flags['dev'];
|
|
86
|
+
let outputDir = flags['output-dir']?.trim() || undefined;
|
|
87
|
+
if (outputDir === '') {
|
|
88
|
+
outputDir = undefined;
|
|
89
|
+
}
|
|
90
|
+
let gitUrl = flags['git-url']?.trim() || undefined;
|
|
91
|
+
if (gitUrl === '') {
|
|
92
|
+
gitUrl = undefined;
|
|
93
|
+
}
|
|
94
|
+
let dockerRegistry = flags['docker-registry']?.trim() || undefined;
|
|
95
|
+
if (dockerRegistry === '') {
|
|
96
|
+
dockerRegistry = undefined;
|
|
97
|
+
}
|
|
98
|
+
if (!interactive) {
|
|
99
|
+
if (!source) {
|
|
100
|
+
this.error('Distribution is required (--source npm|git|docker). Use a terminal for interactive setup, or pass -s/--source.');
|
|
101
|
+
}
|
|
102
|
+
const v = version || 'latest';
|
|
103
|
+
return {
|
|
104
|
+
source,
|
|
105
|
+
version: v,
|
|
106
|
+
replace,
|
|
107
|
+
'dev': dev,
|
|
108
|
+
'output-dir': outputDir,
|
|
109
|
+
'git-url': gitUrl,
|
|
110
|
+
'docker-registry': dockerRegistry,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
p.intro('nb download');
|
|
114
|
+
if (!source) {
|
|
115
|
+
const src = await p.select({
|
|
116
|
+
message: 'How do you want to get NocoBase?',
|
|
117
|
+
options: [
|
|
118
|
+
{ value: 'npm', label: 'npm — create-nocobase-app' },
|
|
119
|
+
{ value: 'git', label: 'git — shallow clone' },
|
|
120
|
+
{ value: 'docker', label: 'docker — pull image' },
|
|
121
|
+
],
|
|
122
|
+
initialValue: 'npm',
|
|
123
|
+
});
|
|
124
|
+
if (p.isCancel(src)) {
|
|
125
|
+
p.cancel('Download cancelled.');
|
|
126
|
+
this.exit(0);
|
|
127
|
+
}
|
|
128
|
+
source = src;
|
|
129
|
+
}
|
|
130
|
+
if (version === undefined) {
|
|
131
|
+
const verAns = await p.text({
|
|
132
|
+
message: 'Version / dist-tag / image tag / branch alias (-v)',
|
|
133
|
+
placeholder: 'latest',
|
|
134
|
+
initialValue: 'latest',
|
|
135
|
+
});
|
|
136
|
+
if (p.isCancel(verAns)) {
|
|
137
|
+
p.cancel('Download cancelled.');
|
|
138
|
+
this.exit(0);
|
|
139
|
+
}
|
|
140
|
+
version = verAns.trim() || 'latest';
|
|
141
|
+
}
|
|
142
|
+
const versionResolved = version || 'latest';
|
|
143
|
+
if (source === 'docker') {
|
|
144
|
+
if (dockerRegistry === undefined) {
|
|
145
|
+
const reg = await p.text({
|
|
146
|
+
message: 'Docker image without tag (--docker-registry)',
|
|
147
|
+
placeholder: 'nocobase/nocobase',
|
|
148
|
+
initialValue: 'nocobase/nocobase',
|
|
149
|
+
});
|
|
150
|
+
if (p.isCancel(reg)) {
|
|
151
|
+
p.cancel('Download cancelled.');
|
|
152
|
+
this.exit(0);
|
|
153
|
+
}
|
|
154
|
+
dockerRegistry = reg.trim() || 'nocobase/nocobase';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (source === 'git') {
|
|
158
|
+
if (gitUrl === undefined) {
|
|
159
|
+
const urlAns = await p.text({
|
|
160
|
+
message: 'Git remote URL (--git-url)',
|
|
161
|
+
placeholder: 'https://github.com/nocobase/nocobase.git',
|
|
162
|
+
initialValue: 'https://github.com/nocobase/nocobase.git',
|
|
163
|
+
});
|
|
164
|
+
if (p.isCancel(urlAns)) {
|
|
165
|
+
p.cancel('Download cancelled.');
|
|
166
|
+
this.exit(0);
|
|
167
|
+
}
|
|
168
|
+
gitUrl = urlAns.trim() || 'https://github.com/nocobase/nocobase.git';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (source === 'npm' || source === 'git') {
|
|
172
|
+
if (outputDir === undefined) {
|
|
173
|
+
const initialOut = this.defaultOutputDir(versionResolved);
|
|
174
|
+
const outAns = await p.text({
|
|
175
|
+
message: 'Output directory relative to cwd (-o)',
|
|
176
|
+
placeholder: initialOut,
|
|
177
|
+
initialValue: initialOut,
|
|
178
|
+
});
|
|
179
|
+
if (p.isCancel(outAns)) {
|
|
180
|
+
p.cancel('Download cancelled.');
|
|
181
|
+
this.exit(0);
|
|
182
|
+
}
|
|
183
|
+
outputDir = outAns.trim() || initialOut;
|
|
184
|
+
}
|
|
185
|
+
const replaceAns = await p.confirm({
|
|
186
|
+
message: 'Delete existing output directory if present, then retry? (--replace)',
|
|
187
|
+
initialValue: replace,
|
|
188
|
+
});
|
|
189
|
+
if (p.isCancel(replaceAns)) {
|
|
190
|
+
p.cancel('Download cancelled.');
|
|
191
|
+
this.exit(0);
|
|
192
|
+
}
|
|
193
|
+
replace = replaceAns;
|
|
194
|
+
}
|
|
195
|
+
if (source === 'npm') {
|
|
196
|
+
const devAns = await p.confirm({
|
|
197
|
+
message: 'Install devDependencies and run a non-production yarn install? (--dev)',
|
|
198
|
+
initialValue: dev,
|
|
199
|
+
});
|
|
200
|
+
if (p.isCancel(devAns)) {
|
|
201
|
+
p.cancel('Download cancelled.');
|
|
202
|
+
this.exit(0);
|
|
203
|
+
}
|
|
204
|
+
dev = devAns;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
source,
|
|
208
|
+
version: versionResolved,
|
|
209
|
+
replace,
|
|
210
|
+
'dev': dev,
|
|
211
|
+
'output-dir': outputDir,
|
|
212
|
+
'git-url': gitUrl,
|
|
213
|
+
'docker-registry': dockerRegistry,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async downloadFromDocker(flags) {
|
|
217
|
+
const image = flags['docker-registry'] ?? 'nocobase/nocobase';
|
|
218
|
+
const tag = flags.version ?? 'latest';
|
|
219
|
+
await run('docker', ['pull', `${image}:${tag}`]);
|
|
220
|
+
}
|
|
221
|
+
async downloadFromNpm(flags) {
|
|
222
|
+
const versionSpec = flags.version || 'latest';
|
|
223
|
+
const outputDir = this.resolveOutputDir(flags);
|
|
224
|
+
const projectRoot = path.resolve(process.cwd(), outputDir);
|
|
225
|
+
const npxArgs = ['-y', `create-nocobase-app@${versionSpec}`, outputDir];
|
|
226
|
+
if (!flags['dev']) {
|
|
227
|
+
npxArgs.push('--skip-dev-dependencies');
|
|
228
|
+
}
|
|
229
|
+
if (flags.replace) {
|
|
230
|
+
await fsp.rm(projectRoot, { recursive: true, force: true });
|
|
231
|
+
}
|
|
232
|
+
await run('npx', npxArgs);
|
|
233
|
+
const installArgs = ['install'];
|
|
234
|
+
if (!flags['dev']) {
|
|
235
|
+
installArgs.push('--production');
|
|
236
|
+
}
|
|
237
|
+
await run('yarn', installArgs, { cwd: projectRoot });
|
|
238
|
+
return projectRoot;
|
|
239
|
+
}
|
|
240
|
+
async downloadFromGit(flags) {
|
|
241
|
+
const repoUrl = flags['git-url'] ?? 'https://github.com/nocobase/nocobase.git';
|
|
242
|
+
const versionSpec = flags.version || 'latest';
|
|
243
|
+
const outputDir = this.resolveOutputDir(flags);
|
|
244
|
+
const versionToRef = {
|
|
245
|
+
'latest': 'main',
|
|
246
|
+
'beta': 'next',
|
|
247
|
+
'alpha': 'develop',
|
|
248
|
+
};
|
|
249
|
+
if (flags.replace) {
|
|
250
|
+
await fsp.rm(path.resolve(process.cwd(), outputDir), { recursive: true, force: true });
|
|
251
|
+
}
|
|
252
|
+
const branch = versionToRef[versionSpec] || versionSpec;
|
|
253
|
+
const gitArgs = ['clone'];
|
|
254
|
+
gitArgs.push('--branch', branch);
|
|
255
|
+
gitArgs.push('--depth', '1', repoUrl, outputDir);
|
|
256
|
+
await run('git', gitArgs);
|
|
257
|
+
const projectRoot = path.resolve(process.cwd(), outputDir);
|
|
258
|
+
await run('yarn', ['install'], { cwd: projectRoot });
|
|
259
|
+
return projectRoot;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* @returns Final resolved flags and, for npm/git, the absolute project directory.
|
|
263
|
+
*/
|
|
264
|
+
async download() {
|
|
265
|
+
const { flags } = await this.parse(Download);
|
|
266
|
+
const resolved = await this.resolveDownloadFlags(flags);
|
|
267
|
+
switch (resolved.source) {
|
|
268
|
+
case 'npm': {
|
|
269
|
+
const projectRoot = await this.downloadFromNpm(resolved);
|
|
270
|
+
return { resolved, projectRoot };
|
|
271
|
+
}
|
|
272
|
+
case 'docker': {
|
|
273
|
+
await this.downloadFromDocker(resolved);
|
|
274
|
+
return { resolved, projectRoot: undefined };
|
|
275
|
+
}
|
|
276
|
+
case 'git': {
|
|
277
|
+
const projectRoot = await this.downloadFromGit(resolved);
|
|
278
|
+
return { resolved, projectRoot };
|
|
279
|
+
}
|
|
280
|
+
default:
|
|
281
|
+
this.error(`Invalid --source: ${resolved.source}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async run() {
|
|
285
|
+
try {
|
|
286
|
+
return await this.download();
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
this.error(message);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import * as p from '@clack/prompts';
|
|
11
|
+
import { upsertEnv } from '../../lib/auth-store.js';
|
|
12
|
+
import { formatCliHomeScope } from '../../lib/cli-home.js';
|
|
13
|
+
import { isInteractiveTerminal, printVerbose, setVerboseMode } from '../../lib/ui.js';
|
|
14
|
+
export default class EnvAdd extends Command {
|
|
15
|
+
static summary = 'Save a named NocoBase API endpoint (token or OAuth), then switch the CLI to use it';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> <%= command.id %>',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> local',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> local --scope project --api-base-url http://localhost:13000/api --auth-type oauth',
|
|
20
|
+
];
|
|
21
|
+
static args = {
|
|
22
|
+
name: Args.string({
|
|
23
|
+
description: 'Label for this environment (optional first argument; in a TTY, prompted when omitted; required when not using a TTY)',
|
|
24
|
+
required: false,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
static flags = {
|
|
28
|
+
env: Flags.string({
|
|
29
|
+
char: 'e',
|
|
30
|
+
hidden: true,
|
|
31
|
+
deprecated: true,
|
|
32
|
+
description: 'Environment name (same as the optional positional argument; for compatibility with -e/--env on other commands)',
|
|
33
|
+
}),
|
|
34
|
+
verbose: Flags.boolean({
|
|
35
|
+
description: 'Print detailed progress while writing config',
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
scope: Flags.string({
|
|
39
|
+
char: 's',
|
|
40
|
+
description: 'Where to store env config: project (.nocobase in the repo) or global (user-level); prompted in a TTY when omitted',
|
|
41
|
+
options: ['project', 'global'],
|
|
42
|
+
}),
|
|
43
|
+
'api-base-url': Flags.string({
|
|
44
|
+
char: 'u',
|
|
45
|
+
aliases: ['base-url'],
|
|
46
|
+
description: 'Root URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api); prompted in a TTY when omitted',
|
|
47
|
+
}),
|
|
48
|
+
'auth-type': Flags.string({
|
|
49
|
+
char: 'a',
|
|
50
|
+
description: 'Authentication: token (API key) or oauth (browser login via `nb env auth`); prompted in a TTY when omitted',
|
|
51
|
+
options: ['token', 'oauth'],
|
|
52
|
+
}),
|
|
53
|
+
'access-token': Flags.string({
|
|
54
|
+
char: 't',
|
|
55
|
+
aliases: ['token'],
|
|
56
|
+
description: 'API key or access token when using --auth-type token (prompted in a TTY when omitted)',
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
exitCancelled() {
|
|
60
|
+
p.cancel('Cancelled.');
|
|
61
|
+
this.exit(0);
|
|
62
|
+
}
|
|
63
|
+
async run() {
|
|
64
|
+
const { args, flags } = await this.parse(EnvAdd);
|
|
65
|
+
setVerboseMode(flags.verbose);
|
|
66
|
+
const nameArg = args.name?.trim();
|
|
67
|
+
const nameFlag = flags.env?.trim() || undefined;
|
|
68
|
+
if (nameArg && nameFlag && nameArg !== nameFlag) {
|
|
69
|
+
this.error(`Environment name was given both as the argument ("${nameArg}") and as --env ("${nameFlag}"); use only one.`);
|
|
70
|
+
}
|
|
71
|
+
let name = nameArg || nameFlag || undefined;
|
|
72
|
+
let scope = flags.scope;
|
|
73
|
+
let baseUrl = flags['api-base-url'] ?? flags['base-url'];
|
|
74
|
+
let authType = flags['auth-type'];
|
|
75
|
+
const interactive = isInteractiveTerminal();
|
|
76
|
+
if (!interactive) {
|
|
77
|
+
const missing = [];
|
|
78
|
+
if (!name?.trim()) {
|
|
79
|
+
missing.push('<name> (first argument or --env)');
|
|
80
|
+
}
|
|
81
|
+
if (!scope) {
|
|
82
|
+
missing.push('--scope');
|
|
83
|
+
}
|
|
84
|
+
if (!baseUrl) {
|
|
85
|
+
missing.push('--api-base-url');
|
|
86
|
+
}
|
|
87
|
+
if (!authType) {
|
|
88
|
+
missing.push('--auth-type');
|
|
89
|
+
}
|
|
90
|
+
if (missing.length > 0) {
|
|
91
|
+
this.error(`Non-interactive mode requires: ${missing.join(', ')}. Example: nb env add -e local --scope project --api-base-url http://localhost:13000/api --auth-type oauth`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
if (!name?.trim()) {
|
|
96
|
+
const answer = await p.text({
|
|
97
|
+
message: 'Environment name',
|
|
98
|
+
placeholder: 'default',
|
|
99
|
+
defaultValue: 'default',
|
|
100
|
+
});
|
|
101
|
+
if (p.isCancel(answer)) {
|
|
102
|
+
this.exitCancelled();
|
|
103
|
+
}
|
|
104
|
+
name = answer;
|
|
105
|
+
}
|
|
106
|
+
if (!scope) {
|
|
107
|
+
const answer = await p.select({
|
|
108
|
+
message: 'Where should this env be stored?',
|
|
109
|
+
options: [
|
|
110
|
+
{ value: 'project', label: 'Project', hint: '.nocobase in this repo' },
|
|
111
|
+
{ value: 'global', label: 'Global', hint: 'user-level config' },
|
|
112
|
+
],
|
|
113
|
+
initialValue: 'project',
|
|
114
|
+
});
|
|
115
|
+
if (p.isCancel(answer)) {
|
|
116
|
+
this.exitCancelled();
|
|
117
|
+
}
|
|
118
|
+
scope = answer;
|
|
119
|
+
}
|
|
120
|
+
if (!baseUrl) {
|
|
121
|
+
const answer = await p.text({
|
|
122
|
+
message: 'API base URL',
|
|
123
|
+
placeholder: 'http://localhost:13000/api',
|
|
124
|
+
defaultValue: 'http://localhost:13000/api',
|
|
125
|
+
});
|
|
126
|
+
if (p.isCancel(answer)) {
|
|
127
|
+
this.exitCancelled();
|
|
128
|
+
}
|
|
129
|
+
baseUrl = answer;
|
|
130
|
+
}
|
|
131
|
+
if (!authType) {
|
|
132
|
+
const answer = await p.select({
|
|
133
|
+
message: 'How do you want to authenticate?',
|
|
134
|
+
options: [
|
|
135
|
+
{ value: 'oauth', label: 'OAuth (browser login)', hint: 'runs nb env auth after save' },
|
|
136
|
+
{ value: 'token', label: 'API token / API key' },
|
|
137
|
+
],
|
|
138
|
+
initialValue: 'oauth',
|
|
139
|
+
});
|
|
140
|
+
if (p.isCancel(answer)) {
|
|
141
|
+
this.exitCancelled();
|
|
142
|
+
}
|
|
143
|
+
authType = answer;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const accessTokenKeys = ['access-token', 'token'];
|
|
147
|
+
const accessTokenFlagPresent = accessTokenKeys.some((key) => Object.prototype.hasOwnProperty.call(flags, key));
|
|
148
|
+
if (accessTokenFlagPresent && !flags['access-token'] && !flags['token']) {
|
|
149
|
+
if (!interactive) {
|
|
150
|
+
this.error('When passing --access-token (or --token) without a value, run in a TTY or provide the token as the flag value.');
|
|
151
|
+
}
|
|
152
|
+
const prompted = await p.password({
|
|
153
|
+
message: 'Access token / API key',
|
|
154
|
+
validate: (value) => (value.trim() ? undefined : 'Token cannot be empty'),
|
|
155
|
+
});
|
|
156
|
+
if (p.isCancel(prompted)) {
|
|
157
|
+
this.exitCancelled();
|
|
158
|
+
}
|
|
159
|
+
flags['access-token'] = prompted;
|
|
160
|
+
}
|
|
161
|
+
let token = flags['access-token'] ?? flags['token'];
|
|
162
|
+
if (!name?.trim()) {
|
|
163
|
+
this.error('Environment name cannot be empty.');
|
|
164
|
+
}
|
|
165
|
+
if (!baseUrl?.trim()) {
|
|
166
|
+
this.error('API base URL cannot be empty.');
|
|
167
|
+
}
|
|
168
|
+
name = name.trim();
|
|
169
|
+
baseUrl = baseUrl.trim();
|
|
170
|
+
if (authType === 'token') {
|
|
171
|
+
if (!token && interactive) {
|
|
172
|
+
const answer = await p.password({
|
|
173
|
+
message: 'Access token / API key',
|
|
174
|
+
validate: (value) => (value.trim() ? undefined : 'Token cannot be empty'),
|
|
175
|
+
});
|
|
176
|
+
if (p.isCancel(answer)) {
|
|
177
|
+
this.exitCancelled();
|
|
178
|
+
}
|
|
179
|
+
token = answer;
|
|
180
|
+
}
|
|
181
|
+
if (!token?.trim()) {
|
|
182
|
+
this.error('Auth type token requires an access token. Pass `--access-token`, or run in a TTY to enter it.');
|
|
183
|
+
}
|
|
184
|
+
printVerbose(`Saving env "${name}" with API base URL ${baseUrl} (token auth)`);
|
|
185
|
+
await upsertEnv(name, { baseUrl, accessToken: token }, { scope });
|
|
186
|
+
this.log(`Saved env "${name}" and set it as current${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
printVerbose(`Saving env "${name}" with API base URL ${baseUrl} (OAuth next)`);
|
|
190
|
+
await upsertEnv(name, { baseUrl }, { scope });
|
|
191
|
+
this.log(`Saved env "${name}"${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}. Starting OAuth login (\`nb env auth ${name}\`).`);
|
|
192
|
+
const authArgv = [name];
|
|
193
|
+
if (scope) {
|
|
194
|
+
authArgv.push('-s', scope);
|
|
195
|
+
}
|
|
196
|
+
await this.config.runCommand('env:auth', authArgv);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName } from '../../lib/auth-store.js';
|
|
11
|
+
import { formatCliHomeScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { authenticateEnvWithOauth } from '../../lib/env-auth.js';
|
|
13
|
+
import { failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
|
+
export default class EnvAuth extends Command {
|
|
15
|
+
static summary = 'Authenticate an environment with OAuth';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> <%= command.id %> prod',
|
|
18
|
+
];
|
|
19
|
+
static args = {
|
|
20
|
+
name: Args.string({
|
|
21
|
+
description: 'Environment name (omit to use the current env)',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
static flags = {
|
|
26
|
+
env: Flags.string({
|
|
27
|
+
char: 'e',
|
|
28
|
+
hidden: true,
|
|
29
|
+
deprecated: true,
|
|
30
|
+
description: 'Environment name (same as the optional positional argument; for compatibility with -e/--env on other commands)',
|
|
31
|
+
}),
|
|
32
|
+
scope: Flags.string({
|
|
33
|
+
char: 's',
|
|
34
|
+
description: 'Config scope',
|
|
35
|
+
options: ['project', 'global'],
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
async run() {
|
|
39
|
+
const { args, flags } = await this.parse(EnvAuth);
|
|
40
|
+
const scope = flags.scope;
|
|
41
|
+
const nameArg = args.name?.trim();
|
|
42
|
+
const nameFlag = flags.env?.trim() || undefined;
|
|
43
|
+
if (nameArg && nameFlag && nameArg !== nameFlag) {
|
|
44
|
+
this.error(`Environment name was given both as the argument ("${nameArg}") and as --env ("${nameFlag}"); use only one.`);
|
|
45
|
+
}
|
|
46
|
+
const envName = nameArg || nameFlag || undefined;
|
|
47
|
+
const envLabel = envName ?? (await getCurrentEnvName({ scope }));
|
|
48
|
+
startTask(`Authenticating env: ${envLabel}${scope ? ` (${formatCliHomeScope(scope)})` : ''}`);
|
|
49
|
+
try {
|
|
50
|
+
await authenticateEnvWithOauth({
|
|
51
|
+
envName,
|
|
52
|
+
scope,
|
|
53
|
+
});
|
|
54
|
+
succeedTask(`Authenticated env "${envLabel}" with OAuth${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
failTask(`Failed to authenticate env "${envLabel}".`);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { listEnvs } from '../../lib/auth-store.js';
|
|
11
|
+
import { formatCliHomeScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { renderTable } from '../../lib/ui.js';
|
|
13
|
+
export default class EnvList extends Command {
|
|
14
|
+
static summary = 'List configured environments';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %>',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
scope: Flags.string({
|
|
20
|
+
char: 's',
|
|
21
|
+
description: 'Config scope',
|
|
22
|
+
options: ['project', 'global'],
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { flags } = await this.parse(EnvList);
|
|
27
|
+
const scope = flags.scope;
|
|
28
|
+
const { currentEnv, envs } = await listEnvs({ scope });
|
|
29
|
+
const names = Object.keys(envs).sort();
|
|
30
|
+
if (!names.length) {
|
|
31
|
+
this.log(`No envs configured${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
32
|
+
this.log('Run `nb env add <name> --base-url <url>` to add one.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rows = names.map((name) => {
|
|
36
|
+
const env = envs[name];
|
|
37
|
+
return [name === currentEnv ? '*' : '', name, env.baseUrl ?? '', env.auth?.type ?? '', env.runtime?.version ?? ''];
|
|
38
|
+
});
|
|
39
|
+
this.log(renderTable(['Current', 'Name', 'Base URL', 'Auth', 'Runtime'], rows));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName, removeEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { formatCliHomeScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { confirmAction, isInteractiveTerminal, printVerbose, setVerboseMode } from '../../lib/ui.js';
|
|
13
|
+
export default class EnvRemove extends Command {
|
|
14
|
+
static summary = 'Remove a configured environment';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %> staging',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> staging -f',
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
force: Flags.boolean({
|
|
21
|
+
char: 'f',
|
|
22
|
+
description: 'Remove without confirmation',
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
verbose: Flags.boolean({
|
|
26
|
+
description: 'Show detailed progress output',
|
|
27
|
+
default: false,
|
|
28
|
+
}),
|
|
29
|
+
scope: Flags.string({
|
|
30
|
+
char: 's',
|
|
31
|
+
description: 'Config scope',
|
|
32
|
+
options: ['project', 'global'],
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
static args = {
|
|
36
|
+
name: Args.string({
|
|
37
|
+
description: 'Configured environment name',
|
|
38
|
+
required: true,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { args, flags } = await this.parse(EnvRemove);
|
|
43
|
+
setVerboseMode(flags.verbose);
|
|
44
|
+
const scope = flags.scope;
|
|
45
|
+
const currentEnv = await getCurrentEnvName({ scope });
|
|
46
|
+
if (args.name === currentEnv && !flags.force) {
|
|
47
|
+
if (!isInteractiveTerminal()) {
|
|
48
|
+
this.error('Refusing to remove the current env without confirmation. Re-run with `--force`.');
|
|
49
|
+
}
|
|
50
|
+
const confirmed = await confirmAction(`Remove current env "${args.name}"?`, { defaultValue: false });
|
|
51
|
+
if (!confirmed) {
|
|
52
|
+
this.log('Canceled.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
printVerbose(`Removing env "${args.name}"`);
|
|
57
|
+
const result = await removeEnv(args.name, { scope });
|
|
58
|
+
this.log(`Removed env "${result.removed}"${scope ? ` from ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
59
|
+
if (result.hasEnvs) {
|
|
60
|
+
this.log(`Current env: ${result.currentEnv}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.log('No envs configured.');
|
|
64
|
+
}
|
|
65
|
+
}
|