@nocobase/cli 2.2.0-beta.1 → 2.2.0-beta.11

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.
Files changed (53) hide show
  1. package/assets/env-proxy/nginx/snippets/proxy-location.conf +1 -0
  2. package/bin/early-locale.js +89 -0
  3. package/bin/node-version.js +35 -0
  4. package/bin/run.js +9 -0
  5. package/bin/windows-admin.js +60 -0
  6. package/dist/commands/app/destroy.js +4 -3
  7. package/dist/commands/app/restart.js +38 -0
  8. package/dist/commands/app/shared.js +49 -3
  9. package/dist/commands/app/start.js +95 -0
  10. package/dist/commands/app/upgrade.js +11 -0
  11. package/dist/commands/env/info.js +11 -1
  12. package/dist/commands/examples/prompts-stages.js +2 -2
  13. package/dist/commands/examples/prompts-test.js +2 -2
  14. package/dist/commands/init.js +33 -11
  15. package/dist/commands/install.js +159 -107
  16. package/dist/commands/license/activate.js +4 -1
  17. package/dist/commands/license/shared.js +24 -15
  18. package/dist/commands/proxy/caddy/generate.js +93 -7
  19. package/dist/commands/proxy/nginx/generate.js +98 -7
  20. package/dist/commands/revision/create.js +1 -1
  21. package/dist/commands/self/check.js +1 -1
  22. package/dist/commands/self/update.js +4 -4
  23. package/dist/commands/skills/check.js +4 -5
  24. package/dist/commands/skills/install.js +18 -1
  25. package/dist/commands/skills/update.js +19 -4
  26. package/dist/commands/source/dev.js +9 -5
  27. package/dist/commands/source/download.js +85 -16
  28. package/dist/lib/api-command-compat.js +51 -8
  29. package/dist/lib/app-managed-resources.js +104 -5
  30. package/dist/lib/auth-store.js +102 -12
  31. package/dist/lib/cli-config.js +73 -1
  32. package/dist/lib/docker-image.js +94 -6
  33. package/dist/lib/env-auth.js +291 -45
  34. package/dist/lib/env-config.js +11 -0
  35. package/dist/lib/env-proxy-config.js +48 -0
  36. package/dist/lib/env-proxy.js +164 -58
  37. package/dist/lib/hook-script.js +160 -0
  38. package/dist/lib/prompt-catalog-terminal.js +32 -19
  39. package/dist/lib/prompt-validators.js +1 -1
  40. package/dist/lib/prompt-web-ui.js +20 -13
  41. package/dist/lib/proxy-caddy.js +77 -9
  42. package/dist/lib/proxy-nginx.js +71 -11
  43. package/dist/lib/run-npm.js +4 -0
  44. package/dist/lib/self-manager.js +254 -46
  45. package/dist/lib/skills-manager.js +116 -23
  46. package/dist/lib/source-publish.js +2 -2
  47. package/dist/lib/startup-update.js +1 -1
  48. package/dist/locale/en-US.json +49 -43
  49. package/dist/locale/zh-CN.json +49 -43
  50. package/package.json +8 -2
  51. package/scripts/build.mjs +0 -34
  52. package/scripts/clean.mjs +0 -9
  53. package/tsconfig.json +0 -19
@@ -10,8 +10,8 @@ import { Flags } from '@oclif/core';
10
10
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
11
11
  import path from 'node:path';
12
12
  import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
13
- import { checkExternalDbConnection, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
14
- import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../lib/docker-image.js";
13
+ import { checkExternalDbConnection, readExternalDbConnectionConfig } from "../../lib/db-connection-check.js";
14
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../../lib/docker-image.js";
15
15
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
16
16
  import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
17
17
  import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
@@ -102,12 +102,7 @@ function buildDockerLicenseDbFlagArgs(envVars) {
102
102
  ];
103
103
  }
