@nocobase/cli 2.1.0-alpha.4 → 2.1.0-alpha.40
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 +393 -19
- package/README.zh-CN.md +343 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +135 -0
- package/bin/session-env.js +39 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/app/down.js +301 -0
- package/dist/commands/app/logs.js +114 -0
- package/dist/commands/app/restart.js +158 -0
- package/dist/commands/app/start.js +305 -0
- package/dist/commands/app/stop.js +115 -0
- package/dist/commands/app/upgrade.js +636 -0
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- package/{src/cli.js → dist/commands/build.js} +4 -11
- package/dist/commands/config/delete.js +30 -0
- package/dist/commands/config/get.js +29 -0
- package/dist/commands/config/index.js +20 -0
- package/dist/commands/config/list.js +29 -0
- package/dist/commands/config/set.js +35 -0
- package/dist/commands/db/check.js +240 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +96 -0
- package/dist/commands/db/start.js +71 -0
- package/dist/commands/db/stop.js +71 -0
- package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
- package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
- package/dist/commands/download.js +13 -0
- package/dist/commands/env/add.js +366 -0
- package/dist/commands/env/auth.js +130 -0
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/info.js +157 -0
- package/dist/commands/env/list.js +44 -0
- package/dist/commands/env/remove.js +84 -0
- package/dist/commands/env/shared.js +158 -0
- package/dist/commands/env/status.js +90 -0
- package/dist/commands/env/update.js +74 -0
- package/dist/commands/env/use.js +38 -0
- package/dist/commands/examples/prompts-stages.js +150 -0
- package/dist/commands/examples/prompts-test.js +181 -0
- package/dist/commands/init.js +1092 -0
- package/dist/commands/install.js +2378 -0
- package/dist/commands/license/activate.js +360 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +108 -0
- package/dist/commands/license/id.js +70 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +115 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +64 -0
- package/dist/commands/license/plugins/shared.js +325 -0
- package/dist/commands/license/plugins/sync.js +285 -0
- package/dist/commands/license/shared.js +423 -0
- package/dist/commands/license/status.js +64 -0
- package/dist/commands/logs.js +12 -0
- package/dist/commands/plugin/disable.js +86 -0
- package/dist/commands/plugin/enable.js +86 -0
- package/dist/commands/plugin/list.js +82 -0
- package/dist/commands/pm/disable.js +12 -0
- package/dist/commands/pm/enable.js +12 -0
- package/dist/commands/pm/list.js +12 -0
- package/dist/commands/restart.js +12 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +95 -0
- package/dist/commands/session/id.js +24 -0
- package/dist/commands/session/remove.js +57 -0
- package/dist/commands/session/setup.js +62 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +80 -0
- package/dist/commands/skills/remove.js +80 -0
- package/dist/commands/skills/update.js +87 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +182 -0
- package/dist/commands/source/download.js +880 -0
- package/dist/commands/source/publish.js +109 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/commands/source/test.js +477 -0
- package/dist/commands/start.js +12 -0
- package/dist/commands/stop.js +12 -0
- package/dist/commands/test.js +12 -0
- package/dist/commands/upgrade.js +12 -0
- package/dist/commands/v1.js +210 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +23 -0
- package/dist/lib/api-client.js +329 -0
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +316 -0
- package/dist/lib/app-runtime.js +180 -0
- package/dist/lib/auth-store.js +368 -0
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +403 -0
- package/dist/lib/build-config.js +18 -0
- package/dist/lib/builtin-db.js +86 -0
- package/dist/lib/cli-config.js +176 -0
- package/dist/lib/cli-home.js +47 -0
- package/dist/lib/cli-locale.js +129 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/db-connection-check.js +158 -0
- package/dist/lib/docker-env-file.js +52 -0
- package/dist/lib/docker-image.js +37 -0
- package/dist/lib/env-auth.js +873 -0
- package/dist/lib/env-config.js +94 -0
- package/dist/lib/env-guard.js +62 -0
- package/dist/lib/generated-command.js +186 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +244 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/plugin-storage.js +64 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog-core.js +185 -0
- package/dist/lib/prompt-catalog-terminal.js +375 -0
- package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
- package/dist/lib/prompt-validators.js +240 -0
- package/dist/lib/prompt-web-ui.js +2103 -0
- package/dist/lib/resource-command.js +357 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +275 -0
- package/dist/lib/runtime-env-vars.js +32 -0
- package/dist/lib/runtime-generator.js +498 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +301 -0
- package/dist/lib/session-id.js +17 -0
- package/dist/lib/session-integration.js +703 -0
- package/dist/lib/session-store.js +118 -0
- package/dist/lib/skills-manager.js +360 -0
- package/dist/lib/source-publish.js +306 -0
- package/dist/lib/source-registry.js +188 -0
- package/dist/lib/startup-update.js +285 -0
- package/dist/lib/ui.js +155 -0
- package/dist/locale/en-US.json +344 -0
- package/dist/locale/zh-CN.json +344 -0
- package/dist/post-processors/data-modeling.js +84 -0
- package/dist/post-processors/data-source-manager.js +138 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +388 -0
- package/package.json +100 -26
- package/LICENSE +0 -661
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- 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/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/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- 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.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- 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/plugin.tsx.tpl +0 -10
- 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,210 @@
|
|
|
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 { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerNocoBaseCommand, runLocalNocoBaseCommand, } from '../lib/app-runtime.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../lib/env-guard.js';
|
|
12
|
+
import { announceTargetEnv } from '../lib/ui.js';
|
|
13
|
+
const SILENT_LIKE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--silent']);
|
|
14
|
+
const SILENT_RUNTIME_ENV_VARS = {
|
|
15
|
+
LOGGER_SILENT: 'true',
|
|
16
|
+
NODE_NO_WARNINGS: '1',
|
|
17
|
+
};
|
|
18
|
+
const SILENT_STDERR_FILTERS = [
|
|
19
|
+
/^\(node:\d+\) \[DEP0040\] DeprecationWarning: The `punycode` module is deprecated\..*$/,
|
|
20
|
+
/^\(Use `node --trace-deprecation .*$/,
|
|
21
|
+
/^About to overwrite ArrayBuffer\.prototype properties /,
|
|
22
|
+
];
|
|
23
|
+
function parseBridgeArgv(argv) {
|
|
24
|
+
let requestedEnv;
|
|
25
|
+
let yes = false;
|
|
26
|
+
const passthroughArgs = [];
|
|
27
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
28
|
+
const token = argv[index];
|
|
29
|
+
if (token === '--') {
|
|
30
|
+
passthroughArgs.push(...argv.slice(index + 1));
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
if (token === '--env') {
|
|
34
|
+
const value = argv[index + 1];
|
|
35
|
+
if (!value || value === '--') {
|
|
36
|
+
throw new Error('Missing value for `--env`.');
|
|
37
|
+
}
|
|
38
|
+
requestedEnv = value.trim() || undefined;
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (token.startsWith('--env=')) {
|
|
43
|
+
requestedEnv = token.slice('--env='.length).trim() || undefined;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (token === '-e') {
|
|
47
|
+
const value = argv[index + 1];
|
|
48
|
+
if (!value || value === '--') {
|
|
49
|
+
throw new Error('Missing value for `-e`.');
|
|
50
|
+
}
|
|
51
|
+
requestedEnv = value.trim() || undefined;
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token.startsWith('-e') && token.length > 2) {
|
|
56
|
+
requestedEnv = token.slice(2).trim() || undefined;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (token === '--yes') {
|
|
60
|
+
yes = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
passthroughArgs.push(...argv.slice(index));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
requestedEnv,
|
|
68
|
+
yes,
|
|
69
|
+
passthroughArgs,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function formatHttpEnvError(envName) {
|
|
73
|
+
return [
|
|
74
|
+
`Can't run \`nb v1\` for "${envName}" yet.`,
|
|
75
|
+
'This env only has an API connection, so the v1 bridge is not available here.',
|
|
76
|
+
'Use a local or Docker env instead.',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
function formatSshEnvError(envName) {
|
|
80
|
+
return [
|
|
81
|
+
`Can't run \`nb v1\` for "${envName}" yet.`,
|
|
82
|
+
'SSH env support is reserved but not implemented yet.',
|
|
83
|
+
'Use a local or Docker env right now.',
|
|
84
|
+
].join('\n');
|
|
85
|
+
}
|
|
86
|
+
function hasSilentLikePassthrough(args) {
|
|
87
|
+
return args.some((arg) => SILENT_LIKE_PASSTHROUGH_FLAGS.has(arg));
|
|
88
|
+
}
|
|
89
|
+
function shouldFilterSilentStderrLine(line) {
|
|
90
|
+
const normalized = line.replace(/\r$/, '');
|
|
91
|
+
return SILENT_STDERR_FILTERS.some((pattern) => pattern.test(normalized));
|
|
92
|
+
}
|
|
93
|
+
function createSilentBridgeOptions() {
|
|
94
|
+
let pendingStderr = '';
|
|
95
|
+
const flushBufferedStderr = (force) => {
|
|
96
|
+
while (true) {
|
|
97
|
+
const newlineIndex = pendingStderr.indexOf('\n');
|
|
98
|
+
if (newlineIndex === -1) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const line = pendingStderr.slice(0, newlineIndex);
|
|
102
|
+
pendingStderr = pendingStderr.slice(newlineIndex + 1);
|
|
103
|
+
if (!shouldFilterSilentStderrLine(line)) {
|
|
104
|
+
process.stderr.write(`${line}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (force && pendingStderr) {
|
|
108
|
+
if (!shouldFilterSilentStderrLine(pendingStderr)) {
|
|
109
|
+
process.stderr.write(pendingStderr);
|
|
110
|
+
}
|
|
111
|
+
pendingStderr = '';
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
commandOptions: {
|
|
116
|
+
stdio: 'pipe',
|
|
117
|
+
env: {
|
|
118
|
+
...SILENT_RUNTIME_ENV_VARS,
|
|
119
|
+
},
|
|
120
|
+
onStdout: (chunk) => {
|
|
121
|
+
process.stdout.write(chunk);
|
|
122
|
+
},
|
|
123
|
+
onStderr: (chunk) => {
|
|
124
|
+
pendingStderr += chunk;
|
|
125
|
+
flushBufferedStderr(false);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
flush: () => {
|
|
129
|
+
flushBufferedStderr(true);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export default class V1 extends Command {
|
|
134
|
+
static hidden = true;
|
|
135
|
+
static strict = false;
|
|
136
|
+
static summary = 'Forward commands to the selected env through the v1 bridge';
|
|
137
|
+
static description = 'Forward v1-compatible commands to the selected env. Defaults to the current env when `--env` is omitted. Local envs run `nocobase-v1`, and Docker envs run inside the saved app container. Bridge flags (`--env`, `--yes`) must appear before the forwarded command. Use `--` when the forwarded command needs the same flag names.';
|
|
138
|
+
static examples = [
|
|
139
|
+
'<%= config.bin %> <%= command.id %> build',
|
|
140
|
+
'<%= config.bin %> <%= command.id %> --env local pm list',
|
|
141
|
+
'<%= config.bin %> <%= command.id %> --env docker-local -- pm enable @nocobase/plugin-sample --yes',
|
|
142
|
+
];
|
|
143
|
+
async run() {
|
|
144
|
+
const originalArgv = [...this.argv];
|
|
145
|
+
await this.parse({ strict: false, flags: {}, args: {} }, []);
|
|
146
|
+
this.argv = originalArgv;
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = parseBridgeArgv(this.argv);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
this.error(message);
|
|
154
|
+
}
|
|
155
|
+
const { requestedEnv, yes, passthroughArgs } = parsed;
|
|
156
|
+
if (passthroughArgs.length === 0) {
|
|
157
|
+
this.error('Pass at least one v1 command to forward.');
|
|
158
|
+
}
|
|
159
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
160
|
+
if (explicitEnvSelection) {
|
|
161
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
162
|
+
command: this,
|
|
163
|
+
requestedEnv,
|
|
164
|
+
yes,
|
|
165
|
+
});
|
|
166
|
+
if (!confirmed) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
171
|
+
if (!runtime) {
|
|
172
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
173
|
+
}
|
|
174
|
+
const silentLike = hasSilentLikePassthrough(passthroughArgs);
|
|
175
|
+
const silentBridge = silentLike ? createSilentBridgeOptions() : undefined;
|
|
176
|
+
if (!silentLike) {
|
|
177
|
+
announceTargetEnv(runtime.envName);
|
|
178
|
+
}
|
|
179
|
+
if (runtime.kind === 'local') {
|
|
180
|
+
try {
|
|
181
|
+
await runLocalNocoBaseCommand(runtime, passthroughArgs, silentBridge?.commandOptions);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
this.error(message);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
silentBridge?.flush();
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (runtime.kind === 'docker') {
|
|
193
|
+
try {
|
|
194
|
+
await runDockerNocoBaseCommand(runtime.containerName, passthroughArgs, silentBridge?.commandOptions);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
+
this.error(message);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
silentBridge?.flush();
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (runtime.kind === 'http') {
|
|
206
|
+
this.error(formatHttpEnvError(runtime.envName));
|
|
207
|
+
}
|
|
208
|
+
this.error(formatSshEnvError(runtime.envName));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
10
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
11
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
12
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
};
|
|
17
|
+
import { Command, loadHelpClass } from '@oclif/core';
|
|
18
|
+
import { dirname, join, relative } from 'node:path';
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
|
+
import { collectCommandModulePaths, commandRelativePathToRegistryKey, } from "../lib/command-discovery.js";
|
|
21
|
+
import { getCurrentEnvName, getEnv } from "../lib/auth-store.js";
|
|
22
|
+
import { createGeneratedFlags, GeneratedApiCommand } from "../lib/generated-command.js";
|
|
23
|
+
import { toKebabCase } from "../lib/naming.js";
|
|
24
|
+
import { loadRuntimeSync } from "../lib/runtime-store.js";
|
|
25
|
+
const registryFilePath = fileURLToPath(import.meta.url);
|
|
26
|
+
const commandsRoot = join(dirname(registryFilePath), '../commands');
|
|
27
|
+
const commandModuleExtension = registryFilePath.endsWith('.ts') ? '.ts' : '.js';
|
|
28
|
+
async function loadCommandsFromDirectory() {
|
|
29
|
+
const absolutePaths = await collectCommandModulePaths(commandsRoot, commandModuleExtension);
|
|
30
|
+
const entries = await Promise.all(absolutePaths.map(async (absolutePath) => {
|
|
31
|
+
const rel = relative(commandsRoot, absolutePath).replace(/\\/g, '/');
|
|
32
|
+
const key = commandRelativePathToRegistryKey(rel);
|
|
33
|
+
const mod = await import(__rewriteRelativeImportExtension(pathToFileURL(absolutePath).href));
|
|
34
|
+
return [key, mod.default];
|
|
35
|
+
}));
|
|
36
|
+
return Object.fromEntries(entries);
|
|
37
|
+
}
|
|
38
|
+
function readEnvName(argv) {
|
|
39
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
40
|
+
const token = argv[index];
|
|
41
|
+
if (token === '--env') {
|
|
42
|
+
return argv[index + 1];
|
|
43
|
+
}
|
|
44
|
+
if (token === '-e') {
|
|
45
|
+
return argv[index + 1];
|
|
46
|
+
}
|
|
47
|
+
if (token.startsWith('--env=')) {
|
|
48
|
+
return token.slice('--env='.length);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function createRuntimeCommand(operation) {
|
|
54
|
+
return class RuntimeCommand extends GeneratedApiCommand {
|
|
55
|
+
static summary = operation.summary;
|
|
56
|
+
static description = operation.description;
|
|
57
|
+
static examples = operation.examples;
|
|
58
|
+
static flags = createGeneratedFlags(operation);
|
|
59
|
+
static operation = operation;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createRuntimeIndexCommand(commandId, metadata) {
|
|
63
|
+
const summary = metadata.summary || `Work with ${commandId}`;
|
|
64
|
+
const description = metadata.description && metadata.description !== summary ? metadata.description : undefined;
|
|
65
|
+
return class RuntimeIndexCommand extends Command {
|
|
66
|
+
static summary = summary;
|
|
67
|
+
static description = description;
|
|
68
|
+
async run() {
|
|
69
|
+
await this.parse(RuntimeIndexCommand);
|
|
70
|
+
const Help = await loadHelpClass(this.config);
|
|
71
|
+
await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
|
|
72
|
+
this.id ?? commandId.replaceAll(' ', ':'),
|
|
73
|
+
...this.argv,
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function getRuntimeTopicEntries(operation) {
|
|
79
|
+
const commandSegments = operation.commandId.split(' ');
|
|
80
|
+
const topLevelCommandId = commandSegments[0];
|
|
81
|
+
const modulePrefix = toKebabCase(operation.moduleDisplayName || operation.moduleName || '');
|
|
82
|
+
const isTopLevelResource = Boolean(topLevelCommandId && modulePrefix && topLevelCommandId !== modulePrefix);
|
|
83
|
+
const entries = [];
|
|
84
|
+
if (!topLevelCommandId) {
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
if (isTopLevelResource) {
|
|
88
|
+
entries.push([
|
|
89
|
+
topLevelCommandId,
|
|
90
|
+
{
|
|
91
|
+
summary: operation.resourceDescription || operation.resourceDisplayName,
|
|
92
|
+
description: operation.resourceDescription,
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
return entries;
|
|
96
|
+
}
|
|
97
|
+
entries.push([
|
|
98
|
+
topLevelCommandId,
|
|
99
|
+
{
|
|
100
|
+
summary: operation.moduleDescription || operation.moduleDisplayName || operation.moduleName,
|
|
101
|
+
description: operation.moduleDescription,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
const resourceCommandId = commandSegments.slice(0, 2).join(' ');
|
|
105
|
+
if (commandSegments[1]) {
|
|
106
|
+
entries.push([
|
|
107
|
+
resourceCommandId,
|
|
108
|
+
{
|
|
109
|
+
summary: operation.resourceDescription || operation.resourceDisplayName,
|
|
110
|
+
description: operation.resourceDescription,
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
const registry = {
|
|
117
|
+
...(await loadCommandsFromDirectory()),
|
|
118
|
+
};
|
|
119
|
+
const envName = readEnvName(process.argv.slice(2)) ?? (await getCurrentEnvName());
|
|
120
|
+
const env = await getEnv(envName);
|
|
121
|
+
const runtime = loadRuntimeSync(env?.runtime?.version);
|
|
122
|
+
for (const operation of runtime?.commands ?? []) {
|
|
123
|
+
const commandSegments = operation.commandId.split(' ');
|
|
124
|
+
const commandKey = commandSegments.join(':');
|
|
125
|
+
registry[`api:${commandKey}`] = createRuntimeCommand(operation);
|
|
126
|
+
for (const [topicCommandId, metadata] of getRuntimeTopicEntries(operation)) {
|
|
127
|
+
const topicKey = `api:${topicCommandId.split(' ').join(':')}`;
|
|
128
|
+
if (!registry[topicKey]) {
|
|
129
|
+
registry[topicKey] = createRuntimeIndexCommand(`api ${topicCommandId}`, metadata);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export default registry;
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { Help } from '@oclif/core';
|
|
10
|
+
export function isTopicIndexCommand(commandId, topics) {
|
|
11
|
+
if (!commandId) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return topics.some((topic) => topic.name.startsWith(`${commandId}:`));
|
|
15
|
+
}
|
|
16
|
+
export default class RuntimeHelp extends Help {
|
|
17
|
+
get sortedCommands() {
|
|
18
|
+
return super.sortedCommands.filter((command) => !isTopicIndexCommand(command.id, this.config.topics));
|
|
19
|
+
}
|
|
20
|
+
get sortedTopics() {
|
|
21
|
+
return super.sortedTopics.filter((topic) => !topic.hidden);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* This file is part of the NocoBase (R) project.
|
|
11
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
12
|
+
* Authors: NocoBase Team.
|
|
13
|
+
*
|
|
14
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
15
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
|
+
*/
|
|
17
|
+
import { createWriteStream } from 'node:fs';
|
|
18
|
+
import { promises as fs } from 'node:fs';
|
|
19
|
+
import { basename, dirname } from 'node:path';
|
|
20
|
+
import { Readable } from 'node:stream';
|
|
21
|
+
import { pipeline } from 'node:stream/promises';
|
|
22
|
+
import { resolveServerRequestTarget } from './env-auth.js';
|
|
23
|
+
import { fetchWithPreservedAuthRedirect } from './http-request.js';
|
|
24
|
+
const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
|
|
25
|
+
const CLI_REQUEST_SOURCE_VALUE = 'cli';
|
|
26
|
+
function stripUtf8Bom(text) {
|
|
27
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
28
|
+
}
|
|
29
|
+
function parseJsonInput(raw, flagName) {
|
|
30
|
+
const content = stripUtf8Bom(raw);
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`Invalid JSON for --${flagName}: ${error?.message ?? 'parse failed'}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function normalizeBaseUrl(baseUrl) {
|
|
39
|
+
return baseUrl.replace(/\/+$/, '');
|
|
40
|
+
}
|
|
41
|
+
async function parseResponse(response) {
|
|
42
|
+
const text = await response.text();
|
|
43
|
+
let data = text;
|
|
44
|
+
if (text) {
|
|
45
|
+
try {
|
|
46
|
+
data = JSON.parse(text);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
data = text;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ok: response.ok,
|
|
54
|
+
status: response.status,
|
|
55
|
+
data,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function parseBinaryResponse(response, outputPath) {
|
|
59
|
+
if (response.ok && response.body) {
|
|
60
|
+
await fs.mkdir(dirname(outputPath), { recursive: true }).catch(() => undefined);
|
|
61
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath));
|
|
62
|
+
return {
|
|
63
|
+
ok: response.ok,
|
|
64
|
+
status: response.status,
|
|
65
|
+
data: {
|
|
66
|
+
output: outputPath,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return parseResponse(response);
|
|
71
|
+
}
|
|
72
|
+
function parseScalarValue(value, type) {
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (type === 'boolean') {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
if (type === 'integer' || type === 'number') {
|
|
80
|
+
return Number(value);
|
|
81
|
+
}
|
|
82
|
+
if (typeof value !== 'string') {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(trimmed);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function hasParameterValue(flags, parameter) {
|
|
97
|
+
const value = flags[parameter.flagName];
|
|
98
|
+
if (parameter.type === 'boolean') {
|
|
99
|
+
return value !== undefined;
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
return value.length > 0;
|
|
103
|
+
}
|
|
104
|
+
return value !== undefined && value !== '';
|
|
105
|
+
}
|
|
106
|
+
function listProvidedBodyFlags(flags, parameters) {
|
|
107
|
+
return parameters
|
|
108
|
+
.filter((parameter) => hasParameterValue(flags, parameter))
|
|
109
|
+
.map((parameter) => `--${parameter.flagName}`);
|
|
110
|
+
}
|
|
111
|
+
function parseBodyFieldValue(rawValue, parameter) {
|
|
112
|
+
if (rawValue === undefined) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (parameter.isArray && !parameter.jsonEncoded) {
|
|
116
|
+
return Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined;
|
|
117
|
+
}
|
|
118
|
+
if (parameter.jsonEncoded || parameter.type === 'object' || parameter.type === 'array') {
|
|
119
|
+
if (typeof rawValue !== 'string') {
|
|
120
|
+
return rawValue;
|
|
121
|
+
}
|
|
122
|
+
const parsed = parseJsonInput(rawValue, parameter.flagName);
|
|
123
|
+
if (parameter.type === 'array' && !Array.isArray(parsed)) {
|
|
124
|
+
throw new Error(`--${parameter.flagName} must be a JSON array`);
|
|
125
|
+
}
|
|
126
|
+
if (parameter.type === 'object' && (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object')) {
|
|
127
|
+
throw new Error(`--${parameter.flagName} must be a JSON object`);
|
|
128
|
+
}
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
return parseScalarValue(rawValue, parameter.type);
|
|
132
|
+
}
|
|
133
|
+
export async function parseBody(flags, operation) {
|
|
134
|
+
if (operation.requestContentType === 'multipart/form-data') {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const inlineBody = flags.body;
|
|
138
|
+
const bodyFile = flags['body-file'];
|
|
139
|
+
const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
|
|
140
|
+
const hasBodyFlags = bodyParameters.some((parameter) => hasParameterValue(flags, parameter));
|
|
141
|
+
if ((inlineBody || bodyFile) && hasBodyFlags) {
|
|
142
|
+
const providedBodyFlags = listProvidedBodyFlags(flags, bodyParameters);
|
|
143
|
+
const rawBodyInput = inlineBody ? '--body' : '--body-file';
|
|
144
|
+
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.`);
|
|
145
|
+
}
|
|
146
|
+
if (inlineBody) {
|
|
147
|
+
return parseJsonInput(inlineBody, 'body');
|
|
148
|
+
}
|
|
149
|
+
if (bodyFile) {
|
|
150
|
+
return fs.readFile(bodyFile, 'utf8').then((content) => parseJsonInput(content, 'body-file'));
|
|
151
|
+
}
|
|
152
|
+
if (!bodyParameters.length) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const body = {};
|
|
156
|
+
for (const parameter of bodyParameters) {
|
|
157
|
+
const rawValue = flags[parameter.flagName];
|
|
158
|
+
const value = parseBodyFieldValue(rawValue, parameter);
|
|
159
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
160
|
+
throw new Error(`Missing required body field --${parameter.flagName}`);
|
|
161
|
+
}
|
|
162
|
+
if (value === undefined) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
body[parameter.name] = value;
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(body).length > 0) {
|
|
168
|
+
return body;
|
|
169
|
+
}
|
|
170
|
+
if (operation.hasBody && operation.bodyRequired) {
|
|
171
|
+
throw new Error('Missing request body. Use body field flags or --body/--body-file.');
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
async function createMultipartBody(flags, operation) {
|
|
176
|
+
const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
|
|
177
|
+
const formData = new FormData();
|
|
178
|
+
let hasValues = false;
|
|
179
|
+
for (const parameter of bodyParameters) {
|
|
180
|
+
const rawValue = flags[parameter.flagName];
|
|
181
|
+
const hasValue = hasParameterValue(flags, parameter);
|
|
182
|
+
if (parameter.required && !hasValue) {
|
|
183
|
+
throw new Error(`Missing required body field --${parameter.flagName}`);
|
|
184
|
+
}
|
|
185
|
+
if (!hasValue) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (parameter.isFile) {
|
|
189
|
+
const filePath = String(rawValue);
|
|
190
|
+
const content = await fs.readFile(filePath);
|
|
191
|
+
const arrayBuffer = content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
|
|
192
|
+
formData.append(parameter.name, new Blob([arrayBuffer]), basename(filePath));
|
|
193
|
+
hasValues = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const value = parseBodyFieldValue(rawValue, parameter);
|
|
197
|
+
if (value === undefined) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
formData.append(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
201
|
+
hasValues = true;
|
|
202
|
+
}
|
|
203
|
+
if (!hasValues && operation.bodyRequired) {
|
|
204
|
+
throw new Error('Missing multipart request body.');
|
|
205
|
+
}
|
|
206
|
+
return hasValues ? formData : undefined;
|
|
207
|
+
}
|
|
208
|
+
export async function executeApiRequest(options) {
|
|
209
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
210
|
+
const headers = new Headers();
|
|
211
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
212
|
+
if (token) {
|
|
213
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
214
|
+
}
|
|
215
|
+
if (options.role) {
|
|
216
|
+
headers.set('x-role', options.role);
|
|
217
|
+
}
|
|
218
|
+
const query = new URLSearchParams();
|
|
219
|
+
let requestPath = options.operation.pathTemplate;
|
|
220
|
+
for (const parameter of options.operation.parameters) {
|
|
221
|
+
if (parameter.in === 'body') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const rawValue = options.flags[parameter.flagName];
|
|
225
|
+
const value = parameter.isArray
|
|
226
|
+
? (Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined)
|
|
227
|
+
: parseScalarValue(rawValue, parameter.type);
|
|
228
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
229
|
+
throw new Error(`Missing required parameter --${parameter.flagName}`);
|
|
230
|
+
}
|
|
231
|
+
if (value === undefined) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (parameter.in === 'path') {
|
|
235
|
+
requestPath = requestPath.replace(`{${parameter.name}}`, encodeURIComponent(String(value)));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (parameter.in === 'query') {
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
value.forEach((item) => query.append(parameter.name, String(parseScalarValue(item, parameter.type))));
|
|
241
|
+
}
|
|
242
|
+
else if (typeof value === 'object') {
|
|
243
|
+
query.set(parameter.name, JSON.stringify(value));
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
query.set(parameter.name, String(value));
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (parameter.in === 'header') {
|
|
251
|
+
headers.set(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const body = options.operation.requestContentType === 'multipart/form-data'
|
|
256
|
+
? await createMultipartBody(options.flags, options.operation)
|
|
257
|
+
: await parseBody(options.flags, options.operation);
|
|
258
|
+
if (body !== undefined && options.operation.requestContentType !== 'multipart/form-data') {
|
|
259
|
+
headers.set('content-type', 'application/json');
|
|
260
|
+
}
|
|
261
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
|
|
262
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
263
|
+
const response = await fetchWithPreservedAuthRedirect(url.toString(), {
|
|
264
|
+
method: options.operation.method.toUpperCase(),
|
|
265
|
+
headers,
|
|
266
|
+
body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body),
|
|
267
|
+
});
|
|
268
|
+
if (options.operation.responseType === 'binary') {
|
|
269
|
+
const outputPath = options.flags.output;
|
|
270
|
+
if (!outputPath) {
|
|
271
|
+
throw new Error('Missing required output path --output');
|
|
272
|
+
}
|
|
273
|
+
return parseBinaryResponse(response, outputPath);
|
|
274
|
+
}
|
|
275
|
+
return parseResponse(response);
|
|
276
|
+
}
|
|
277
|
+
export async function executeRawApiRequest(options) {
|
|
278
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
279
|
+
const headers = new Headers();
|
|
280
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
281
|
+
if (token) {
|
|
282
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
283
|
+
}
|
|
284
|
+
if (options.role) {
|
|
285
|
+
headers.set('x-role', options.role);
|
|
286
|
+
}
|
|
287
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
288
|
+
if (value === undefined || value === null || value === '') {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
headers.set(name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
292
|
+
}
|
|
293
|
+
if (options.body !== undefined) {
|
|
294
|
+
headers.set('content-type', 'application/json');
|
|
295
|
+
}
|
|
296
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${options.path}`);
|
|
297
|
+
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
298
|
+
if (value === undefined) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (Array.isArray(value)) {
|
|
302
|
+
for (const item of value) {
|
|
303
|
+
url.searchParams.append(key, typeof item === 'object' ? JSON.stringify(item) : String(item));
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
308
|
+
}
|
|
309
|
+
const controller = options.timeoutMs && options.timeoutMs > 0 ? new AbortController() : undefined;
|
|
310
|
+
const timeout = controller
|
|
311
|
+
? setTimeout(() => {
|
|
312
|
+
controller.abort();
|
|
313
|
+
}, options.timeoutMs)
|
|
314
|
+
: undefined;
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetchWithPreservedAuthRedirect(url.toString(), {
|
|
317
|
+
method: options.method.toUpperCase(),
|
|
318
|
+
headers,
|
|
319
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
320
|
+
signal: controller?.signal,
|
|
321
|
+
});
|
|
322
|
+
return parseResponse(response);
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
if (timeout) {
|
|
326
|
+
clearTimeout(timeout);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|