@nocobase/cli 2.1.0-alpha.19 → 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/README.md CHANGED
@@ -35,19 +35,19 @@ npm install -g @nocobase/ctl@latest
35
35
  Add an environment:
36
36
 
37
37
  ```bash
38
- nb env add --name local --base-url http://localhost:13000/api
38
+ nb env add local --base-url http://localhost:13000/api
39
39
  ```
40
40
 
41
41
  Add an environment with an API key:
42
42
 
43
43
  ```bash
44
- nb env add --name local --base-url http://localhost:13000/api --token <api-key>
44
+ nb env add local --base-url http://localhost:13000/api --auth-type token --token <api-key>
45
45
  ```
46
46
 
47
47
  Authenticate an environment with OAuth:
48
48
 
49
49
  ```bash
50
- nb env auth -e local
50
+ nb env auth local
51
51
  ```
52
52
 
53
53
  Show the current environment:
@@ -72,7 +72,7 @@ Update the runtime command cache from `swagger:get`:
72
72
 
73
73
  ```bash
74
74
  nb env update
75
- nb env update -e local
75
+ nb env update local
76
76
  ```
77
77
 
78
78
  Use the generic resource commands:
@@ -96,10 +96,16 @@ If the `API documentation plugin` is disabled, the CLI will prompt to enable it.
96
96
 
97
97
  ## Environment Selection
98
98
 
99
- Use `-e, --env` to temporarily select an environment:
99
+ `nb env update` and `nb env auth` take an optional environment name as the first argument (omit it to use the current env):
100
+
101
+ ```bash
102
+ nb env update prod
103
+ nb env auth prod
104
+ ```
105
+
106
+ Use `-e, --env` on **runtime / API** commands to temporarily select an environment:
100
107
 
101
108
  ```bash
102
- nb env update -e prod
103
109
  nb api resource list --resource users -e prod
104
110
  ```
105
111
 
@@ -120,8 +126,8 @@ Use `-s, --scope` to select one explicitly:
120
126
 
121
127
  ```bash
122
128
  nb env list -s project
123
- nb env add -s global --name prod --base-url http://example.com/api --token <api-key>
124
- nb env auth -e prod -s global
129
+ nb env add prod -s global --base-url http://example.com/api --auth-type token --token <api-key>
130
+ nb env auth prod -s global
125
131
  nb env use local -s project
126
132
  ```
127
133
 
@@ -157,7 +163,7 @@ nb api resource --help
157
163
  Example:
158
164
 
159
165
  ```bash
160
- nb env update -e prod -s global
166
+ nb env update prod -s global
161
167
  nb api resource list --resource users -e prod -j
162
168
  nb api resource list --resource users -e prod --role admin
163
169
  ```
package/bin/run.js CHANGED
@@ -10,7 +10,7 @@ const root = path.resolve(__dirname, '..');
10
10
  const realRoot = fs.realpathSync(root);
11
11
  const isSourcePackage = realRoot.split(path.sep).join('/').endsWith('/packages/core/cli');
12
12
  let isDev = isSourcePackage;
13
- if (process.env.NODE_ENV === 'production') {
13
+ if (process.env.NB_CLI_USE_DIST === '1') {
14
14
  isDev = false;
15
15
  }
16
16
 
@@ -47,15 +47,21 @@ if (isDev) {
47
47
  }
48
48
 
49
49
  function getCommandToken(argv) {
50
+ const tokens = [];
51
+
50
52
  for (const token of argv) {
51
53
  if (!token || token.startsWith('-')) {
52
54
  continue;
53
55
  }
54
56
 
55
- return token;
57
+ tokens.push(token);
58
+ }
59
+
60
+ if (tokens[0] === 'api') {
61
+ return tokens[1] ?? tokens[0];
56
62
  }
57
63
 
58
- return undefined;
64
+ return tokens[0];
59
65
  }
60
66
 
61
67
  function formatCliEntryError(error, argv) {
@@ -0,0 +1,20 @@
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, loadHelpClass } from '@oclif/core';
10
+ export default class Resource extends Command {
11
+ static summary = 'Work with generic collection resources';
12
+ async run() {
13
+ await this.parse(Resource);
14
+ const Help = await loadHelpClass(this.config);
15
+ await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
16
+ this.id ?? 'api:resource',
17
+ ...this.argv,
18
+ ]);
19
+ }
20
+ }
@@ -26,7 +26,7 @@ export default class Build extends Command {
26
26
  '<%= config.bin %> <%= command.id %> @nocobase/acl @nocobase/actions',
27
27
  ];
