@nocobase/cli 2.2.0-beta.1 → 2.2.0-beta.11
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/assets/env-proxy/nginx/snippets/proxy-location.conf +1 -0
- 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 +95 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +33 -11
- package/dist/commands/install.js +159 -107
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/proxy/caddy/generate.js +93 -7
- package/dist/commands/proxy/nginx/generate.js +98 -7
- package/dist/commands/revision/create.js +1 -1
- 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 +85 -16
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +104 -5
- package/dist/lib/auth-store.js +102 -12
- package/dist/lib/cli-config.js +73 -1
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-auth.js +291 -45
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +164 -58
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-validators.js +1 -1
- package/dist/lib/prompt-web-ui.js +20 -13
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -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 +49 -43
- package/dist/locale/zh-CN.json +49 -43
- package/package.json +8 -2
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
|
@@ -12,11 +12,15 @@ import path from 'node:path';
|
|
|
12
12
|
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
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
15
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveOfficialDockerRegistry, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
|
+
import { getEnv } from '../../lib/auth-store.js';
|
|
17
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
18
|
+
import { getCliConfigValue } from '../../lib/cli-config.js';
|
|
19
|
+
import { buildBeforeDependencyInstallHookContext, runBeforeDependencyInstallHook, } from '../../lib/hook-script.js';
|
|
16
20
|
import { run } from "../../lib/run-npm.js";
|
|
17
21
|
import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
|
|
18
22
|
const DEFAULT_DOCKER_PLATFORM = 'auto';
|
|
19
|
-
const DEFAULT_DOWNLOAD_VERSION = '
|
|
23
|
+
const DEFAULT_DOWNLOAD_VERSION = 'latest';
|
|
20
24
|
const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
|
|
21
25
|
const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
|
|
22
26
|
function defaultOutputDirForVersion(versionTag) {
|
|
@@ -231,6 +235,26 @@ export default class SourceDownload extends Command {
|
|
|
231
235
|
'npm-registry': Flags.string({
|
|
232
236
|
description: 'npm registry for npm/git downloads and dependency installation.',
|
|
233
237
|
}),
|
|
238
|
+
'hook-script': Flags.string({
|
|
239
|
+
description: 'Hook module to run after npm scaffold or git clone and before dependency installation.',
|
|
240
|
+
}),
|
|
241
|
+
'hook-phase': Flags.string({
|
|
242
|
+
hidden: true,
|
|
243
|
+
options: ['init', 'upgrade', 'restore', 'source-download', 'app-start'],
|
|
244
|
+
}),
|
|
245
|
+
'hook-command': Flags.string({
|
|
246
|
+
hidden: true,
|
|
247
|
+
options: ['init', 'source:download', 'app:start', 'app:restart', 'app:upgrade'],
|
|
248
|
+
}),
|
|
249
|
+
'hook-env-name': Flags.string({
|
|
250
|
+
hidden: true,
|
|
251
|
+
}),
|
|
252
|
+
'hook-app-path': Flags.string({
|
|
253
|
+
hidden: true,
|
|
254
|
+
}),
|
|
255
|
+
'hook-storage-path': Flags.string({
|
|
256
|
+
hidden: true,
|
|
257
|
+
}),
|
|
234
258
|
build: Flags.boolean({
|
|
235
259
|
allowNo: true,
|
|
236
260
|
description: 'Build npm/git source after dependencies are installed.',
|
|
@@ -276,7 +300,6 @@ export default class SourceDownload extends Command {
|
|
|
276
300
|
value: 'latest',
|
|
277
301
|
label: downloadText('prompts.version.latestLabel'),
|
|
278
302
|
hint: downloadText('prompts.version.latestHint'),
|
|
279
|
-
disabled: true,
|
|
280
303
|
},
|
|
281
304
|
{
|
|
282
305
|
value: 'beta',
|
|
@@ -322,8 +345,8 @@ export default class SourceDownload extends Command {
|
|
|
322
345
|
label: downloadText('prompts.dockerPlatform.autoLabel'),
|
|
323
346
|
hint: downloadText('prompts.dockerPlatform.autoHint'),
|
|
324
347
|
},
|
|
325
|
-
{ value: 'linux/amd64', label: '
|
|
326
|
-
{ value: 'linux/arm64', label: '
|
|
348
|
+
{ value: 'linux/amd64', label: 'amd64' },
|
|
349
|
+
{ value: 'linux/arm64', label: 'arm64' },
|
|
327
350
|
],
|
|
328
351
|
initialValue: DEFAULT_DOCKER_PLATFORM,
|
|
329
352
|
yesInitialValue: DEFAULT_DOCKER_PLATFORM,
|
|
@@ -428,9 +451,9 @@ export default class SourceDownload extends Command {
|
|
|
428
451
|
}
|
|
429
452
|
return outputAbs;
|
|
430
453
|
}
|
|
431
|
-
dockerTarPath(flags, outputAbs) {
|
|
454
|
+
async dockerTarPath(flags, outputAbs) {
|
|
432
455
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
433
|
-
defaultRegistry:
|
|
456
|
+
defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
|
|
434
457
|
defaultVersion: 'latest',
|
|
435
458
|
});
|
|
436
459
|
const safeBase = imageRef.replace(/[\\/:]/g, '-');
|
|
@@ -440,9 +463,11 @@ export default class SourceDownload extends Command {
|
|
|
440
463
|
* Defaults for prompts only. Keys present in **`preset`** are omitted so `runPromptCatalog` uses
|
|
441
464
|
* **`values`** (preset) alone for those steps — no duplicate prefill for skipped prompts.
|
|
442
465
|
*/
|
|
443
|
-
|
|
466
|
+
async resolveConfiguredDockerRegistryDefault() {
|
|
467
|
+
return resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
|
|
468
|
+
}
|
|
469
|
+
buildInitialValuesFromParsed(flags, preset, defaultDockerRegistry) {
|
|
444
470
|
const initialValues = {};
|
|
445
|
-
const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
|
|
446
471
|
const source = flags.source?.trim();
|
|
447
472
|
if (source) {
|
|
448
473
|
initialValues.source = source;
|
|
@@ -466,7 +491,7 @@ export default class SourceDownload extends Command {
|
|
|
466
491
|
initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
|
|
467
492
|
}
|
|
468
493
|
else {
|
|
469
|
-
initialValues.dockerRegistry =
|
|
494
|
+
initialValues.dockerRegistry = defaultDockerRegistry;
|
|
470
495
|
}
|
|
471
496
|
initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
|
|
472
497
|
initialValues.dockerSave = flags['docker-save'];
|
|
@@ -547,7 +572,7 @@ export default class SourceDownload extends Command {
|
|
|
547
572
|
}
|
|
548
573
|
return flags.build;
|
|
549
574
|
}
|
|
550
|
-
mapCatalogResultsToResolved(results, flags) {
|
|
575
|
+
mapCatalogResultsToResolved(results, flags, defaultDockerRegistry) {
|
|
551
576
|
const source = String(results.source);
|
|
552
577
|
const version = resolveVersionFromResults(results, flags.version) || 'latest';
|
|
553
578
|
const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
|
|
@@ -561,7 +586,7 @@ export default class SourceDownload extends Command {
|
|
|
561
586
|
const dockerRegistry = source === 'docker'
|
|
562
587
|
? results.dockerRegistry !== undefined
|
|
563
588
|
? String(results.dockerRegistry).trim() || undefined
|
|
564
|
-
: flags['docker-registry']?.trim() ||
|
|
589
|
+
: flags['docker-registry']?.trim() || defaultDockerRegistry
|
|
565
590
|
: undefined;
|
|
566
591
|
const dockerPlatform = source === 'docker'
|
|
567
592
|
? normalizeDockerPlatform(results.dockerPlatform !== undefined ? results.dockerPlatform : flags['docker-platform'])
|
|
@@ -574,6 +599,9 @@ export default class SourceDownload extends Command {
|
|
|
574
599
|
const npmRegistryRaw = results.npmRegistry !== undefined ? String(results.npmRegistry) : flags['npm-registry'] ?? '';
|
|
575
600
|
const npmRegistry = npmRegistryRaw.trim() || undefined;
|
|
576
601
|
const npmDevDependencies = devDependencies ?? false;
|
|
602
|
+
const hookScript = flags['hook-script']?.trim()
|
|
603
|
+
? path.resolve(process.cwd(), flags['hook-script'].trim())
|
|
604
|
+
: undefined;
|
|
577
605
|
return {
|
|
578
606
|
source,
|
|
579
607
|
version,
|
|
@@ -587,6 +615,14 @@ export default class SourceDownload extends Command {
|
|
|
587
615
|
...(source === 'docker' ? { 'docker-platform': dockerPlatform } : {}),
|
|
588
616
|
...(source === 'docker' ? { 'docker-save': dockerSave } : {}),
|
|
589
617
|
...(npmRegistry ? { 'npm-registry': npmRegistry } : {}),
|
|
618
|
+
...(hookScript ? { 'hook-script': hookScript } : {}),
|
|
619
|
+
...(flags['hook-phase'] ? { 'hook-phase': flags['hook-phase'] } : {}),
|
|
620
|
+
...(flags['hook-command'] ? { 'hook-command': flags['hook-command'] } : {}),
|
|
621
|
+
...(flags['hook-env-name'] ? { 'hook-env-name': flags['hook-env-name'].trim() } : {}),
|
|
622
|
+
...(flags['hook-app-path'] ? { 'hook-app-path': path.resolve(process.cwd(), flags['hook-app-path']) } : {}),
|
|
623
|
+
...(flags['hook-storage-path']
|
|
624
|
+
? { 'hook-storage-path': path.resolve(process.cwd(), flags['hook-storage-path']) }
|
|
625
|
+
: {}),
|
|
590
626
|
};
|
|
591
627
|
}
|
|
592
628
|
async resolveDownloadFlags(flags) {
|
|
@@ -595,7 +631,8 @@ export default class SourceDownload extends Command {
|
|
|
595
631
|
this.error('Download source is required in non-interactive mode. Use --source npm, --source git, or --source docker.');
|
|
596
632
|
}
|
|
597
633
|
const presetValues = this.buildPresetValuesFromFlags(flags);
|
|
598
|
-
const
|
|
634
|
+
const defaultDockerRegistry = await this.resolveConfiguredDockerRegistryDefault();
|
|
635
|
+
const initialValues = this.buildInitialValuesFromParsed(flags, presetValues, defaultDockerRegistry);
|
|
599
636
|
const results = await runPromptCatalog(SourceDownload.prompts, {
|
|
600
637
|
initialValues,
|
|
601
638
|
values: presetValues,
|
|
@@ -621,7 +658,7 @@ export default class SourceDownload extends Command {
|
|
|
621
658
|
if (flags['docker-save'] && source !== 'docker') {
|
|
622
659
|
this.error('--docker-save is only available when --source docker is selected.');
|
|
623
660
|
}
|
|
624
|
-
return this.mapCatalogResultsToResolved(results, flags);
|
|
661
|
+
return this.mapCatalogResultsToResolved(results, flags, defaultDockerRegistry);
|
|
625
662
|
}
|
|
626
663
|
npmRegistryUrl(flags) {
|
|
627
664
|
const url = flags['npm-registry']?.trim();
|
|
@@ -719,9 +756,39 @@ export default class SourceDownload extends Command {
|
|
|
719
756
|
}
|
|
720
757
|
return argv;
|
|
721
758
|
}
|
|
759
|
+
async runBeforeDependencyInstallHookIfNeeded(flags, projectRoot) {
|
|
760
|
+
const hookScript = flags['hook-script']?.trim();
|
|
761
|
+
if (!hookScript || (flags.source !== 'npm' && flags.source !== 'git')) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const envName = flags['hook-env-name']?.trim() || '';
|
|
765
|
+
const env = envName ? await getEnv(envName, { scope: resolveDefaultConfigScope() }) : undefined;
|
|
766
|
+
const appPath = flags['hook-app-path']?.trim() || projectRoot;
|
|
767
|
+
const storagePath = flags['hook-storage-path']?.trim() || path.join(appPath, 'storage');
|
|
768
|
+
const context = buildBeforeDependencyInstallHookContext({
|
|
769
|
+
phase: flags['hook-phase'] ?? 'source-download',
|
|
770
|
+
command: flags['hook-command'] ?? 'source:download',
|
|
771
|
+
envName,
|
|
772
|
+
source: flags.source,
|
|
773
|
+
version: flags.version,
|
|
774
|
+
appPath,
|
|
775
|
+
sourcePath: projectRoot,
|
|
776
|
+
storagePath,
|
|
777
|
+
hookScript,
|
|
778
|
+
envConfig: env?.config ?? {},
|
|
779
|
+
});
|
|
780
|
+
if (!context) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
this.log(`Running hook before dependency install: ${hookScript}`);
|
|
784
|
+
await runBeforeDependencyInstallHook({
|
|
785
|
+
hookScriptPath: hookScript,
|
|
786
|
+
context,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
722
789
|
async downloadFromDocker(flags) {
|
|
723
790
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
724
|
-
defaultRegistry:
|
|
791
|
+
defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
|
|
725
792
|
defaultVersion: 'latest',
|
|
726
793
|
});
|
|
727
794
|
const platform = dockerPlatformArg(flags['docker-platform']);
|
|
@@ -748,7 +815,7 @@ export default class SourceDownload extends Command {
|
|
|
748
815
|
await fsp.rm(outAbs, { recursive: true, force: true });
|
|
749
816
|
}
|
|
750
817
|
await fsp.mkdir(outAbs, { recursive: true });
|
|
751
|
-
const tarPath = this.dockerTarPath(flags, outAbs);
|
|
818
|
+
const tarPath = await this.dockerTarPath(flags, outAbs);
|
|
752
819
|
this.log(`Saving Docker image tarball to ${tarPath}`);
|
|
753
820
|
await this.runExternalCommand('docker', ['save', '-o', tarPath, imageRef], {
|
|
754
821
|
errorName: 'docker save',
|
|
@@ -778,6 +845,7 @@ export default class SourceDownload extends Command {
|
|
|
778
845
|
errorName: 'npx create-nocobase-app',
|
|
779
846
|
loadingMessage: 'Creating the app scaffold',
|
|
780
847
|
});
|
|
848
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
781
849
|
const installArgs = ['install'];
|
|
782
850
|
if (!flags['dev-dependencies']) {
|
|
783
851
|
installArgs.push('--production');
|
|
@@ -821,6 +889,7 @@ export default class SourceDownload extends Command {
|
|
|
821
889
|
});
|
|
822
890
|
const projectRoot = path.resolve(process.cwd(), outputDir);
|
|
823
891
|
const registryEnv = this.npmRegistryEnv(flags);
|
|
892
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
824
893
|
this.log(`Installing dependencies in ${projectRoot}`);
|
|
825
894
|
await this.runExternalCommand('yarn', ['install'], {
|
|
826
895
|
...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
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
17
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageContainerPort, 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));
|
|
@@ -141,6 +192,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
141
192
|
defaultRegistry: DEFAULT_DOCKER_REGISTRY,
|
|
142
193
|
defaultVersion: DEFAULT_DOCKER_VERSION,
|
|
143
194
|
});
|
|
195
|
+
const containerPort = resolveDockerImageContainerPort(imageRef);
|
|
144
196
|
const missing = [];
|
|
145
197
|
if (!storagePath) {
|
|
146
198
|
missing.push('storagePath');
|
|
@@ -174,8 +226,6 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
174
226
|
'-d',
|
|
175
227
|
'--name',
|
|
176
228
|
runtime.containerName,
|
|
177
|
-
'--restart',
|
|
178
|
-
'always',
|
|
179
229
|
'--network',
|
|
180
230
|
runtime.workspaceName,
|
|
181
231
|
];
|
|
@@ -184,7 +234,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
184
234
|
args.push('--platform', dockerPlatform);
|
|
185
235
|
}
|
|
186
236
|
if (appPort) {
|
|
187
|
-
args.push('-p', `${appPort}
|
|
237
|
+
args.push('-p', `${appPort}:${containerPort}`);
|
|
188
238
|
}
|
|
189
239
|
if (envFile) {
|
|
190
240
|
args.push('--env-file', envFile);
|
|
@@ -302,6 +352,13 @@ export function buildSavedLocalDownloadArgv(runtime, options) {
|
|
|
302
352
|
if (config.buildDts === true) {
|
|
303
353
|
argv.push('--build-dts');
|
|
304
354
|
}
|
|
355
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
356
|
+
appPath: runtime.env.appPath,
|
|
357
|
+
hookScript: config.hookScript,
|
|
358
|
+
});
|
|
359
|
+
if (hookScriptPath) {
|
|
360
|
+
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);
|
|
361
|
+
}
|
|
305
362
|
return argv;
|
|
306
363
|
}
|
|
307
364
|
export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
@@ -313,6 +370,8 @@ export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
|
313
370
|
try {
|
|
314
371
|
await runCommand('source:download', buildSavedLocalDownloadArgv(runtime, {
|
|
315
372
|
verbose: options?.verbose,
|
|
373
|
+
hookPhase: options?.hookPhase,
|
|
374
|
+
hookCommand: options?.hookCommand,
|
|
316
375
|
}));
|
|
317
376
|
options?.onSucceedTask?.(`NocoBase files are ready for "${runtime.envName}".`);
|
|
318
377
|
}
|
|
@@ -335,3 +394,43 @@ export async function ensureLocalPostinstall(runtime, options) {
|
|
|
335
394
|
throw new Error(formatLocalPostinstallFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
|
|
336
395
|
}
|
|
337
396
|
}
|
|
397
|
+
export async function ensureNpmSourceDevDependencies(runtime, options) {
|
|
398
|
+
if (runtime.source !== 'npm') {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
let taskStarted = false;
|
|
402
|
+
try {
|
|
403
|
+
const packageJson = await readPackageJson(runtime.projectRoot);
|
|
404
|
+
const appVersion = getStringDependency(packageJson, 'dependencies', '@nocobase/app');
|
|
405
|
+
const devtoolsVersion = getStringDependency(packageJson, 'devDependencies', '@nocobase/devtools');
|
|
406
|
+
let updatedPackageJson = false;
|
|
407
|
+
if (!devtoolsVersion) {
|
|
408
|
+
if (!appVersion) {
|
|
409
|
+
throw new Error('Cannot determine @nocobase/devtools version because dependencies["@nocobase/app"] is missing.');
|
|
410
|
+
}
|
|
411
|
+
const devDependencies = ensureDevDependencies(packageJson);
|
|
412
|
+
devDependencies['@nocobase/devtools'] = appVersion;
|
|
413
|
+
updatedPackageJson = true;
|
|
414
|
+
await writeFile(path.join(runtime.projectRoot, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8');
|
|
415
|
+
}
|
|
416
|
+
const needsInstall = updatedPackageJson || !hasNpmSourceDevtools(runtime.projectRoot);
|
|
417
|
+
if (!needsInstall) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
options?.onStartTask?.(`Preparing source dev dependencies for "${runtime.envName}"...`);
|
|
421
|
+
taskStarted = true;
|
|
422
|
+
await run('yarn', ['install'], {
|
|
423
|
+
cwd: runtime.projectRoot,
|
|
424
|
+
env: npmRegistryEnv(runtime),
|
|
425
|
+
errorName: 'yarn install',
|
|
426
|
+
stdio: commandStdio(options?.verbose),
|
|
427
|
+
});
|
|
428
|
+
options?.onSucceedTask?.(`Source dev dependencies are ready for "${runtime.envName}".`);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (taskStarted) {
|
|
432
|
+
options?.onFailTask?.(`Failed to prepare source dev dependencies for "${runtime.envName}".`);
|
|
433
|
+
}
|
|
434
|
+
throw new Error(formatNpmSourceDevDependenciesFailure(runtime.envName, runtime.projectRoot, error instanceof Error ? error.message : String(error)));
|
|
435
|
+
}
|
|
436
|
+
}
|