@qmuse/qmuse-cli 1.0.3-dev.4 → 1.0.3-dev.5
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/README.md +2 -2
- package/dist/cli.js +73 -18
- package/dist/cloud.js +3 -0
- package/dist/dev/index.js +6 -0
- package/dist/import/diagnostic.js +19 -1
- package/dist/import/guidance.js +35 -0
- package/dist/import/project.js +26 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ qmuse import /path/to/react-project --space-id <spaceId>
|
|
|
50
50
|
|
|
51
51
|
### 更新已有应用
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
没有关联应用时,首次导入会创建新应用并自动保存本地绑定。之后在同一项目目录执行 `qmuse import .`,即可更新对应应用。请保留 `.qmuse/project.json`,从 QMuse 下载且带有绑定的项目也使用相同命令更新。
|
|
54
54
|
|
|
55
55
|
平台已有新版本时,终端会询问是否用本地全部源码覆盖,不会自动合并平台修改。确认覆盖可执行 `qmuse import . --force`;脚本或 `--json` 模式须显式指定该参数。覆盖会在原应用创建新版本,仍需通过正常导入校验。
|
|
56
56
|
|
|
@@ -101,7 +101,7 @@ CLI 将本地 Runtime 的 `__MUSE__.domain` 设置为实际监听地址(如 `h
|
|
|
101
101
|
|
|
102
102
|
服务端必须先部署 `/api/appwrite/local-dev/context` 和 `/api/appwrite/local-dev/mutation-events`,再发布并安装新版 CLI;缺少接口时不会降级为模拟云服务。
|
|
103
103
|
|
|
104
|
-
页面改动热更新,Schema/函数改动继续执行 `qmuse import
|
|
104
|
+
页面改动热更新,Schema/函数改动继续执行 `qmuse import .`。CLI 启动期间设置 `QMUSE_HMR_GATE_ENABLED=false`,退出后恢复原值;项目的 `@qmuse/vite-config` 必须支持此开关,否则平台批量改码门禁仍会暂存本地修改。导入更新本地绑定后,连接会重新获取配置并刷新页面。切换登录账号或应用绑定需重启连接;退出 CLI 不会自动撤销浏览器已经建立的 AppBase Session。SDK 的匿名降级行为保持不变,验收必须检查实际 Session 用户,不能只看页面是否显示。
|
|
105
105
|
|
|
106
106
|
联调验收包括 CRUD、刷新读回、文件上传/预览、函数调用和权限拒绝;浏览器 Cookie 限制需在目标浏览器验证。`.muse-tmp`、`.qmuse` 和系统临时配置不进入导入包。
|
|
107
107
|
|
package/dist/cli.js
CHANGED
|
@@ -17,9 +17,10 @@ import { configureAppImport, getAppOpenUrl, getImportStatusUrl, queryAppImportSt
|
|
|
17
17
|
import { pollAppImport } from './import/poll.js';
|
|
18
18
|
import { preflightImport } from './import/preflight.js';
|
|
19
19
|
import { resolveUpdateBase } from './import/update.js';
|
|
20
|
-
import { clearPendingImport, completeManagedImport, loadManagedProject, saveManagedProject, setPendingImport, } from './import/project.js';
|
|
21
|
-
import { clearImportFailureDiagnostic, writeImportFailureDiagnostic, } from './import/diagnostic.js';
|
|
22
|
-
import { checkCloudInit, CLOUD_INIT_SUCCESS_MESSAGE } from './cloud.js';
|
|
20
|
+
import { clearPendingImport, completeManagedImport, createExternalProject, loadManagedProject, saveManagedProject, resolveProjectEnvironment, setPendingImport, } from './import/project.js';
|
|
21
|
+
import { clearImportFailureDiagnostic, writeImportFailureDiagnostic, importRetryGuidance, } from './import/diagnostic.js';
|
|
22
|
+
import { checkCloudInit, checkCloudEntitlement, CLOUD_INIT_SUCCESS_MESSAGE } from './cloud.js';
|
|
23
|
+
import { configurationGuidance } from './import/guidance.js';
|
|
23
24
|
import { QMUSE_CLI_VERSION } from './version.js';
|
|
24
25
|
import { startUpdateNotice } from './update-notice.js';
|
|
25
26
|
import { startDevelopment } from './dev/index.js';
|
|
@@ -191,11 +192,14 @@ async function pollImportWithLogs(jobId, cfg, flags) {
|
|
|
191
192
|
process.removeListener('SIGINT', onInterrupt);
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
|
-
async function resolveImportConfiguration(status, cfg, flags) {
|
|
195
|
+
async function resolveImportConfiguration(status, cfg, flags, cloudTarget) {
|
|
195
196
|
if (status.stage === 'QMUSE_CLOUD_ADAPTATION_REQUIRED')
|
|
196
197
|
return undefined;
|
|
197
198
|
const interactive = process.stdin.isTTY === true && flags['--json'] !== true;
|
|
198
199
|
let cloudMode = parseImportCloudMode(str(flags, '--cloud-mode'));
|
|
200
|
+
if (cloudMode === 'AUTO' && ['QMUSE', 'EXTERNAL'].includes(status.cloudMode ?? '')) {
|
|
201
|
+
cloudMode = status.cloudMode;
|
|
202
|
+
}
|
|
199
203
|
if (cloudMode === 'AUTO') {
|
|
200
204
|
if (!interactive)
|
|
201
205
|
return undefined;
|
|
@@ -235,6 +239,8 @@ async function resolveImportConfiguration(status, cfg, flags) {
|
|
|
235
239
|
if (!acknowledgeEmptyCloudData)
|
|
236
240
|
return undefined;
|
|
237
241
|
}
|
|
242
|
+
if (cloudMode === 'QMUSE')
|
|
243
|
+
await checkCloudEntitlement(cloudTarget, cfg);
|
|
238
244
|
const configured = await configureAppImport({
|
|
239
245
|
jobId: status.jobId,
|
|
240
246
|
cloudMode,
|
|
@@ -251,23 +257,34 @@ async function resolveImportConfiguration(status, cfg, flags) {
|
|
|
251
257
|
}
|
|
252
258
|
async function finishImport(status, sourceHash, cfg, flags, managedProject, rootDir) {
|
|
253
259
|
if (status.status !== 'READY' || !status.appId || !status.versionId) {
|
|
260
|
+
const guidance = configurationGuidance(status, resolveProjectEnvironment(cfg, str(flags, '--env')), parseImportCloudMode(str(flags, '--cloud-mode')));
|
|
254
261
|
if (flags['--json'] === true) {
|
|
255
|
-
output({ success: false, ...status }, flags);
|
|
262
|
+
output({ success: false, ...status, ...(guidance ? { configurationRequired: guidance } : {}) }, flags);
|
|
256
263
|
}
|
|
257
264
|
else {
|
|
258
265
|
printImportStatus(status, flags);
|
|
259
266
|
if (status.status === 'FAILED')
|
|
260
267
|
log.error('导入失败');
|
|
261
|
-
else
|
|
262
|
-
log.warn(
|
|
268
|
+
else if (guidance) {
|
|
269
|
+
log.warn(guidance.message);
|
|
270
|
+
if (guidance.resumeCommand)
|
|
271
|
+
log.warn(`继续配置:${guidance.resumeCommand}`);
|
|
272
|
+
if (guidance.waitCommand)
|
|
273
|
+
log.warn(`配置后在原项目目录执行:${guidance.waitCommand}`);
|
|
274
|
+
}
|
|
263
275
|
}
|
|
264
276
|
process.exitCode = status.status === 'FAILED' ? 1 : 2;
|
|
265
277
|
if (status.status === 'FAILED' && rootDir) {
|
|
266
|
-
const
|
|
278
|
+
const diagnosticProject = managedProject ?? await loadManagedProject(rootDir, cfg, str(flags, '--env'));
|
|
279
|
+
const diagnosticPath = await writeImportFailureDiagnostic({ rootDir, status, project: diagnosticProject,
|
|
280
|
+
environment: resolveProjectEnvironment(cfg, str(flags, '--env')) });
|
|
267
281
|
if (flags['--json'] !== true) {
|
|
268
282
|
log.warn(`诊断已写入 ${diagnosticPath}`);
|
|
269
283
|
if (status.failureDiagnostic?.fixableByModel) {
|
|
270
|
-
|
|
284
|
+
const retry = importRetryGuidance(status, diagnosticProject);
|
|
285
|
+
log.warn(`请让当前大模型读取 ${diagnosticPath},修复源码。${retry.retryAdvice}`);
|
|
286
|
+
if (retry.retryCommand)
|
|
287
|
+
log.warn(retry.retryCommand);
|
|
271
288
|
}
|
|
272
289
|
}
|
|
273
290
|
}
|
|
@@ -369,27 +386,53 @@ async function cmdImport(command, flags) {
|
|
|
369
386
|
const mode = parseImportCloudMode(str(flags, '--cloud-mode'));
|
|
370
387
|
if (mode === 'AUTO')
|
|
371
388
|
fail('configure 必须指定 --cloud-mode EXTERNAL 或 QMUSE');
|
|
389
|
+
const current = await queryAppImportStatus(jobId, cfg);
|
|
390
|
+
if (!current.ok || !current.data)
|
|
391
|
+
throw new Error(`查询导入任务失败:${current.errorMessage ?? current.errorCode}`);
|
|
392
|
+
const guidance = configurationGuidance(current.data, resolveProjectEnvironment(cfg, str(flags, '--env')), mode);
|
|
393
|
+
if (mode === 'QMUSE' && guidance?.requiredFlags?.some(flag => flags[flag] !== true)) {
|
|
394
|
+
await finishImport(current.data, undefined, cfg, flags);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (mode === 'QMUSE') {
|
|
398
|
+
const project = await loadManagedProject(process.cwd(), cfg, str(flags, '--env'));
|
|
399
|
+
const pending = project?.manifest.pendingImports?.[project.environment];
|
|
400
|
+
const matchingProject = pending?.jobId === jobId ? project : undefined;
|
|
401
|
+
const appId = current.data.appId || matchingProject?.target?.appId;
|
|
402
|
+
const spaceId = pending?.jobId === jobId ? pending.spaceId : str(flags, '--space-id');
|
|
403
|
+
if (appId || spaceId)
|
|
404
|
+
await checkCloudEntitlement({ appId, spaceId: spaceId ?? '' }, cfg);
|
|
405
|
+
}
|
|
372
406
|
const result = await configureAppImport({
|
|
373
407
|
jobId,
|
|
374
408
|
cloudMode: mode,
|
|
375
409
|
...(mode === 'EXTERNAL'
|
|
376
410
|
? { publicConfig: readPublicConfig(str(flags, '--public-config')) }
|
|
377
|
-
:
|
|
378
|
-
|
|
379
|
-
|
|
411
|
+
: {
|
|
412
|
+
confirmCloudService: flags['--confirm-cloud-service'] === true,
|
|
413
|
+
acknowledgeEmptyCloudData: flags['--acknowledge-empty-cloud-data'] === true,
|
|
414
|
+
}),
|
|
380
415
|
}, cfg);
|
|
381
416
|
if (!result.ok || !result.data) {
|
|
382
417
|
fail(`提交导入配置失败:${result.errorMessage ?? result.errorCode ?? `HTTP ${result.status}`}`);
|
|
383
418
|
}
|
|
384
|
-
|
|
419
|
+
if (['WAITING_CLOUD_CONFIRMATION', 'NEEDS_CONFIGURATION', 'FAILED'].includes(result.data.status)) {
|
|
420
|
+
await finishImport(result.data, undefined, cfg, flags);
|
|
421
|
+
}
|
|
422
|
+
else
|
|
423
|
+
printImportStatus(result.data, flags);
|
|
385
424
|
return;
|
|
386
425
|
}
|
|
387
426
|
const directory = subcommand;
|
|
388
427
|
if (!directory)
|
|
389
428
|
fail('用法: qmuse import <directory> [--space-id <id>]');
|
|
390
429
|
const rootDir = path.resolve(directory);
|
|
391
|
-
await
|
|
392
|
-
|
|
430
|
+
const managedProject = await loadManagedProject(rootDir, cfg, str(flags, '--env')) ??
|
|
431
|
+
createExternalProject(rootDir, cfg, str(flags, '--env'));
|
|
432
|
+
const pendingImport = managedProject.manifest.pendingImports?.[managedProject.environment];
|
|
433
|
+
if (pendingImport) {
|
|
434
|
+
throw new Error(`当前目录有尚未完成的导入任务,请先执行 qmuse import status ${pendingImport.jobId} --wait --env ${managedProject.environment},避免重复创建应用`);
|
|
435
|
+
}
|
|
393
436
|
const explicitSpaceId = str(flags, '--space-id');
|
|
394
437
|
const operation = managedProject?.target ? 'UPDATE' : 'CREATE';
|
|
395
438
|
if (flags['--force'] === true && !managedProject?.target) {
|
|
@@ -435,6 +478,12 @@ async function cmdImport(command, flags) {
|
|
|
435
478
|
log.success('源码没有变化,无需创建新版本');
|
|
436
479
|
return;
|
|
437
480
|
}
|
|
481
|
+
if (cloudMode === 'QMUSE') {
|
|
482
|
+
const quota = await checkCloudEntitlement({ appId: managedProject.target?.appId, spaceId }, cfg);
|
|
483
|
+
if (flags['--json'] !== true && quota.appLimit !== undefined) {
|
|
484
|
+
log.info(`QMuse 云服务额度:已使用 ${quota.enabledAppCount ?? 0}/${quota.appLimit}${quota.alreadyEnabled ? '(当前应用已开通)' : ''}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
438
487
|
if (flags['--json'] !== true) {
|
|
439
488
|
log.success(`识别为 React 项目(${preflight.fileCount} 个文件)`);
|
|
440
489
|
if (flags['--log'] === true) {
|
|
@@ -449,6 +498,7 @@ async function cmdImport(command, flags) {
|
|
|
449
498
|
const archive = await createImportArchive(preflight);
|
|
450
499
|
let sourceToken;
|
|
451
500
|
try {
|
|
501
|
+
await saveManagedProject(managedProject);
|
|
452
502
|
sourceToken = await uploadImportArchive({
|
|
453
503
|
filePath: archive.filePath,
|
|
454
504
|
spaceId,
|
|
@@ -467,7 +517,7 @@ async function cmdImport(command, flags) {
|
|
|
467
517
|
sourceType: 'REACT_VITE',
|
|
468
518
|
cloudMode,
|
|
469
519
|
idempotencyKey: str(flags, '--idempotency-key'),
|
|
470
|
-
sourceScenario: managedProject
|
|
520
|
+
sourceScenario: managedProject.manifest.projectKind,
|
|
471
521
|
operation,
|
|
472
522
|
...(managedProject?.target
|
|
473
523
|
? {
|
|
@@ -495,7 +545,7 @@ async function cmdImport(command, flags) {
|
|
|
495
545
|
log.success(`已创建导入任务 ${jobId}`);
|
|
496
546
|
let polled = await pollImportWithLogs(jobId, cfg, flags);
|
|
497
547
|
while (polled.reason === 'CONFIGURATION_REQUIRED' && polled.status) {
|
|
498
|
-
const configured = await resolveImportConfiguration(polled.status, cfg, flags);
|
|
548
|
+
const configured = await resolveImportConfiguration(polled.status, cfg, flags, { appId: managedProject.target?.appId, spaceId });
|
|
499
549
|
if (!configured) {
|
|
500
550
|
await finishImport(polled.status, preflight.sourceHash, cfg, flags, managedProject, rootDir);
|
|
501
551
|
return;
|
|
@@ -769,7 +819,12 @@ async function main() {
|
|
|
769
819
|
catch (err) {
|
|
770
820
|
finishUpdateNotice();
|
|
771
821
|
const msg = err instanceof Error ? err.message : String(err);
|
|
772
|
-
|
|
822
|
+
if (flags['--json'] === true) {
|
|
823
|
+
output({ success: false, message: msg }, flags);
|
|
824
|
+
process.exitCode = 1;
|
|
825
|
+
}
|
|
826
|
+
else
|
|
827
|
+
fail(msg);
|
|
773
828
|
}
|
|
774
829
|
finally {
|
|
775
830
|
finishUpdateNotice();
|
package/dist/cloud.js
CHANGED
|
@@ -20,6 +20,9 @@ export async function checkCloudInit(rootDir, cfg, explicitEnvironment) {
|
|
|
20
20
|
if (!spaceId) {
|
|
21
21
|
throw new Error('请先执行 qmuse space use <spaceId>,或通过 --space-id 指定目标空间');
|
|
22
22
|
}
|
|
23
|
+
return checkCloudEntitlement({ appId, spaceId }, cfg);
|
|
24
|
+
}
|
|
25
|
+
export async function checkCloudEntitlement({ appId, spaceId }, cfg) {
|
|
23
26
|
const response = appId
|
|
24
27
|
? await apiCall('POST', '/api/appwrite/entitlement/check', { appId }, cfg)
|
|
25
28
|
: await apiCall('POST', '/api/appwrite/entitlement/space/check', { spaceId }, cfg);
|
package/dist/dev/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export async function startDevelopment(options) {
|
|
|
26
26
|
const directory = await mkdtemp(path.join(tmpdir(), 'qmuse-local-dev-'));
|
|
27
27
|
const runtimePath = path.join(directory, 'runtime.json');
|
|
28
28
|
const previousPath = process.env.MUSE_RUNTIME_CONFIG_PATH;
|
|
29
|
+
const previousHmrGate = process.env.QMUSE_HMR_GATE_ENABLED;
|
|
29
30
|
let server;
|
|
30
31
|
let closed = false;
|
|
31
32
|
const manifestPath = path.join(root, '.qmuse/project.json');
|
|
@@ -58,11 +59,16 @@ export async function startDevelopment(options) {
|
|
|
58
59
|
delete process.env.MUSE_RUNTIME_CONFIG_PATH;
|
|
59
60
|
else
|
|
60
61
|
process.env.MUSE_RUNTIME_CONFIG_PATH = previousPath;
|
|
62
|
+
if (previousHmrGate === undefined)
|
|
63
|
+
delete process.env.QMUSE_HMR_GATE_ENABLED;
|
|
64
|
+
else
|
|
65
|
+
process.env.QMUSE_HMR_GATE_ENABLED = previousHmrGate;
|
|
61
66
|
}
|
|
62
67
|
};
|
|
63
68
|
try {
|
|
64
69
|
await writeRuntime();
|
|
65
70
|
process.env.MUSE_RUNTIME_CONFIG_PATH = runtimePath;
|
|
71
|
+
process.env.QMUSE_HMR_GATE_ENABLED = 'false';
|
|
66
72
|
const vite = await import(pathToFileURL(vitePath).href);
|
|
67
73
|
server = await vite.createServer({
|
|
68
74
|
root,
|
|
@@ -2,6 +2,15 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
export const QMUSE_IMPORT_DIAGNOSTIC_PATH = '.qmuse/last-import-error.json';
|
|
5
|
+
export function importRetryGuidance(status, project) {
|
|
6
|
+
const target = project?.target;
|
|
7
|
+
const pending = project?.manifest.pendingImports?.[project.environment];
|
|
8
|
+
const canRetryUpdate = target && status.operation !== 'CREATE' &&
|
|
9
|
+
(status.appId === target.appId || (!status.appId && pending?.jobId === status.jobId && pending.operation === 'UPDATE'));
|
|
10
|
+
return canRetryUpdate
|
|
11
|
+
? { retryCommand: `qmuse import . --env ${project.environment}`, retryAdvice: '在当前项目目录修复源码后执行,将更新已绑定的应用。' }
|
|
12
|
+
: { retryCommand: null, retryAdvice: '当前任务没有可确认的原应用绑定。请先核对任务是否已创建应用,并恢复对应环境的项目绑定;直接重新 import 可能创建新应用。' };
|
|
13
|
+
}
|
|
5
14
|
export async function writeImportFailureDiagnostic(input) {
|
|
6
15
|
if (input.status.status !== 'FAILED') {
|
|
7
16
|
throw new Error('只能为失败的导入任务写入诊断文件');
|
|
@@ -17,10 +26,18 @@ export async function writeImportFailureDiagnostic(input) {
|
|
|
17
26
|
message: input.status.message,
|
|
18
27
|
diagnostic: input.status.diagnostic,
|
|
19
28
|
failureDiagnostic: input.status.failureDiagnostic,
|
|
20
|
-
|
|
29
|
+
...importRetryGuidance(input.status, input.project),
|
|
30
|
+
environment: input.project?.environment ?? input.environment,
|
|
21
31
|
generatedAt: new Date().toISOString(),
|
|
22
32
|
};
|
|
33
|
+
const historyPath = path.join(input.rootDir, '.qmuse', 'import-errors', input.project?.environment ?? input.environment ?? 'unknown', `${encodeURIComponent(input.status.jobId)}.json`);
|
|
34
|
+
await mkdir(path.dirname(historyPath), { recursive: true });
|
|
35
|
+
const historyTemporaryPath = `${historyPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
23
36
|
try {
|
|
37
|
+
await writeFile(historyTemporaryPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
38
|
+
encoding: 'utf8', mode: 0o600, flag: 'wx',
|
|
39
|
+
});
|
|
40
|
+
await rename(historyTemporaryPath, historyPath);
|
|
24
41
|
await writeFile(temporaryPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
25
42
|
encoding: 'utf8',
|
|
26
43
|
mode: 0o600,
|
|
@@ -29,6 +46,7 @@ export async function writeImportFailureDiagnostic(input) {
|
|
|
29
46
|
await rename(temporaryPath, filePath);
|
|
30
47
|
}
|
|
31
48
|
catch (error) {
|
|
49
|
+
await rm(historyTemporaryPath, { force: true });
|
|
32
50
|
await rm(temporaryPath, { force: true });
|
|
33
51
|
throw error;
|
|
34
52
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function configurationGuidance(status, environment, requestedMode = 'AUTO') {
|
|
2
|
+
if (!['WAITING_CLOUD_CONFIRMATION', 'NEEDS_CONFIGURATION'].includes(status.status))
|
|
3
|
+
return undefined;
|
|
4
|
+
if (status.stage === 'QMUSE_CLOUD_ADAPTATION_REQUIRED') {
|
|
5
|
+
return { message: '请先根据诊断完成源码的 QMuse 云服务适配,再重新导入。' };
|
|
6
|
+
}
|
|
7
|
+
const mode = requestedMode === 'AUTO' ? status.cloudMode : requestedMode;
|
|
8
|
+
if (!/^[a-zA-Z0-9_-]+$/u.test(status.jobId))
|
|
9
|
+
return { message: '任务标识格式异常,请联系平台支持。' };
|
|
10
|
+
const base = `qmuse import configure ${status.jobId} --env ${environment}`;
|
|
11
|
+
const waitCommand = `qmuse import status ${status.jobId} --wait --env ${environment} --json`;
|
|
12
|
+
if (mode === 'QMUSE') {
|
|
13
|
+
const flag = status.sourceScenario === 'QMUSE_TEMPLATE'
|
|
14
|
+
? '--confirm-cloud-service' : '--acknowledge-empty-cloud-data';
|
|
15
|
+
return {
|
|
16
|
+
message: `任务尚未完成,需要确认:模板使用 --confirm-cloud-service,外部项目使用 --acknowledge-empty-cloud-data。当前任务请使用 ${flag}。`,
|
|
17
|
+
requiredFlags: [flag],
|
|
18
|
+
resumeCommand: `${base} --cloud-mode QMUSE ${flag} --json`,
|
|
19
|
+
waitCommand,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (mode === 'EXTERNAL') {
|
|
23
|
+
return {
|
|
24
|
+
message: `请提供外部云服务所需的公开配置:${(status.requiredConfigKeys ?? []).join(', ') || '请参考任务诊断'}。`,
|
|
25
|
+
requiredConfigKeys: status.requiredConfigKeys ?? [],
|
|
26
|
+
resumeCommand: `${base} --cloud-mode EXTERNAL --public-config @public-config.json --json`,
|
|
27
|
+
waitCommand,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
message: '请先选择 --cloud-mode QMUSE 或 EXTERNAL;QMUSE 模板需 --confirm-cloud-service,外部项目需 --acknowledge-empty-cloud-data。',
|
|
32
|
+
requiredFlags: ['--cloud-mode'],
|
|
33
|
+
waitCommand,
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/import/project.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
export const QMUSE_PROJECT_PATH = '.qmuse/project.json';
|
|
5
5
|
function isRecord(value) {
|
|
@@ -73,20 +73,21 @@ function parseManifest(value) {
|
|
|
73
73
|
if (value.schemaVersion !== 1) {
|
|
74
74
|
throw new Error(`${QMUSE_PROJECT_PATH} 的 schemaVersion 不受支持`);
|
|
75
75
|
}
|
|
76
|
-
if (value.projectKind !== 'QMUSE_TEMPLATE') {
|
|
77
|
-
throw new Error(`${QMUSE_PROJECT_PATH} 的 projectKind
|
|
76
|
+
if (value.projectKind !== 'QMUSE_TEMPLATE' && value.projectKind !== 'EXTERNAL_REPOSITORY') {
|
|
77
|
+
throw new Error(`${QMUSE_PROJECT_PATH} 的 projectKind 无效`);
|
|
78
78
|
}
|
|
79
79
|
if (value.appType !== 'GENERAL') {
|
|
80
80
|
throw new Error(`${QMUSE_PROJECT_PATH} 的 appType 必须是 GENERAL`);
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
const isTemplate = value.projectKind === 'QMUSE_TEMPLATE';
|
|
83
|
+
if (isTemplate && (!Number.isSafeInteger(value.packageVersion) || Number(value.packageVersion) < 1)) {
|
|
83
84
|
throw new Error(`${QMUSE_PROJECT_PATH} 的 packageVersion 无效`);
|
|
84
85
|
}
|
|
85
|
-
if (!isRecord(value.skillVersions)) {
|
|
86
|
+
if (isTemplate && !isRecord(value.skillVersions)) {
|
|
86
87
|
throw new Error(`${QMUSE_PROJECT_PATH} 的 skillVersions 必须是对象`);
|
|
87
88
|
}
|
|
88
89
|
const skillVersions = {};
|
|
89
|
-
for (const [name, version] of Object.entries(value.skillVersions)) {
|
|
90
|
+
for (const [name, version] of Object.entries(isTemplate ? value.skillVersions : {})) {
|
|
90
91
|
skillVersions[requireNonEmptyString(name, 'skillVersions key')] = requireNonEmptyString(version, `skillVersions.${name}`);
|
|
91
92
|
}
|
|
92
93
|
const defaultSpaceId = value.defaultSpaceId === undefined
|
|
@@ -94,11 +95,13 @@ function parseManifest(value) {
|
|
|
94
95
|
: requireNonEmptyString(value.defaultSpaceId, 'defaultSpaceId');
|
|
95
96
|
return {
|
|
96
97
|
schemaVersion: 1,
|
|
97
|
-
|
|
98
|
-
projectKind: 'QMUSE_TEMPLATE',
|
|
98
|
+
projectKind: value.projectKind,
|
|
99
99
|
appType: 'GENERAL',
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
...(isTemplate ? {
|
|
101
|
+
packageVersion: Number(value.packageVersion),
|
|
102
|
+
templateCommit: requireNonEmptyString(value.templateCommit, 'templateCommit'),
|
|
103
|
+
skillVersions,
|
|
104
|
+
} : {}),
|
|
102
105
|
...(defaultSpaceId ? { defaultSpaceId } : {}),
|
|
103
106
|
targets: parseEnvironmentMap(value.targets, 'targets', parseTarget),
|
|
104
107
|
...(value.pendingImports === undefined
|
|
@@ -150,6 +153,7 @@ export async function loadManagedProject(rootDir, cfg, explicitEnvironment) {
|
|
|
150
153
|
return { manifestPath, manifest, environment, target };
|
|
151
154
|
}
|
|
152
155
|
export async function saveManagedProject(context) {
|
|
156
|
+
await mkdir(path.dirname(context.manifestPath), { recursive: true });
|
|
153
157
|
const temporaryPath = `${context.manifestPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
154
158
|
const content = `${JSON.stringify(context.manifest, null, 2)}\n`;
|
|
155
159
|
try {
|
|
@@ -162,6 +166,18 @@ export async function saveManagedProject(context) {
|
|
|
162
166
|
throw error;
|
|
163
167
|
}
|
|
164
168
|
}
|
|
169
|
+
export function createExternalProject(rootDir, cfg, explicitEnvironment) {
|
|
170
|
+
return {
|
|
171
|
+
manifestPath: path.join(rootDir, QMUSE_PROJECT_PATH),
|
|
172
|
+
environment: resolveProjectEnvironment(cfg, explicitEnvironment),
|
|
173
|
+
manifest: {
|
|
174
|
+
schemaVersion: 1,
|
|
175
|
+
projectKind: 'EXTERNAL_REPOSITORY',
|
|
176
|
+
appType: 'GENERAL',
|
|
177
|
+
targets: {},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
165
181
|
export function setPendingImport(context, pending) {
|
|
166
182
|
context.manifest.pendingImports = {
|
|
167
183
|
...context.manifest.pendingImports,
|