28
28
  static flags = {
29
- env: Flags.string({ description: 'Environment', char: 'e', required: false }),
29
+ 'cwd': Flags.string({ description: 'Current working directory', char: 'c', required: false }),
30
30
  'no-dts': Flags.boolean({ description: 'not generate dts' }),
31
31
  sourcemap: Flags.boolean({ description: 'generate sourcemap' }),
32
32
  };
@@ -41,7 +41,7 @@ export default class Build extends Command {
41
41
  npmArgs.push('--sourcemap');
42
42
  }
43
43
  try {
44
- await runNocoBaseCommand(npmArgs, process.cwd());
44
+ await runNocoBaseCommand(npmArgs, { cwd: flags['cwd'] });
45
45
  }
46
46
  catch (error) {
47
47
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,22 @@
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 } from '@oclif/core';
10
+ export default class DbStart extends Command {
11
+ static args = {
12
+ file: Args.string({ description: 'file to read' }),
13
+ };
14
+ static description = 'describe the command here';
15
+ static examples = [
16
+ '<%= config.bin %> <%= command.id %>',
17
+ ];
18
+ static flags = {};
19
+ async run() {
20
+ const { args, flags } = await this.parse(DbStart);
21
+ }
22
+ }
@@ -48,7 +48,7 @@ export default class Dev extends Command {
48
48
  npmArgs.push('--inspect', flags.inspect);
49
49
  }
50
50
  try {
51
- await runNocoBaseCommand(npmArgs, process.cwd());
51
+ await runNocoBaseCommand(npmArgs);
52
52
  }
53
53
  catch (error) {
54
54
  const message = error instanceof Error ? error.message : String(error);
@@ -8,110 +8,282 @@
8
8
  */
9
9
  import fsp from 'node:fs/promises';
10
10
  import { Command, Flags } from '@oclif/core';
11
+ import * as p from '@clack/prompts';
11
12
  import path from 'node:path';
13
+ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
12
14
  import { run } from "../lib/run-npm.js";
13
15
  export default class Download extends Command {
14
- static description = 'Download a NocoBase app from npm (create-nocobase-app), Docker Hub, or git.';
16
+ static description = 'Scaffold or fetch NocoBase: npm (create-nocobase-app), docker (image pull), or git (shallow clone).';
15
17
  static examples = [
16
- '<%= config.bin %> <%= command.id %> -s npm -v latest --app-root-path=./app --env-file=.env',
17
- '<%= config.bin %> <%= command.id %> -s docker -v latest --docker-registry=nocobase/nocobase',
18
- '<%= config.bin %> <%= command.id %> -s git -v main --git-url=https://github.com/nocobase/nocobase.git --app-root-path=./my-nocobase-app',
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',
19
24
  ];
20
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
+ }),
21
31
  source: Flags.string({
22
32
  char: 's',
23
- description: 'Where to download from',
33
+ description: 'Distribution: npm runs create-nocobase-app, docker runs docker pull, git clones the repository',
24
34
  options: ['docker', 'npm', 'git'],
25
- required: true,
35
+ required: false,
26
36
  }),
27
37
  version: Flags.string({
28
38
  char: 'v',
29
- default: 'latest',
30
- description: 'Version or tag: npm package dist-tag/version, Docker image tag, or git branch/tag',
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',
31
40
  }),
32
- 'force-overwrite': Flags.boolean({
33
- char: 'f',
34
- description: 'Pass --force to create-nocobase-app (npm) or overwrite when supported',
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',
35
44
  default: false,
36
45
  }),
37
46
  'dev': Flags.boolean({
38
- description: 'Include dev dependencies in the app directory',
47
+ description: 'npm: install devDependencies in create-nocobase-app and run a non-production yarn install; git/docker: ignored',
39
48
  default: false,
40
49
  }),
41
- 'env-file': Flags.string({
42
- description: 'Dotenv file whose variables are passed to create-nocobase-app as -e KEY=value (npm only)',
43
- }),
44
- 'app-root-path': Flags.string({
45
- description: 'Project directory name or path for the new app (default: my-nocobase-app)',
46
- default: 'my-nocobase-app',
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',
47
53
  }),
