@nocobase/cli 2.1.0-beta.15 → 2.1.0-beta.16
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 +307 -63
- package/README.zh-CN.md +332 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +114 -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/build.js +57 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +81 -0
- package/dist/commands/db/start.js +70 -0
- package/dist/commands/db/stop.js +70 -0
- package/dist/commands/dev.js +149 -0
- package/dist/commands/down.js +193 -0
- package/dist/commands/download.js +743 -0
- package/dist/commands/env/add.js +327 -0
- package/dist/commands/env/auth.js +62 -0
- package/dist/commands/env/list.js +41 -0
- package/dist/commands/env/remove.js +65 -0
- package/dist/commands/env/update.js +73 -0
- package/dist/commands/env/use.js +36 -0
- package/dist/commands/init.js +806 -0
- package/dist/commands/install.js +1840 -0
- package/dist/commands/logs.js +90 -0
- package/dist/commands/pm/disable.js +63 -0
- package/dist/commands/pm/enable.js +63 -0
- package/dist/commands/pm/list.js +54 -0
- package/dist/commands/prompts-stages.js +150 -0
- package/dist/commands/prompts-test.js +181 -0
- package/dist/commands/ps.js +116 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/start.js +211 -0
- package/dist/commands/stop.js +90 -0
- package/dist/commands/upgrade.js +583 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +20 -0
- package/dist/lib/api-client.js +243 -0
- package/dist/lib/app-runtime.js +142 -0
- package/dist/lib/auth-store.js +241 -0
- package/dist/lib/bootstrap.js +387 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +30 -0
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/env-auth.js +872 -0
- package/dist/lib/generated-command.js +142 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog.js +574 -0
- package/dist/lib/prompt-validators.js +185 -0
- package/dist/lib/prompt-web-ui.js +2061 -0
- package/dist/lib/resource-command.js +335 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +123 -0
- package/dist/lib/runtime-generator.js +419 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/ui.js +175 -0
- package/dist/locale/en-US.json +282 -0
- package/dist/locale/zh-CN.json +282 -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 +287 -0
- package/package.json +52 -25
- package/LICENSE +0 -201
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/cli.js +0 -19
- 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/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 -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/models/index.ts +0 -12
- 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,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,20 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
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 { promises as fs } from 'node:fs';
|
|
10
|
+
import { resolveServerRequestTarget } from './env-auth.js';
|
|
11
|
+
const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
|
|
12
|
+
const CLI_REQUEST_SOURCE_VALUE = 'cli';
|
|
13
|
+
function stripUtf8Bom(text) {
|
|
14
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
15
|
+
}
|
|
16
|
+
function parseJsonInput(raw, flagName) {
|
|
17
|
+
const content = stripUtf8Bom(raw);
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`Invalid JSON for --${flagName}: ${error?.message ?? 'parse failed'}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function normalizeBaseUrl(baseUrl) {
|
|
26
|
+
return baseUrl.replace(/\/+$/, '');
|
|
27
|
+
}
|
|
28
|
+
async function parseResponse(response) {
|
|
29
|
+
const text = await response.text();
|
|
30
|
+
let data = text;
|
|
31
|
+
if (text) {
|
|
32
|
+
try {
|
|
33
|
+
data = JSON.parse(text);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
data = text;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
ok: response.ok,
|
|
41
|
+
status: response.status,
|
|
42
|
+
data,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseScalarValue(value, type) {
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (type === 'boolean') {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
if (type === 'integer' || type === 'number') {
|
|
53
|
+
return Number(value);
|
|
54
|
+
}
|
|
55
|
+
if (typeof value !== 'string') {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function hasParameterValue(flags, parameter) {
|
|
70
|
+
const value = flags[parameter.flagName];
|
|
71
|
+
if (parameter.type === 'boolean') {
|
|
72
|
+
return value !== undefined;
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
return value.length > 0;
|
|
76
|
+
}
|
|
77
|
+
return value !== undefined && value !== '';
|
|
78
|
+
}
|
|
79
|
+
function listProvidedBodyFlags(flags, parameters) {
|
|
80
|
+
return parameters
|
|
81
|
+
.filter((parameter) => hasParameterValue(flags, parameter))
|
|
82
|
+
.map((parameter) => `--${parameter.flagName}`);
|
|
83
|
+
}
|
|
84
|
+
function parseBodyFieldValue(rawValue, parameter) {
|
|
85
|
+
if (rawValue === undefined) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
if (parameter.isArray && !parameter.jsonEncoded) {
|
|
89
|
+
return Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined;
|
|
90
|
+
}
|
|
91
|
+
if (parameter.jsonEncoded || parameter.type === 'object' || parameter.type === 'array') {
|
|
92
|
+
if (typeof rawValue !== 'string') {
|
|
93
|
+
return rawValue;
|
|
94
|
+
}
|
|
95
|
+
const parsed = parseJsonInput(rawValue, parameter.flagName);
|
|
96
|
+
if (parameter.type === 'array' && !Array.isArray(parsed)) {
|
|
97
|
+
throw new Error(`--${parameter.flagName} must be a JSON array`);
|
|
98
|
+
}
|
|
99
|
+
if (parameter.type === 'object' && (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object')) {
|
|
100
|
+
throw new Error(`--${parameter.flagName} must be a JSON object`);
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
return parseScalarValue(rawValue, parameter.type);
|
|
105
|
+
}
|
|
106
|
+
export async function parseBody(flags, operation) {
|
|
107
|
+
const inlineBody = flags.body;
|
|
108
|
+
const bodyFile = flags['body-file'];
|
|
109
|
+
const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
|
|
110
|
+
const hasBodyFlags = bodyParameters.some((parameter) => hasParameterValue(flags, parameter));
|
|
111
|
+
if ((inlineBody || bodyFile) && hasBodyFlags) {
|
|
112
|
+
const providedBodyFlags = listProvidedBodyFlags(flags, bodyParameters);
|
|
113
|
+
const rawBodyInput = inlineBody ? '--body' : '--body-file';
|
|
114
|
+
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.`);
|
|
115
|
+
}
|
|
116
|
+
if (inlineBody) {
|
|
117
|
+
return parseJsonInput(inlineBody, 'body');
|
|
118
|
+
}
|
|
119
|
+
if (bodyFile) {
|
|
120
|
+
return fs.readFile(bodyFile, 'utf8').then((content) => parseJsonInput(content, 'body-file'));
|
|
121
|
+
}
|
|
122
|
+
if (!bodyParameters.length) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const body = {};
|
|
126
|
+
for (const parameter of bodyParameters) {
|
|
127
|
+
const rawValue = flags[parameter.flagName];
|
|
128
|
+
const value = parseBodyFieldValue(rawValue, parameter);
|
|
129
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
130
|
+
throw new Error(`Missing required body field --${parameter.flagName}`);
|
|
131
|
+
}
|
|
132
|
+
if (value === undefined) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
body[parameter.name] = value;
|
|
136
|
+
}
|
|
137
|
+
if (Object.keys(body).length > 0) {
|
|
138
|
+
return body;
|
|
139
|
+
}
|
|
140
|
+
if (operation.hasBody && operation.bodyRequired) {
|
|
141
|
+
throw new Error('Missing request body. Use body field flags or --body/--body-file.');
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
export async function executeApiRequest(options) {
|
|
146
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
147
|
+
const headers = new Headers();
|
|
148
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
149
|
+
if (token) {
|
|
150
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
151
|
+
}
|
|
152
|
+
if (options.role) {
|
|
153
|
+
headers.set('x-role', options.role);
|
|
154
|
+
}
|
|
155
|
+
const query = new URLSearchParams();
|
|
156
|
+
let requestPath = options.operation.pathTemplate;
|
|
157
|
+
for (const parameter of options.operation.parameters) {
|
|
158
|
+
if (parameter.in === 'body') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const rawValue = options.flags[parameter.flagName];
|
|
162
|
+
const value = parameter.isArray
|
|
163
|
+
? (Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined)
|
|
164
|
+
: parseScalarValue(rawValue, parameter.type);
|
|
165
|
+
if (parameter.required && (value === undefined || value === '')) {
|
|
166
|
+
throw new Error(`Missing required parameter --${parameter.flagName}`);
|
|
167
|
+
}
|
|
168
|
+
if (value === undefined) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (parameter.in === 'path') {
|
|
172
|
+
requestPath = requestPath.replace(`{${parameter.name}}`, encodeURIComponent(String(value)));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (parameter.in === 'query') {
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
value.forEach((item) => query.append(parameter.name, String(parseScalarValue(item, parameter.type))));
|
|
178
|
+
}
|
|
179
|
+
else if (typeof value === 'object') {
|
|
180
|
+
query.set(parameter.name, JSON.stringify(value));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
query.set(parameter.name, String(value));
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (parameter.in === 'header') {
|
|
188
|
+
headers.set(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const body = await parseBody(options.flags, options.operation);
|
|
193
|
+
if (body !== undefined) {
|
|
194
|
+
headers.set('content-type', 'application/json');
|
|
195
|
+
}
|
|
196
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
|
|
197
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
198
|
+
const response = await fetch(url, {
|
|
199
|
+
method: options.operation.method.toUpperCase(),
|
|
200
|
+
headers,
|
|
201
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
202
|
+
});
|
|
203
|
+
return parseResponse(response);
|
|
204
|
+
}
|
|
205
|
+
export async function executeRawApiRequest(options) {
|
|
206
|
+
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
207
|
+
const headers = new Headers();
|
|
208
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
209
|
+
if (token) {
|
|
210
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
211
|
+
}
|
|
212
|
+
if (options.role) {
|
|
213
|
+
headers.set('x-role', options.role);
|
|
214
|
+
}
|
|
215
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
216
|
+
if (value === undefined || value === null || value === '') {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
headers.set(name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
220
|
+
}
|
|
221
|
+
if (options.body !== undefined) {
|
|
222
|
+
headers.set('content-type', 'application/json');
|
|
223
|
+
}
|
|
224
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${options.path}`);
|
|
225
|
+
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
226
|
+
if (value === undefined) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(value)) {
|
|
230
|
+
for (const item of value) {
|
|
231
|
+
url.searchParams.append(key, typeof item === 'object' ? JSON.stringify(item) : String(item));
|
|
232
|
+
}
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
236
|
+
}
|
|
237
|
+
const response = await fetch(url, {
|
|
238
|
+
method: options.method.toUpperCase(),
|
|
239
|
+
headers,
|
|
240
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
241
|
+
});
|
|
242
|
+
return parseResponse(response);
|
|
243
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
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 path from 'node:path';
|
|
10
|
+
import { getEnv, loadAuthConfig } from './auth-store.js';
|
|
11
|
+
import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from './run-npm.js';
|
|
12
|
+
const DOCKER_APP_WORKDIR = '/app/nocobase';
|
|
13
|
+
function sanitizeDockerResourceName(value) {
|
|
14
|
+
const normalized = value
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9_.-]+/g, '-')
|
|
18
|
+
.replace(/-+/g, '-')
|
|
19
|
+
.replace(/^-+|-+$/g, '');
|
|
20
|
+
return normalized || 'nocobase';
|
|
21
|
+
}
|
|
22
|
+
export function defaultWorkspaceName(cwd = process.cwd()) {
|
|
23
|
+
return sanitizeDockerResourceName(`nb-${path.basename(cwd)}`);
|
|
24
|
+
}
|
|
25
|
+
export function buildDockerAppContainerName(envName, workspaceName) {
|
|
26
|
+
const workspace = workspaceName?.trim() || defaultWorkspaceName();
|
|
27
|
+
return sanitizeDockerResourceName(`${workspace}-${envName}-app`);
|
|
28
|
+
}
|
|
29
|
+
export function buildDockerDbContainerName(envName, dbDialect, workspaceName) {
|
|
30
|
+
const workspace = workspaceName?.trim() || defaultWorkspaceName();
|
|
31
|
+
const dialect = dbDialect.trim() || 'postgres';
|
|
32
|
+
return sanitizeDockerResourceName(`${workspace}-${envName}-${dialect}`);
|
|
33
|
+
}
|
|
34
|
+
function normalizeEnvSource(env) {
|
|
35
|
+
const source = String(env.config.source ?? '').trim();
|
|
36
|
+
if (source === 'docker' || source === 'npm' || source === 'git') {
|
|
37
|
+
return source;
|
|
38
|
+
}
|
|
39
|
+
if (env.config.appRootPath) {
|
|
40
|
+
return 'local';
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
export async function resolveManagedAppRuntime(envName) {
|
|
45
|
+
const config = await loadAuthConfig();
|
|
46
|
+
const env = await getEnv(envName, { config });
|
|
47
|
+
if (!env) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const resolvedName = env.name || envName?.trim() || config.currentEnv || 'default';
|
|
51
|
+
const source = normalizeEnvSource(env);
|
|
52
|
+
const workspaceName = config.name?.trim() || defaultWorkspaceName();
|
|
53
|
+
if (source === 'docker') {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'docker',
|
|
56
|
+
env,
|
|
57
|
+
envName: resolvedName,
|
|
58
|
+
source,
|
|
59
|
+
workspaceName,
|
|
60
|
+
containerName: buildDockerAppContainerName(resolvedName, workspaceName),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (env.config.appRootPath) {
|
|
64
|
+
return {
|
|
65
|
+
kind: 'local',
|
|
66
|
+
env,
|
|
67
|
+
envName: resolvedName,
|
|
68
|
+
source: source === 'git' ? 'git' : source === 'npm' ? 'npm' : 'local',
|
|
69
|
+
projectRoot: env.appRootPath,
|
|
70
|
+
workspaceName,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
kind: 'remote',
|
|
75
|
+
env,
|
|
76
|
+
envName: resolvedName,
|
|
77
|
+
source,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function formatMissingManagedAppEnvMessage(envName) {
|
|
81
|
+
const requested = String(envName ?? '').trim();
|
|
82
|
+
if (requested) {
|
|
83
|
+
return [
|
|
84
|
+
`Env "${requested}" is not configured in this workspace.`,
|
|
85
|
+
`If you want to create a new NocoBase AI environment, run \`nb init --env ${requested}\` first.`,
|
|
86
|
+
].join('\n');
|
|
87
|
+
}
|
|
88
|
+
return 'No NocoBase env is configured yet. Run `nb init` to create one first.';
|
|
89
|
+
}
|
|
90
|
+
export async function runLocalNocoBaseCommand(runtime, args, options) {
|
|
91
|
+
await runNocoBaseCommand(args, {
|
|
92
|
+
cwd: runtime.projectRoot,
|
|
93
|
+
env: runtime.env.envVars,
|
|
94
|
+
stdio: options?.stdio,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export async function dockerContainerExists(containerName) {
|
|
98
|
+
return await commandSucceeds('docker', ['container', 'inspect', containerName]);
|
|
99
|
+
}
|
|
100
|
+
export async function dockerContainerIsRunning(containerName) {
|
|
101
|
+
try {
|
|
102
|
+
const output = await commandOutput('docker', ['inspect', '--format', '{{.State.Running}}', containerName], { errorName: 'docker inspect' });
|
|
103
|
+
return output.trim() === 'true';
|
|
104
|
+
}
|
|
105
|
+
catch (_error) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function startDockerContainer(containerName, options) {
|
|
110
|
+
const exists = await dockerContainerExists(containerName);
|
|
111
|
+
if (!exists) {
|
|
112
|
+
throw new Error(`Docker app container "${containerName}" does not exist.`);
|
|
113
|
+
}
|
|
114
|
+
if (await dockerContainerIsRunning(containerName)) {
|
|
115
|
+
return 'already-running';
|
|
116
|
+
}
|
|
117
|
+
await run('docker', ['start', containerName], {
|
|
118
|
+
errorName: 'docker start',
|
|
119
|
+
stdio: options?.stdio,
|
|
120
|
+
});
|
|
121
|
+
return 'started';
|
|
122
|
+
}
|
|
123
|
+
export async function stopDockerContainer(containerName, options) {
|
|
124
|
+
const exists = await dockerContainerExists(containerName);
|
|
125
|
+
if (!exists) {
|
|
126
|
+
throw new Error(`Docker app container "${containerName}" does not exist.`);
|
|
127
|
+
}
|
|
128
|
+
if (!(await dockerContainerIsRunning(containerName))) {
|
|
129
|
+
return 'already-stopped';
|
|
130
|
+
}
|
|
131
|
+
await run('docker', ['stop', containerName], {
|
|
132
|
+
errorName: 'docker stop',
|
|
133
|
+
stdio: options?.stdio,
|
|
134
|
+
});
|
|
135
|
+
return 'stopped';
|
|
136
|
+
}
|
|
137
|
+
export async function runDockerNocoBaseCommand(containerName, args) {
|
|
138
|
+
await startDockerContainer(containerName);
|
|
139
|
+
await run('docker', ['exec', '-w', DOCKER_APP_WORKDIR, containerName, 'yarn', 'nocobase', ...args], {
|
|
140
|
+
errorName: 'docker exec',
|
|
141
|
+
});
|
|
142
|
+
}
|