@nocobase/cli 2.1.0-alpha.16 → 2.1.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +107 -0
- package/README.md +134 -63
- package/bin/run.cmd +3 -0
- package/bin/run.js +87 -0
- package/dist/commands/api/index.js +8 -0
- package/dist/commands/env/add.js +53 -0
- package/dist/commands/env/auth.js +36 -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 +58 -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 +88 -0
- package/dist/lib/api-client.js +199 -0
- package/dist/lib/auth-store.js +155 -0
- package/dist/lib/bootstrap.js +349 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +30 -0
- package/dist/lib/env-auth.js +405 -0
- package/dist/lib/generated-command.js +142 -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 +335 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/runtime-generator.js +408 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/ui.js +169 -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 +50 -25
- package/LICENSE +0 -201
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -184
- package/src/cli.js +0 -28
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -81
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -168
- package/src/commands/create-nginx-conf.js +0 -53
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -290
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/index.js +0 -17
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/index.js +0 -14
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -607
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client-v2.d.ts +0 -2
- package/templates/plugin/client-v2.js +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -12
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/models/index.ts +0 -12
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/client-v2/client.d.ts +0 -103
- package/templates/plugin/src/client-v2/index.tsx.tpl +0 -1
- package/templates/plugin/src/client-v2/plugin.tsx.tpl +0 -7
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,58 @@
|
|
|
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 = 'Refresh an environment runtime from swagger:get and persist connection overrides';
|
|
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. When provided, persist it to the target env before saving the refreshed runtime.',
|
|
27
|
+
}),
|
|
28
|
+
role: Flags.string({
|
|
29
|
+
description: 'Role override, sent as X-Role',
|
|
30
|
+
}),
|
|
31
|
+
token: Flags.string({
|
|
32
|
+
char: 't',
|
|
33
|
+
description: 'API key override. When provided, persist it to the target env before saving the refreshed runtime.',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { flags } = await this.parse(EnvUpdate);
|
|
38
|
+
const scope = flags.scope;
|
|
39
|
+
const envLabel = flags.env ?? 'current';
|
|
40
|
+
startTask(`Updating env runtime: ${envLabel}${scope ? ` (${formatCliHomeScope(scope)})` : ''}`);
|
|
41
|
+
try {
|
|
42
|
+
const runtime = await updateEnvRuntime({
|
|
43
|
+
envName: flags.env,
|
|
44
|
+
scope,
|
|
45
|
+
baseUrl: flags['base-url'],
|
|
46
|
+
role: flags.role,
|
|
47
|
+
token: flags.token,
|
|
48
|
+
configFile: path.join(path.dirname(path.dirname(path.dirname(__dirname))), 'nocobase-ctl.config.json'),
|
|
49
|
+
verbose: flags.verbose,
|
|
50
|
+
});
|
|
51
|
+
succeedTask(`Updated env "${envLabel}" to runtime "${runtime.version}"${scope ? ` in ${formatCliHomeScope(scope)} scope` : ''}.`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
failTask(`Failed to update env "${envLabel}".`);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -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,88 @@
|
|
|
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 } from '@oclif/core';
|
|
10
|
+
import EnvAdd from "../commands/env/add.js";
|
|
11
|
+
import EnvAuth from "../commands/env/auth.js";
|
|
12
|
+
import EnvList from "../commands/env/list.js";
|
|
13
|
+
import EnvRemove from "../commands/env/remove.js";
|
|
14
|
+
import EnvUpdate from "../commands/env/update.js";
|
|
15
|
+
import EnvUse from "../commands/env/use.js";
|
|
16
|
+
import ResourceCreate from "../commands/resource/create.js";
|
|
17
|
+
import ResourceDestroy from "../commands/resource/destroy.js";
|
|
18
|
+
import ResourceGet from "../commands/resource/get.js";
|
|
19
|
+
import ResourceList from "../commands/resource/list.js";
|
|
20
|
+
import ResourceQuery from "../commands/resource/query.js";
|
|
21
|
+
import ResourceUpdate from "../commands/resource/update.js";
|
|
22
|
+
import { getCurrentEnvName, getEnv } from "../lib/auth-store.js";
|
|
23
|
+
import { createGeneratedFlags, GeneratedApiCommand } from "../lib/generated-command.js";
|
|
24
|
+
import { loadRuntimeSync } from "../lib/runtime-store.js";
|
|
25
|
+
function readEnvName(argv) {
|
|
26
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
27
|
+
const token = argv[index];
|
|
28
|
+
if (token === '--env') {
|
|
29
|
+
return argv[index + 1];
|
|
30
|
+
}
|
|
31
|
+
if (token === '-e') {
|
|
32
|
+
return argv[index + 1];
|
|
33
|
+
}
|
|
34
|
+
if (token.startsWith('--env=')) {
|
|
35
|
+
return token.slice('--env='.length);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function createRuntimeCommand(operation) {
|
|
41
|
+
return class RuntimeCommand extends GeneratedApiCommand {
|
|
42
|
+
static summary = operation.summary;
|
|
43
|
+
static description = operation.description;
|
|
44
|
+
static examples = operation.examples;
|
|
45
|
+
static flags = createGeneratedFlags(operation);
|
|
46
|
+
static operation = operation;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function createRuntimeIndexCommand(commandId, operation) {
|
|
50
|
+
return class RuntimeIndexCommand extends Command {
|
|
51
|
+
static summary = operation.resourceDescription || operation.resourceDisplayName || `Work with ${commandId}`;
|
|
52
|
+
static description = operation.resourceDescription;
|
|
53
|
+
async run() {
|
|
54
|
+
this.log(`Use \`nb ${commandId} --help\` to view available subcommands.`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const registry = {
|
|
59
|
+
// env: Env,
|
|
60
|
+
'env:add': EnvAdd,
|
|
61
|
+
'env:auth': EnvAuth,
|
|
62
|
+
'env:list': EnvList,
|
|
63
|
+
'env:remove': EnvRemove,
|
|
64
|
+
'env:update': EnvUpdate,
|
|
65
|
+
'env:use': EnvUse,
|
|
66
|
+
// 'api:resource': Resource,
|
|
67
|
+
'api:resource:create': ResourceCreate,
|
|
68
|
+
'api:resource:destroy': ResourceDestroy,
|
|
69
|
+
'api:resource:get': ResourceGet,
|
|
70
|
+
'api:resource:list': ResourceList,
|
|
71
|
+
'api:resource:query': ResourceQuery,
|
|
72
|
+
'api:resource:update': ResourceUpdate,
|
|
73
|
+
};
|
|
74
|
+
const envName = readEnvName(process.argv.slice(2)) ?? (await getCurrentEnvName());
|
|
75
|
+
const env = await getEnv(envName);
|
|
76
|
+
const runtime = loadRuntimeSync(env?.runtime?.version);
|
|
77
|
+
for (const operation of runtime?.commands ?? []) {
|
|
78
|
+
const commandSegments = operation.commandId.split(' ');
|
|
79
|
+
const commandKey = commandSegments.join(':');
|
|
80
|
+
registry[`api:${commandKey}`] = createRuntimeCommand(operation);
|
|
81
|
+
// const topLevelCommandId = commandSegments[0];
|
|
82
|
+
// const modulePrefix = toKebabCase(operation.moduleDisplayName || operation.moduleName || '');
|
|
83
|
+
// const isTopLevelResource = Boolean(topLevelCommandId && modulePrefix && topLevelCommandId !== modulePrefix);
|
|
84
|
+
// if (isTopLevelResource && !registry[`api:${topLevelCommandId}`]) {
|
|
85
|
+
// registry[`api:${topLevelCommandId}`] = createRuntimeIndexCommand(`api ${topLevelCommandId}`, operation);
|
|
86
|
+
// }
|
|
87
|
+
}
|
|
88
|
+
export default registry;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { resolveServerRequestTarget } from './env-auth.js';
|
|
3
|
+
function normalizeBaseUrl(baseUrl) {
|
|
4
|
+
return baseUrl.replace(/\/+$/, '');
|
|
5
|
+
}
|
|
6
|
+
async function parseResponse(response) {
|
|
7
|
+
const text = await response.text();
|
|
8
|
+
let data = text;
|
|
9
|
+
if (text) {
|
|
10
|
+
try {
|
|
11
|
+
data = JSON.parse(text);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
data = text;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
ok: response.ok,
|
|
19
|
+
status: response.status,
|
|
20
|
+
data,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseScalarValue(value, type) {
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
if (type === 'boolean') {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
if (type === 'integer' || type === 'number') {
|
|
31
|
+
return Number(value);
|
|
32
|
+
}
|
|
33
|
+
if (typeof value !== 'string') {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(trimmed);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function hasParameterValue(flags, parameter) {
|
|
48
|
+
const value = flags[parameter.flagName];
|
|
49
|
+
if (parameter.type === 'boolean') {
|
|
50
|
+
return value !== undefined;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.length > 0;
|
|
54
|
+
}
|
|
55
|
+
return value !== undefined && value !== '';
|
|
56
|
+
}
|
|
57
|
+
function listProvidedBodyFlags(flags, parameters) {
|
|
58
|
+
return parameters
|
|
59
|
+
.filter((parameter) => hasParameterValue(flags, parameter))
|
|
60
|
+
.map((parameter) => `--${parameter.flagName}`);
|
|
61
|
+
}
|
|
62
|
+
export async function parseBody(flags, operation) {
|
|
63
|
+
const inlineBody = flags.body;
|
|
64
|
+
const bodyFile = flags['body-file'];
|
|
65
|
+
const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
|
|
66
|
+
const hasBodyFlags = bodyParameters.some((parameter) => hasParameterValue(flags, parameter));
|
|
67
|
+
if ((inlineBody || bodyFile) && hasBodyFlags) {
|
|
68
|
+
const providedBodyFlags = listProvidedBodyFlags(flags, bodyParameters);
|
|
69
|
+
const rawBodyInput = inlineBody ? '--body' : '--body-file';
|
|
70
|
+
throw new Error(`Conflicting request body inputs: received ${rawBodyInput} together with body field flags (${providedBodyFlags.join(', ')}). Use either body field flags or --body/--body-file.`);
|
|
71
|
+
}
|
|
72
|
+
if (inlineBody) {
|
|
73
|
+
return JSON.parse(inlineBody);
|
|
74
|
+
}
|
|
75
|
+
if (bodyFile) {
|
|
76
|
+
return fs.readFile(bodyFile, 'utf8').then((content) => JSON.parse(content));
|
|
77
|
+
}
|
|
78
|
+
if (!bodyParameters.length) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const body = {};
|
|
82
|
+
for (const parameter of bodyParameters) {
|
|
83
|
+
const rawValue = flags[parameter.flagName];
|
|
84
|
+
const value = parameter.isArray && !parameter.jsonEncoded
|
|
85
|
+
? (Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined)
|
|
86
|
+
: parseScalarValue(rawValue, parameter.type);
|
|
87
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
88
|
+
throw new Error(`Missing required body field --${parameter.flagName}`);
|
|
89
|
+
}
|
|
90
|
+
if (value === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
body[parameter.name] = value;
|
|
94
|
+
}
|
|
95
|
+
if (Object.keys(body).length > 0) {
|
|
96
|
+
return body;
|
|
97
|
+
}
|
|
98
|
+
if (operation.hasBody && operation.bodyRequired) {
|
|
99
|
+
throw new Error('Missing request body. Use body field flags or --body/--body-file.');
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
export async function executeApiRequest(options) {
|
|
104
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
105
|
+
const headers = new Headers();
|
|
106
|
+
if (token) {
|
|
107
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
108
|
+
}
|
|
109
|
+
if (options.role) {
|
|
110
|
+
headers.set('x-role', options.role);
|
|
111
|
+
}
|
|
112
|
+
const query = new URLSearchParams();
|
|
113
|
+
let requestPath = options.operation.pathTemplate;
|
|
114
|
+
for (const parameter of options.operation.parameters) {
|
|
115
|
+
if (parameter.in === 'body') {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const rawValue = options.flags[parameter.flagName];
|
|
119
|
+
const value = parameter.isArray
|
|
120
|
+
? (Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined)
|
|
121
|
+
: parseScalarValue(rawValue, parameter.type);
|
|
122
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
123
|
+
throw new Error(`Missing required parameter --${parameter.flagName}`);
|
|
124
|
+
}
|
|
125
|
+
if (value === undefined) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (parameter.in === 'path') {
|
|
129
|
+
requestPath = requestPath.replace(`{${parameter.name}}`, encodeURIComponent(String(value)));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (parameter.in === 'query') {
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
value.forEach((item) => query.append(parameter.name, String(parseScalarValue(item, parameter.type))));
|
|
135
|
+
}
|
|
136
|
+
else if (typeof value === 'object') {
|
|
137
|
+
query.set(parameter.name, JSON.stringify(value));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
query.set(parameter.name, String(value));
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (parameter.in === 'header') {
|
|
145
|
+
headers.set(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const body = await parseBody(options.flags, options.operation);
|
|
150
|
+
if (body !== undefined) {
|
|
151
|
+
headers.set('content-type', 'application/json');
|
|
152
|
+
}
|
|
153
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
|
|
154
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
155
|
+
const response = await fetch(url, {
|
|
156
|
+
method: options.operation.method.toUpperCase(),
|
|
157
|
+
headers,
|
|
158
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
159
|
+
});
|
|
160
|
+
return parseResponse(response);
|
|
161
|
+
}
|
|
162
|
+
export async function executeRawApiRequest(options) {
|
|
163
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
164
|
+
const headers = new Headers();
|
|
165
|
+
if (token) {
|
|
166
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
167
|
+
}
|
|
168
|
+
if (options.role) {
|
|
169
|
+
headers.set('x-role', options.role);
|
|
170
|
+
}
|
|
171
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
172
|
+
if (value === undefined || value === null || value === '') {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
headers.set(name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
176
|
+
}
|
|
177
|
+
if (options.body !== undefined) {
|
|
178
|
+
headers.set('content-type', 'application/json');
|
|
179
|
+
}
|
|
180
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${options.path}`);
|
|
181
|
+
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
182
|
+
if (value === undefined) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
for (const item of value) {
|
|
187
|
+
url.searchParams.append(key, typeof item === 'object' ? JSON.stringify(item) : String(item));
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
192
|
+
}
|
|
193
|
+
const response = await fetch(url, {
|
|
194
|
+
method: options.method.toUpperCase(),
|
|
195
|
+
headers,
|
|
196
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
197
|
+
});
|
|
198
|
+
return parseResponse(response);
|
|
199
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
currentEnv: 'default',
|
|
6
|
+
envs: {},
|
|
7
|
+
};
|
|
8
|
+
function getConfigFile(options = {}) {
|
|
9
|
+
return path.join(resolveCliHomeDir(options.scope), 'config.json');
|
|
10
|
+
}
|
|
11
|
+
export async function loadAuthConfig(options = {}) {
|
|
12
|
+
try {
|
|
13
|
+
const content = await fs.readFile(getConfigFile(options), 'utf8');
|
|
14
|
+
const parsed = JSON.parse(content);
|
|
15
|
+
return {
|
|
16
|
+
currentEnv: parsed.currentEnv || 'default',
|
|
17
|
+
envs: parsed.envs || {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (_error) {
|
|
21
|
+
return DEFAULT_CONFIG;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function saveAuthConfig(config, options = {}) {
|
|
25
|
+
const filePath = getConfigFile(options);
|
|
26
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
27
|
+
await fs.writeFile(filePath, JSON.stringify(config, null, 2));
|
|
28
|
+
}
|
|
29
|
+
export async function listEnvs(options = {}) {
|
|
30
|
+
const config = await loadAuthConfig(options);
|
|
31
|
+
return {
|
|
32
|
+
currentEnv: config.currentEnv || 'default',
|
|
33
|
+
envs: config.envs,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function getCurrentEnvName(options = {}) {
|
|
37
|
+
const config = await loadAuthConfig(options);
|
|
38
|
+
return config.currentEnv || 'default';
|
|
39
|
+
}
|
|
40
|
+
export async function setCurrentEnv(envName, options = {}) {
|
|
41
|
+
const config = await loadAuthConfig(options);
|
|
42
|
+
if (!config.envs[envName]) {
|
|
43
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
44
|
+
}
|
|
45
|
+
config.currentEnv = envName;
|
|
46
|
+
await saveAuthConfig(config, options);
|
|
47
|
+
}
|
|
48
|
+
export async function getEnv(envName, options = {}) {
|
|
49
|
+
const config = await loadAuthConfig(options);
|
|
50
|
+
const resolved = envName || config.currentEnv || 'default';
|
|
51
|
+
return config.envs[resolved];
|
|
52
|
+
}
|
|
53
|
+
function areAuthConfigsEquivalent(left, right) {
|
|
54
|
+
if (!left && !right) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (!left || !right || left.type !== right.type) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (left.type === 'token' && right.type === 'token') {
|
|
61
|
+
return left.accessToken === right.accessToken;
|
|
62
|
+
}
|
|
63
|
+
if (left.type === 'oauth' && right.type === 'oauth') {
|
|
64
|
+
return (left.accessToken === right.accessToken &&
|
|
65
|
+
left.refreshToken === right.refreshToken &&
|
|
66
|
+
left.expiresAt === right.expiresAt &&
|
|
67
|
+
left.scope === right.scope &&
|
|
68
|
+
left.issuer === right.issuer &&
|
|
69
|
+
left.clientId === right.clientId &&
|
|
70
|
+
left.resource === right.resource);
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
async function writeEnv(envName, updater, options = {}) {
|
|
75
|
+
const config = await loadAuthConfig(options);
|
|
76
|
+
const previous = config.envs[envName];
|
|
77
|
+
config.envs[envName] = updater(previous);
|
|
78
|
+
config.currentEnv = envName;
|
|
79
|
+
await saveAuthConfig(config, options);
|
|
80
|
+
}
|
|
81
|
+
export async function upsertEnv(envName, baseUrl, accessToken, options = {}) {
|
|
82
|
+
await writeEnv(envName, (previous) => {
|
|
83
|
+
const baseUrlChanged = previous?.baseUrl !== baseUrl;
|
|
84
|
+
const nextAuth = accessToken
|
|
85
|
+
? {
|
|
86
|
+
type: 'token',
|
|
87
|
+
accessToken,
|
|
88
|
+
}
|
|
89
|
+
: baseUrlChanged || previous?.auth?.type === 'token'
|
|
90
|
+
? undefined
|
|
91
|
+
: previous?.auth;
|
|
92
|
+
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
93
|
+
return {
|
|
94
|
+
...previous,
|
|
95
|
+
baseUrl,
|
|
96
|
+
auth: nextAuth,
|
|
97
|
+
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
98
|
+
};
|
|
99
|
+
}, options);
|
|
100
|
+
}
|
|
101
|
+
export async function updateEnvConnection(envName, updates, options = {}) {
|
|
102
|
+
await writeEnv(envName, (previous) => {
|
|
103
|
+
const nextBaseUrl = updates.baseUrl ?? previous?.baseUrl;
|
|
104
|
+
const baseUrlChanged = previous?.baseUrl !== nextBaseUrl;
|
|
105
|
+
const nextAuth = updates.accessToken
|
|
106
|
+
? {
|
|
107
|
+
type: 'token',
|
|
108
|
+
accessToken: updates.accessToken,
|
|
109
|
+
}
|
|
110
|
+
: baseUrlChanged || previous?.auth?.type === 'token'
|
|
111
|
+
? undefined
|
|
112
|
+
: previous?.auth;
|
|
113
|
+
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
114
|
+
return {
|
|
115
|
+
...previous,
|
|
116
|
+
...(nextBaseUrl !== undefined ? { baseUrl: nextBaseUrl } : {}),
|
|
117
|
+
auth: nextAuth,
|
|
118
|
+
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
119
|
+
};
|
|
120
|
+
}, options);
|
|
121
|
+
}
|
|
122
|
+
export async function setEnvOauthSession(envName, auth, options = {}) {
|
|
123
|
+
await writeEnv(envName, (previous) => ({
|
|
124
|
+
...previous,
|
|
125
|
+
auth,
|
|
126
|
+
runtime: options.preserveRuntime ? previous?.runtime : undefined,
|
|
127
|
+
}), options);
|
|
128
|
+
}
|
|
129
|
+
export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
130
|
+
const config = await loadAuthConfig(options);
|
|
131
|
+
const current = config.envs[envName] ?? {};
|
|
132
|
+
config.envs[envName] = {
|
|
133
|
+
...current,
|
|
134
|
+
runtime,
|
|
135
|
+
};
|
|
136
|
+
config.currentEnv = envName;
|
|
137
|
+
await saveAuthConfig(config, options);
|
|
138
|
+
}
|
|
139
|
+
export async function removeEnv(envName, options = {}) {
|
|
140
|
+
const config = await loadAuthConfig(options);
|
|
141
|
+
if (!config.envs[envName]) {
|
|
142
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
143
|
+
}
|
|
144
|
+
delete config.envs[envName];
|
|
145
|
+
if (config.currentEnv === envName) {
|
|
146
|
+
const nextEnv = Object.keys(config.envs).sort()[0];
|
|
147
|
+
config.currentEnv = nextEnv ?? 'default';
|
|
148
|
+
}
|
|
149
|
+
await saveAuthConfig(config, options);
|
|
150
|
+
return {
|
|
151
|
+
removed: envName,
|
|
152
|
+
currentEnv: config.currentEnv || 'default',
|
|
153
|
+
hasEnvs: Object.keys(config.envs).length > 0,
|
|
154
|
+
};
|
|
155
|
+
}
|