104
104
  async function runDockerLicenseJsonCommand(runtime, commandArgs) {
105
- const args = [
106
- 'run',
107
- '--rm',
108
- '--network',
109
- runtime.dockerNetworkName || runtime.workspaceName,
110
- ];
105
+ const args = ['run', '--rm', '--network', runtime.dockerNetworkName || runtime.workspaceName];
111
106
  const dockerPlatform = normalizeDockerPlatform(runtime.env.config?.dockerPlatform);
112
107
  if (dockerPlatform) {
113
108
  args.push('--platform', dockerPlatform);
@@ -193,11 +188,11 @@ export async function generateValidatedInstanceIdFromEnvVars(envVars) {
193
188
  }
194
189
  async function generateInstanceIdForDockerRuntime(runtime) {
195
190
  const envVars = await buildRuntimeEnvVars(runtime);
196
- const payload = await runDockerLicenseJsonCommand(runtime, [
191
+ const payload = (await runDockerLicenseJsonCommand(runtime, [
197
192
  'license',
198
193
  'generate-id',
199
194
  ...buildDockerLicenseDbFlagArgs(envVars),
200
- ]);
195
+ ]));
201
196
  const instanceId = trimValue(payload.instanceId);
202
197
  if (!instanceId) {
203
198
  throw new Error('Docker instance ID generation did not return an instance ID.');
@@ -337,7 +332,7 @@ export async function getLicenseStatus(keyData) {
337
332
  }),
338
333
  signal: controller.signal,
339
334
  });
340
- const payload = await response.json();
335
+ const payload = (await response.json());
341
336
  return payload?.data?.status === 'active' ? 'active' : 'invalid';
342
337
  }
