@nocobase/ctl 0.1.5
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 +164 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +14 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +20 -0
- package/dist/commands/env/add.js +51 -0
- package/dist/commands/env/index.js +27 -0
- package/dist/commands/env/list.js +31 -0
- package/dist/commands/env/remove.js +54 -0
- package/dist/commands/env/update.js +54 -0
- package/dist/commands/env/use.js +26 -0
- package/dist/commands/resource/create.js +15 -0
- package/dist/commands/resource/destroy.js +15 -0
- package/dist/commands/resource/get.js +15 -0
- package/dist/commands/resource/index.js +7 -0
- package/dist/commands/resource/list.js +16 -0
- package/dist/commands/resource/query.js +15 -0
- package/dist/commands/resource/update.js +15 -0
- package/dist/generated/command-registry.js +81 -0
- package/dist/lib/api-client.js +196 -0
- package/dist/lib/auth-store.js +92 -0
- package/dist/lib/bootstrap.js +263 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +30 -0
- package/dist/lib/generated-command.js +113 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +254 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/resource-command.js +331 -0
- package/dist/lib/resource-request.js +103 -0
- package/dist/lib/runtime-generator.js +383 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/ui.js +154 -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 +327 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# NocoBase CLI
|
|
2
|
+
|
|
3
|
+
NocoBase CLI combines:
|
|
4
|
+
|
|
5
|
+
- built-in commands for environment management and generic resource access
|
|
6
|
+
- runtime-generated commands loaded from your NocoBase application's Swagger schema
|
|
7
|
+
|
|
8
|
+
This allows the CLI to stay aligned with the target application instead of relying on a fixed command list.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install dependencies for local development:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run in development mode:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
node ./bin/dev.js --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Build the CLI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run the built CLI:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node ./bin/run.js --help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
After packaging or linking, the executable name is:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
nocobase
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
Add an environment:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
nocobase env add --name local --base-url http://localhost:13000/api --token <token>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Show the current environment:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
nocobase env
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
List configured environments:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
nocobase env list
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Switch the current environment:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
nocobase env use local
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Update the runtime command cache from `swagger:get`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
nocobase env update
|
|
72
|
+
nocobase env update -e local
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use the generic resource commands:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
nocobase resource list --resource users
|
|
79
|
+
nocobase resource get --resource users --filter-by-tk 1
|
|
80
|
+
nocobase resource create --resource users --values '{"nickname":"Ada"}'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Runtime Commands
|
|
84
|
+
|
|
85
|
+
When you execute a runtime command, the CLI will:
|
|
86
|
+
|
|
87
|
+
1. resolve the target environment
|
|
88
|
+
2. read the application's Swagger schema from `swagger:get`
|
|
89
|
+
3. generate or reuse a cached runtime command set for that application version
|
|
90
|
+
4. execute the requested command
|
|
91
|
+
|
|
92
|
+
If the `API documentation plugin` is disabled, the CLI will prompt to enable it.
|
|
93
|
+
|
|
94
|
+
## Environment Selection
|
|
95
|
+
|
|
96
|
+
Use `-e, --env` to temporarily select an environment:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
nocobase env update -e prod
|
|
100
|
+
nocobase resource list --resource users -e prod
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This does not change the current environment unless you explicitly run:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
nocobase env use <name>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Config Scope
|
|
110
|
+
|
|
111
|
+
The `env` command supports two config scopes:
|
|
112
|
+
|
|
113
|
+
- `project`: use `./.nocobase-cli` in the current working directory
|
|
114
|
+
- `global`: use the global `.nocobase-cli` directory
|
|
115
|
+
|
|
116
|
+
Use `-s, --scope` to select one explicitly:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
nocobase env list -s project
|
|
120
|
+
nocobase env add -s global --name prod --base-url http://example.com/api --token <token>
|
|
121
|
+
nocobase env use local -s project
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If you do not pass `--scope`, the CLI uses automatic resolution:
|
|
125
|
+
|
|
126
|
+
1. current working directory if `./.nocobase-cli` exists
|
|
127
|
+
2. `NOCOBASE_HOME_CLI`
|
|
128
|
+
3. your home directory
|
|
129
|
+
|
|
130
|
+
## Built-in Commands
|
|
131
|
+
|
|
132
|
+
Current built-in topics:
|
|
133
|
+
|
|
134
|
+
- `env`
|
|
135
|
+
- `resource`
|
|
136
|
+
|
|
137
|
+
Check available commands at any time:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
nocobase --help
|
|
141
|
+
nocobase env --help
|
|
142
|
+
nocobase resource --help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Common Flags
|
|
146
|
+
|
|
147
|
+
- `-e, --env`: temporary environment selection
|
|
148
|
+
- `-s, --scope`: config scope for `env` commands
|
|
149
|
+
- `-t, --token`: token override
|
|
150
|
+
- `-j, --json-output`: print raw JSON response
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
nocobase env update -e prod -s global
|
|
156
|
+
nocobase resource list --resource users -e prod -j
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Local Data
|
|
160
|
+
|
|
161
|
+
The CLI stores its local state in `.nocobase-cli`, including:
|
|
162
|
+
|
|
163
|
+
- `config.json`: environment definitions and current selection
|
|
164
|
+
- `versions/<version>/commands.json`: cached runtime commands for a generated version
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --import tsx --disable-warning=ExperimentalWarning
|
|
2
|
+
|
|
3
|
+
import { ensureRuntimeFromArgv } from '../src/lib/bootstrap.ts';
|
|
4
|
+
import { execute } from '@oclif/core';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
await ensureRuntimeFromArgv(process.argv.slice(2), {
|
|
11
|
+
configFile: path.join(path.dirname(__dirname), 'nocobase-cli.config.json'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await execute({ development: true, dir: import.meta.url });
|
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ensureRuntimeFromArgv } from '../dist/lib/bootstrap.js';
|
|
4
|
+
import { execute } from '@oclif/core';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await ensureRuntimeFromArgv(process.argv.slice(2), {
|
|
12
|
+
configFile: path.join(path.dirname(__dirname), 'nocobase-cli.config.json'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await execute({ dir: import.meta.url });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
+
console.error(message);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { upsertEnv } from "../../lib/auth-store.js";
|
|
3
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
4
|
+
import { isInteractiveTerminal, printVerbose, setVerboseMode, promptText } from "../../lib/ui.js";
|
|
5
|
+
export default class EnvAdd extends Command {
|
|
6
|
+
static summary = 'Add or update a NocoBase environment';
|
|
7
|
+
static id = 'env add';
|
|
8
|
+
static flags = {
|
|
9
|
+
verbose: Flags.boolean({
|
|
10
|
+
description: 'Show detailed progress output',
|
|
11
|
+
default: false,
|
|
12
|
+
}),
|
|
13
|
+
name: Flags.string({
|
|
14
|
+
description: 'Environment name',
|
|
15
|
+
default: 'default',
|
|
16
|
+
}),
|
|
17
|
+
scope: Flags.string({
|
|
18
|
+
char: 's',
|
|
19
|
+
description: 'Config scope',
|
|
20
|
+
options: ['project', 'global'],
|
|
21
|
+
}),
|
|
22
|
+
'base-url': Flags.string({
|
|
23
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
24
|
+
}),
|
|
25
|
+
token: Flags.string({
|
|
26
|
+
char: 't',
|
|
27
|
+
description: 'Bearer token',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { flags } = await this.parse(EnvAdd);
|
|
32
|
+
setVerboseMode(flags.verbose);
|
|
33
|
+
const name = flags.name || 'default';
|
|
34
|
+
const scope = flags.scope;
|
|
35
|
+
const baseUrl = flags['base-url'] ||
|
|
36
|
+
(isInteractiveTerminal()
|
|
37
|
+
? await promptText('Base URL', { defaultValue: 'http://localhost:13000/api' })
|
|
38
|
+
: '');
|
|
39
|
+
const token = flags.token ||
|
|
40
|
+
(isInteractiveTerminal() ? await promptText('Bearer token', { secret: true }) : '');
|
|
41
|
+
if (!baseUrl) {
|
|
42
|
+
this.error('Missing base URL. Pass `--base-url <url>` or run in a TTY to enter it interactively.');
|
|
43
|
+
}
|
|
44
|
+
if (!token) {
|
|
45
|
+
this.error('Missing token. Pass `--token <token>` or run in a TTY to enter it interactively.');
|
|
46
|
+
}
|
|
47
|
+
printVerbose(`Saving env "${name}" with base URL ${baseUrl}`);
|
|
48
|
+
await upsertEnv(name, baseUrl, token, { scope });
|
|
49
|
+
this.log(`Saved env "${name}" and set it as current${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getCurrentEnvName, getEnv } from "../../lib/auth-store.js";
|
|
3
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
4
|
+
import { renderTable } from "../../lib/ui.js";
|
|
5
|
+
export default class Env extends Command {
|
|
6
|
+
static summary = 'Show the current environment';
|
|
7
|
+
static id = 'env';
|
|
8
|
+
static flags = {
|
|
9
|
+
scope: Flags.string({
|
|
10
|
+
char: 's',
|
|
11
|
+
description: 'Config scope',
|
|
12
|
+
options: ['project', 'global'],
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { flags } = await this.parse(Env);
|
|
17
|
+
const scope = flags.scope;
|
|
18
|
+
const envName = await getCurrentEnvName({ scope });
|
|
19
|
+
const env = await getEnv(envName, { scope });
|
|
20
|
+
if (!env?.baseUrl) {
|
|
21
|
+
this.log(`No current env is configured${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
22
|
+
this.log('Run `nocobase env add --name <name> --base-url <url> --token <token>` to add one.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.log(renderTable(['Name', 'Base URL', 'Runtime'], [[envName, env?.baseUrl ?? '', env?.runtime?.version ?? '']]));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { listEnvs } from "../../lib/auth-store.js";
|
|
3
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
4
|
+
import { renderTable } from "../../lib/ui.js";
|
|
5
|
+
export default class EnvList extends Command {
|
|
6
|
+
static summary = 'List configured environments';
|
|
7
|
+
static id = 'env list';
|
|
8
|
+
static flags = {
|
|
9
|
+
scope: Flags.string({
|
|
10
|
+
char: 's',
|
|
11
|
+
description: 'Config scope',
|
|
12
|
+
options: ['project', 'global'],
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { flags } = await this.parse(EnvList);
|
|
17
|
+
const scope = flags.scope;
|
|
18
|
+
const { currentEnv, envs } = await listEnvs({ scope });
|
|
19
|
+
const names = Object.keys(envs).sort();
|
|
20
|
+
if (!names.length) {
|
|
21
|
+
this.log(`No envs configured${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
22
|
+
this.log('Run `nocobase env add --name <name> --base-url <url> --token <token>` to add one.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const rows = names.map((name) => {
|
|
26
|
+
const env = envs[name];
|
|
27
|
+
return [name === currentEnv ? '*' : '', name, env.baseUrl ?? '', env.runtime?.version ?? ''];
|
|
28
|
+
});
|
|
29
|
+
this.log(renderTable(['Current', 'Name', 'Base URL', 'Runtime'], rows));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getCurrentEnvName, removeEnv } from "../../lib/auth-store.js";
|
|
3
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
4
|
+
import { confirmAction, isInteractiveTerminal, printVerbose, setVerboseMode } from "../../lib/ui.js";
|
|
5
|
+
export default class EnvRemove extends Command {
|
|
6
|
+
static id = 'env remove';
|
|
7
|
+
static summary = 'Remove a configured environment';
|
|
8
|
+
static flags = {
|
|
9
|
+
force: Flags.boolean({
|
|
10
|
+
char: 'f',
|
|
11
|
+
description: 'Remove without confirmation',
|
|
12
|
+
default: false,
|
|
13
|
+
}),
|
|
14
|
+
verbose: Flags.boolean({
|
|
15
|
+
description: 'Show detailed progress output',
|
|
16
|
+
default: false,
|
|
17
|
+
}),
|
|
18
|
+
scope: Flags.string({
|
|
19
|
+
char: 's',
|
|
20
|
+
description: 'Config scope',
|
|
21
|
+
options: ['project', 'global'],
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
static args = {
|
|
25
|
+
name: Args.string({
|
|
26
|
+
description: 'Configured environment name',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { args, flags } = await this.parse(EnvRemove);
|
|
32
|
+
setVerboseMode(flags.verbose);
|
|
33
|
+
const scope = flags.scope;
|
|
34
|
+
const currentEnv = await getCurrentEnvName({ scope });
|
|
35
|
+
if (args.name === currentEnv && !flags.force) {
|
|
36
|
+
if (!isInteractiveTerminal()) {
|
|
37
|
+
this.error('Refusing to remove the current env without confirmation. Re-run with `--force`.');
|
|
38
|
+
}
|
|
39
|
+
const confirmed = await confirmAction(`Remove current env "${args.name}"?`, { defaultValue: false });
|
|
40
|
+
if (!confirmed) {
|
|
41
|
+
this.log('Canceled.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
printVerbose(`Removing env "${args.name}"`);
|
|
46
|
+
const result = await removeEnv(args.name, { scope });
|
|
47
|
+
this.log(`Removed env "${result.removed}"${scope ? ` from ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
48
|
+
if (result.hasEnvs) {
|
|
49
|
+
this.log(`Current env: ${result.currentEnv}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.log('No envs configured.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { Command, Flags } from '@oclif/core';
|
|
4
|
+
import { updateEnvRuntime } from "../../lib/bootstrap.js";
|
|
5
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
6
|
+
import { failTask, startTask, succeedTask } from "../../lib/ui.js";
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
export default class EnvUpdate extends Command {
|
|
9
|
+
static summary = 'Update commands for an environment from swagger:get';
|
|
10
|
+
static id = 'env update';
|
|
11
|
+
static flags = {
|
|
12
|
+
verbose: Flags.boolean({
|
|
13
|
+
description: 'Show detailed progress output',
|
|
14
|
+
default: false,
|
|
15
|
+
}),
|
|
16
|
+
env: Flags.string({
|
|
17
|
+
char: 'e',
|
|
18
|
+
description: 'Environment name',
|
|
19
|
+
}),
|
|
20
|
+
scope: Flags.string({
|
|
21
|
+
char: 's',
|
|
22
|
+
description: 'Config scope',
|
|
23
|
+
options: ['project', 'global'],
|
|
24
|
+
}),
|
|
25
|
+
'base-url': Flags.string({
|
|
26
|
+
description: 'NocoBase API base URL override',
|
|
27
|
+
}),
|
|
28
|
+
token: Flags.string({
|
|
29
|
+
char: 't',
|
|
30
|
+
description: 'Bearer token override',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
const { flags } = await this.parse(EnvUpdate);
|
|
35
|
+
const scope = flags.scope;
|
|
36
|
+
const envLabel = flags.env ?? 'current';
|
|
37
|
+
startTask(`Updating env runtime: ${envLabel}${scope ? ` (${formatCliHomeScope(scope)})` : ''}`);
|
|
38
|
+
try {
|
|
39
|
+
const runtime = await updateEnvRuntime({
|
|
40
|
+
envName: flags.env,
|
|
41
|
+
scope,
|
|
42
|
+
baseUrl: flags['base-url'],
|
|
43
|
+
token: flags.token,
|
|
44
|
+
configFile: path.join(path.dirname(path.dirname(path.dirname(__dirname))), 'nocobase-ctl.config.json'),
|
|
45
|
+
verbose: flags.verbose,
|
|
46
|
+
});
|
|
47
|
+
succeedTask(`Updated env "${envLabel}" to runtime "${runtime.version}"${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
failTask(`Failed to update env "${envLabel}".`);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { setCurrentEnv } from "../../lib/auth-store.js";
|
|
3
|
+
import { formatCliHomeScope } from "../../lib/cli-home.js";
|
|
4
|
+
export default class EnvUse extends Command {
|
|
5
|
+
static summary = 'Switch the current environment';
|
|
6
|
+
static id = 'env use';
|
|
7
|
+
static flags = {
|
|
8
|
+
scope: Flags.string({
|
|
9
|
+
char: 's',
|
|
10
|
+
description: 'Config scope',
|
|
11
|
+
options: ['project', 'global'],
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static args = {
|
|
15
|
+
name: Args.string({
|
|
16
|
+
description: 'Configured environment name',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(EnvUse);
|
|
22
|
+
const scope = flags.scope;
|
|
23
|
+
await setCurrentEnv(args.name, { scope });
|
|
24
|
+
this.log(`Current env: ${args.name}${scope ? ` (${formatCliHomeScope(scope)} scope)` : ''}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildCreateArgs, createFlags, runResourceCommand } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceCreate extends Command {
|
|
4
|
+
static summary = 'Create a record in a resource';
|
|
5
|
+
static description = 'Create a record in a generic resource. Pass record content through --values as a JSON object.';
|
|
6
|
+
static examples = [
|
|
7
|
+
`<%= config.bin %> <%= command.id %> --resource users --values '{"nickname":"Ada"}'`,
|
|
8
|
+
`<%= config.bin %> <%= command.id %> --resource posts.comments --source-id 1 --values '{"content":"Hello"}'`,
|
|
9
|
+
];
|
|
10
|
+
static flags = createFlags;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ResourceCreate);
|
|
13
|
+
await runResourceCommand(this, 'create', flags, buildCreateArgs(flags));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildDestroyArgs, destroyFlags, runResourceCommand } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceDestroy extends Command {
|
|
4
|
+
static summary = 'Delete records from a resource';
|
|
5
|
+
static description = 'Delete records from a generic resource. Target records with --filter-by-tk or --filter.';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --resource users --filter-by-tk 1',
|
|
8
|
+
`<%= config.bin %> <%= command.id %> --resource posts --filter '{"status":"archived"}'`,
|
|
9
|
+
];
|
|
10
|
+
static flags = destroyFlags;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ResourceDestroy);
|
|
13
|
+
await runResourceCommand(this, 'destroy', flags, buildDestroyArgs(flags));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildGetArgs, getFlags, runResourceCommand } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceGet extends Command {
|
|
4
|
+
static summary = 'Get a record from a resource';
|
|
5
|
+
static description = 'Get a record from a generic resource. Use --filter-by-tk for the primary key and association resource names with --source-id when needed.';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --resource users --filter-by-tk 1',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --resource posts.comments --source-id 1 --filter-by-tk 2',
|
|
9
|
+
];
|
|
10
|
+
static flags = getFlags;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ResourceGet);
|
|
13
|
+
await runResourceCommand(this, 'get', flags, buildGetArgs(flags));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildListArgs, listFlags, runResourceCommand } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceList extends Command {
|
|
4
|
+
static summary = 'List records from a resource';
|
|
5
|
+
static description = 'List records from a generic resource. Use association resource names like posts.comments with --source-id when needed.';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --resource users',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --resource posts.comments --source-id 1 --fields id --fields content',
|
|
9
|
+
`<%= config.bin %> <%= command.id %> --resource users --filter '{"status":"active"}' --sort=-createdAt`,
|
|
10
|
+
];
|
|
11
|
+
static flags = listFlags;
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(ResourceList);
|
|
14
|
+
await runResourceCommand(this, 'list', flags, buildListArgs(flags));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildQueryArgs, queryFlags, runResourceCommand } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceQuery extends Command {
|
|
4
|
+
static summary = 'Run an aggregate query on a resource';
|
|
5
|
+
static description = 'Run an aggregate query on a generic resource. Pass measures, dimensions, and orders as JSON arrays.';
|
|
6
|
+
static examples = [
|
|
7
|
+
`<%= config.bin %> <%= command.id %> --resource orders --measures '[{"field":["id"],"aggregation":"count","alias":"count"}]'`,
|
|
8
|
+
`<%= config.bin %> <%= command.id %> --resource orders --dimensions '[{"field":["status"],"alias":"status"}]' --orders '[{"field":["createdAt"],"order":"desc"}]'`,
|
|
9
|
+
];
|
|
10
|
+
static flags = queryFlags;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ResourceQuery);
|
|
13
|
+
await runResourceCommand(this, 'query', flags, buildQueryArgs(flags));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { buildUpdateArgs, runResourceCommand, updateFlags } from "../../lib/resource-command.js";
|
|
3
|
+
export default class ResourceUpdate extends Command {
|
|
4
|
+
static summary = 'Update records in a resource';
|
|
5
|
+
static description = 'Update records in a generic resource. Target records with --filter-by-tk or --filter, and pass updated values through --values.';
|
|
6
|
+
static examples = [
|
|
7
|
+
`<%= config.bin %> <%= command.id %> --resource users --filter-by-tk 1 --values '{"nickname":"Grace"}'`,
|
|
8
|
+
`<%= config.bin %> <%= command.id %> --resource posts --filter '{"status":"draft"}' --values '{"status":"published"}'`,
|
|
9
|
+
];
|
|
10
|
+
static flags = updateFlags;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ResourceUpdate);
|
|
13
|
+
await runResourceCommand(this, 'update', flags, buildUpdateArgs(flags));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import EnvAdd from "../commands/env/add.js";
|
|
3
|
+
import Env from "../commands/env/index.js";
|
|
4
|
+
import EnvList from "../commands/env/list.js";
|
|
5
|
+
import EnvRemove from "../commands/env/remove.js";
|
|
6
|
+
import EnvUpdate from "../commands/env/update.js";
|
|
7
|
+
import EnvUse from "../commands/env/use.js";
|
|
8
|
+
import ResourceCreate from "../commands/resource/create.js";
|
|
9
|
+
import ResourceDestroy from "../commands/resource/destroy.js";
|
|
10
|
+
import ResourceGet from "../commands/resource/get.js";
|
|
11
|
+
import Resource from "../commands/resource/index.js";
|
|
12
|
+
import ResourceList from "../commands/resource/list.js";
|
|
13
|
+
import ResourceQuery from "../commands/resource/query.js";
|
|
14
|
+
import ResourceUpdate from "../commands/resource/update.js";
|
|
15
|
+
import { getCurrentEnvName, getEnv } from "../lib/auth-store.js";
|
|
16
|
+
import { createGeneratedFlags, GeneratedApiCommand } from "../lib/generated-command.js";
|
|
17
|
+
import { toKebabCase } from "../lib/naming.js";
|
|
18
|
+
import { loadRuntimeSync } from "../lib/runtime-store.js";
|
|
19
|
+
function readEnvName(argv) {
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const token = argv[index];
|
|
22
|
+
if (token === '--env') {
|
|
23
|
+
return argv[index + 1];
|
|
24
|
+
}
|
|
25
|
+
if (token === '-e') {
|
|
26
|
+
return argv[index + 1];
|
|
27
|
+
}
|
|
28
|
+
if (token.startsWith('--env=')) {
|
|
29
|
+
return token.slice('--env='.length);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function createRuntimeCommand(operation) {
|
|
35
|
+
return class RuntimeCommand extends GeneratedApiCommand {
|
|
36
|
+
static summary = operation.summary;
|
|
37
|
+
static description = operation.description;
|
|
38
|
+
static examples = operation.examples;
|
|
39
|
+
static flags = createGeneratedFlags(operation);
|
|
40
|
+
static operation = operation;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createRuntimeIndexCommand(commandId, operation) {
|
|
44
|
+
return class RuntimeIndexCommand extends Command {
|
|
45
|
+
static summary = operation.resourceDescription || operation.resourceDisplayName || `Work with ${commandId}`;
|
|
46
|
+
static description = operation.resourceDescription;
|
|
47
|
+
async run() {
|
|
48
|
+
this.log(`Use \`nocobase ${commandId} --help\` to view available subcommands.`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const registry = {
|
|
53
|
+
env: Env,
|
|
54
|
+
'env:add': EnvAdd,
|
|
55
|
+
'env:list': EnvList,
|
|
56
|
+
'env:remove': EnvRemove,
|
|
57
|
+
'env:update': EnvUpdate,
|
|
58
|
+
'env:use': EnvUse,
|
|
59
|
+
resource: Resource,
|
|
60
|
+
'resource:create': ResourceCreate,
|
|
61
|
+
'resource:destroy': ResourceDestroy,
|
|
62
|
+
'resource:get': ResourceGet,
|
|
63
|
+
'resource:list': ResourceList,
|
|
64
|
+
'resource:query': ResourceQuery,
|
|
65
|
+
'resource:update': ResourceUpdate,
|
|
66
|
+
};
|
|
67
|
+
const envName = readEnvName(process.argv.slice(2)) ?? (await getCurrentEnvName());
|
|
68
|
+
const env = await getEnv(envName);
|
|
69
|
+
const runtime = loadRuntimeSync(env?.runtime?.version);
|
|
70
|
+
for (const operation of runtime?.commands ?? []) {
|
|
71
|
+
const commandSegments = operation.commandId.split(' ');
|
|
72
|
+
const commandKey = commandSegments.join(':');
|
|
73
|
+
registry[commandKey] = createRuntimeCommand(operation);
|
|
74
|
+
const topLevelCommandId = commandSegments[0];
|
|
75
|
+
const modulePrefix = toKebabCase(operation.moduleDisplayName || operation.moduleName || '');
|
|
76
|
+
const isTopLevelResource = Boolean(topLevelCommandId && modulePrefix && topLevelCommandId !== modulePrefix);
|
|
77
|
+
if (isTopLevelResource && !registry[topLevelCommandId]) {
|
|
78
|
+
registry[topLevelCommandId] = createRuntimeIndexCommand(topLevelCommandId, operation);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export default registry;
|