48
- 'db-password': Flags.string({ description: 'DB password (npm: DB_PASSWORD)' }),
49
54
  'git-url': Flags.string({
50
- description: 'Git remote URL to clone (git source)',
55
+ description: 'git: remote URL to clone (default: https://github.com/nocobase/nocobase.git)',
51
56
  }),
52
57
  'docker-registry': Flags.string({
53
- description: 'Docker image repository without tag (default: nocobase/nocobase). Example: ghcr.io/nocobase/nocobase',
58
+ description: 'docker: image reference without tag (default: nocobase/nocobase); use -v for the tag, e.g. ghcr.io/nocobase/nocobase',
54
59
  }),
55
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
+ }
56
216
  async downloadFromDocker(flags) {
57
217
  const image = flags['docker-registry'] ?? 'nocobase/nocobase';
58
218
  const tag = flags.version ?? 'latest';
59
- await run('docker', ['pull', `${image}:${tag}`], process.cwd());
219
+ await run('docker', ['pull', `${image}:${tag}`]);
60
220
  }
61
221
  async downloadFromNpm(flags) {
62
- const appRoot = flags['app-root-path'] ?? 'my-nocobase-app';
63
222
  const versionSpec = flags.version || 'latest';
64
- const npxArgs = ['-y', `create-nocobase-app@${versionSpec}`, appRoot];
65
- if (!flags['--dev']) {
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']) {
66
227
  npxArgs.push('--skip-dev-dependencies');
67
228
  }
68
- if (flags['force-overwrite']) {
69
- await fsp.rm(path.resolve(process.cwd(), appRoot), { recursive: true, force: true });
229
+ if (flags.replace) {
230
+ await fsp.rm(projectRoot, { recursive: true, force: true });
70
231
  }
71
- await run('npx', npxArgs, process.cwd());
232
+ await run('npx', npxArgs);
72
233
  const installArgs = ['install'];
73
- if (!flags['--dev']) {
234
+ if (!flags['dev']) {
74
235
  installArgs.push('--production');
75
236
  }
76
- await run('yarn', installArgs, path.resolve(process.cwd(), appRoot));
237
+ await run('yarn', installArgs, { cwd: projectRoot });
238
+ return projectRoot;
77
239
  }
78
240
  async downloadFromGit(flags) {
79
241
  const repoUrl = flags['git-url'] ?? 'https://github.com/nocobase/nocobase.git';
80
- const appRoot = flags['app-root-path'] ?? 'my-nocobase-app';
242
+ const versionSpec = flags.version || 'latest';
243
+ const outputDir = this.resolveOutputDir(flags);
81
244
  const versionToRef = {
82
245
  'latest': 'main',
83
246
  'beta': 'next',
84
247
  'alpha': 'develop',
85
248
  };
86
- if (flags['force-overwrite']) {
87
- await fsp.rm(path.resolve(process.cwd(), appRoot), { recursive: true, force: true });
249
+ if (flags.replace) {
250
+ await fsp.rm(path.resolve(process.cwd(), outputDir), { recursive: true, force: true });
88
251
  }
89
- const branch = versionToRef[flags.version || 'latest'] || flags.version || 'latest';
252
+ const branch = versionToRef[versionSpec] || versionSpec;
90
253
  const gitArgs = ['clone'];
91
254
  gitArgs.push('--branch', branch);
92
- gitArgs.push('--depth', '1', repoUrl, appRoot);
93
- await run('git', gitArgs, process.cwd());
94
- await run('yarn', ['install'], path.resolve(process.cwd(), appRoot));
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;
95
260
  }
261
+ /**
262
+ * @returns Final resolved flags and, for npm/git, the absolute project directory.
263
+ */
96
264
  async download() {
97
265
  const { flags } = await this.parse(Download);
98
- switch (flags.source) {
99
- case 'npm':
100
- await this.downloadFromNpm(flags);
101
- break;
102
- case 'docker':
103
- await this.downloadFromDocker(flags);
104
- break;
105
- case 'git':
106
- await this.downloadFromGit(flags);
107
- break;
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
+ }
108
280
  default:
109
- this.error(`Invalid source: ${flags.source}`);
281
+ this.error(`Invalid --source: ${resolved.source}`);
110
282
  }
111
283
  }
112
284
  async run() {
113
285
  try {
114
- await this.download();
286
+ return await this.download();
115
287
  }
116
288
  catch (error) {
117
289
  const message = error instanceof Error ? error.message : String(error);