@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.22

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 (74) hide show
  1. package/README.md +32 -50
  2. package/README.zh-CN.md +29 -46
  3. package/bin/run.js +15 -0
  4. package/dist/commands/app/down.js +260 -0
  5. package/dist/commands/app/info.js +140 -0
  6. package/dist/commands/app/logs.js +98 -0
  7. package/dist/commands/app/ps.js +60 -0
  8. package/dist/commands/app/restart.js +75 -0
  9. package/dist/commands/app/shared.js +95 -0
  10. package/dist/commands/app/start.js +252 -0
  11. package/dist/commands/app/stop.js +98 -0
  12. package/dist/commands/app/upgrade.js +595 -0
  13. package/dist/commands/build.js +3 -48
  14. package/dist/commands/db/shared.js +19 -5
  15. package/dist/commands/dev.js +3 -140
  16. package/dist/commands/down.js +3 -184
  17. package/dist/commands/download.js +4 -856
  18. package/dist/commands/env/add.js +33 -48
  19. package/dist/commands/env/auth.js +6 -13
  20. package/dist/commands/env/list.js +10 -15
  21. package/dist/commands/env/remove.js +4 -10
  22. package/dist/commands/env/update.js +7 -13
  23. package/dist/commands/env/use.js +5 -13
  24. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  25. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  26. package/dist/commands/init.js +262 -63
  27. package/dist/commands/install.js +352 -86
  28. package/dist/commands/logs.js +3 -81
  29. package/dist/commands/plugin/disable.js +64 -0
  30. package/dist/commands/plugin/enable.js +64 -0
  31. package/dist/commands/plugin/list.js +62 -0
  32. package/dist/commands/pm/disable.js +3 -54
  33. package/dist/commands/pm/enable.js +3 -54
  34. package/dist/commands/pm/list.js +3 -45
  35. package/dist/commands/ps.js +3 -107
  36. package/dist/commands/restart.js +3 -65
  37. package/dist/commands/scaffold/migration.js +1 -1
  38. package/dist/commands/scaffold/plugin.js +1 -1
  39. package/dist/commands/self/check.js +1 -1
  40. package/dist/commands/self/update.js +13 -3
  41. package/dist/commands/skills/check.js +11 -5
  42. package/dist/commands/skills/index.js +1 -1
  43. package/dist/commands/skills/install.js +20 -7
  44. package/dist/commands/skills/remove.js +71 -0
  45. package/dist/commands/skills/update.js +27 -7
  46. package/dist/commands/source/build.js +58 -0
  47. package/dist/commands/source/dev.js +157 -0
  48. package/dist/commands/source/download.js +866 -0
  49. package/dist/commands/source/test.js +467 -0
  50. package/dist/commands/start.js +3 -202
  51. package/dist/commands/stop.js +3 -81
  52. package/dist/commands/test.js +3 -457
  53. package/dist/commands/upgrade.js +3 -574
  54. package/dist/help/runtime-help.js +3 -0
  55. package/dist/lib/api-client.js +3 -2
  56. package/dist/lib/app-health.js +126 -0
  57. package/dist/lib/app-managed-resources.js +264 -0
  58. package/dist/lib/app-runtime.js +16 -5
  59. package/dist/lib/auth-store.js +162 -43
  60. package/dist/lib/bootstrap.js +13 -12
  61. package/dist/lib/cli-home.js +38 -6
  62. package/dist/lib/cli-locale.js +15 -1
  63. package/dist/lib/env-auth.js +3 -3
  64. package/dist/lib/env-config.js +80 -0
  65. package/dist/lib/generated-command.js +10 -2
  66. package/dist/lib/http-request.js +49 -0
  67. package/dist/lib/resource-command.js +10 -2
  68. package/dist/lib/runtime-generator.js +1 -1
  69. package/dist/lib/self-manager.js +1 -1
  70. package/dist/lib/skills-manager.js +173 -79
  71. package/dist/lib/startup-update.js +203 -0
  72. package/dist/locale/en-US.json +4 -1
  73. package/dist/locale/zh-CN.json +4 -1
  74. package/package.json +26 -3
@@ -59,7 +59,7 @@ export default class SelfCheck extends Command {
59
59
  ['Update available', status.updateAvailable ? 'yes' : 'no'],
60
60
  ]));
