@nocobase/cli 2.1.3 → 2.1.4-test.1
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.
|
@@ -143,6 +143,7 @@ export default class AppDestroy extends Command {
|
|
|
143
143
|
}
|
|
144
144
|
announceTargetEnv(runtime.envName);
|
|
145
145
|
try {
|
|
146
|
+
const retryCommand = `nb env remove ${runtime.envName} --purge --force`;
|
|
146
147
|
if (runtime.kind === 'docker') {
|
|
147
148
|
startTask(`Removing Docker app container for "${runtime.envName}"...`);
|
|
148
149
|
const state = await removeDockerContainerIfExists(runtime.containerName, {
|
|
@@ -192,7 +193,7 @@ export default class AppDestroy extends Command {
|
|
|
192
193
|
const localAppPath = resolveManagedLocalAppPath(runtime);
|
|
193
194
|
if (localAppPath && removesManagedLocalAppFiles) {
|
|
194
195
|
startTask(`Removing managed local app files for "${runtime.envName}"...`);
|
|
195
|
-
await removePathIfExists(localAppPath, `managed app files for "${runtime.envName}"
|
|
196
|
+
await removePathIfExists(localAppPath, `managed app files for "${runtime.envName}"`, { retryCommand });
|
|
196
197
|
succeedTask(`Managed local app files removed for "${runtime.envName}".`);
|
|
197
198
|
}
|
|
198
199
|
else {
|
|
@@ -206,13 +207,13 @@ export default class AppDestroy extends Command {
|
|
|
206
207
|
];
|
|
207
208
|
startTask(`Removing proxy entry files for "${runtime.envName}"...`);
|
|
208
209
|
for (const proxyEntryDir of proxyEntryDirs) {
|
|
209
|
-
await removePathIfExists(proxyEntryDir, `proxy entry files for "${runtime.envName}"
|
|
210
|
+
await removePathIfExists(proxyEntryDir, `proxy entry files for "${runtime.envName}"`, { retryCommand });
|
|
210
211
|
}
|
|
211
212
|
succeedTask(`Proxy entry files removed for "${runtime.envName}".`);
|
|
212
213
|
const configuredStoragePath = resolveConfiguredStoragePath(runtime.env.config);
|
|
213
214
|
if (configuredStoragePath) {
|
|
214
215
|
startTask(`Removing storage data for "${runtime.envName}"...`);
|
|
215
|
-
await removePathIfExists(configuredStoragePath, `storage data for "${runtime.envName}"
|
|
216
|
+
await removePathIfExists(configuredStoragePath, `storage data for "${runtime.envName}"`, { retryCommand });
|
|
216
217
|
succeedTask(`Storage data removed for "${runtime.envName}".`);
|
|
217
218
|
}
|
|
218
219
|
else {
|
|
@@ -25,10 +25,54 @@ function assertSafeRemovalPath(target, label) {
|
|
|
25
25
|
throw new Error(`Refusing to remove ${label} at "${resolved}" because it is too broad.`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
function getErrorCode(error) {
|
|
29
|
+
if (!(error instanceof Error)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const { code } = error;
|
|
33
|
+
return typeof code === 'string' ? code : undefined;
|
|
34
|
+
}
|
|
35
|
+
function isPermissionDeniedError(error) {
|
|
36
|
+
const code = getErrorCode(error);
|
|
37
|
+
return code === 'EACCES' || code === 'EPERM';
|
|
38
|
+
}
|
|
39
|
+
function formatOriginalError(error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
const code = getErrorCode(error);
|
|
42
|
+
return code && !message.includes(code) ? `${code}: ${message}` : message;
|
|
43
|
+
}
|
|
44
|
+
function quoteShellValue(value) {
|
|
45
|
+
return `"${value.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
46
|
+
}
|
|
47
|
+
function formatPermissionDeniedRemovalError(target, label, error, options) {
|
|
48
|
+
const retryLines = os.platform() === 'win32' ? [] : [` sudo chown -R "$(id -u):$(id -g)" ${quoteShellValue(target)}`];
|
|
49
|
+
if (options.retryCommand) {
|
|
50
|
+
retryLines.push(` ${options.retryCommand}`);
|
|
51
|
+
}
|
|
52
|
+
return [
|
|
53
|
+
`Failed to remove ${label} at "${target}".`,
|
|
54
|
+
'The current user cannot delete one or more files under this path. Files may have been created by a Docker container running as root.',
|
|
55
|
+
'',
|
|
56
|
+
retryLines.length > 0
|
|
57
|
+
? 'Fix ownership or permissions, then retry:'
|
|
58
|
+
: 'Fix ownership or permissions, then retry the command.',
|
|
59
|
+
...retryLines,
|
|
60
|
+
'',
|
|
61
|
+
`Original error: ${formatOriginalError(error)}`,
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
export async function removePathIfExists(target, label, options = {}) {
|
|
29
65
|
const resolved = path.resolve(target);
|
|
30
66
|
assertSafeRemovalPath(resolved, label);
|
|
31
|
-
|
|
67
|
+
try {
|
|
68
|
+
await fsp.rm(resolved, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isPermissionDeniedError(error)) {
|
|
72
|
+
throw new Error(formatPermissionDeniedRemovalError(resolved, label, error, options));
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
32
76
|
}
|
|
33
77
|
function isMissingDockerContainerError(error) {
|
|
34
78
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -116,7 +160,9 @@ export function managedDockerNetworkName(runtime) {
|
|
|
116
160
|
return runtime.dockerNetworkName?.trim() || runtime.workspaceName?.trim() || undefined;
|
|
117
161
|
}
|
|
118
162
|
export function resolveManagedLocalAppPath(runtime) {
|
|
119
|
-
return resolveConfiguredAppPath(runtime.env.config) ||
|
|
163
|
+
return (resolveConfiguredAppPath(runtime.env.config) ||
|
|
164
|
+
runtime.projectRoot ||
|
|
165
|
+
resolveConfiguredPath(runtime.env.config.appRootPath));
|
|
120
166
|
}
|
|
121
167
|
export function shouldRemoveManagedLocalAppFiles(runtime) {
|
|
122
168
|
return runtime.source === 'npm' || runtime.source === 'git' || runtime.source === 'local';
|
|
@@ -143,11 +143,14 @@ export default class LicenseActivate extends Command {
|
|
|
143
143
|
const validation = await validateLicenseKey(runtime, resolvedKey);
|
|
144
144
|
const ok = !validation.keyStatus && validation.envMatch && validation.domainMatch && validation.licenseStatus === 'active';
|
|
145
145
|
const licenseKeyPath = ok ? await saveLicenseKey(runtime, resolvedKey) : resolveLicenseKeyFile(runtime);
|
|
146
|
+
const shouldResolveInstanceId = Boolean(interactiveKeyFlowInstanceId || ok || (flags.json && !validation.keyStatus));
|
|
146
147
|
const payload = {
|
|
147
148
|
ok,
|
|
148
149
|
env: runtime.envName,
|
|
149
150
|
kind: runtime.kind,
|
|
150
|
-
instanceId:
|
|
151
|
+
instanceId: shouldResolveInstanceId
|
|
152
|
+
? interactiveKeyFlowInstanceId ?? (await ensureInstanceId(runtime))
|
|
153
|
+
: undefined,
|
|
151
154
|
mode: 'key',
|
|
152
155
|
key: redactLicenseKey(resolvedKey),
|
|
153
156
|
keyFile: keyFile || undefined,
|
|
@@ -10,8 +10,8 @@ import { Flags } from '@oclif/core';
|
|
|
10
10
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
|
|
13
|
-
import { checkExternalDbConnection, readExternalDbConnectionConfig
|
|
14
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef
|
|
13
|
+
import { checkExternalDbConnection, readExternalDbConnectionConfig } from "../../lib/db-connection-check.js";
|
|
14
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../../lib/docker-image.js";
|
|
15
15
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
16
16
|
import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
|
|
17
17
|
import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
|
|
@@ -102,12 +102,7 @@ function buildDockerLicenseDbFlagArgs(envVars) {
|
|
|
102
102
|
];
|
|
103
103
|
}
|
|
104
104
|
async function runDockerLicenseJsonCommand(runtime, commandArgs) {
|
|
105
|
-
const args = [
|
|
106
|
-
'run',
|
|
107
|
-
'--rm',
|
|
108
|
-
'--network',
|
|
109
|
-
runtime.dockerNetworkName || runtime.workspaceName,
|
|
110
|
-
];
|
|
105
|
+
const args = ['run', '--rm', '--network', runtime.dockerNetworkName || runtime.workspaceName];
|
|
111
106
|
const dockerPlatform = normalizeDockerPlatform(runtime.env.config?.dockerPlatform);
|
|
112
107
|
if (dockerPlatform) {
|
|
113
108
|
args.push('--platform', dockerPlatform);
|
|
@@ -193,11 +188,11 @@ export async function generateValidatedInstanceIdFromEnvVars(envVars) {
|
|
|
193
188
|
}
|
|
194
189
|
async function generateInstanceIdForDockerRuntime(runtime) {
|
|
195
190
|
const envVars = await buildRuntimeEnvVars(runtime);
|
|
196
|
-
const payload = await runDockerLicenseJsonCommand(runtime, [
|
|
191
|
+
const payload = (await runDockerLicenseJsonCommand(runtime, [
|
|
197
192
|
'license',
|
|
198
193
|
'generate-id',
|
|
199
194
|
...buildDockerLicenseDbFlagArgs(envVars),
|
|
200
|
-
]);
|
|
195
|
+
]));
|
|
201
196
|
const instanceId = trimValue(payload.instanceId);
|
|
202
197
|
if (!instanceId) {
|
|
203
198
|
throw new Error('Docker instance ID generation did not return an instance ID.');
|
|
@@ -337,7 +332,7 @@ export async function getLicenseStatus(keyData) {
|
|
|
337
332
|
}),
|
|
338
333
|
signal: controller.signal,
|
|
339
334
|
});
|
|
340
|
-
const payload = await response.json();
|
|
335
|
+
const payload = (await response.json());
|
|
341
336
|
return payload?.data?.status === 'active' ? 'active' : 'invalid';
|
|
342
337
|
}
|
|
343
338
|
catch {
|
|
@@ -356,6 +351,22 @@ export async function validateLicenseKey(runtime, key) {
|
|
|
356
351
|
catch {
|
|
357
352
|
keyStatus = 'invalid';
|
|
358
353
|
}
|
|
354
|
+
if (keyStatus) {
|
|
355
|
+
const currentDomain = appUrl(runtime);
|
|
356
|
+
return {
|
|
357
|
+
current: {
|
|
358
|
+
env: undefined,
|
|
359
|
+
domain: currentDomain ? new URL(currentDomain).host : '',
|
|
360
|
+
},
|
|
361
|
+
keyData,
|
|
362
|
+
keyStatus,
|
|
363
|
+
dbMatch: false,
|
|
364
|
+
sysMatch: false,
|
|
365
|
+
envMatch: false,
|
|
366
|
+
domainMatch: false,
|
|
367
|
+
licenseStatus: 'invalid',
|
|
368
|
+
};
|
|
369
|
+
}
|
|
359
370
|
const currentEnv = await getCurrentLicenseEnv(runtime);
|
|
360
371
|
const currentDomain = appUrl(runtime);
|
|
361
372
|
const dbMatch = isDbMatch(currentEnv, keyData);
|
|
@@ -391,7 +402,7 @@ export async function resolveLicenseServiceUrl(value) {
|
|
|
391
402
|
return (await resolveLicensePkgUrl(value)).replace(/\/+$/, '');
|
|
392
403
|
}
|
|
393
404
|
export async function resolveLicensePkgUrl(value) {
|
|
394
|
-
const normalized = String(value ?? '').trim() || await resolveLicensePkgUrlFromConfig();
|
|
405
|
+
const normalized = String(value ?? '').trim() || (await resolveLicensePkgUrlFromConfig());
|
|
395
406
|
return normalized.replace(/\/+$/, '') + '/';
|
|
396
407
|
}
|
|
397
408
|
function shouldRedactOutputKey(key) {
|
|
@@ -414,9 +425,7 @@ export function sanitizeLicenseOutput(value) {
|
|
|
414
425
|
if (value && typeof value === 'object') {
|
|
415
426
|
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
|
|
416
427
|
key,
|
|
417
|
-
shouldRedactOutputKey(key)
|
|
418
|
-
? redactOutputValue(String(nestedValue ?? ''))
|
|
419
|
-
: sanitizeLicenseOutput(nestedValue),
|
|
428
|
+
shouldRedactOutputKey(key) ? redactOutputValue(String(nestedValue ?? '')) : sanitizeLicenseOutput(nestedValue),
|
|
420
429
|
]));
|
|
421
430
|
}
|
|
422
431
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4-test.1",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -142,6 +142,5 @@
|
|
|
142
142
|
"repository": {
|
|
143
143
|
"type": "git",
|
|
144
144
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
145
|
-
}
|
|
146
|
-
"gitHead": "f61e75119a74bbac25879f4edb8cf9913c99098a"
|
|
145
|
+
}
|
|
147
146
|
}
|