@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.2
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/bin/early-locale.js +89 -0
- package/bin/node-version.js +35 -0
- package/bin/run.js +9 -0
- package/bin/windows-admin.js +60 -0
- package/dist/commands/app/destroy.js +4 -3
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/shared.js +49 -3
- package/dist/commands/app/start.js +92 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +22 -10
- package/dist/commands/install.js +135 -4
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +4 -4
- package/dist/commands/skills/check.js +4 -5
- package/dist/commands/skills/install.js +18 -1
- package/dist/commands/skills/update.js +19 -4
- package/dist/commands/source/dev.js +9 -5
- package/dist/commands/source/download.js +67 -2
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +101 -1
- package/dist/lib/auth-store.js +34 -12
- package/dist/lib/cli-config.js +19 -0
- package/dist/lib/env-config.js +6 -0
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/prompt-web-ui.js +7 -11
- package/dist/lib/run-npm.js +4 -0
- package/dist/lib/self-manager.js +254 -46
- package/dist/lib/skills-manager.js +116 -23
- package/dist/lib/source-publish.js +2 -2
- package/dist/lib/startup-update.js +1 -1
- package/dist/locale/en-US.json +10 -4
- package/dist/locale/zh-CN.json +10 -4
- package/package.json +7 -2
- package/assets/env-proxy/nginx/app.conf.tpl +0 -23
- package/assets/env-proxy/nginx/nocobase.conf.tpl +0 -5
- package/assets/env-proxy/nginx/snippets/dist-location.conf +0 -5
- package/assets/env-proxy/nginx/snippets/gzip.conf +0 -17
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +0 -13
- package/assets/env-proxy/nginx/snippets/maps-http.conf +0 -14
- package/assets/env-proxy/nginx/snippets/mime-types.conf +0 -98
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +0 -17
- package/assets/env-proxy/nginx/snippets/spa-location.conf +0 -6
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +0 -21
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
-
import { ensureLocalPostinstall } from '../../lib/app-managed-resources.js';
|
|
11
|
+
import { ensureLocalPostinstall, ensureNpmSourceDevDependencies } from '../../lib/app-managed-resources.js';
|
|
12
12
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
13
|
+
import { findAvailableTcpPort } from '../../lib/prompt-validators.js';
|
|
13
14
|
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
15
|
function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
15
16
|
if (kind === 'docker') {
|
|
@@ -129,10 +130,7 @@ export default class SourceDev extends Command {
|
|
|
129
130
|
this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
|
|
130
131
|
}
|
|
131
132
|
announceTargetEnv(runtime.envName);
|
|
132
|
-
const devPort = flags.port ||
|
|
133
|
-
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
134
|
-
? String(runtime.env.appPort).trim()
|
|
135
|
-
: undefined);
|
|
133
|
+
const devPort = flags.port?.trim() || (await findAvailableTcpPort());
|
|
136
134
|
const appUrl = appUrlForPort(devPort);
|
|
137
135
|
if (await isAppAlreadyRunning(appUrl)) {
|
|
138
136
|
this.error([
|
|
@@ -160,6 +158,12 @@ export default class SourceDev extends Command {
|
|
|
160
158
|
}
|
|
161
159
|
printInfo(`Starting NocoBase dev mode for "${runtime.envName}" from ${runtime.projectRoot}. Press Ctrl+C to stop.`);
|
|
162
160
|
try {
|
|
161
|
+
await ensureNpmSourceDevDependencies(runtime, {
|
|
162
|
+
onStartTask: startTask,
|
|
163
|
+
onSucceedTask: succeedTask,
|
|
164
|
+
onFailTask: failTask,
|
|
165
|
+
verbose: true,
|
|
166
|
+
});
|
|
163
167
|
await ensureLocalPostinstall(runtime, {
|
|
164
168
|
onStartTask: startTask,
|
|
165
169
|
onSucceedTask: succeedTask,
|
|
@@ -13,10 +13,13 @@ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
|
13
13
|
import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
|
|
14
14
|
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../../lib/cli-locale.js";
|
|
15
15
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
|
+
import { getEnv } from '../../lib/auth-store.js';
|
|
17
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
18
|
+
import { buildBeforeDependencyInstallHookContext, runBeforeDependencyInstallHook, } from '../../lib/hook-script.js';
|
|
16
19
|
import { run } from "../../lib/run-npm.js";
|
|
17
20
|
import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
|
|
18
21
|
const DEFAULT_DOCKER_PLATFORM = 'auto';
|
|
19
|
-
const DEFAULT_DOWNLOAD_VERSION = '
|
|
22
|
+
const DEFAULT_DOWNLOAD_VERSION = 'latest';
|
|
20
23
|
const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
|
|
21
24
|
const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
|
|
22
25
|
function defaultOutputDirForVersion(versionTag) {
|
|
@@ -231,6 +234,26 @@ export default class SourceDownload extends Command {
|
|
|
231
234
|
'npm-registry': Flags.string({
|
|
232
235
|
description: 'npm registry for npm/git downloads and dependency installation.',
|
|
233
236
|
}),
|
|
237
|
+
'hook-script': Flags.string({
|
|
238
|
+
description: 'Hook module to run after npm scaffold or git clone and before dependency installation.',
|
|
239
|
+
}),
|
|
240
|
+
'hook-phase': Flags.string({
|
|
241
|
+
hidden: true,
|
|
242
|
+
options: ['init', 'upgrade', 'restore', 'source-download', 'app-start'],
|
|
243
|
+
}),
|
|
244
|
+
'hook-command': Flags.string({
|
|
245
|
+
hidden: true,
|
|
246
|
+
options: ['init', 'source:download', 'app:start', 'app:restart', 'app:upgrade'],
|
|
247
|
+
}),
|
|
248
|
+
'hook-env-name': Flags.string({
|
|
249
|
+
hidden: true,
|
|
250
|
+
}),
|
|
251
|
+
'hook-app-path': Flags.string({
|
|
252
|
+
hidden: true,
|
|
253
|
+
}),
|
|
254
|
+
'hook-storage-path': Flags.string({
|
|
255
|
+
hidden: true,
|
|
256
|
+
}),
|
|
234
257
|
build: Flags.boolean({
|
|
235
258
|
allowNo: true,
|
|
236
259
|
description: 'Build npm/git source after dependencies are installed.',
|
|
@@ -276,7 +299,6 @@ export default class SourceDownload extends Command {
|
|
|
276
299
|
value: 'latest',
|
|
277
300
|
label: downloadText('prompts.version.latestLabel'),
|
|
278
301
|
hint: downloadText('prompts.version.latestHint'),
|
|
279
|
-
disabled: true,
|
|
280
302
|
},
|
|
281
303
|
{
|
|
282
304
|
value: 'beta',
|
|
@@ -574,6 +596,9 @@ export default class SourceDownload extends Command {
|
|
|
574
596
|
const npmRegistryRaw = results.npmRegistry !== undefined ? String(results.npmRegistry) : flags['npm-registry'] ?? '';
|
|
575
597
|
const npmRegistry = npmRegistryRaw.trim() || undefined;
|
|
576
598
|
const npmDevDependencies = devDependencies ?? false;
|
|
599
|
+
const hookScript = flags['hook-script']?.trim()
|
|
600
|
+
? path.resolve(process.cwd(), flags['hook-script'].trim())
|
|
601
|
+
: undefined;
|
|
577
602
|
return {
|
|
578
603
|
source,
|
|
579
604
|
version,
|
|
@@ -587,6 +612,14 @@ export default class SourceDownload extends Command {
|
|
|
587
612
|
...(source === 'docker' ? { 'docker-platform': dockerPlatform } : {}),
|
|
588
613
|
...(source === 'docker' ? { 'docker-save': dockerSave } : {}),
|
|
589
614
|
...(npmRegistry ? { 'npm-registry': npmRegistry } : {}),
|
|
615
|
+
...(hookScript ? { 'hook-script': hookScript } : {}),
|
|
616
|
+
...(flags['hook-phase'] ? { 'hook-phase': flags['hook-phase'] } : {}),
|
|
617
|
+
...(flags['hook-command'] ? { 'hook-command': flags['hook-command'] } : {}),
|
|
618
|
+
...(flags['hook-env-name'] ? { 'hook-env-name': flags['hook-env-name'].trim() } : {}),
|
|
619
|
+
...(flags['hook-app-path'] ? { 'hook-app-path': path.resolve(process.cwd(), flags['hook-app-path']) } : {}),
|
|
620
|
+
...(flags['hook-storage-path']
|
|
621
|
+
? { 'hook-storage-path': path.resolve(process.cwd(), flags['hook-storage-path']) }
|
|
622
|
+
: {}),
|
|
590
623
|
};
|
|
591
624
|
}
|
|
592
625
|
async resolveDownloadFlags(flags) {
|
|
@@ -719,6 +752,36 @@ export default class SourceDownload extends Command {
|
|
|
719
752
|
}
|
|
720
753
|
return argv;
|
|
721
754
|
}
|
|
755
|
+
async runBeforeDependencyInstallHookIfNeeded(flags, projectRoot) {
|
|
756
|
+
const hookScript = flags['hook-script']?.trim();
|
|
757
|
+
if (!hookScript || (flags.source !== 'npm' && flags.source !== 'git')) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const envName = flags['hook-env-name']?.trim() || '';
|
|
761
|
+
const env = envName ? await getEnv(envName, { scope: resolveDefaultConfigScope() }) : undefined;
|
|
762
|
+
const appPath = flags['hook-app-path']?.trim() || projectRoot;
|
|
763
|
+
const storagePath = flags['hook-storage-path']?.trim() || path.join(appPath, 'storage');
|
|
764
|
+
const context = buildBeforeDependencyInstallHookContext({
|
|
765
|
+
phase: flags['hook-phase'] ?? 'source-download',
|
|
766
|
+
command: flags['hook-command'] ?? 'source:download',
|
|
767
|
+
envName,
|
|
768
|
+
source: flags.source,
|
|
769
|
+
version: flags.version,
|
|
770
|
+
appPath,
|
|
771
|
+
sourcePath: projectRoot,
|
|
772
|
+
storagePath,
|
|
773
|
+
hookScript,
|
|
774
|
+
envConfig: env?.config ?? {},
|
|
775
|
+
});
|
|
776
|
+
if (!context) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
this.log(`Running hook before dependency install: ${hookScript}`);
|
|
780
|
+
await runBeforeDependencyInstallHook({
|
|
781
|
+
hookScriptPath: hookScript,
|
|
782
|
+
context,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
722
785
|
async downloadFromDocker(flags) {
|
|
723
786
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
724
787
|
defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
|
|
@@ -778,6 +841,7 @@ export default class SourceDownload extends Command {
|
|
|
778
841
|
errorName: 'npx create-nocobase-app',
|
|
779
842
|
loadingMessage: 'Creating the app scaffold',
|
|
780
843
|
});
|
|
844
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
781
845
|
const installArgs = ['install'];
|
|
782
846
|
if (!flags['dev-dependencies']) {
|
|
783
847
|
installArgs.push('--production');
|
|
@@ -821,6 +885,7 @@ export default class SourceDownload extends Command {
|
|
|
821
885
|
});
|
|
822
886
|
const projectRoot = path.resolve(process.cwd(), outputDir);
|
|
823
887
|
const registryEnv = this.npmRegistryEnv(flags);
|
|
888
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
824
889
|
this.log(`Installing dependencies in ${projectRoot}`);
|
|
825
890
|
await this.runExternalCommand('yarn', ['install'], {
|
|
826
891
|
...this.runOptionsWithCwd(projectRoot, registryEnv),
|
|
@@ -114,12 +114,22 @@ function compareWithOperator(version, operator, expected) {
|
|
|
114
114
|
}
|
|
115
115
|
function normalizeAppByChannelComparableVersion(version) {
|
|
116
116
|
const normalized = String(version ?? '').trim();
|
|
117
|
-
const match = normalized.match(/^(\d+\.\d+\.\d+)
|
|
117
|
+
const match = normalized.match(/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-.]+))?$/);
|
|
118
118
|
if (!match) {
|
|
119
119
|
return normalized;
|
|
120
120
|
}
|
|
121
|
-
const [, base,
|
|
122
|
-
|
|
121
|
+
const [, base, prerelease] = match;
|
|
122
|
+
if (!prerelease) {
|
|
123
|
+
return base;
|
|
124
|
+
}
|
|
125
|
+
const [channel, sequence] = prerelease.split('.');
|
|
126
|
+
if ((channel === 'alpha' || channel === 'beta') && sequence && /^\d+$/.test(sequence)) {
|
|
127
|
+
return `${base}-${channel}.${sequence}`;
|
|
128
|
+
}
|
|
129
|
+
if (channel === 'alpha' || channel === 'beta') {
|
|
130
|
+
return `${base}-${channel}`;
|
|
131
|
+
}
|
|
132
|
+
return base;
|
|
123
133
|
}
|
|
124
134
|
function normalizeAppByChannelCondition(condition) {
|
|
125
135
|
if (!condition) {
|
|
@@ -134,6 +144,42 @@ function normalizeAppByChannelCondition(condition) {
|
|
|
134
144
|
}
|
|
135
145
|
return normalized;
|
|
136
146
|
}
|
|
147
|
+
function compareAppByChannelVersions(version, expected) {
|
|
148
|
+
const comparableVersion = normalizeAppByChannelComparableVersion(version);
|
|
149
|
+
const comparableExpected = normalizeAppByChannelComparableVersion(expected);
|
|
150
|
+
if (comparableVersion === comparableExpected ||
|
|
151
|
+
comparableVersion.startsWith(`${comparableExpected}.`) ||
|
|
152
|
+
comparableVersion.startsWith(`${comparableExpected}-`)) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
return compareVersions(comparableVersion, comparableExpected);
|
|
156
|
+
}
|
|
157
|
+
function compareAppByChannelWithOperator(version, operator, expected) {
|
|
158
|
+
const compared = compareAppByChannelVersions(version, expected);
|
|
159
|
+
switch (operator) {
|
|
160
|
+
case 'eq':
|
|
161
|
+
return compared === 0;
|
|
162
|
+
case 'gt':
|
|
163
|
+
return compared > 0;
|
|
164
|
+
case 'gte':
|
|
165
|
+
return compared >= 0;
|
|
166
|
+
case 'lt':
|
|
167
|
+
return compared < 0;
|
|
168
|
+
case 'lte':
|
|
169
|
+
return compared <= 0;
|
|
170
|
+
default:
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function matchesAppByChannelVersionCondition(version, condition) {
|
|
175
|
+
if (!condition) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return ['eq', 'gt', 'gte', 'lt', 'lte'].every((operator) => {
|
|
179
|
+
const expected = condition[operator];
|
|
180
|
+
return expected ? compareAppByChannelWithOperator(version, operator, expected) : true;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
137
183
|
function matchesVersionCondition(version, condition) {
|
|
138
184
|
if (!condition) {
|
|
139
185
|
return true;
|
|
@@ -168,10 +214,7 @@ function resolveAppChannel(version) {
|
|
|
168
214
|
if (channel === 'beta') {
|
|
169
215
|
return 'beta';
|
|
170
216
|
}
|
|
171
|
-
|
|
172
|
-
return 'rc';
|
|
173
|
-
}
|
|
174
|
-
return 'unknownPrerelease';
|
|
217
|
+
return 'stable';
|
|
175
218
|
}
|
|
176
219
|
function evaluateAppByChannelCondition(version, condition) {
|
|
177
220
|
if (!condition) {
|
|
@@ -208,7 +251,7 @@ function evaluateAppByChannelCondition(version, condition) {
|
|
|
208
251
|
const comparableVersion = normalizeAppByChannelComparableVersion(version);
|
|
209
252
|
const comparableCondition = normalizeAppByChannelCondition(channelCondition);
|
|
210
253
|
return {
|
|
211
|
-
result:
|
|
254
|
+
result: matchesAppByChannelVersionCondition(comparableVersion, comparableCondition) ? 'match' : 'mismatch',
|
|
212
255
|
channel,
|
|
213
256
|
condition: channelCondition,
|
|
214
257
|
};
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
10
12
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
13
|
import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseCommand, startDockerContainer, } from './app-runtime.js';
|
|
12
14
|
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
13
15
|
import { resolveConfiguredStoragePath } from './env-paths.js';
|
|
14
16
|
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
15
17
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
18
|
+
import { resolveHookScriptPath } from './hook-script.js';
|
|
16
19
|
import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
|
|
17
20
|
import Install from '../commands/install.js';
|
|
18
21
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
@@ -90,6 +93,15 @@ function formatLocalPostinstallFailure(envName, message) {
|
|
|
90
93
|
`Details: ${message}`,
|
|
91
94
|
].join('\n');
|
|
92
95
|
}
|
|
96
|
+
function formatNpmSourceDevDependenciesFailure(envName, projectRoot, message) {
|
|
97
|
+
return [
|
|
98
|
+
`Couldn't prepare source dev dependencies for "${envName}".`,
|
|
99
|
+
'`nb source dev` requires @nocobase/devtools in npm source envs.',
|
|
100
|
+
`Source directory: ${projectRoot}`,
|
|
101
|
+
`Run \`cd ${projectRoot} && yarn install\` after fixing package.json, then try again.`,
|
|
102
|
+
`Details: ${message}`,
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
93
105
|
function formatSavedDockerSettingsIncomplete(envName, missing) {
|
|
94
106
|
return [
|
|
95
107
|
`Can't start NocoBase for "${envName}" yet.`,
|
|
@@ -114,6 +126,45 @@ async function localProjectHasFiles(projectRoot) {
|
|
|
114
126
|
return false;
|
|
115
127
|
}
|
|
116
128
|
}
|
|
129
|
+
function isRecord(value) {
|
|
130
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
131
|
+
}
|
|
132
|
+
async function readPackageJson(projectRoot) {
|
|
133
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
134
|
+
const content = await readFile(packageJsonPath, 'utf-8');
|
|
135
|
+
const parsed = JSON.parse(content);
|
|
136
|
+
if (!isRecord(parsed)) {
|
|
137
|
+
throw new Error(`${packageJsonPath} must contain a JSON object.`);
|
|
138
|
+
}
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
function getStringDependency(packageJson, section, packageName) {
|
|
142
|
+
const dependencies = packageJson[section];
|
|
143
|
+
if (!isRecord(dependencies)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const version = dependencies[packageName];
|
|
147
|
+
return typeof version === 'string' && version.trim() ? version.trim() : undefined;
|
|
148
|
+
}
|
|
149
|
+
function ensureDevDependencies(packageJson) {
|
|
150
|
+
const devDependencies = packageJson.devDependencies;
|
|
151
|
+
if (devDependencies === undefined) {
|
|
152
|
+
const next = {};
|
|
153
|
+
packageJson.devDependencies = next;
|
|
154
|
+
return next;
|
|
155
|
+
}
|
|
156
|
+
if (!isRecord(devDependencies)) {
|
|
157
|
+
throw new Error('package.json devDependencies must be an object.');
|
|
158
|
+
}
|
|
159
|
+
return devDependencies;
|
|
160
|
+
}
|
|
161
|
+
function hasNpmSourceDevtools(projectRoot) {
|
|
162
|
+
return existsSync(path.join(projectRoot, 'node_modules', '@nocobase', 'devtools', 'package.json'));
|
|
163
|
+
}
|
|
164
|
+
function npmRegistryEnv(runtime) {
|
|
165
|
+
const npmRegistry = String(runtime.env.config?.npmRegistry ?? '').trim();
|
|
166
|
+
return npmRegistry ? { npm_config_registry: npmRegistry } : undefined;
|
|
167
|
+
}
|
|
117
168
|
export async function buildSavedDockerRunArgs(runtime, options) {
|
|
118
169
|
const config = runtime.env.config ?? {};
|
|
119
170
|
const storagePath = trimValue(resolveConfiguredStoragePath(config));
|
|
@@ -302,6 +353,13 @@ export function buildSavedLocalDownloadArgv(runtime, options) {
|
|
|
302
353
|
if (config.buildDts === true) {
|
|
303
354
|
argv.push('--build-dts');
|
|
304
355
|
}
|
|
356
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
357
|
+
appPath: runtime.env.appPath,
|
|
358
|
+
hookScript: config.hookScript,
|
|
359
|
+
});
|
|
360
|
+
if (hookScriptPath) {
|
|
361
|
+
argv.push('--hook-script', hookScriptPath, '--hook-phase', options?.hookPhase ?? 'restore', '--hook-command', options?.hookCommand ?? 'app:start', '--hook-env-name', runtime.envName, '--hook-app-path', runtime.env.appPath, '--hook-storage-path', runtime.env.storagePath);
|
|
362
|
+
}
|
|
305
363
|
return argv;
|
|
306
364
|
}
|
|
307
365
|
export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
@@ -313,6 +371,8 @@ export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
|
313
371
|
try {
|
|
314
372
|
await runCommand('source:download', buildSavedLocalDownloadArgv(runtime, {
|
|
315
373
|
verbose: options?.verbose,
|
|
374
|
+
hookPhase: options?.hookPhase,
|
|
375
|
+
hookCommand: options?.hookCommand,
|
|
316
376
|
}));
|
|
317
377
|
options?.onSucceedTask?.(`NocoBase files are ready for "${runtime.envName}".`);
|
|
318
378
|
}
|
|
@@ -335,3 +395,43 @@ export async function ensureLocalPostinstall(runtime, options) {
|
|
|
335
395
|
throw new Error(formatLocalPostinstallFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
|
|
336
396
|
}
|
|
337
397
|
}
|
|
398
|
+
export async function ensureNpmSourceDevDependencies(runtime, options) {
|
|
399
|
+
if (runtime.source !== 'npm') {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
let taskStarted = false;
|
|
403
|
+
try {
|
|
404
|
+
const packageJson = await readPackageJson(runtime.projectRoot);
|
|
405
|
+
const appVersion = getStringDependency(packageJson, 'dependencies', '@nocobase/app');
|
|
406
|
+
const devtoolsVersion = getStringDependency(packageJson, 'devDependencies', '@nocobase/devtools');
|
|
407
|
+
let updatedPackageJson = false;
|
|
408
|
+
if (!devtoolsVersion) {
|
|
409
|
+
if (!appVersion) {
|
|
410
|
+
throw new Error('Cannot determine @nocobase/devtools version because dependencies["@nocobase/app"] is missing.');
|
|
411
|
+
}
|
|
412
|
+
const devDependencies = ensureDevDependencies(packageJson);
|
|
413
|
+
devDependencies['@nocobase/devtools'] = appVersion;
|
|
414
|
+
updatedPackageJson = true;
|
|
415
|
+
await writeFile(path.join(runtime.projectRoot, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8');
|
|
416
|
+
}
|
|
417
|
+
const needsInstall = updatedPackageJson || !hasNpmSourceDevtools(runtime.projectRoot);
|
|
418
|
+
if (!needsInstall) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
options?.onStartTask?.(`Preparing source dev dependencies for "${runtime.envName}"...`);
|
|
422
|
+
taskStarted = true;
|
|
423
|
+
await run('yarn', ['install'], {
|
|
424
|
+
cwd: runtime.projectRoot,
|
|
425
|
+
env: npmRegistryEnv(runtime),
|
|
426
|
+
errorName: 'yarn install',
|
|
427
|
+
stdio: commandStdio(options?.verbose),
|
|
428
|
+
});
|
|
429
|
+
options?.onSucceedTask?.(`Source dev dependencies are ready for "${runtime.envName}".`);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (taskStarted) {
|
|
433
|
+
options?.onFailTask?.(`Failed to prepare source dev dependencies for "${runtime.envName}".`);
|
|
434
|
+
}
|
|
435
|
+
throw new Error(formatNpmSourceDevDependenciesFailure(runtime.envName, runtime.projectRoot, error instanceof Error ? error.message : String(error)));
|
|
436
|
+
}
|
|
437
|
+
}
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -12,6 +12,7 @@ import { resolveAppPublicPath } from './app-public-path.js';
|
|
|
12
12
|
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
13
13
|
import { normalizeCliLocale } from './cli-locale.js';
|
|
14
14
|
import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
|
|
15
|
+
import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
|
|
15
16
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
16
17
|
function normalizeStoredEnvKind(value) {
|
|
17
18
|
const kind = String(value ?? '').trim();
|
|
@@ -76,11 +77,13 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
76
77
|
if (!entry) {
|
|
77
78
|
return entry;
|
|
78
79
|
}
|
|
79
|
-
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, ...rest } = entry;
|
|
80
|
+
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, schemaVersion: _schemaVersion, ...rest } = entry;
|
|
80
81
|
const normalizedKind = resolveEnvKind(entry);
|
|
81
82
|
const apiBaseUrl = readEnvApiBaseUrl(entry);
|
|
83
|
+
const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
|
|
82
84
|
return {
|
|
83
85
|
...rest,
|
|
86
|
+
...(schemaVersion ? { schemaVersion } : {}),
|
|
84
87
|
...(normalizedKind ? { kind: normalizedKind } : {}),
|
|
85
88
|
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
86
89
|
...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
|
|
@@ -96,6 +99,17 @@ function normalizeAuthConfig(config) {
|
|
|
96
99
|
? settings.log.retentionDays
|
|
97
100
|
: undefined;
|
|
98
101
|
const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
|
|
102
|
+
const hasBinSettings = settings.bin?.docker ||
|
|
103
|
+
settings.bin?.caddy ||
|
|
104
|
+
settings.bin?.git ||
|
|
105
|
+
settings.bin?.nginx ||
|
|
106
|
+
settings.bin?.pnpm ||
|
|
107
|
+
settings.bin?.yarn;
|
|
108
|
+
const hasProxySettings = settings.proxy?.nbCliRoot ||
|
|
109
|
+
settings.proxy?.caddyDriver ||
|
|
110
|
+
settings.proxy?.nginxDriver ||
|
|
111
|
+
settings.proxy?.upstreamHost ||
|
|
112
|
+
settings.proxy?.host;
|
|
99
113
|
return {
|
|
100
114
|
name: config.name || config.dockerResourcePrefix,
|
|
101
115
|
settings: {
|
|
@@ -120,22 +134,19 @@ function normalizeAuthConfig(config) {
|
|
|
120
134
|
},
|
|
121
135
|
}
|
|
122
136
|
: {}),
|
|
123
|
-
...(
|
|
137
|
+
...(hasBinSettings
|
|
124
138
|
? {
|
|
125
139
|
bin: {
|
|
126
140
|
...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
|
|
127
141
|
...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
|
|
128
142
|
...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
|
|
129
143
|
...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
|
|
144
|
+
...(settings.bin?.pnpm ? { pnpm: normalizeOptionalString(settings.bin.pnpm) } : {}),
|
|
130
145
|
...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
|
|
131
146
|
},
|
|
132
147
|
}
|
|
133
148
|
: {}),
|
|
134
|
-
...(
|
|
135
|
-
settings.proxy?.caddyDriver ||
|
|
136
|
-
settings.proxy?.nginxDriver ||
|
|
137
|
-
settings.proxy?.upstreamHost ||
|
|
138
|
-
settings.proxy?.host
|
|
149
|
+
...(hasProxySettings
|
|
139
150
|
? {
|
|
140
151
|
proxy: {
|
|
141
152
|
...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
|
|
@@ -366,7 +377,13 @@ function areAuthConfigsEquivalent(left, right) {
|
|
|
366
377
|
async function writeEnv(envName, updater, options = {}) {
|
|
367
378
|
const config = await loadExactAuthConfig(options);
|
|
368
379
|
const previous = config.envs[envName];
|
|
369
|
-
|
|
380
|
+
const next = updater(previous);
|
|
381
|
+
config.envs[envName] = {
|
|
382
|
+
...next,
|
|
383
|
+
schemaVersion: normalizeEnvConfigSchemaVersion(next.schemaVersion) ??
|
|
384
|
+
normalizeEnvConfigSchemaVersion(previous?.schemaVersion) ??
|
|
385
|
+
ENV_CONFIG_SCHEMA_VERSION,
|
|
386
|
+
};
|
|
370
387
|
await saveAuthConfig(config, options);
|
|
371
388
|
}
|
|
372
389
|
function normalizeConfiguredAuthType(value) {
|
|
@@ -377,18 +394,19 @@ export function resolveConfiguredAuthType(config) {
|
|
|
377
394
|
}
|
|
378
395
|
export async function upsertEnv(envName, config, options = {}) {
|
|
379
396
|
await writeEnv(envName, (previous) => {
|
|
380
|
-
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
397
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, schemaVersion, ...rest } = config;
|
|
381
398
|
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
382
399
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
383
400
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
384
401
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
385
402
|
const requestedAuthType = normalizeConfiguredAuthType(authType);
|
|
386
|
-
const
|
|
403
|
+
const nextAccessToken = normalizeOptionalString(accessToken);
|
|
404
|
+
const nextAuthType = requestedAuthType ?? (nextAccessToken ? 'token' : previousAuthType);
|
|
387
405
|
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
|
|
388
|
-
const nextAuth =
|
|
406
|
+
const nextAuth = nextAccessToken
|
|
389
407
|
? {
|
|
390
408
|
type: 'token',
|
|
391
|
-
accessToken,
|
|
409
|
+
accessToken: nextAccessToken,
|
|
392
410
|
}
|
|
393
411
|
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
394
412
|
? previous.auth
|
|
@@ -396,6 +414,9 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
396
414
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
397
415
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
398
416
|
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
417
|
+
const nextSchemaVersion = normalizeEnvConfigSchemaVersion(schemaVersion) ??
|
|
418
|
+
normalizeEnvConfigSchemaVersion(previous?.schemaVersion) ??
|
|
419
|
+
ENV_CONFIG_SCHEMA_VERSION;
|
|
399
420
|
return {
|
|
400
421
|
...previous,
|
|
401
422
|
apiBaseUrl: nextApiBaseUrl,
|
|
@@ -403,6 +424,7 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
403
424
|
authUsername: nextAuthUsername,
|
|
404
425
|
auth: nextAuth,
|
|
405
426
|
...rest,
|
|
427
|
+
schemaVersion: nextSchemaVersion,
|
|
406
428
|
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
407
429
|
};
|
|
408
430
|
}, options);
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -16,6 +16,7 @@ export const DEFAULT_DOCKER_BIN = 'docker';
|
|
|
16
16
|
export const DEFAULT_CADDY_BIN = 'caddy';
|
|
17
17
|
export const DEFAULT_GIT_BIN = 'git';
|
|
18
18
|
export const DEFAULT_NGINX_BIN = 'nginx';
|
|
19
|
+
export const DEFAULT_PNPM_BIN = 'pnpm';
|
|
19
20
|
export const PROXY_PROVIDER_OPTIONS = ['nginx', 'caddy'];
|
|
20
21
|
export const DEFAULT_PROXY_PROVIDER = 'nginx';
|
|
21
22
|
export const NGINX_PROXY_DRIVER_OPTIONS = ['local', 'docker'];
|
|
@@ -40,6 +41,7 @@ export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
|
40
41
|
'bin.caddy',
|
|
41
42
|
'bin.git',
|
|
42
43
|
'bin.nginx',
|
|
44
|
+
'bin.pnpm',
|
|
43
45
|
'proxy.nb-cli-root',
|
|
44
46
|
'proxy.caddy-driver',
|
|
45
47
|
'proxy.nginx-driver',
|
|
@@ -136,6 +138,7 @@ function pruneSettings(config) {
|
|
|
136
138
|
!trimValue(bin.caddy) &&
|
|
137
139
|
!trimValue(bin.git) &&
|
|
138
140
|
!trimValue(bin.nginx) &&
|
|
141
|
+
!trimValue(bin.pnpm) &&
|
|
139
142
|
!trimValue(bin.yarn)) {
|
|
140
143
|
delete config.settings?.bin;
|
|
141
144
|
}
|
|
@@ -187,6 +190,8 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
187
190
|
return trimValue(config.settings?.bin?.git);
|
|
188
191
|
case 'bin.nginx':
|
|
189
192
|
return trimValue(config.settings?.bin?.nginx);
|
|
193
|
+
case 'bin.pnpm':
|
|
194
|
+
return trimValue(config.settings?.bin?.pnpm);
|
|
190
195
|
case 'proxy.nb-cli-root':
|
|
191
196
|
return trimValue(config.settings?.proxy?.nbCliRoot);
|
|
192
197
|
case 'proxy.caddy-driver':
|
|
@@ -233,6 +238,8 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
233
238
|
return DEFAULT_GIT_BIN;
|
|
234
239
|
case 'bin.nginx':
|
|
235
240
|
return DEFAULT_NGINX_BIN;
|
|
241
|
+
case 'bin.pnpm':
|
|
242
|
+
return DEFAULT_PNPM_BIN;
|
|
236
243
|
case 'proxy.nb-cli-root':
|
|
237
244
|
return explicit ?? resolveCliHomeRoot();
|
|
238
245
|
case 'proxy.caddy-driver':
|
|
@@ -387,6 +394,12 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
387
394
|
nginx: normalized,
|
|
388
395
|
};
|
|
389
396
|
break;
|
|
397
|
+
case 'bin.pnpm':
|
|
398
|
+
config.settings.bin = {
|
|
399
|
+
...(config.settings.bin ?? {}),
|
|
400
|
+
pnpm: normalized,
|
|
401
|
+
};
|
|
402
|
+
break;
|
|
390
403
|
case 'proxy.nb-cli-root':
|
|
391
404
|
config.settings.proxy = {
|
|
392
405
|
...(config.settings.proxy ?? {}),
|
|
@@ -496,6 +509,11 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
496
509
|
delete config.settings.bin.nginx;
|
|
497
510
|
}
|
|
498
511
|
break;
|
|
512
|
+
case 'bin.pnpm':
|
|
513
|
+
if (config.settings.bin) {
|
|
514
|
+
delete config.settings.bin.pnpm;
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
499
517
|
case 'proxy.nb-cli-root':
|
|
500
518
|
if (config.settings.proxy) {
|
|
501
519
|
delete config.settings.proxy.nbCliRoot;
|
|
@@ -556,6 +574,7 @@ const CONFIGURABLE_COMMAND_KEYS = {
|
|
|
556
574
|
caddy: 'bin.caddy',
|
|
557
575
|
git: 'bin.git',
|
|
558
576
|
nginx: 'bin.nginx',
|
|
577
|
+
pnpm: 'bin.pnpm',
|
|
559
578
|
yarn: 'bin.yarn',
|
|
560
579
|
};
|
|
561
580
|
export function isConfigurableCommandName(value) {
|
package/dist/lib/env-config.js
CHANGED
|
@@ -14,6 +14,7 @@ const STRING_ENV_CONFIG_KEYS = [
|
|
|
14
14
|
'dockerPlatform',
|
|
15
15
|
'gitUrl',
|
|
16
16
|
'npmRegistry',
|
|
17
|
+
'hookScript',
|
|
17
18
|
'appPath',
|
|
18
19
|
'appRootPath',
|
|
19
20
|
'storagePath',
|
|
@@ -46,6 +47,7 @@ const BOOLEAN_ENV_CONFIG_KEYS = [
|
|
|
46
47
|
'buildDts',
|
|
47
48
|
'dbUnderscored',
|
|
48
49
|
];
|
|
50
|
+
export const ENV_CONFIG_SCHEMA_VERSION = 1;
|
|
49
51
|
function trimConfigValue(value) {
|
|
50
52
|
const text = String(value ?? '').trim();
|
|
51
53
|
return text || undefined;
|
|
@@ -53,6 +55,9 @@ function trimConfigValue(value) {
|
|
|
53
55
|
function resolveSetupState(value) {
|
|
54
56
|
return value === 'prepared' || value === 'installed' ? value : undefined;
|
|
55
57
|
}
|
|
58
|
+
export function normalizeEnvConfigSchemaVersion(value) {
|
|
59
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
60
|
+
}
|
|
56
61
|
function resolveEnvKind(input) {
|
|
57
62
|
const source = trimConfigValue(input.source);
|
|
58
63
|
const appPath = trimConfigValue(input.appPath);
|
|
@@ -67,6 +72,7 @@ function resolveEnvKind(input) {
|
|
|
67
72
|
}
|
|
68
73
|
export function buildStoredEnvConfig(input) {
|
|
69
74
|
const envConfig = {
|
|
75
|
+
schemaVersion: normalizeEnvConfigSchemaVersion(input.schemaVersion) ?? ENV_CONFIG_SCHEMA_VERSION,
|
|
70
76
|
kind: resolveEnvKind(input),
|
|
71
77
|
apiBaseUrl: trimConfigValue(input.apiBaseUrl) ?? '',
|
|
72
78
|
};
|