61
61
  if (status.updateAvailable && status.updatable) {
62
- printInfo('Run `nb self update` to update the CLI.');
62
+ printInfo('Run `nb self update`.');
63
63
  }
64
64
  else if (status.updateAvailable && status.updateBlockedReason) {
65
65
  printInfo(status.updateBlockedReason);
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { formatSelfUpdateUnavailableMessage, formatUnsupportedSelfUpdateMessage, inspectSelfStatus, updateSelf, } from '../../lib/self-manager.js';
12
12
  export default class SelfUpdate extends Command {
13
13
  static summary = 'Update the globally installed NocoBase CLI';
@@ -32,9 +32,14 @@ export default class SelfUpdate extends Command {
32
32
  description: 'Output the result as JSON',
33
33
  default: false,
34
34
  }),
35
+ verbose: Flags.boolean({
36
+ description: 'Show detailed update output',
37
+ default: false,
38
+ }),
35
39
  };
36
40
  async run() {
37
41
  const { flags } = await this.parse(SelfUpdate);
42
+ setVerboseMode(flags.verbose);
38
43
  const status = await inspectSelfStatus({
39
44
  channel: flags.channel,
40
45
  });
@@ -53,6 +58,7 @@ export default class SelfUpdate extends Command {
53
58
  }
54
59
  const result = await updateSelf({
55
60
  channel: flags.channel,
61
+ verbose: flags.verbose,
56
62
  });
57
63
  if (flags.json) {
58
64
  this.log(JSON.stringify({
@@ -68,9 +74,13 @@ export default class SelfUpdate extends Command {
68
74
  return;
69
75
  }
70
76
  if (result.action === 'noop') {
71
- this.log(`NocoBase CLI is already up to date at ${result.status.currentVersion}.`);
77
+ this.log(flags.verbose
78
+ ? `NocoBase CLI is already up to date at ${result.status.currentVersion}.`
79
+ : `NocoBase CLI is up to date: ${result.status.currentVersion}.`);
72
80
  return;
73
81
  }
74
- this.log(`Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`);
82
+ this.log(flags.verbose
83
+ ? `Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`
84
+ : `Updated NocoBase CLI: ${result.status.currentVersion} -> ${result.targetVersion}.`);
75
85
  }
76
86
  }
@@ -10,8 +10,8 @@ import { Command, Flags } from '@oclif/core';
10
10
  import { inspectSkillsStatus } from '../../lib/skills-manager.js';
11
11
  import { printInfo, renderTable } from '../../lib/ui.js';
12
12
  export default class SkillsCheck extends Command {
13
- static summary = 'Check the NocoBase AI coding skills installed for this workspace';
14
- static description = 'Inspect the current workspace for NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
13
+ static summary = 'Check the globally installed NocoBase AI coding skills';
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
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --json',
@@ -29,11 +29,15 @@ export default class SkillsCheck extends Command {
29
29
  this.log(JSON.stringify({
30
30
  ok: true,
31
31
  kind: 'skills',
32
+ globalRoot: status.globalRoot,
32
33
  workspaceRoot: status.workspaceRoot,
33
34
  installed: status.installed,
34
35
  managedByNb: status.managedByNb,
35
36
  sourcePackage: status.sourcePackage,
37
+ npmPackageName: status.npmPackageName,
36
38
  installedSkillNames: status.installedSkillNames,
39
+ installedVersion: status.installedVersion,
40
+ latestVersion: status.latestVersion,
37
41
  installedRef: status.installedRef,
38
42
  latestRef: status.latestRef,
39
43
  updateAvailable: status.updateAvailable,
@@ -43,18 +47,20 @@ export default class SkillsCheck extends Command {
43
47
  return;
44
48
  }
45
49
  this.log(renderTable(['Field', 'Value'], [
46
- ['Workspace', status.workspaceRoot],
50
+ ['Skills home', status.globalRoot],
47
51
  ['Installed', status.installed ? 'yes' : 'no'],
48
52
  ['Managed by nb', status.managedByNb ? 'yes' : 'no'],
49
53
  ['Installed skills', status.installedSkillNames.length ? status.installedSkillNames.join(', ') : '(none)'],
54
+ ['Installed version', status.installedVersion ?? '(unknown)'],
55
+ ['Latest version', status.latestVersion ?? '(unknown)'],
50
56
  ['Update available', status.updateAvailable === null ? 'unknown' : status.updateAvailable ? 'yes' : 'no'],
51
57
  ]));
52
58
  if (!status.installed) {
53
- printInfo('Run `nb skills install` to install the NocoBase AI coding skills for this workspace.');
59
+ printInfo('Run `nb skills install` to install the NocoBase AI coding skills globally.');
54
60
  return;
55
61
  }
56
62
  if (status.updateAvailable) {
57
- printInfo('Run `nb skills update` to refresh the NocoBase AI coding skills for this workspace.');
63
+ printInfo('Run `nb skills update` to refresh the global NocoBase AI coding skills.');
58
64
  }
59
65
  if (status.registryError) {
60
66
  printInfo(`Update check warning: ${status.registryError}`);
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, loadHelpClass } from '@oclif/core';
10
10
  export default class Skills extends Command {
11
- static summary = 'Inspect or synchronize NocoBase AI coding skills for this workspace';
11
+ static summary = 'Inspect or synchronize global NocoBase AI coding skills';
12
12
  async run() {
13
13
  await this.parse(Skills);
14
14
  const Help = await loadHelpClass(this.config);
@@ -7,11 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { installNocoBaseSkills } from '../../lib/skills-manager.js';
12
12
  export default class SkillsInstall extends Command {
13
- static summary = 'Install the NocoBase AI coding skills for this workspace';
14
- static description = 'Install the NocoBase AI coding skills for the current workspace. If they are already installed, this command does not update them.';
13
+ static summary = 'Install the NocoBase AI coding skills globally';
14
+ static description = 'Install the NocoBase AI coding skills globally. If they are already installed, this command does not update them.';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --yes',
@@ -27,32 +27,45 @@ export default class SkillsInstall extends Command {
27
27
  description: 'Output the result as JSON',
28
28
  default: false,
29
29
  }),
30
+ verbose: Flags.boolean({
31
+ description: 'Show detailed install output',
32
+ default: false,
33
+ }),
30
34
  };
31
35
  async run() {
32
36
  const { flags } = await this.parse(SkillsInstall);
37
+ setVerboseMode(flags.verbose);
33
38
  if (!flags.yes) {
34
- const confirmed = await confirmAction('Install the NocoBase AI coding skills for this workspace?', { defaultValue: true });
39
+ const confirmed = await confirmAction('Install the NocoBase AI coding skills globally?', { defaultValue: true });
35
40
  if (!confirmed) {
36
41
  this.log('Skipped skills install.');
37
42
  return;
38
43
  }
39
44
  }
40
- const result = await installNocoBaseSkills();
45
+ const result = await installNocoBaseSkills({
46
+ verbose: flags.verbose,
47
+ });
41
48
  if (flags.json) {
42
49
  this.log(JSON.stringify({
43
50
  ok: true,
44
51
  kind: 'skills',
45
52
  action: result.action,
53
+ globalRoot: result.status.globalRoot,
46
54
  workspaceRoot: result.status.workspaceRoot,
47
55
  installedSkillNames: result.status.installedSkillNames,
56
+ installedVersion: result.status.installedVersion,
48
57
  installedRef: result.status.installedRef,
49
58
  }, null, 2));
50
59
  return;
51
60
  }
52
61
  if (result.action === 'noop') {
53
- this.log('NocoBase AI coding skills are already installed for this workspace. Run `nb skills update` to refresh them.');
62
+ this.log(flags.verbose
63
+ ? 'NocoBase AI coding skills are already installed globally. Run `nb skills update` to refresh them.'
64
+ : 'NocoBase AI coding skills are already installed globally.');
54
65
  return;
55
66
  }
56
- this.log('Installed the NocoBase AI coding skills for this workspace.');
67
+ this.log(flags.verbose
68
+ ? 'Installed the NocoBase AI coding skills globally.'
69
+ : 'Installed NocoBase AI coding skills globally.');
57
70
  }
58
71
  }
@@ -0,0 +1,71 @@
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 { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
+ import { removeNocoBaseSkills } from '../../lib/skills-manager.js';
12
+ export default class SkillsRemove extends Command {
13
+ static summary = 'Remove the globally installed NocoBase AI coding skills';
14
+ static description = 'Remove the skills installed from nocobase/skills globally. This only removes the skills managed by `nb`.';
15
+ static examples = [
16
+ '<%= config.bin %> <%= command.id %>',
17
+ '<%= config.bin %> <%= command.id %> --yes',
18
+ '<%= config.bin %> <%= command.id %> --json',
19
+ ];
20
+ static flags = {
21
+ yes: Flags.boolean({
22
+ char: 'y',
23
+ description: 'Skip the remove confirmation prompt',
24
+ default: false,
25
+ }),
26
+ json: Flags.boolean({
27
+ description: 'Output the result as JSON',
28
+ default: false,
29
+ }),
30
+ verbose: Flags.boolean({
31
+ description: 'Show detailed remove output',
32
+ default: false,
33
+ }),
34
+ };
35
+ async run() {
36
+ const { flags } = await this.parse(SkillsRemove);
37
+ setVerboseMode(flags.verbose);
38
+ if (!flags.yes) {
39
+ const confirmed = await confirmAction('Remove the globally installed NocoBase AI coding skills?', { defaultValue: true });
40
+ if (!confirmed) {
41
+ this.log('Skipped skills removal.');
42
+ return;
43
+ }
44
+ }
45
+ const result = await removeNocoBaseSkills({
46
+ verbose: flags.verbose,
47
+ });
48
+ if (flags.json) {
49
+ this.log(JSON.stringify({
50
+ ok: true,
51
+ kind: 'skills',
52
+ action: result.action,
53
+ globalRoot: result.status.globalRoot,
54
+ workspaceRoot: result.status.workspaceRoot,
55
+ installedSkillNames: result.status.installedSkillNames,
56
+ installedVersion: result.status.installedVersion,
57
+ installedRef: result.status.installedRef,
58
+ }, null, 2));
59
+ return;
60
+ }
61
+ if (result.action === 'noop') {
62
+ this.log(flags.verbose
63
+ ? 'NocoBase AI coding skills are not installed globally.'
64
+ : 'NocoBase AI coding skills are not installed.');
65
+ return;
66
+ }
67
+ this.log(flags.verbose
68
+ ? 'Removed the global NocoBase AI coding skills.'
69
+ : 'Removed NocoBase AI coding skills globally.');
70
+ }
71
+ }
@@ -7,11 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
12
12
  export default class SkillsUpdate extends Command {
13
- static summary = 'Update the NocoBase AI coding skills for this workspace';
14
- static description = 'Refresh the NocoBase AI coding skills for the current workspace. This command only updates an existing nocobase/skills install.';
13
+ static summary = 'Update the globally installed NocoBase AI coding skills';
14
+ static description = 'Refresh the globally installed NocoBase AI coding skills. This command only updates an existing @nocobase/skills install.';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --yes',
@@ -27,32 +27,52 @@ export default class SkillsUpdate extends Command {
27
27
  description: 'Output the result as JSON',
28
28
  default: false,
29
29
  }),
30
+ verbose: Flags.boolean({
31
+ description: 'Show detailed update output',
32
+ default: false,
33
+ }),
30
34
  };
31
35
  async run() {
32
36
  const { flags } = await this.parse(SkillsUpdate);
37
+ setVerboseMode(flags.verbose);
33
38
  if (!flags.yes) {
34
- const confirmed = await confirmAction('Update the NocoBase AI coding skills for this workspace?', { defaultValue: true });
39
+ const confirmed = await confirmAction('Update the globally installed NocoBase AI coding skills?', { defaultValue: true });
35
40
  if (!confirmed) {
36
41
  this.log('Skipped skills update.');
37
42
  return;
38
43
  }
39
44
  }
40
- const result = await updateNocoBaseSkills();
45
+ const result = await updateNocoBaseSkills({
46
+ verbose: flags.verbose,
47
+ });
41
48
  if (flags.json) {
42
49
  this.log(JSON.stringify({
43
50
  ok: true,
44
51
  kind: 'skills',
45
52
  action: result.action,
53
+ reason: result.action === 'noop' ? result.reason : undefined,
54
+ globalRoot: result.status.globalRoot,
46
55
  workspaceRoot: result.status.workspaceRoot,
47
56
  installedSkillNames: result.status.installedSkillNames,
57
+ installedVersion: result.status.installedVersion,
48
58
  installedRef: result.status.installedRef,
49
59
  }, null, 2));
50
60
  return;
51
61
  }
52
62
  if (result.action === 'noop') {
53
- this.log('NocoBase AI coding skills are already up to date for this workspace.');
63
+ if (result.reason === 'not-installed') {
64
+ this.log(flags.verbose
65
+ ? 'NocoBase AI coding skills are not installed globally. Run `nb skills install` first.'
66
+ : 'Skipped skills update because NocoBase AI coding skills are not installed.');
67
+ return;
68
+ }
69
+ this.log(flags.verbose
70
+ ? 'NocoBase AI coding skills are already up to date globally.'
71
+ : 'NocoBase AI coding skills are up to date.');
54
72
  return;
55
73
  }
56
- this.log('Updated the NocoBase AI coding skills for this workspace.');
74
+ this.log(flags.verbose
75
+ ? 'Updated the global NocoBase AI coding skills.'
76
+ : 'Updated NocoBase AI coding skills globally.');
57
77
  }
58
78
  }
@@ -0,0 +1,58 @@
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 { runNocoBaseCommand } from "../../lib/run-npm.js";
11
+ import { setVerboseMode } from '../../lib/ui.js';
12
+ export default class SourceBuild extends Command {
13
+ static hidden = false;
14
+ static args = {
15
+ /** Matches `nb source build @nocobase/acl @nocobase/actions` — zero or more package names. */
16
+ packages: Args.string({
17
+ description: 'package names to build',
18
+ multiple: true,
19
+ required: false,
20
+ }),
21
+ };
22
+ static description = 'Run the legacy NocoBase build for the local source project (forwards to `npm run build` in the repo root)';
23
+ static examples = [
24
+ '<%= config.bin %> <%= command.id %>',
25
+ '<%= config.bin %> <%= command.id %> --no-dts',
26
+ '<%= config.bin %> <%= command.id %> --sourcemap',
27
+ '<%= config.bin %> <%= command.id %> @nocobase/acl',
28
+ '<%= config.bin %> <%= command.id %> @nocobase/acl @nocobase/actions',
29
+ ];
30
+ static flags = {
31
+ 'cwd': Flags.string({ description: 'Current working directory', char: 'c', required: false }),
32
+ 'no-dts': Flags.boolean({ description: 'not generate dts' }),
33
+ sourcemap: Flags.boolean({ description: 'generate sourcemap' }),
34
+ verbose: Flags.boolean({ description: 'Show detailed command output', default: false }),
35
+ };
36
+ async run() {
37
+ const { args, flags } = await this.parse(SourceBuild);
38
+ setVerboseMode(flags.verbose);
39
+ const packages = args.packages ?? [];
40
+ const npmArgs = ['build', ...packages];
41
+ if (flags['no-dts']) {
42
+ npmArgs.push('--no-dts');
43
+ }
44
+ if (flags.sourcemap) {
45
+ npmArgs.push('--sourcemap');
46
+ }
47
+ try {
48
+ await runNocoBaseCommand(npmArgs, {
49
+ cwd: flags['cwd'],
50
+ stdio: flags.verbose ? 'inherit' : 'ignore',
51
+ });
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ this.error(message);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,157 @@
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 { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
11
+ import { printInfo } from '../../lib/ui.js';
12
+ function formatUnsupportedRuntimeMessage(kind, envName) {
13
+ if (kind === 'docker') {
14
+ return [
15
+ `Can't run dev mode for "${envName}".`,
16
+ 'This env is managed by Docker, but `nb source dev` requires a local npm or Git source directory.',
17
+ `Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --env ${envName} --source git\`.`,
18
+ ].join('\n');
19
+ }
20
+ if (kind === 'ssh') {
21
+ return [
22
+ `Can't run dev mode for "${envName}" yet.`,
23
+ 'SSH env support is reserved but not implemented yet.',
24
+ `Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode right now.`,
25
+ ].join('\n');
26
+ }
27
+ return [
28
+ `Can't run dev mode for "${envName}".`,
29
+ 'This env only has an API connection, but `nb source dev` requires a local npm or Git source directory.',
30
+ `Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode.`,
31
+ ].join('\n');
32
+ }
33
+ function appUrlForPort(port) {
34
+ const value = String(port ?? '').trim();
35
+ if (!value) {
36
+ return undefined;
37
+ }
38
+ return `http://127.0.0.1:${value}`;
39
+ }
40
+ async function isAppAlreadyRunning(appUrl) {
41
+ if (!appUrl) {
42
+ return false;
43
+ }
44
+ const controller = new AbortController();
45
+ const timeout = setTimeout(() => controller.abort(), 1500);
46
+ try {
47
+ const response = await fetch(`${appUrl}/api/__health_check`, {
48
+ signal: controller.signal,
49
+ });
50
+ const text = await response.text();
51
+ return response.ok && text.trim().toLowerCase() === 'ok';
52
+ }
53
+ catch (_error) {
54
+ return false;
55
+ }
56
+ finally {
57
+ clearTimeout(timeout);
58
+ }
59
+ }
60
+ export default class SourceDev extends Command {
61
+ static hidden = false;
62
+ static description = 'Run NocoBase in local development mode for an npm or Git source env.';
63
+ static examples = [
64
+ '<%= config.bin %> <%= command.id %>',
65
+ '<%= config.bin %> <%= command.id %> --env app1',
66
+ '<%= config.bin %> <%= command.id %> --env app1 --db-sync',
67
+ '<%= config.bin %> <%= command.id %> --env app1 --port 12000',
68
+ '<%= config.bin %> <%= command.id %> --env app1 --client',
69
+ '<%= config.bin %> <%= command.id %> --env app1 --server',
70
+ '<%= config.bin %> <%= command.id %> --env app1 --inspect 9229',
71
+ ];
72
+ static flags = {
73
+ env: Flags.string({
74
+ char: 'e',
75
+ description: 'CLI env name for dev mode. Defaults to the current env when omitted',
76
+ required: false,
77
+ }),
78
+ 'db-sync': Flags.boolean({
79
+ description: 'Synchronize the database before starting dev mode',
80
+ required: false,
81
+ }),
82
+ port: Flags.string({
83
+ description: 'Development server port',
84
+ char: 'p',
85
+ required: false,
86
+ }),
87
+ client: Flags.boolean({
88
+ description: 'Run client dev mode only',
89
+ char: 'c',
90
+ required: false,
91
+ }),
92
+ server: Flags.boolean({
93
+ description: 'Run server dev mode only',
94
+ char: 's',
95
+ required: false,
96
+ }),
97
+ inspect: Flags.string({
98
+ description: 'Node.js inspect port for server debugging',
99
+ char: 'i',
100
+ required: false,
101
+ }),
102
+ };
103
+ async run() {
104
+ const { flags } = await this.parse(SourceDev);
105
+ const requestedEnv = flags.env?.trim() || undefined;
106
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
107
+ if (!runtime) {
108
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
109
+ }
110
+ if (runtime.kind === 'docker' || runtime.kind === 'http' || runtime.kind === 'ssh') {
111
+ this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
112
+ }
113
+ const devPort = flags.port
114
+ || (runtime.env.appPort !== undefined && runtime.env.appPort !== null
115
+ ? String(runtime.env.appPort).trim()
116
+ : undefined);
117
+ const appUrl = appUrlForPort(devPort);
118
+ if (await isAppAlreadyRunning(appUrl)) {
119
+ this.error([
120
+ `NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`,
121
+ flags.port
122
+ ? `Choose another dev port with --port, or stop the running app with \`nb app stop --env ${runtime.envName}\` first.`
123
+ : `Run \`nb app stop --env ${runtime.envName}\` before starting dev mode, or choose another dev port with --port.`,
124
+ ].join('\n'));
125
+ }
126
+ const npmArgs = ['dev', '--rsbuild'];
127
+ if (flags['db-sync']) {
128
+ npmArgs.push('--db-sync');
129
+ }
130
+ if (devPort) {
131
+ npmArgs.push('--port', devPort);
132
+ }
133
+ if (flags.client) {
134
+ npmArgs.push('--client');
135
+ }
136
+ if (flags.server) {
137
+ npmArgs.push('--server');
138
+ }
139
+ if (flags.inspect) {
140
+ npmArgs.push('--inspect', flags.inspect);
141
+ }
142
+ printInfo(`Starting NocoBase dev mode for "${runtime.envName}" from ${runtime.projectRoot}. Press Ctrl+C to stop.`);
143
+ try {
144
+ await runLocalNocoBaseCommand(runtime, npmArgs, {
145
+ stdio: 'inherit',
146
+ });
147
+ }
148
+ catch (error) {
149
+ const message = error instanceof Error ? error.message : String(error);
150
+ this.error([
151
+ `Couldn't start dev mode for "${runtime.envName}".`,
152
+ 'Check that dependencies are installed and the saved env settings are valid, then try again.',
153
+ `Details: ${message}`,
154
+ ].join('\n'));
155
+ }
156
+ }
157
+ }