@nocobase/cli 2.1.0-beta.34 → 2.1.0-beta.36
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/run.js +2 -1
- package/bin/session-env.js +12 -0
- package/dist/commands/app/start.js +12 -1
- package/dist/commands/app/upgrade.js +45 -16
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- package/dist/commands/env/add.js +63 -9
- package/dist/commands/env/auth.js +85 -11
- package/dist/commands/init.js +71 -13
- package/dist/commands/install.js +140 -11
- package/dist/commands/license/activate.js +6 -4
- package/dist/commands/source/dev.js +8 -1
- package/dist/commands/source/publish.js +109 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/commands/v1.js +210 -0
- package/dist/lib/app-managed-resources.js +42 -2
- package/dist/lib/app-runtime.js +13 -4
- package/dist/lib/auth-store.js +28 -5
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +23 -13
- package/dist/lib/env-config.js +6 -0
- package/dist/lib/run-npm.js +35 -10
- package/dist/lib/source-publish.js +306 -0
- package/dist/lib/source-registry.js +188 -0
- package/package.json +6 -3
|
@@ -0,0 +1,306 @@
|
|
|
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 { commandOutput, resolveProjectCwd, run, runNocoBaseCommand } from './run-npm.js';
|
|
11
|
+
import { DEFAULT_SOURCE_REGISTRY_PORT, parseSourceRegistryUrl, resolveSourceRegistryInfo } from './source-registry.js';
|
|
12
|
+
function trimValue(value) {
|
|
13
|
+
return String(value ?? '').trim();
|
|
14
|
+
}
|
|
15
|
+
function sanitizeEnvSegment(value) {
|
|
16
|
+
return trimValue(value).replace(/[^A-Za-z0-9]+/g, '').slice(0, 16);
|
|
17
|
+
}
|
|
18
|
+
export async function resolveSourcePublishRegistry(explicitRegistry) {
|
|
19
|
+
const normalized = trimValue(explicitRegistry);
|
|
20
|
+
if (normalized) {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
const info = await resolveSourceRegistryInfo();
|
|
24
|
+
if (info.status === 'running') {
|
|
25
|
+
return info.url;
|
|
26
|
+
}
|
|
27
|
+
throw new Error([
|
|
28
|
+
'No npm registry was provided for source publish.',
|
|
29
|
+
'Start the local source registry with `nb source registry start`, or pass `--npm-registry <url>` explicitly.',
|
|
30
|
+
].join('\n'));
|
|
31
|
+
}
|
|
32
|
+
export async function resolveGitSha(cwd) {
|
|
33
|
+
return await commandOutput('git', ['rev-parse', '--short', 'HEAD'], {
|
|
34
|
+
cwd,
|
|
35
|
+
errorName: 'git rev-parse',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function readRootVersion(cwd) {
|
|
39
|
+
const root = resolveProjectCwd(cwd);
|
|
40
|
+
const { readFile } = await import('node:fs/promises');
|
|
41
|
+
const content = await readFile(path.join(root, 'lerna.json'), 'utf8');
|
|
42
|
+
const parsed = JSON.parse(content);
|
|
43
|
+
const version = trimValue(parsed.version);
|
|
44
|
+
if (!version) {
|
|
45
|
+
throw new Error(`Couldn't read a version from ${path.join(root, 'lerna.json')}.`);
|
|
46
|
+
}
|
|
47
|
+
return version;
|
|
48
|
+
}
|
|
49
|
+
export function buildSnapshotVersion(baseVersion, gitSha, now = new Date()) {
|
|
50
|
+
const yyyy = String(now.getFullYear()).padStart(4, '0');
|
|
51
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
52
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
53
|
+
return `${baseVersion}-snapshot.${yyyy}${mm}${dd}.${gitSha}`;
|
|
54
|
+
}
|
|
55
|
+
export async function resolveGitBranch(cwd) {
|
|
56
|
+
const branch = trimValue(await commandOutput('git', ['branch', '--show-current'], {
|
|
57
|
+
cwd,
|
|
58
|
+
errorName: 'git branch --show-current',
|
|
59
|
+
}));
|
|
60
|
+
if (!branch) {
|
|
61
|
+
throw new Error('`nb source publish --snapshot` requires a named Git branch. Detached HEAD is not supported.');
|
|
62
|
+
}
|
|
63
|
+
return branch;
|
|
64
|
+
}
|
|
65
|
+
export async function hasLocalGitChanges(cwd) {
|
|
66
|
+
const output = await commandOutput('git', ['status', '--short', '--untracked-files=all'], {
|
|
67
|
+
cwd,
|
|
68
|
+
errorName: 'git status',
|
|
69
|
+
});
|
|
70
|
+
return trimValue(output).length > 0;
|
|
71
|
+
}
|
|
72
|
+
export function buildSourcePublishBranchName(gitSha, now = new Date()) {
|
|
73
|
+
const yyyy = String(now.getFullYear()).padStart(4, '0');
|
|
74
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
75
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
76
|
+
const hh = String(now.getHours()).padStart(2, '0');
|
|
77
|
+
const mi = String(now.getMinutes()).padStart(2, '0');
|
|
78
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
79
|
+
return `nb/source-publish-${yyyy}${mm}${dd}${hh}${mi}${ss}-${gitSha}`;
|
|
80
|
+
}
|
|
81
|
+
async function runGit(args, options) {
|
|
82
|
+
await run('git', args, {
|
|
83
|
+
cwd: options?.cwd,
|
|
84
|
+
stdio: options?.stdio,
|
|
85
|
+
env: options?.env,
|
|
86
|
+
errorName: options?.errorName ?? `git ${args.join(' ')}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function commitSourceSnapshotVersion(params) {
|
|
90
|
+
await runGit(['add', '-A'], {
|
|
91
|
+
cwd: params.cwd,
|
|
92
|
+
stdio: params.stdio,
|
|
93
|
+
errorName: 'git add',
|
|
94
|
+
});
|
|
95
|
+
await runGit(['commit', '--no-verify', '-m', `chore(source-publish): ${params.version}`], {
|
|
96
|
+
cwd: params.cwd,
|
|
97
|
+
stdio: params.stdio,
|
|
98
|
+
errorName: 'git commit',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function buildSourceSnapshot(params) {
|
|
102
|
+
const args = ['build'];
|
|
103
|
+
if (!params.buildDts) {
|
|
104
|
+
args.push('--no-dts');
|
|
105
|
+
}
|
|
106
|
+
await runNocoBaseCommand(args, {
|
|
107
|
+
cwd: params.cwd,
|
|
108
|
+
stdio: params.stdio,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function createSourcePublishStash(params) {
|
|
112
|
+
if (!(await hasLocalGitChanges(params.cwd))) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
await runGit(['stash', 'push', '-u', '-m', params.label], {
|
|
116
|
+
cwd: params.cwd,
|
|
117
|
+
stdio: params.stdio,
|
|
118
|
+
errorName: 'git stash push',
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
commit: trimValue(await commandOutput('git', ['rev-parse', '--verify', 'refs/stash'], {
|
|
122
|
+
cwd: params.cwd,
|
|
123
|
+
errorName: 'git rev-parse refs/stash',
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function resolveSourcePublishStashReference(params) {
|
|
128
|
+
const output = trimValue(await commandOutput('git', ['stash', 'list', '--format=%gd%x00%H'], {
|
|
129
|
+
cwd: params.cwd,
|
|
130
|
+
errorName: 'git stash list',
|
|
131
|
+
}));
|
|
132
|
+
for (const line of output.split('\n')) {
|
|
133
|
+
const [reference, commit] = line.split('\x00');
|
|
134
|
+
if (trimValue(commit) === params.stash.commit) {
|
|
135
|
+
return trimValue(reference);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`Couldn't locate the saved stash for source publish: ${params.stash.commit}`);
|
|
139
|
+
}
|
|
140
|
+
function buildSourcePublishRecoveryError(params) {
|
|
141
|
+
const originalMessage = params.originalError instanceof Error
|
|
142
|
+
? params.originalError.message
|
|
143
|
+
: String(params.originalError);
|
|
144
|
+
const cleanupMessage = params.cleanupError instanceof Error
|
|
145
|
+
? params.cleanupError.message
|
|
146
|
+
: String(params.cleanupError);
|
|
147
|
+
const recoveryHints = [
|
|
148
|
+
`Project root: ${params.projectRoot}`,
|
|
149
|
+
`Temporary branch: ${params.temporaryBranch}`,
|
|
150
|
+
];
|
|
151
|
+
if (params.stash) {
|
|
152
|
+
recoveryHints.push(`Saved stash commit: ${params.stash.commit}`);
|
|
153
|
+
}
|
|
154
|
+
return new Error([
|
|
155
|
+
originalMessage,
|
|
156
|
+
'',
|
|
157
|
+
'Cleanup also failed after the publish attempt.',
|
|
158
|
+
`Cleanup error: ${cleanupMessage}`,
|
|
159
|
+
...recoveryHints,
|
|
160
|
+
].join('\n'));
|
|
161
|
+
}
|
|
162
|
+
export async function publishSourceSnapshot(params) {
|
|
163
|
+
const projectRoot = resolveProjectCwd(params.cwd);
|
|
164
|
+
const npmRegistry = await resolveSourcePublishRegistry(params.npmRegistry);
|
|
165
|
+
const originalBranch = await resolveGitBranch(projectRoot);
|
|
166
|
+
const gitSha = trimValue(await resolveGitSha(projectRoot));
|
|
167
|
+
const baseVersion = await readRootVersion(projectRoot);
|
|
168
|
+
const version = buildSnapshotVersion(baseVersion, gitSha, params.now);
|
|
169
|
+
const temporaryBranch = buildSourcePublishBranchName(gitSha, params.now);
|
|
170
|
+
const stdio = params.verbose ? 'inherit' : 'ignore';
|
|
171
|
+
const shouldBuild = params.build !== false;
|
|
172
|
+
const shouldBuildDts = params.buildDts !== false;
|
|
173
|
+
let stash;
|
|
174
|
+
let onTemporaryBranch = false;
|
|
175
|
+
let branchCreated = false;
|
|
176
|
+
let publishError;
|
|
177
|
+
let result;
|
|
178
|
+
try {
|
|
179
|
+
stash = await createSourcePublishStash({
|
|
180
|
+
cwd: projectRoot,
|
|
181
|
+
label: temporaryBranch,
|
|
182
|
+
stdio,
|
|
183
|
+
});
|
|
184
|
+
await runGit(['switch', '-c', temporaryBranch], {
|
|
185
|
+
cwd: projectRoot,
|
|
186
|
+
stdio,
|
|
187
|
+
errorName: 'git switch',
|
|
188
|
+
});
|
|
189
|
+
branchCreated = true;
|
|
190
|
+
onTemporaryBranch = true;
|
|
191
|
+
if (stash) {
|
|
192
|
+
await runGit(['stash', 'apply', '--index', await resolveSourcePublishStashReference({
|
|
193
|
+
cwd: projectRoot,
|
|
194
|
+
stash,
|
|
195
|
+
})], {
|
|
196
|
+
cwd: projectRoot,
|
|
197
|
+
stdio,
|
|
198
|
+
errorName: 'git stash apply',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (shouldBuild) {
|
|
202
|
+
await buildSourceSnapshot({
|
|
203
|
+
cwd: projectRoot,
|
|
204
|
+
buildDts: shouldBuildDts,
|
|
205
|
+
stdio,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
await run('yarn', ['lerna', 'version', version, '--force-publish=*', '--no-git-tag-version', '-y'], {
|
|
209
|
+
cwd: projectRoot,
|
|
210
|
+
errorName: 'lerna version',
|
|
211
|
+
stdio,
|
|
212
|
+
});
|
|
213
|
+
await commitSourceSnapshotVersion({
|
|
214
|
+
cwd: projectRoot,
|
|
215
|
+
version,
|
|
216
|
+
stdio,
|
|
217
|
+
});
|
|
218
|
+
await run('yarn', ['lerna', 'publish', 'from-package', '--registry', npmRegistry, '--dist-tag', 'local', '--yes', '--no-verify-access', '--git-head', gitSha], {
|
|
219
|
+
cwd: projectRoot,
|
|
220
|
+
errorName: 'lerna publish',
|
|
221
|
+
stdio,
|
|
222
|
+
env: {
|
|
223
|
+
npm_config_registry: npmRegistry,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
result = {
|
|
227
|
+
version,
|
|
228
|
+
npmRegistry,
|
|
229
|
+
gitSha,
|
|
230
|
+
projectRoot,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
publishError = error;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
if (onTemporaryBranch) {
|
|
238
|
+
await runGit(['reset', '--hard', 'HEAD'], {
|
|
239
|
+
cwd: projectRoot,
|
|
240
|
+
stdio,
|
|
241
|
+
errorName: 'git reset --hard',
|
|
242
|
+
});
|
|
243
|
+
await runGit(['clean', '-fd'], {
|
|
244
|
+
cwd: projectRoot,
|
|
245
|
+
stdio,
|
|
246
|
+
errorName: 'git clean -fd',
|
|
247
|
+
});
|
|
248
|
+
await runGit(['switch', originalBranch], {
|
|
249
|
+
cwd: projectRoot,
|
|
250
|
+
stdio,
|
|
251
|
+
errorName: 'git switch',
|
|
252
|
+
});
|
|
253
|
+
onTemporaryBranch = false;
|
|
254
|
+
}
|
|
255
|
+
if (stash) {
|
|
256
|
+
await runGit(['stash', 'pop', '--index', await resolveSourcePublishStashReference({
|
|
257
|
+
cwd: projectRoot,
|
|
258
|
+
stash,
|
|
259
|
+
})], {
|
|
260
|
+
cwd: projectRoot,
|
|
261
|
+
stdio,
|
|
262
|
+
errorName: 'git stash pop',
|
|
263
|
+
});
|
|
264
|
+
stash = undefined;
|
|
265
|
+
}
|
|
266
|
+
if (branchCreated) {
|
|
267
|
+
await runGit(['branch', '-D', temporaryBranch], {
|
|
268
|
+
cwd: projectRoot,
|
|
269
|
+
stdio,
|
|
270
|
+
errorName: 'git branch -D',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (cleanupError) {
|
|
275
|
+
if (publishError) {
|
|
276
|
+
throw buildSourcePublishRecoveryError({
|
|
277
|
+
originalError: publishError,
|
|
278
|
+
cleanupError,
|
|
279
|
+
stash,
|
|
280
|
+
temporaryBranch,
|
|
281
|
+
projectRoot,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
throw new Error([
|
|
285
|
+
'The source snapshot was published, but local Git cleanup failed afterwards.',
|
|
286
|
+
`Cleanup error: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`,
|
|
287
|
+
`Project root: ${projectRoot}`,
|
|
288
|
+
`Temporary branch: ${temporaryBranch}`,
|
|
289
|
+
...(stash ? [`Saved stash commit: ${stash.commit}`] : []),
|
|
290
|
+
].join('\n'));
|
|
291
|
+
}
|
|
292
|
+
if (publishError) {
|
|
293
|
+
throw publishError;
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
export function buildSuggestedInitCommand(result) {
|
|
298
|
+
const { host, port } = parseSourceRegistryUrl(result.npmRegistry);
|
|
299
|
+
const normalizedRegistry = result.npmRegistry || `http://${host}:${port || DEFAULT_SOURCE_REGISTRY_PORT}`;
|
|
300
|
+
const suggestedEnv = ['snapshot', sanitizeEnvSegment(result.gitSha)].filter(Boolean).join('');
|
|
301
|
+
return [
|
|
302
|
+
`nb init --env ${suggestedEnv} --yes --source npm`,
|
|
303
|
+
`--version ${result.version}`,
|
|
304
|
+
`--npm-registry=${normalizedRegistry}`,
|
|
305
|
+
].join(' ');
|
|
306
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
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 fsp from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { commandOutput, commandSucceeds, resolveCwd, resolveProjectCwd, run } from './run-npm.js';
|
|
12
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
13
|
+
export const DEFAULT_SOURCE_REGISTRY_HOST = '127.0.0.1';
|
|
14
|
+
export const DEFAULT_SOURCE_REGISTRY_PORT = 4873;
|
|
15
|
+
export const DEFAULT_SOURCE_REGISTRY_CONTAINER_NAME = 'nb-source-registry';
|
|
16
|
+
export const DEFAULT_SOURCE_REGISTRY_IMAGE = 'verdaccio/verdaccio';
|
|
17
|
+
export function parseSourceRegistryUrl(url) {
|
|
18
|
+
const parsed = new URL(url);
|
|
19
|
+
const host = trimValue(parsed.hostname) || DEFAULT_SOURCE_REGISTRY_HOST;
|
|
20
|
+
const portText = trimValue(parsed.port);
|
|
21
|
+
const port = portText ? Number(portText) : DEFAULT_SOURCE_REGISTRY_PORT;
|
|
22
|
+
return {
|
|
23
|
+
host,
|
|
24
|
+
port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SOURCE_REGISTRY_PORT,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function trimValue(value) {
|
|
28
|
+
return String(value ?? '').trim();
|
|
29
|
+
}
|
|
30
|
+
function asPosixPathForDockerMount(value) {
|
|
31
|
+
return resolveCwd(value).replace(/\\/g, '/');
|
|
32
|
+
}
|
|
33
|
+
export function resolveSourceRegistryRootDir() {
|
|
34
|
+
return path.join(resolveCliHomeDir(), 'verdaccio');
|
|
35
|
+
}
|
|
36
|
+
export function resolveSourceRegistryConfigPath() {
|
|
37
|
+
return path.join(resolveSourceRegistryRootDir(), 'config.yaml');
|
|
38
|
+
}
|
|
39
|
+
export function resolveSourceRegistryStorageDir() {
|
|
40
|
+
return path.join(resolveSourceRegistryRootDir(), 'storage');
|
|
41
|
+
}
|
|
42
|
+
export function resolveSourceRegistryUrl(host = DEFAULT_SOURCE_REGISTRY_HOST, port = DEFAULT_SOURCE_REGISTRY_PORT) {
|
|
43
|
+
return `http://${host}:${port}`;
|
|
44
|
+
}
|
|
45
|
+
export function getSourceRegistryInfo() {
|
|
46
|
+
const host = DEFAULT_SOURCE_REGISTRY_HOST;
|
|
47
|
+
const port = DEFAULT_SOURCE_REGISTRY_PORT;
|
|
48
|
+
const rootDir = resolveSourceRegistryRootDir();
|
|
49
|
+
return {
|
|
50
|
+
containerName: DEFAULT_SOURCE_REGISTRY_CONTAINER_NAME,
|
|
51
|
+
image: DEFAULT_SOURCE_REGISTRY_IMAGE,
|
|
52
|
+
host,
|
|
53
|
+
port,
|
|
54
|
+
url: resolveSourceRegistryUrl(host, port),
|
|
55
|
+
rootDir,
|
|
56
|
+
configPath: resolveSourceRegistryConfigPath(),
|
|
57
|
+
storageDir: resolveSourceRegistryStorageDir(),
|
|
58
|
+
status: 'missing',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function resolveSourceRegistryTemplatePath(cwd) {
|
|
62
|
+
return path.join(resolveProjectCwd(cwd), 'config.yaml');
|
|
63
|
+
}
|
|
64
|
+
function applySourceRegistryTemplateOverrides(template) {
|
|
65
|
+
return template
|
|
66
|
+
.replace(/\r\n/g, '\n')
|
|
67
|
+
.replace(/^storage:\s*.+$/m, 'storage: /verdaccio/storage')
|
|
68
|
+
.replace(/^(\s*)file:\s*\.\/*htpasswd\s*$/m, '$1file: /verdaccio/storage/htpasswd')
|
|
69
|
+
.replace(/^(\s*)publish:\s+\$authenticated\s*$/gm, '$1publish: $all')
|
|
70
|
+
.replace(/^(\s*)unpublish:\s+\$authenticated\s*$/gm, '$1unpublish: $all');
|
|
71
|
+
}
|
|
72
|
+
async function buildFallbackSourceRegistryConfigTemplate() {
|
|
73
|
+
return [
|
|
74
|
+
'storage: ./storage',
|
|
75
|
+
'auth:',
|
|
76
|
+
' htpasswd:',
|
|
77
|
+
' file: ./htpasswd',
|
|
78
|
+
'uplinks:',
|
|
79
|
+
' npmjs:',
|
|
80
|
+
' url: https://registry.npmmirror.com/',
|
|
81
|
+
'packages:',
|
|
82
|
+
" '@*/*':",
|
|
83
|
+
' access: $all',
|
|
84
|
+
' publish: $authenticated',
|
|
85
|
+
' unpublish: $authenticated',
|
|
86
|
+
' proxy: npmjs',
|
|
87
|
+
" '**':",
|
|
88
|
+
' access: $all',
|
|
89
|
+
' publish: $authenticated',
|
|
90
|
+
' unpublish: $authenticated',
|
|
91
|
+
' proxy: npmjs',
|
|
92
|
+
'server:',
|
|
93
|
+
' keepAliveTimeout: 60',
|
|
94
|
+
' dotfiles: ignore',
|
|
95
|
+
'max_body_size: 100mb',
|
|
96
|
+
'middlewares:',
|
|
97
|
+
' audit:',
|
|
98
|
+
' enabled: true',
|
|
99
|
+
'',
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
export async function buildSourceRegistryConfig(cwd) {
|
|
103
|
+
const templatePath = resolveSourceRegistryTemplatePath(cwd);
|
|
104
|
+
try {
|
|
105
|
+
const template = await fsp.readFile(templatePath, 'utf8');
|
|
106
|
+
return applySourceRegistryTemplateOverrides(template);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
const fallback = await buildFallbackSourceRegistryConfigTemplate();
|
|
110
|
+
return applySourceRegistryTemplateOverrides(fallback);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function ensureSourceRegistryFiles(cwd) {
|
|
114
|
+
const info = getSourceRegistryInfo();
|
|
115
|
+
await fsp.mkdir(info.storageDir, { recursive: true });
|
|
116
|
+
await fsp.mkdir(info.rootDir, { recursive: true });
|
|
117
|
+
await fsp.writeFile(info.configPath, await buildSourceRegistryConfig(cwd), 'utf8');
|
|
118
|
+
return info;
|
|
119
|
+
}
|
|
120
|
+
export async function sourceRegistryContainerExists(containerName = DEFAULT_SOURCE_REGISTRY_CONTAINER_NAME) {
|
|
121
|
+
return await commandSucceeds('docker', ['container', 'inspect', containerName]);
|
|
122
|
+
}
|
|
123
|
+
export async function sourceRegistryContainerIsRunning(containerName = DEFAULT_SOURCE_REGISTRY_CONTAINER_NAME) {
|
|
124
|
+
try {
|
|
125
|
+
const output = await commandOutput('docker', ['inspect', '--format', '{{.State.Running}}', containerName], { errorName: 'docker inspect' });
|
|
126
|
+
return trimValue(output) === 'true';
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export async function resolveSourceRegistryInfo() {
|
|
133
|
+
const base = getSourceRegistryInfo();
|
|
134
|
+
if (!(await sourceRegistryContainerExists(base.containerName))) {
|
|
135
|
+
return base;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...base,
|
|
139
|
+
status: (await sourceRegistryContainerIsRunning(base.containerName)) ? 'running' : 'stopped',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export async function startSourceRegistry(options) {
|
|
143
|
+
const info = await ensureSourceRegistryFiles(options?.cwd);
|
|
144
|
+
const exists = await sourceRegistryContainerExists(info.containerName);
|
|
145
|
+
if (exists) {
|
|
146
|
+
if (await sourceRegistryContainerIsRunning(info.containerName)) {
|
|
147
|
+
return 'already-running';
|
|
148
|
+
}
|
|
149
|
+
await run('docker', ['start', info.containerName], {
|
|
150
|
+
errorName: 'docker start',
|
|
151
|
+
stdio: options?.stdio,
|
|
152
|
+
});
|
|
153
|
+
return 'started';
|
|
154
|
+
}
|
|
155
|
+
const configMount = `${asPosixPathForDockerMount(info.configPath)}:/verdaccio/conf/config.yaml`;
|
|
156
|
+
const storageMount = `${asPosixPathForDockerMount(info.storageDir)}:/verdaccio/storage`;
|
|
157
|
+
await run('docker', [
|
|
158
|
+
'run',
|
|
159
|
+
'-d',
|
|
160
|
+
'--name',
|
|
161
|
+
info.containerName,
|
|
162
|
+
'-p',
|
|
163
|
+
`${info.port}:4873`,
|
|
164
|
+
'-v',
|
|
165
|
+
configMount,
|
|
166
|
+
'-v',
|
|
167
|
+
storageMount,
|
|
168
|
+
info.image,
|
|
169
|
+
], {
|
|
170
|
+
errorName: 'docker run',
|
|
171
|
+
stdio: options?.stdio,
|
|
172
|
+
});
|
|
173
|
+
return 'started';
|
|
174
|
+
}
|
|
175
|
+
export async function stopSourceRegistry(options) {
|
|
176
|
+
const info = getSourceRegistryInfo();
|
|
177
|
+
if (!(await sourceRegistryContainerExists(info.containerName))) {
|
|
178
|
+
return 'already-stopped';
|
|
179
|
+
}
|
|
180
|
+
if (!(await sourceRegistryContainerIsRunning(info.containerName))) {
|
|
181
|
+
return 'already-stopped';
|
|
182
|
+
}
|
|
183
|
+
await run('docker', ['stop', info.containerName], {
|
|
184
|
+
errorName: 'docker stop',
|
|
185
|
+
stdio: options?.stdio,
|
|
186
|
+
});
|
|
187
|
+
return 'stopped';
|
|
188
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.36",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
"self": {
|
|
68
68
|
"description": "Inspect or update the NocoBase CLI itself."
|
|
69
69
|
},
|
|
70
|
+
"backup": {
|
|
71
|
+
"description": "Create and restore NocoBase backups."
|
|
72
|
+
},
|
|
70
73
|
"skills": {
|
|
71
74
|
"description": "Inspect or synchronize NocoBase AI coding skills for the current workspace."
|
|
72
75
|
},
|
|
@@ -93,7 +96,7 @@
|
|
|
93
96
|
"ora": "^8.2.0",
|
|
94
97
|
"pg": "^8.14.1",
|
|
95
98
|
"picocolors": "^1.1.1",
|
|
96
|
-
"tar": "^7.
|
|
99
|
+
"tar": "^7.5.15"
|
|
97
100
|
},
|
|
98
101
|
"devDependencies": {
|
|
99
102
|
"@types/node": "^18.19.130",
|
|
@@ -105,5 +108,5 @@
|
|
|
105
108
|
"type": "git",
|
|
106
109
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
107
110
|
},
|
|
108
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "397d45c744f6eb48b3a0cd785c87cbf1257c3513"
|
|
109
112
|
}
|