343
338
  catch {
@@ -356,6 +351,22 @@ export async function validateLicenseKey(runtime, key) {
356
351
  catch {
357
352
  keyStatus = 'invalid';
358
353
  }
354
+ if (keyStatus) {
355
+ const currentDomain = appUrl(runtime);
356
+ return {
357
+ current: {
358
+ env: undefined,
359
+ domain: currentDomain ? new URL(currentDomain).host : '',
360
+ },
361
+ keyData,
362
+ keyStatus,
363
+ dbMatch: false,
364
+ sysMatch: false,
365
+ envMatch: false,
366
+ domainMatch: false,
367
+ licenseStatus: 'invalid',
368
+ };
369
+ }
359
370
  const currentEnv = await getCurrentLicenseEnv(runtime);
360
371
  const currentDomain = appUrl(runtime);
361
372
  const dbMatch = isDbMatch(currentEnv, keyData);
@@ -391,7 +402,7 @@ export async function resolveLicenseServiceUrl(value) {
391
402
  return (await resolveLicensePkgUrl(value)).replace(/\/+$/, '');
392
403
  }
393
404
  export async function resolveLicensePkgUrl(value) {
394
- const normalized = String(value ?? '').trim() || await resolveLicensePkgUrlFromConfig();
405
+ const normalized = String(value ?? '').trim() || (await resolveLicensePkgUrlFromConfig());
395
406
  return normalized.replace(/\/+$/, '') + '/';
396
407
  }
397
408
  function shouldRedactOutputKey(key) {
@@ -414,9 +425,7 @@ export function sanitizeLicenseOutput(value) {
414
425
  if (value && typeof value === 'object') {
415
426
  return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
416
427
  key,
417
- shouldRedactOutputKey(key)
418
- ? redactOutputValue(String(nestedValue ?? ''))
419
- : sanitizeLicenseOutput(nestedValue),
428
+ shouldRedactOutputKey(key) ? redactOutputValue(String(nestedValue ?? '')) : sanitizeLicenseOutput(nestedValue),
420
429
  ]));
421
430
  }
422
431
  return value;
@@ -8,20 +8,51 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
- import { getCaddyProxyDriver, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
11
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
13
+ import { getCaddyProxyDriver, writeManualCaddyProxyBundle, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
12
14
  import { normalizeProxyListenPort } from '../../../lib/proxy-nginx.js';
13
15
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
14
16
  export default class ProxyCaddyGenerate extends Command {
15
17
  static summary = 'Generate caddy proxy files for one managed env';
16
18
  static examples = [
19
+ '<%= config.bin %> proxy caddy generate --host app1.example.com',
17
20
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com',
18
21
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com --port 8080',
22
+ '<%= config.bin %> proxy caddy generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
19
23
  ];
20
24
  static flags = {
21
25
  env: Flags.string({
22
26
  char: 'e',
23
- description: 'CLI env name to generate proxy files for',
24
- required: true,
27
+ description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
28
+ }),
29
+ manual: Flags.boolean({
30
+ description: 'Generate proxy files from explicit runtime flags instead of a saved env',
31
+ default: false,
32
+ }),
33
+ name: Flags.string({
34
+ description: 'Output bundle name used under .nocobase/proxy/caddy in manual mode',
35
+ }),
36
+ 'storage-path': Flags.string({
37
+ description: 'Path to the NocoBase storage directory in manual mode',
38
+ }),
39
+ 'dist-root-path': Flags.string({
40
+ description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
41
+ }),
42
+ 'runtime-version': Flags.string({
43
+ description: 'Frontend runtime version under dist-root-path in manual mode',
44
+ }),
45
+ 'app-public-path': Flags.string({
46
+ description: 'Public base path served by the proxied app in manual mode. Defaults to /',
47
+ }),
48
+ 'upstream-host': Flags.string({
49
+ description: 'Upstream host used by caddy reverse_proxy in manual mode',
50
+ }),
51
+ 'upstream-port': Flags.string({
52
+ description: 'Upstream port used by caddy reverse_proxy in manual mode',
53
+ }),
54
+ 'cdn-base-url': Flags.string({
55
+ description: 'Client asset CDN base URL used when generating runtime HTML',
25
56
  }),
26
57
  host: Flags.string({
27
58
  description: 'Host exposed by the caddy site block, such as example.com or localhost',
@@ -35,9 +66,56 @@ export default class ProxyCaddyGenerate extends Command {
35
66
  const requestedEnv = flags.env?.trim() || undefined;
36
67
  const requestedPort = flags.port?.trim() || undefined;
37
68
  const normalizedPort = normalizeProxyListenPort(requestedPort);
69
+ const manual = Boolean(flags.manual);
38
70
  if (requestedPort && !normalizedPort) {
39
71
  this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
40
72
  }
73
+ if (manual && requestedEnv) {
74
+ this.error('`--manual` cannot be combined with `--env`.');
75
+ }
76
+ if (manual) {
77
+ const name = flags.name?.trim() || undefined;
78
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
79
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
80
+ const storagePath = flags['storage-path']?.trim() || undefined;
81
+ const distRootPath = flags['dist-root-path']?.trim() || undefined;
82
+ const runtimeVersion = flags['runtime-version']?.trim() || undefined;
83
+ if (requestedUpstreamPort && !upstreamPort) {
84
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
85
+ }
86
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
87
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
88
+ }
89
+ const driver = await getCaddyProxyDriver();
90
+ const runtimeContext = await resolveCaddyProxyRuntimeContext();
91
+ announceTargetEnv(name);
92
+ startTask(`Generating caddy proxy config for env "${name}" with the ${driver} driver...`);
93
+ try {
94
+ const { bundle, status } = await writeManualCaddyProxyBundle({
95
+ name,
96
+ storagePath,
97
+ distRootPath,
98
+ runtimeVersion,
99
+ appPublicPath: flags['app-public-path']?.trim() || undefined,
100
+ upstreamHost: flags['upstream-host']?.trim() || undefined,
101
+ upstreamPort,
102
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
103
+ }, {
104
+ host: flags.host?.trim() || undefined,
105
+ port: normalizedPort,
106
+ }, runtimeContext, {
107
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
108
+ });
109
+ succeedTask(status === 'created'
110
+ ? `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
111
+ : `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
112
+ }
113
+ catch (error) {
114
+ failTask(`Failed to generate caddy proxy config for env "${name}".`);
115
+ this.error(error instanceof Error ? error.message : String(error));
116
+ }
117
+ return;
118
+ }
41
119
  const runtime = await resolveManagedAppRuntime(requestedEnv);
42
120
  if (!runtime) {
43
121
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -53,10 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
53
131
  announceTargetEnv(runtime.envName);
54
132
  startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
55
133
  try {
56
- const { bundle, status } = await writeCaddyProxyBundle(runtime, {
57
- host: flags.host?.trim() || undefined,
58
- port: normalizedPort,
59
- }, runtimeContext);
134
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'caddy');
135
+ const appEntryOptions = {
136
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
137
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
138
+ };
139
+ const { bundle, status } = await writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, {
140
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
141
+ });
142
+ await setEnvProxyEntry(runtime.envName, 'caddy', {
143
+ host: appEntryOptions.host,
144
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
145
+ }, { scope: resolveDefaultConfigScope() });
60
146
  succeedTask(status === 'created'
61
147
  ? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
62
148
  : `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
@@ -8,19 +8,54 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
- import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
11
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
12
+ import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeManualNginxProxyBundle, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
13
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
14
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
13
15
  export default class ProxyNginxGenerate extends Command {
14
16
  static summary = 'Generate nginx proxy files for one managed env';
15
17
  static examples = [
18
+ '<%= config.bin %> proxy nginx generate --host app1.example.com',
16
19
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com',
17
20
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com --port 8080',
21
+ '<%= config.bin %> proxy nginx generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
18
22
  ];
19
23
  static flags = {
20
24
  env: Flags.string({
21
25
  char: 'e',
22
- description: 'CLI env name to generate proxy files for',
23
- required: true,
26
+ description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
27
+ }),
28
+ manual: Flags.boolean({
29
+ description: 'Generate proxy files from explicit runtime flags instead of a saved env',
30
+ default: false,
31
+ }),
32
+ name: Flags.string({
33
+ description: 'Output bundle name used under .nocobase/proxy/nginx in manual mode',
34
+ }),
35
+ 'storage-path': Flags.string({
36
+ description: 'Path to the NocoBase storage directory in manual mode',
37
+ }),
38
+ 'dist-root-path': Flags.string({
39
+ description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
40
+ }),
41
+ 'runtime-version': Flags.string({
42
+ description: 'Frontend runtime version under dist-root-path in manual mode',
43
+ }),
44
+ 'app-public-path': Flags.string({
45
+ description: 'Public base path served by the proxied app in manual mode. Defaults to /',
46
+ }),
47
+ 'upstream-host': Flags.string({
48
+ description: 'Upstream host used by nginx proxy_pass in manual mode',
49
+ }),
50
+ 'upstream-port': Flags.string({
51
+ description: 'Upstream port used by nginx proxy_pass in manual mode',
52
+ }),
53
+ 'cdn-base-url': Flags.string({
54
+ description: 'Client asset CDN base URL used when generating runtime HTML',
55
+ }),
56
+ force: Flags.boolean({
57
+ description: 'Overwrite existing app.conf even when the managed block is missing',
58
+ default: false,
24
59
  }),
25
60
  host: Flags.string({
26
61
  description: 'Host exposed by the nginx entry config, such as example.com or localhost',
@@ -34,9 +69,56 @@ export default class ProxyNginxGenerate extends Command {
34
69
  const requestedEnv = flags.env?.trim() || undefined;
35
70
  const requestedPort = flags.port?.trim() || undefined;
36
71
  const normalizedPort = normalizeProxyListenPort(requestedPort);
72
+ const manual = Boolean(flags.manual);
37
73
  if (requestedPort && !normalizedPort) {
38
74
  this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
39
75
  }
76
+ if (manual && requestedEnv) {
77
+ this.error('`--manual` cannot be combined with `--env`.');
78
+ }
79
+ if (manual) {
80
+ const name = flags.name?.trim() || undefined;
81
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
82
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
83
+ const storagePath = flags['storage-path']?.trim() || undefined;
84
+ const distRootPath = flags['dist-root-path']?.trim() || undefined;
85
+ const runtimeVersion = flags['runtime-version']?.trim() || undefined;
86
+ if (requestedUpstreamPort && !upstreamPort) {
87
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
88
+ }
89
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
90
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
91
+ }
92
+ const driver = await getNginxProxyDriver();
93
+ const runtimeContext = await resolveNginxProxyRuntimeContext();
94
+ announceTargetEnv(name);
95
+ startTask(`Generating nginx proxy config for env "${name}" with the ${driver} driver...`);
96
+ try {
97
+ const { bundle, status } = await writeManualNginxProxyBundle({
98
+ name,
99
+ storagePath,
100
+ distRootPath,
101
+ runtimeVersion,
102
+ appPublicPath: flags['app-public-path']?.trim() || undefined,
103
+ upstreamHost: flags['upstream-host']?.trim() || undefined,
104
+ upstreamPort,
105
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
106
+ }, {
107
+ host: flags.host?.trim() || undefined,
108
+ port: normalizedPort,
109
+ }, runtimeContext, {
110
+ force: flags.force,
111
+ });
112
+ succeedTask(status === 'created'
113
+ ? `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
114
+ : `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
115
+ }
116
+ catch (error) {
117
+ failTask(`Failed to generate nginx proxy config for env "${name}".`);
118
+ this.error(error instanceof Error ? error.message : String(error));
119
+ }
120
+ return;
121
+ }
40
122
  const runtime = await resolveManagedAppRuntime(requestedEnv);
41
123
  if (!runtime) {
42
124
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -52,10 +134,19 @@ export default class ProxyNginxGenerate extends Command {
52
134
  announceTargetEnv(runtime.envName);
53
135
  startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
54
136
  try {
55
- const { bundle, status } = await writeNginxProxyBundle(runtime, {
56
- host: flags.host?.trim() || undefined,
57
- port: normalizedPort,
58
- }, runtimeContext);
137
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'nginx');
138
+ const appEntryOptions = {
139
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
140
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
141
+ };
142
+ const { bundle, status } = await writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, {
143
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
144
+ force: flags.force,
145
+ });
146
+ await setEnvProxyEntry(runtime.envName, 'nginx', {
147
+ host: appEntryOptions.host,
148
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
149
+ }, { scope: resolveDefaultConfigScope() });
59
150
  succeedTask(status === 'created'
60
151
  ? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
61
152
  : `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
@@ -100,7 +100,7 @@ export default class RevisionCreate extends Command {
100
100
  path: '/app:publishEvent',
101
101
  body: {
102
102
  plugin: 'plugin-version-control',
103
- command: 'revision:create',
103
+ command: 'revision.create',
104
104
  payload: {
105
105
  description,
106
106
  },
@@ -20,7 +20,7 @@ export default class SelfCheck extends Command {
20
20
  static flags = {
21
21
  channel: Flags.string({
22
22
  description: 'Release channel to compare against. Defaults to the current CLI channel.',
23
- options: ['auto', 'latest', 'beta', 'alpha'],
23
+ options: ['auto', 'latest', 'test', 'beta', 'alpha'],
24
24
  default: 'auto',
25
25
  }),
26
26
  json: Flags.boolean({
@@ -26,17 +26,17 @@ function formatSkillsUpdateMessage(result, verbose) {
26
26
  }
27
27
  export default class SelfUpdate extends Command {
28
28
  static summary = 'Update the globally installed NocoBase CLI';
29
- static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
29
+ static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm, pnpm, or yarn install.';
30
30
  static examples = [
31
31
  '<%= config.bin %> <%= command.id %>',
32
32
  '<%= config.bin %> <%= command.id %> --yes',
33
33
  '<%= config.bin %> <%= command.id %> --skills',
34
- '<%= config.bin %> <%= command.id %> --channel alpha --json',
34
+ '<%= config.bin %> <%= command.id %> --channel test --json',
35
35
  ];
36
36
  static flags = {
37
37
  channel: Flags.string({
38
38
  description: 'Release channel to update to. Defaults to the current CLI channel.',
39
- options: ['auto', 'latest', 'beta', 'alpha'],
39
+ options: ['auto', 'latest', 'test', 'beta', 'alpha'],
40
40
  default: 'auto',
41
41
  }),
42
42
  yes: Flags.boolean({
@@ -77,7 +77,7 @@ export default class SelfUpdate extends Command {
77
77
  message: flags.skills
78
78
  ? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
79
79
  : `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
80
- default: false,
80
+ default: true,
81
81
  });
82
82
  }
83
83
  catch {
@@ -12,10 +12,7 @@ import { printInfo, renderTable } from '../../lib/ui.js';
12
12
  export default class SkillsCheck extends Command {
13
13
  static summary = 'Check the globally installed NocoBase AI coding skills';
14
14
  static description = 'Inspect the global NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
15
- static examples = [
16
- '<%= config.bin %> <%= command.id %>',
17
- '<%= config.bin %> <%= command.id %> --json',
18
- ];
15
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json'];
19
16
  static flags = {
20
17
  json: Flags.boolean({
21
18
  description: 'Output the result as JSON',
@@ -25,6 +22,7 @@ export default class SkillsCheck extends Command {
25
22
  async run() {
26
23
  const { flags } = await this.parse(SkillsCheck);
27
24
  const status = await inspectSkillsStatus();
25
+ const displaySkillNames = status.packageSkillNames.length ? status.packageSkillNames : status.installedSkillNames;
28
26
  if (flags.json) {
29
27
  this.log(JSON.stringify({
30
28
  ok: true,
@@ -35,6 +33,7 @@ export default class SkillsCheck extends Command {
35
33
  managedByNb: status.managedByNb,
36
34
  sourcePackage: status.sourcePackage,
37
35
  npmPackageName: status.npmPackageName,
36
+ packageSkillNames: status.packageSkillNames,
38
37
  installedSkillNames: status.installedSkillNames,
39
38
  installedVersion: status.installedVersion,
40
39
  latestVersion: status.latestVersion,
@@ -50,7 +49,7 @@ export default class SkillsCheck extends Command {
50
49
  ['Skills home', status.globalRoot],
51
50
  ['Installed', status.installed ? 'yes' : 'no'],
52
51
  ['Managed by nb', status.managedByNb ? 'yes' : 'no'],
53
- ['Installed skills', status.installedSkillNames.length ? status.installedSkillNames.join(', ') : '(none)'],
52
+ ['Installed skills', displaySkillNames.length ? displaySkillNames.join(', ') : '(none)'],
54
53
  ['Installed version', status.installedVersion ?? '(unknown)'],
55
54
  ['Latest version', status.latestVersion ?? '(unknown)'],
56
55
  ['Update available', status.updateAvailable === null ? 'unknown' : status.updateAvailable ? 'yes' : 'no'],
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { confirm } from "../../lib/inquirer.js";
11
- import { setVerboseMode } from '../../lib/ui.js';
11
+ import { setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
12
12
  import { installNocoBaseSkills } from '../../lib/skills-manager.js';
13
13
  export default class SkillsInstall extends Command {
14
14
  static summary = 'Install the NocoBase AI coding skills globally';
@@ -16,6 +16,8 @@ export default class SkillsInstall extends Command {
16
16
  static examples = [
17
17
  '<%= config.bin %> <%= command.id %>',
18
18
  '<%= config.bin %> <%= command.id %> --yes',
19
+ '<%= config.bin %> <%= command.id %> --version 1.0.4',
20
+ '<%= config.bin %> <%= command.id %> --verbose',
19
21
  '<%= config.bin %> <%= command.id %> --json',
20
22
  ];
21
23
  static flags = {
@@ -32,6 +34,9 @@ export default class SkillsInstall extends Command {
32
34
  description: 'Show detailed install output',
33
35
  default: false,
34
36
  }),
37
+ version: Flags.string({
38
+ description: 'Install a specific @nocobase/skills version',
39
+ }),
35
40
  };
36
41
  async run() {
37
42
  const { flags } = await this.parse(SkillsInstall);
@@ -51,8 +56,20 @@ export default class SkillsInstall extends Command {
51
56
  return;
52
57
  }
53
58
  }
59
+ const shouldShowLoading = !flags.json && !flags.verbose;
60
+ if (shouldShowLoading) {
61
+ startTask(flags.version
62
+ ? `Installing NocoBase AI coding skills ${flags.version}...`
63
+ : 'Installing NocoBase AI coding skills...');
64
+ }
54
65
  const result = await installNocoBaseSkills({
66
+ targetVersion: flags.version,
55
67
  verbose: flags.verbose,
68
+ onProgress: shouldShowLoading ? updateTask : undefined,
69
+ }).finally(() => {
70
+ if (shouldShowLoading) {
71
+ stopTask();
72
+ }
56
73
  });
57
74
  if (flags.json) {
58
75
  this.log(JSON.stringify({
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { confirm } from "../../lib/inquirer.js";
11
- import { setVerboseMode } from '../../lib/ui.js';
11
+ import { setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
12
12
  import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
13
13
  export default class SkillsUpdate extends Command {
14
14
  static summary = 'Update the globally installed NocoBase AI coding skills';
@@ -16,6 +16,8 @@ export default class SkillsUpdate extends Command {
16
16
  static examples = [
17
17
  '<%= config.bin %> <%= command.id %>',
18
18
  '<%= config.bin %> <%= command.id %> --yes',
19
+ '<%= config.bin %> <%= command.id %> --version 1.0.4',
20
+ '<%= config.bin %> <%= command.id %> --verbose',
19
21
  '<%= config.bin %> <%= command.id %> --json',
20
22
  ];
21
23
  static flags = {
@@ -32,6 +34,9 @@ export default class SkillsUpdate extends Command {
32
34
  description: 'Show detailed update output',
33
35
  default: false,
34
36
  }),
37
+ version: Flags.string({
38
+ description: 'Sync to a specific @nocobase/skills version',
39
+ }),
35
40
  };
36
41
  async run() {
37
42
  const { flags } = await this.parse(SkillsUpdate);
@@ -51,8 +56,20 @@ export default class SkillsUpdate extends Command {
51
56
  return;
52
57
  }
53
58
  }
59
+ const shouldShowLoading = !flags.json && !flags.verbose;
60
+ if (shouldShowLoading) {
61
+ startTask(flags.version
62
+ ? `Syncing NocoBase AI coding skills to ${flags.version}...`
63
+ : 'Updating NocoBase AI coding skills...');
64
+ }
54
65
  const result = await updateNocoBaseSkills({
66
+ targetVersion: flags.version,
55
67
  verbose: flags.verbose,
68
+ onProgress: shouldShowLoading ? updateTask : undefined,
69
+ }).finally(() => {
70
+ if (shouldShowLoading) {
71
+ stopTask();
72
+ }
56
73
  });
57
74
  if (flags.json) {
58
75
  this.log(JSON.stringify({
@@ -80,8 +97,6 @@ export default class SkillsUpdate extends Command {
80
97
  : 'NocoBase AI coding skills are up to date.');
81
98
  return;
82
99
  }
83
- this.log(flags.verbose
84
- ? 'Updated the global NocoBase AI coding skills.'
85
- : 'Updated NocoBase AI coding skills globally.');
100
+ this.log(flags.verbose ? 'Updated the global NocoBase AI coding skills.' : 'Updated NocoBase AI coding skills globally.');
86
101
  }
87
102
  }
@@ -8,8 +8,9 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
11
- import { ensureLocalPostinstall } from '../../lib/app-managed-resources.js';
11
+ import { ensureLocalPostinstall, ensureNpmSourceDevDependencies } from '../../lib/app-managed-resources.js';
12
12
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
13
+ import { findAvailableTcpPort } from '../../lib/prompt-validators.js';
13
14
  import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
14
15
  function formatUnsupportedRuntimeMessage(kind, envName) {
15
16
  if (kind === 'docker') {
@@ -129,10 +130,7 @@ export default class SourceDev extends Command {
129
130
  this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
130
131
  }
131
132
  announceTargetEnv(runtime.envName);
132
- const devPort = flags.port ||
133
- (runtime.env.appPort !== undefined && runtime.env.appPort !== null
134
- ? String(runtime.env.appPort).trim()
135
- : undefined);
133
+ const devPort = flags.port?.trim() || (await findAvailableTcpPort());
136
134
  const appUrl = appUrlForPort(devPort);
137
135
  if (await isAppAlreadyRunning(appUrl)) {
138
136
  this.error([
@@ -160,6 +158,12 @@ export default class SourceDev extends Command {
160
158
  }
161
159
  printInfo(`Starting NocoBase dev mode for "${runtime.envName}" from ${runtime.projectRoot}. Press Ctrl+C to stop.`);
162
160
  try {
161
+ await ensureNpmSourceDevDependencies(runtime, {
162
+ onStartTask: startTask,
163
+ onSucceedTask: succeedTask,
164
+ onFailTask: failTask,
165
+ verbose: true,
166
+ });
163
167
  await ensureLocalPostinstall(runtime, {
164
168
  onStartTask: startTask,
165
169
  onSucceedTask: succeedTask,