@lark-apaas/miaoda-cli 0.1.39-alpha.24bc5ff → 0.1.39-alpha.2937ea8
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 +22 -1
- package/dist/api/deploy/api.js +3 -1
- package/dist/api/deploy/approval.js +1 -1
- package/dist/cli/commands/app/index.js +163 -15
- package/dist/cli/handlers/app/import.js +191 -0
- package/dist/cli/handlers/app/index.js +7 -1
- package/dist/cli/handlers/app/init.js +74 -2
- package/dist/cli/handlers/app/migrate.js +131 -212
- package/dist/cli/handlers/app/sync.js +41 -4
- package/dist/cli/handlers/deploy/get.js +20 -3
- package/dist/cli/handlers/deploy/helpers.js +9 -16
- package/dist/config/fullstack-managed-files.js +67 -0
- package/dist/config/migrate-configs/html-to-design-html.js +1 -1
- package/dist/config/migrate-configs/index.js +9 -0
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +227 -197
- package/dist/config/sync-configs/design-stack.js +3 -4
- package/dist/config/sync-configs/nestjs-react-fullstack.js +11 -23
- package/dist/services/app/import/download.js +141 -0
- package/dist/services/app/import/index.js +39 -0
- package/dist/services/app/import/manifest.js +97 -0
- package/dist/services/app/import/place.js +264 -0
- package/dist/services/app/import/rules.js +293 -0
- package/dist/services/app/init/async-install.js +21 -0
- package/dist/services/app/init/index.js +3 -1
- package/dist/services/app/init/workspace-check.js +105 -0
- package/dist/services/app/migrate/detect.js +34 -0
- package/dist/services/app/migrate/index.js +8 -0
- package/dist/services/app/migrate/restart.js +59 -0
- package/dist/services/app/migrate/run.js +565 -0
- package/dist/services/app/migrate/symlinks.js +146 -0
- package/dist/utils/engineering-state.js +52 -0
- package/dist/utils/migrate-adapters.js +634 -0
- package/dist/utils/migrate-rule.js +801 -36
- package/dist/utils/project-snapshot.js +99 -0
- package/dist/utils/spark-meta.js +31 -10
- package/dist/utils/sync-rule.js +80 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,10 +48,30 @@ miaoda file ls --output json
|
|
|
48
48
|
| `miaoda observability ...` | 线上日志、链路、前端源码堆栈反查、监控指标、运营指标 |
|
|
49
49
|
| `miaoda comment ...` | 应用评论:查看评论列表、解决评论(仅 design-html 场景开放) |
|
|
50
50
|
| `miaoda registry ...` | 组件 registry:列出 / copy-in SDK 文件(本地执行) |
|
|
51
|
-
| `miaoda app ...` |
|
|
51
|
+
| `miaoda app ...` | 应用生命周期:元数据、初始化、外部项目导入、平台同步、源码导出、静态产物导出 |
|
|
52
52
|
|
|
53
53
|
完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
|
|
54
54
|
|
|
55
|
+
## 工程初始化、同步与转换
|
|
56
|
+
|
|
57
|
+
`miaoda app init / import / sync / migrate` 管理本地应用工程(平台调用方与本地开发共用):
|
|
58
|
+
|
|
59
|
+
- `init`:按 `--template` 或 `--app-type` 渲染模板(`full_stack → nestjs-react-fullstack`,`frontend / jspage → vite-react`)。工程已存在时不重新渲染、不覆盖业务代码,只输出就绪核验结果 `ready / problems`;`--strict` 下未就绪(meta 损坏、stack 不符、必要文件缺失、依赖未安装、上次操作失败)退出码 1。同步安装失败输出 `status=failed`、`failedStage=install`;退出码只在 `--strict` 时为 1,不带 `--strict` 时为 0(依赖由后续 `sync` 补装)。
|
|
60
|
+
- `import`:外部项目导入(平台在 `init` 渲染模板之后、首次 git 提交之前于工程根目录调用)。`--zip <path>` 或 `--zip-url <预签名 URL>` 二选一,`--platform lovable|replit|gemini|zip`(默认 `zip`)。按平台规则把用户代码叠加到 `client/src`、`client/public`、`shared`,其余原样落到 `source_package/`;从不新建 / 覆盖模板托管文件(根 `package.json`、`package-lock.json`、`.npmrc`、`tsconfig*.json`、`vite.config.*`、`eslint.config.*`、`AGENTS.md`、`.githooks/`、`scripts/`、`.spark/`、`node_modules/`、`server/main.ts`、`client/src/index.tsx`),命中时改放 `source_package/` 并记入 `protectedConflicts`。**不合并 package.json、不装依赖**:用户 `package.json` 保留为 `source_package/package.json`,依赖静态分类(`platform_managed` / `present` / `build_chain` / `missing`)写进 `.spark/import/manifest.json`,由 Coding Agent 后续按需安装。先算完整计划、整体暂存再移入,失败时工程原样(`WRITE_FAILED`);同时写待迁移标记 `.spark/import/MIGRATION_PENDING`(沙箱 skill `import-migration` 以它为加载条件,Coding Agent 迁移完成后删除);manifest 最后写,存在即视为已导入(`alreadyImported=true`,不再改动)。`--dry-run` 只输出计划;`--rules-json` 覆盖默认排除路径。错误码:`ARGS_INVALID`(退出码 2)、`NOT_INITIALIZED`、`DOWNLOAD_FAILED`、`INVALID_ZIP`(非 zip / 超 50MB / 解压失败)、`WRITE_FAILED`。
|
|
61
|
+
- `sync`:按 stack 的同步规则更新平台托管内容。全栈工程的托管文件是明确清单(`scripts/dev.sh`、`dev.js`、`dev-local.js`、`build.sh`、`run.sh`、`lint.js`、`prune-smart.js`、`hooks/run-precommit.js` 与 `.githooks/pre-commit`),`scripts/` 下其他用户文件保留;连续执行无漂移。依赖安装失败输出 `status=failed`、`failedStage=install`;退出码只在 `--strict`(平台严格调用)时为 1,不带 `--strict` 时为 0,`dev.sh` 与 lark-cli `+init` 不带它。
|
|
62
|
+
- `migrate`(不在 help 列表中展示):工程转换原语,在原工程目录内执行。目标模板先渲染到临时模板目录(不复制用户工程),再只读预检冲突(同路径不同内容、重复插件 ID、自定义同名 script、无法确定的 package.json / tsconfig / vite.config / index.html 形态等),随后在原目录定向转换(`src/ public/ → client/`、插件迁 `server/capabilities`、配置定向适配)并安装依赖;迁移配置声明了验证步骤时再执行验证(vite-react → 全栈不做完整构建)。冲突按 `--purpose` 决定的策略处理,结果字段 `conflictPolicy` 给出本次策略:
|
|
63
|
+
- `stop`(`--purpose upgrade` / `history_restore`,平台调用):预检发现冲突返回 `status=conflict` 与具体路径,工程不写入;执行中才出现的冲突即停、不覆盖用户不同内容。
|
|
64
|
+
- `overwrite`(`--purpose manual`,默认;feida-ai 升级全栈与本机手动执行)且跨 stack 转换:冲突处按模板覆盖并继续 —— 自定义同名 script 改成平台命令,tsconfig、`server/main.ts` / `app.module.ts`、无法适配的 `index.html` 换成模板,定制的 `eslint.config.mjs` 删除,`src/ public/` 与 `client/` 下同名文件由前者覆盖。被替换的用户内容逐条列在 `overwritten`(`path` / `reason` / `with`),不备份、不自动提交,需要时从 git 历史找回。无可替换模板内容的保留用户内容并给出 `code=MIGRATE_CONFLICT_KEPT` 的 warning:自定义 `prepare` script、不同文件的重复插件 ID(两份都搬入)、未用平台 preset 的 `vite.config.ts`(只改 alias)、非 `.ts` 的 vite 配置。文件 / 目录类型不一致与 `package.json` 不可解析仍按冲突停止。
|
|
65
|
+
- `keep`(`--purpose manual` 且磁盘已是目标布局):冲突处保留用户在已转换工程上的改动(同样给出 `MIGRATE_CONFLICT_KEPT` warning),其余照常修复。
|
|
66
|
+
|
|
67
|
+
不安全的符号链接在任何策略下都按冲突停止(见下)。任一阶段失败退出码 1,`failure.stage` 给出 `install` / `verify` 等阶段,现场保留、不自动回滚;例外是 `--purpose manual` 只有依赖安装失败时:结果仍是 `status=failed`、`install.status=failed`,退出码 0,由调用方补装(平台调用 `upgrade` / `history_restore` 仍为 1)。`--app-id` / `MIAODA_APP_ID` 与 meta 的 `app_id` 不一致时,沙箱内按当前应用改写 meta(复制来的工程,warning `MIGRATE_APP_ID_REWRITTEN`),本机拒绝(`MIGRATE_APP_ID_MISMATCH`)。`originalIntact=true` 仅表示失败发生在任何工程写入之前,写入后一律为 false(已写入文件见 `changes`)。磁盘已是目标布局时走 repair(补齐适配 / 安装,保留已有 `node_modules` 接续安装),不返回 NOOP;meta 已是目标 stack 不代表上次成功,以 `.spark/local/engineering-state.json` 的终态为准。放弃转换由调用方按代码版本恢复。输出包含源 / 目标 stack、模板与 CLI 版本、真实阶段结果与冲突路径;运行 readiness 由调用方核验(`readiness.status=pending_caller_verification`)。沙箱内只向工作目录就是被转换目录的 dev 进程发重启信号(在工程副本等其他目录执行不影响正在运行的应用),`restartSignalSent` 只表示发出了重启信号。执行期间的并发编辑由调用方现有互斥与调度保证。
|
|
68
|
+
|
|
69
|
+
`migrate` 不自动解引用工程外文件。迁移涉及的文件或父目录存在符号链接时,在规则执行前返回 `MIGRATE_UNSAFE_SYMLINK` 和具体 `conflicts[].path`;请改为工程拥有的普通文件或目录后重试。`html → design-html` 的 `src → .` 涉及整个工程根目录。原目录 install/build 前拒绝绝对链接、工程外链接、无法解析的链接及指向排除目录的链接;未触及且在工程内解析的相对业务链接保留,安装生成的普通 `node_modules/.bin` 相对链接允许。规则执行后、安装后及构建前再次检查链接边界。扫描失败返回 `MIGRATE_SYMLINK_CHECK_FAILED`;失败或链接冲突在安全的 `.spark/local` 写入终态,状态写路径不安全时不写入并给出 warning。该检查不是 npm 脚本的安全沙箱,不限制脚本主动访问其他绝对路径。
|
|
70
|
+
|
|
71
|
+
临时模板目录清理失败只加入 `warnings`(`path`、系统错误 `code`、`message`),不影响 `status`。
|
|
72
|
+
|
|
73
|
+
全栈 / design-stack 的 `postinstall` 只升级完整历史平台值(`fullstack-cli action-plugin init` 或 `npx -y @lark-apaas/fullstack-cli action-plugin init`),目标为 `npx -y @lark-apaas/fullstack-cli@latest action-plugin init`;`latest` 是渠道,不是固定版本。带追加命令、参数或用户指定版本的值保留。`migrate` 在原目录执行正常 npm 安装生命周期,包括用户脚本及依赖补丁;生命周期失败即 `install` 阶段失败,保留现场。
|
|
74
|
+
|
|
55
75
|
## 全栈应用的 MCP UI 检查
|
|
56
76
|
|
|
57
77
|
创建 MCP UI 时,Agent 在 `server/mcp/ui` 中同时生成 `tsconfig.json` 和 `eslint.config.cjs`,修改 UI 或其 shared 依赖后主动运行独立检查。模板和 CLI 不生成 UI 配置,也不将 UI 检查接入主工程 lint/type:check。
|
|
@@ -122,6 +142,7 @@ miaoda registry add deck-slide --overwrite # 目标已存在时覆盖(
|
|
|
122
142
|
## 环境变量
|
|
123
143
|
|
|
124
144
|
- `MIAODA_APP_ID`:默认应用 ID,等价于 `--app-id`。
|
|
145
|
+
- `MIAODA_MIGRATE_STEP_TIMEOUT_MS`:`app migrate` 单个依赖安装 / 构建步骤的超时(毫秒),默认 20 分钟。
|
|
125
146
|
- `MIAODA_CANARY_HEADER`:HTTP 请求的 `x-tt-env` 小流量头取值。未设置时默认 `boe_miaoda_ccm`;设为空字符串则不带该头(例:`export MIAODA_CANARY_HEADER=boe_xxx`)。值形如 `ppe_xxx` 时额外带上 `x-use-ppe: 1`。
|
|
126
147
|
|
|
127
148
|
## 输出契约
|
package/dist/api/deploy/api.js
CHANGED
|
@@ -50,9 +50,11 @@ async function getErrorLog(req) {
|
|
|
50
50
|
* GET /v1/pipeline/app/:appID/instance/:instanceID/detail — 轮询发布状态
|
|
51
51
|
*
|
|
52
52
|
* 走 lark.apaas.devops_platform PSM;URL 前缀不同但共用同一管理端 gateway。
|
|
53
|
+
* 显式带 withTransformApprovalData=true 取审批展示视图(job 上挂 approval);IDL 默认 false,
|
|
54
|
+
* 不带时拿不到审批链接与拒绝原因。该参数只改返回投影,无副作用。
|
|
53
55
|
*/
|
|
54
56
|
async function queryPipelineInstance(req) {
|
|
55
|
-
const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/${encodeURIComponent(req.instanceID)}/detail`;
|
|
57
|
+
const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/${encodeURIComponent(req.instanceID)}/detail?withTransformApprovalData=true`;
|
|
56
58
|
return (0, http_1.getInnerApi)(url, {
|
|
57
59
|
errPrefix: 'Failed to query pipeline',
|
|
58
60
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
@@ -49,7 +49,7 @@ function* jobsOf(stages) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* 从 CliQueryPipelineInstance 的 PipelineInstance
|
|
52
|
+
* 从 CliQueryPipelineInstance 的 PipelineInstance(请求带 withTransformApprovalData=true,返回审批展示视图)中摘出需要输出的卡点:
|
|
53
53
|
* 第一个 status 为 PENDING / REJECTED / FAILED 的 Job.approval。一次发布可以被拦多次
|
|
54
54
|
* (build / db_check / deploy),已通过的卡点可能排在前面,不能取它;流水线串行,需要输出的至多一个。
|
|
55
55
|
* 与妙搭发布面板 findApprovalJob 同一口径。
|
|
@@ -25,6 +25,7 @@ function registerAppCommands(program, opts = {}) {
|
|
|
25
25
|
registerAppPack(appCmd);
|
|
26
26
|
if (opts.includeInit) {
|
|
27
27
|
registerAppInit(appCmd);
|
|
28
|
+
registerAppImport(appCmd);
|
|
28
29
|
registerAppSync(appCmd);
|
|
29
30
|
registerAppMigrate(appCmd);
|
|
30
31
|
}
|
|
@@ -40,24 +41,74 @@ function registerAppMigrate(parent) {
|
|
|
40
41
|
// 用 addCommand(cmd, { hidden: true }) 而非 parent.command(...),因为 commander 13
|
|
41
42
|
// 的 fluent `.command()` 不支持设置 hidden。
|
|
42
43
|
const cmd = new commander_1.Command('migrate')
|
|
43
|
-
.description('
|
|
44
|
+
.description('工程转换原语:原目录内按 MigrateConfig 预检冲突 → 定向转换 → 安装;冲突按调用目的即停或按模板覆盖,失败保留现场')
|
|
44
45
|
.requiredOption('--to <stack>', '目标 stack 短名')
|
|
45
|
-
.option('--from <stack>', '源 stack
|
|
46
|
-
.option('--dir <path>', '项目目录,默认 cwd
|
|
46
|
+
.option('--from <stack>', '源 stack;默认按磁盘实际布局识别(识别不了再读 .spark/meta.json)')
|
|
47
|
+
.option('--dir <path>', '项目目录,默认 cwd')
|
|
48
|
+
.option('--purpose <purpose>', '调用目的:manual / upgrade / history_restore;决定冲突策略(见下文),同时用于记录与关联', 'manual')
|
|
49
|
+
.option('--operation-ref <id>', '调用方已有的操作 / 任务关联标识,原样回显')
|
|
50
|
+
.addOption((0, shared_1.appIdOption)())
|
|
47
51
|
.addHelpText('after', `
|
|
48
52
|
已支持的迁移路径
|
|
49
53
|
${supportedDesc || '(none)'}
|
|
54
|
+
磁盘已是目标布局(同目标)时走 repair:补齐适配 / 安装 / 构建,不返回 NOOP。
|
|
55
|
+
|
|
56
|
+
文件规则
|
|
57
|
+
明确托管的平台脚本(scripts 清单 + .githooks/pre-commit)按模板更新,scripts/ 下用户文件保留;
|
|
58
|
+
src/ public/ 搬入 client/、插件迁 server/capabilities;server / shared 只补平台缺失文件,
|
|
59
|
+
app.module.ts 定向注册平台模块;package.json scripts、tsconfig、vite.config、index.html 定向适配。
|
|
60
|
+
同路径不同内容、重复插件 ID、自定义同名 script、无法确定的配置形态即为冲突,按冲突策略处理。
|
|
61
|
+
|
|
62
|
+
冲突策略(conflictPolicy,由 --purpose 与磁盘布局决定)
|
|
63
|
+
stop --purpose upgrade / history_restore(平台调用):预检发现冲突即停、工程不写入;
|
|
64
|
+
执行中才出现的冲突即停,不覆盖用户内容
|
|
65
|
+
overwrite --purpose manual(默认)且跨 stack 转换:冲突处按模板覆盖 —— 自定义同名 script 改成平台命令,
|
|
66
|
+
tsconfig / server 入口 / 无法适配的 index.html 换成模板,定制的 eslint.config.mjs 删除,
|
|
67
|
+
src/ public/ 与 client/ 同名文件由前者覆盖,被替换的列在 overwritten;
|
|
68
|
+
自定义 prepare script、不同文件的重复插件 ID(两份都搬入)、未用平台 preset 的 vite.config(只改 alias)、
|
|
69
|
+
非 .ts 的 vite 配置保留用户内容,记为 code=MIGRATE_CONFLICT_KEPT 的 warning;
|
|
70
|
+
文件 / 目录类型不一致、package.json 不可解析仍按冲突停止
|
|
71
|
+
keep --purpose manual 且磁盘已是目标布局(repair):冲突处保留用户内容(warning 同上),其余照常修复
|
|
72
|
+
不安全的符号链接在任何策略下都按冲突停止(MIGRATE_UNSAFE_SYMLINK)。
|
|
73
|
+
|
|
74
|
+
结果语义
|
|
75
|
+
status=converted 原目录内文件适配、依赖安装、配置了的验证步骤全部完成;
|
|
76
|
+
运行 readiness 由调用方核验(readiness.status=pending_caller_verification);
|
|
77
|
+
沙箱里只向工作目录为本工程的 dev 进程发重启信号,
|
|
78
|
+
restartSignalSent / devRestarted 只表示发出了重启信号
|
|
79
|
+
status=conflict conflicts 给出具体路径 / ID,冲突文件不覆盖
|
|
80
|
+
status=failed failure.stage / failure.code 给出失败阶段与原因,现场保留、不自动回滚
|
|
81
|
+
originalIntact=true 仅表示失败发生在任何工程写入之前;写入后为 false(部分改动见 changes)。
|
|
82
|
+
失败后对同一工程重跑同目标 migrate 即按真实布局 repair(可接续安装 / 构建)。
|
|
83
|
+
非 converted 退出码为 1。例外:--purpose manual 时只有依赖安装失败(failure.stage=install),
|
|
84
|
+
结果仍是 status=failed、install.status=failed,但退出码 0 —— 文件已转换,依赖由调用方补装;
|
|
85
|
+
平台调用(upgrade / history_restore)安装失败仍为退出码 1。
|
|
86
|
+
|
|
87
|
+
应用身份(app_id)
|
|
88
|
+
--app-id / MIAODA_APP_ID 与 .spark/meta.json 的 app_id 不一致时:
|
|
89
|
+
沙箱内 → 视为复制来的工程,按当前应用改写 meta.app_id(随转换写入),warnings 给出
|
|
90
|
+
code=MIGRATE_APP_ID_REWRITTEN
|
|
91
|
+
本机 → 拒绝(MIGRATE_APP_ID_MISMATCH),不渲染、不写入
|
|
50
92
|
|
|
51
93
|
JSON 输出
|
|
52
|
-
{"data": {"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
94
|
+
{"data": {"status": "converted|conflict|failed", "mode": "migrate|repair", "runId": "...",
|
|
95
|
+
"purpose": "...", "conflictPolicy": "stop|overwrite|keep", "operationRef": "...",
|
|
96
|
+
"source": {"metaStack": "...", "detectedStack": "...", "stack": "..."},
|
|
97
|
+
"target": {"stack": "...", "templatePackage": "...", "templateVersion": "...", "cliVersion": "..."},
|
|
98
|
+
"stages": [{"name": "preflight|render|precheck|apply|install|verify|restart", "status": "...", "durationMs": 0}],
|
|
99
|
+
"failure": {"stage": "...", "code": "...", "message": "...", "retryable": true},
|
|
100
|
+
"conflicts": [{"path": "...", "reason": "...", "with": "...", "id": "..."}],
|
|
101
|
+
"overwritten": [{"path": "...", "reason": "...", "with": "..."}],
|
|
102
|
+
"warnings": [{"path": "...", "code": "MIGRATE_CONFLICT_KEPT", "message": "..."}],
|
|
103
|
+
"changes": {"added": [...], "modified": [...], "deleted": [...]},
|
|
104
|
+
"install": {"status": "ok|failed|not_run"}, "verify": {"status": "...", "steps": [...]},
|
|
105
|
+
"originalIntact": true,
|
|
106
|
+
"restartSignalSent": false, "readiness": {"status": "...", "detail": "..."},
|
|
107
|
+
"appliedRules": [...], "nextActions": [...]}}
|
|
57
108
|
|
|
58
109
|
示例
|
|
59
110
|
$ miaoda app migrate --to nestjs-react-fullstack
|
|
60
|
-
$ miaoda app migrate --
|
|
111
|
+
$ miaoda app migrate --to nestjs-react-fullstack --purpose upgrade --operation-ref op_demo_xxx
|
|
61
112
|
$ miaoda app migrate --to nestjs-react-fullstack --dir /path/to/app
|
|
62
113
|
`);
|
|
63
114
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
@@ -65,6 +116,9 @@ JSON 输出
|
|
|
65
116
|
dir: rawOpts.dir,
|
|
66
117
|
from: rawOpts.from,
|
|
67
118
|
to: rawOpts.to,
|
|
119
|
+
appId: rawOpts.appId,
|
|
120
|
+
purpose: rawOpts.purpose,
|
|
121
|
+
operationRef: rawOpts.operationRef,
|
|
68
122
|
});
|
|
69
123
|
}));
|
|
70
124
|
parent.addCommand(cmd, { hidden: true });
|
|
@@ -75,6 +129,7 @@ function registerAppSync(parent) {
|
|
|
75
129
|
.alias('upgrade')
|
|
76
130
|
.description('一站式同步:按 stack 的 SyncRule 全套 apply + 按钉死版本表升 @lark-apaas/* deps + npm install')
|
|
77
131
|
.option('--dir <path>', '项目目录,默认 cwd(需含 .spark/meta.json)')
|
|
132
|
+
.option('--strict', '平台严格调用:依赖安装失败时以退出码 1 结束', false)
|
|
78
133
|
.addHelpText('after', `
|
|
79
134
|
别名
|
|
80
135
|
miaoda app sync 与 miaoda app upgrade 等价(upgrade 是历史别名,新调用方请用 sync)。
|
|
@@ -85,7 +140,7 @@ function registerAppSync(parent) {
|
|
|
85
140
|
file → 单文件覆盖(可选 onlyIfExists / overwrite)
|
|
86
141
|
merge-json → JSON deep merge(可选 arrayMerge by key 去重)
|
|
87
142
|
add-script → npm script 不存在时添加(overwrite=false 默认不覆盖)
|
|
88
|
-
patch-script → npm script
|
|
143
|
+
patch-script → npm script 以指定前缀开头、或整值匹配平台旧值时改写(守护用户自定义)
|
|
89
144
|
add-line → 行不存在时追加到文件
|
|
90
145
|
remove-line → 行存在时移除
|
|
91
146
|
delete-file/delete-directory → 删除
|
|
@@ -97,12 +152,18 @@ function registerAppSync(parent) {
|
|
|
97
152
|
4. npm install --no-audit --no-fund --ignore-scripts。--ignore-scripts 是为绕开
|
|
98
153
|
action-plugin postinstall 在 npm install 时缺平台 env 的卡点(需要时再
|
|
99
154
|
FORCE_AUTHN_INNERAPI_DOMAIN=... npm install 手动跑一次)。
|
|
155
|
+
安装失败时输出 status=failed、failedStage=install 与 installError(已应用的文件规则保留,
|
|
156
|
+
重跑 sync 幂等补齐)。退出码:--strict 时为 1;不带 --strict 时为 0,调用方按 status 判断。
|
|
157
|
+
|
|
158
|
+
nestjs-react-fullstack 的平台托管文件是明确清单(scripts/dev.sh、dev.js、dev-local.js、
|
|
159
|
+
build.sh、run.sh、lint.js、prune-smart.js、hooks/run-precommit.js 与 .githooks/pre-commit),
|
|
160
|
+
同名更新;scripts/ 下其他用户文件保留。内容未变化的规则报 noop,连续 sync 不产生漂移。
|
|
100
161
|
|
|
101
162
|
stack 未纳入 sync 范围(无 SyncConfig 且无 upgrade/templates/<stack>/,如 design-html
|
|
102
163
|
buildless 静态 HTML)→ 跳过同步(no-op, skipped=true),退出码 0,不阻断 dev/启动链路。
|
|
103
164
|
|
|
104
165
|
JSON 输出
|
|
105
|
-
{"data": {"stack": "...",
|
|
166
|
+
{"data": {"stack": "...", "status": "succeeded|failed", "failedStage": "install",
|
|
106
167
|
"appliedRules": [{"type": "...", "action": "...", "path": "...", "detail": "..."}],
|
|
107
168
|
"syncedFiles": [...],
|
|
108
169
|
"mergedJsonFiles": [{"path": "...", "keys": [...]}],
|
|
@@ -113,11 +174,13 @@ JSON 输出
|
|
|
113
174
|
示例
|
|
114
175
|
$ miaoda app sync
|
|
115
176
|
$ miaoda app sync --dir /path/to/app
|
|
177
|
+
$ miaoda app sync --strict # 平台调用:安装失败退出码 1
|
|
116
178
|
$ miaoda app upgrade # 等价于 miaoda app sync
|
|
117
179
|
`);
|
|
118
180
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
119
181
|
await (0, index_1.handleAppSync)({
|
|
120
182
|
dir: rawOpts.dir,
|
|
183
|
+
strict: rawOpts.strict,
|
|
121
184
|
});
|
|
122
185
|
}));
|
|
123
186
|
}
|
|
@@ -308,14 +371,22 @@ function registerAppInit(parent) {
|
|
|
308
371
|
.option('--conf [json]', 'init 配置 JSON。支持 {"version": "<template 版本>"},默认 latest')
|
|
309
372
|
.option('--skip-install', '跳过依赖安装', false)
|
|
310
373
|
.option('--async-install', '派发后台进程装依赖并立即返回(与 --skip-install 互斥)', false)
|
|
374
|
+
.option('--strict', '平台严格调用:工程已存在时核验是否真正就绪(meta 可解析、stack 匹配、必要文件、依赖安装、上次操作结果),未就绪退出码 1;新初始化时同步安装失败也以退出码 1 结束', false)
|
|
311
375
|
.addOption((0, shared_1.appIdOption)())
|
|
312
376
|
.addHelpText('after', `
|
|
313
377
|
幂等
|
|
314
|
-
.spark/meta.json
|
|
315
|
-
|
|
316
|
-
meta
|
|
378
|
+
.spark/meta.json 已存在时不再渲染 / 安装(initialized=false, reason=already_initialized),
|
|
379
|
+
不覆盖已有业务代码;同时输出 ready 与 problems(工程就绪核验结果)。
|
|
380
|
+
meta 存在不等于就绪:--strict 时 problems 非空(meta 损坏、stack 与请求不符、必要文件缺失、
|
|
381
|
+
依赖未安装、上次 init/sync/migrate 失败)退出码 1,供平台严格调用判断。
|
|
317
382
|
上一次失败留下半渲染状态时(package.json 在但 meta.json 没在)允许重跑。
|
|
318
383
|
|
|
384
|
+
依赖安装失败
|
|
385
|
+
同步安装失败时 init 输出 status=failed、failedStage=install 与 installError;
|
|
386
|
+
已渲染的脚手架与 meta 保留(避免重跑时覆盖已开始编辑的文件),本地执行记录标为失败,
|
|
387
|
+
后续 'miaoda app sync' 会补装依赖,--strict 在补装前报告未就绪。
|
|
388
|
+
退出码:--strict 时为 1;不带 --strict 时为 0,调用方按 status 判断。
|
|
389
|
+
|
|
319
390
|
应用身份(app_id)
|
|
320
391
|
--app-id <id>(或 MIAODA_APP_ID env)提供时写入 .spark/meta.json 的 app_id 字段;
|
|
321
392
|
未提供则不写。运行端命令(deploy / file / plugin 等)仍优先读 MIAODA_APP_ID env,
|
|
@@ -358,7 +429,8 @@ function registerAppInit(parent) {
|
|
|
358
429
|
与 --skip-install 互斥。
|
|
359
430
|
|
|
360
431
|
JSON 输出
|
|
361
|
-
已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "..."
|
|
432
|
+
已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "...",
|
|
433
|
+
"ready": true|false, "problems": [{"code": "...", "message": "..."}]}}
|
|
362
434
|
新初始化:{"data": {"initialized": true, "template": "...", "templateVersion": "...", "steeringVersion": "...",
|
|
363
435
|
"appId": "...", "appType": "modern_html", "sourceImported": true, "importedFileCount": 7,
|
|
364
436
|
"platformStackFound": true, "platformSyncedFiles": [...],
|
|
@@ -387,6 +459,82 @@ JSON 输出
|
|
|
387
459
|
skipInstall: rawOpts.skipInstall,
|
|
388
460
|
appId: rawOpts.appId,
|
|
389
461
|
asyncInstall: rawOpts.asyncInstall,
|
|
462
|
+
strict: rawOpts.strict,
|
|
463
|
+
});
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
function registerAppImport(parent) {
|
|
467
|
+
const cmd = parent
|
|
468
|
+
.command('import')
|
|
469
|
+
.description('外部项目导入:把 zip 包(lovable / replit / gemini / 普通 zip)按平台规则叠加进已初始化的工程;不碰模板托管文件、不合并 package.json、不装依赖')
|
|
470
|
+
.option('--zip <path>', '本地 zip 路径(与 --zip-url 二选一)')
|
|
471
|
+
.option('--zip-url <url>', 'zip 下载地址(预签名 GET URL,与 --zip 二选一)')
|
|
472
|
+
.option('--platform <platform>', `来源平台(${index_1.IMPORT_PLATFORMS.join(' / ')})`, index_1.DEFAULT_IMPORT_PLATFORM)
|
|
473
|
+
.option('--rules-json <json>', '覆盖默认排除路径:{"excludedPaths": [...]},提供后整体替换该平台默认值')
|
|
474
|
+
.option('--dry-run', '只计算并输出放置计划,不写任何文件', false)
|
|
475
|
+
.option('--dir <path>', '项目目录,默认 cwd(需含 .spark/meta.json)')
|
|
476
|
+
.addHelpText('after', `
|
|
477
|
+
前置条件
|
|
478
|
+
工程已经 'miaoda app init' 过(.spark/meta.json 存在),否则报 NOT_INITIALIZED。
|
|
479
|
+
平台在模板渲染完成、首次 git 提交之前于工程根目录调用;依赖由 init 的后台安装负责,
|
|
480
|
+
本命令不读写 package.json / package-lock.json / node_modules。
|
|
481
|
+
|
|
482
|
+
幂等
|
|
483
|
+
.spark/import/manifest.json 已存在 → 不做任何改动,输出 alreadyImported=true。
|
|
484
|
+
manifest 最后写入,是导入完成的唯一标记。
|
|
485
|
+
|
|
486
|
+
导入包处理
|
|
487
|
+
大小上限 50MB(超出 / 非 zip / 解压失败 → INVALID_ZIP;下载失败 → DOWNLOAD_FAILED)。
|
|
488
|
+
丢弃 __MACOSX/、.DS_Store、含 ".." 的条目、.git 元数据(.gitignore / .github 保留)、
|
|
489
|
+
node_modules / dist / build 目录、符号链接。
|
|
490
|
+
所有条目共享一个不含 "." 的顶层目录时剥掉该目录(gemini 保留原始路径)。
|
|
491
|
+
超过 60 字节的路径段改名为 truncated-<hash><ext>。
|
|
492
|
+
|
|
493
|
+
放置规则(未命中的一律放 source_package/<原路径>)
|
|
494
|
+
lovable src/** → client/src/**(命中排除路径 → source_package/src/**);public/** → client/public/**
|
|
495
|
+
replit client/src/** → client/src/**(命中排除路径 → source_package/);shared/** → shared/**;
|
|
496
|
+
attached_assets/** → client/public/assets/**
|
|
497
|
+
gemini components/** → client/src/components/**;services/** → client/src/services/**;
|
|
498
|
+
types.ts / constants.ts → client/src/
|
|
499
|
+
zip src/** → client/src/**(命中排除路径 → source_package/src/**);public/** → client/public/**;
|
|
500
|
+
src/components/ui/<name>.<ext> 与工程已有 client/src/components/ui/<name>.* 同名时,
|
|
501
|
+
删除模板文件、写入用户文件
|
|
502
|
+
受保护目标(根 package.json / package-lock.json / .npmrc / tsconfig*.json / vite.config.* /
|
|
503
|
+
eslint.config.* / AGENTS.md / .githooks/ / scripts/ / .spark/ / node_modules/ / server/main.ts /
|
|
504
|
+
client/src/index.tsx)从不新建或覆盖,命中时改放 source_package/ 并记入 protectedConflicts。
|
|
505
|
+
用户的 package.json 保留在 source_package/package.json。
|
|
506
|
+
|
|
507
|
+
原子性
|
|
508
|
+
先算完整计划、整体暂存,再移入工程;移入前任何失败工程保持原样(WRITE_FAILED)。
|
|
509
|
+
|
|
510
|
+
依赖清单(.spark/import/manifest.json 的 dependencies,只做静态分析,不安装、不联网)
|
|
511
|
+
status 优先级:platform_managed(@lark-apaas/*)> excluded(排除依赖)>
|
|
512
|
+
present(工程已有,附 templateRange)> build_chain(构建链 / 工具链)> missing
|
|
513
|
+
|
|
514
|
+
JSON 输出
|
|
515
|
+
{"data": {"imported": true, "alreadyImported": false, "platform": "zip",
|
|
516
|
+
"fileCounts": {"client/src": 0, "client/public": 0, "shared": 0, "source_package": 0},
|
|
517
|
+
"manifestPath": ".spark/import/manifest.json", "warnings": [...]}}
|
|
518
|
+
--dry-run 额外带 "dryRun": true 与 "plan": {placements, deletions, overwrites, excludedPaths,
|
|
519
|
+
protectedConflicts, replacedTemplateFiles, dependencies}
|
|
520
|
+
|
|
521
|
+
错误码
|
|
522
|
+
ARGS_INVALID(退出码 2)/ NOT_INITIALIZED / DOWNLOAD_FAILED / INVALID_ZIP / WRITE_FAILED(退出码 1)
|
|
523
|
+
|
|
524
|
+
示例
|
|
525
|
+
$ miaoda app import --zip-url 'https://tos.example.com/demo.zip?sign=...' --platform lovable --json
|
|
526
|
+
$ miaoda app import --zip ./demo.zip
|
|
527
|
+
$ miaoda app import --zip ./demo.zip --platform replit --dry-run
|
|
528
|
+
$ miaoda app import --zip ./demo.zip --rules-json '{"excludedPaths": ["src/legacy/"]}'
|
|
529
|
+
`);
|
|
530
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
531
|
+
await (0, index_1.handleAppImport)({
|
|
532
|
+
zip: rawOpts.zip,
|
|
533
|
+
zipUrl: rawOpts.zipUrl,
|
|
534
|
+
platform: rawOpts.platform,
|
|
535
|
+
rulesJson: rawOpts.rulesJson,
|
|
536
|
+
dryRun: rawOpts.dryRun,
|
|
537
|
+
dir: rawOpts.dir,
|
|
390
538
|
});
|
|
391
539
|
}));
|
|
392
540
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleAppImport = handleAppImport;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const index_1 = require("../../../services/app/import/index");
|
|
11
|
+
const error_1 = require("../../../utils/error");
|
|
12
|
+
const logger_1 = require("../../../utils/logger");
|
|
13
|
+
const output_1 = require("../../../utils/output");
|
|
14
|
+
function readPackageJson(file, label, warnings) {
|
|
15
|
+
try {
|
|
16
|
+
return (0, index_1.readJsonFile)(file);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
warnings.push(`${label} 不是合法 JSON,依赖清单按空处理:${err instanceof Error ? err.message : String(err)}`);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readPrevFileCounts(manifestFile) {
|
|
24
|
+
try {
|
|
25
|
+
const prev = JSON.parse(node_fs_1.default.readFileSync(manifestFile, 'utf-8'));
|
|
26
|
+
return prev.fileCounts;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* miaoda app import --zip <path> | --zip-url <url> [--platform <p>] [--rules-json <json>] [--dry-run]
|
|
34
|
+
*
|
|
35
|
+
* 外部平台(lovable / replit / gemini / 普通 zip)项目导入:在已 `app init` 的工程里,
|
|
36
|
+
* 把用户代码按平台规则叠加到 client/ shared/,其余原样落到 source_package/。
|
|
37
|
+
* 不碰模板托管文件、不合并 package.json、不装依赖 —— 用户 package.json 保留在
|
|
38
|
+
* source_package/package.json,依赖分析写进 manifest 交给 Coding Agent 后续按需安装。
|
|
39
|
+
*
|
|
40
|
+
* 落盘两阶段(先暂存、再 rename),manifest 最后写,作为幂等标记。
|
|
41
|
+
*/
|
|
42
|
+
async function handleAppImport(opts) {
|
|
43
|
+
const hasZip = Boolean(opts.zip);
|
|
44
|
+
const hasUrl = Boolean(opts.zipUrl);
|
|
45
|
+
if (hasZip === hasUrl) {
|
|
46
|
+
throw new error_1.AppError('ARGS_INVALID', '--zip 与 --zip-url 必须且只能传一个');
|
|
47
|
+
}
|
|
48
|
+
const platform = (0, index_1.parsePlatform)(opts.platform);
|
|
49
|
+
const rules = (0, index_1.resolveImportRules)(platform, opts.rulesJson);
|
|
50
|
+
const projectDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
|
|
51
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(projectDir, '.spark', 'meta.json'))) {
|
|
52
|
+
throw new error_1.AppError('NOT_INITIALIZED', `工程未初始化(缺少 .spark/meta.json):${projectDir}`, {
|
|
53
|
+
next_actions: ['先执行 miaoda app init --template <stack> 再导入'],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const manifestFile = (0, index_1.importManifestPath)(projectDir);
|
|
57
|
+
if (node_fs_1.default.existsSync(manifestFile)) {
|
|
58
|
+
(0, logger_1.log)('import', `Already imported (${index_1.IMPORT_MANIFEST_REL} exists), skip`);
|
|
59
|
+
if (!(0, output_1.isJsonMode)())
|
|
60
|
+
process.stdout.write(`Already imported: ${manifestFile}\n`);
|
|
61
|
+
(0, output_1.emit)({
|
|
62
|
+
data: {
|
|
63
|
+
imported: false,
|
|
64
|
+
alreadyImported: true,
|
|
65
|
+
platform,
|
|
66
|
+
fileCounts: readPrevFileCounts(manifestFile),
|
|
67
|
+
manifestPath: index_1.IMPORT_MANIFEST_REL,
|
|
68
|
+
warnings: [],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const tmpDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'miaoda-import-'));
|
|
74
|
+
try {
|
|
75
|
+
const warnings = [];
|
|
76
|
+
let zipPath;
|
|
77
|
+
if (opts.zipUrl) {
|
|
78
|
+
zipPath = node_path_1.default.join(tmpDir, 'import.zip');
|
|
79
|
+
await (0, index_1.downloadZip)(opts.zipUrl, zipPath);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
zipPath = node_path_1.default.resolve(opts.zip ?? '');
|
|
83
|
+
(0, index_1.assertLocalZip)(zipPath);
|
|
84
|
+
}
|
|
85
|
+
const extractDir = node_path_1.default.join(tmpDir, 'extracted');
|
|
86
|
+
warnings.push(...(0, index_1.extractImportZip)(zipPath, extractDir).warnings);
|
|
87
|
+
const { entries, renamed } = (0, index_1.normalizeEntries)((0, index_1.collectFiles)(extractDir), platform);
|
|
88
|
+
if (entries.length === 0) {
|
|
89
|
+
throw new error_1.AppError('INVALID_ZIP', '导入包内没有可导入的文件');
|
|
90
|
+
}
|
|
91
|
+
for (const r of renamed)
|
|
92
|
+
warnings.push(`路径段过长已重命名:${r.from} → ${r.to}`);
|
|
93
|
+
const plan = (0, index_1.buildImportPlan)({ entries, platform, rules, projectDir });
|
|
94
|
+
warnings.push(...plan.warnings);
|
|
95
|
+
for (const c of plan.protectedConflicts) {
|
|
96
|
+
warnings.push(`目标为模板受保护路径,已改放 ${c.placedAt}:${c.target}`);
|
|
97
|
+
}
|
|
98
|
+
if (plan.overwrites.length > 0) {
|
|
99
|
+
warnings.push(`覆盖了工程中已有的 ${String(plan.overwrites.length)} 个文件`);
|
|
100
|
+
}
|
|
101
|
+
const dependencies = analyzeDependencies({
|
|
102
|
+
entries,
|
|
103
|
+
extractDir,
|
|
104
|
+
projectDir,
|
|
105
|
+
warnings,
|
|
106
|
+
});
|
|
107
|
+
if (opts.dryRun) {
|
|
108
|
+
if (!(0, output_1.isJsonMode)()) {
|
|
109
|
+
process.stdout.write(`Dry run: ${String(plan.placements.length)} file(s) planned\n`);
|
|
110
|
+
}
|
|
111
|
+
(0, output_1.emit)({
|
|
112
|
+
data: {
|
|
113
|
+
imported: false,
|
|
114
|
+
alreadyImported: false,
|
|
115
|
+
dryRun: true,
|
|
116
|
+
platform,
|
|
117
|
+
fileCounts: plan.fileCounts,
|
|
118
|
+
manifestPath: index_1.IMPORT_MANIFEST_REL,
|
|
119
|
+
warnings,
|
|
120
|
+
plan: {
|
|
121
|
+
placements: plan.placements.map((p) => ({ source: p.source, target: p.target })),
|
|
122
|
+
deletions: plan.deletions,
|
|
123
|
+
overwrites: plan.overwrites,
|
|
124
|
+
excludedPaths: plan.excludedPaths,
|
|
125
|
+
protectedConflicts: plan.protectedConflicts,
|
|
126
|
+
replacedTemplateFiles: plan.replacedTemplateFiles,
|
|
127
|
+
dependencies,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
applyWithStaging({ plan, extractDir, projectDir });
|
|
134
|
+
const manifest = (0, index_1.buildImportManifest)({ platform, plan, dependencies });
|
|
135
|
+
try {
|
|
136
|
+
(0, index_1.writeImportPendingMarker)(projectDir);
|
|
137
|
+
(0, index_1.writeImportManifest)(projectDir, manifest);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
throw new error_1.AppError('WRITE_FAILED', `写 ${index_1.IMPORT_MANIFEST_REL} 失败:${err instanceof Error ? err.message : String(err)}`);
|
|
141
|
+
}
|
|
142
|
+
(0, logger_1.log)('import', `Imported ${String(plan.placements.length)} file(s) (${platform})`);
|
|
143
|
+
if (!(0, output_1.isJsonMode)()) {
|
|
144
|
+
process.stdout.write(`✓ Imported ${String(plan.placements.length)} file(s) from ${platform} package into ${projectDir}\n`);
|
|
145
|
+
}
|
|
146
|
+
(0, output_1.emit)({
|
|
147
|
+
data: {
|
|
148
|
+
imported: true,
|
|
149
|
+
alreadyImported: false,
|
|
150
|
+
platform,
|
|
151
|
+
fileCounts: plan.fileCounts,
|
|
152
|
+
manifestPath: index_1.IMPORT_MANIFEST_REL,
|
|
153
|
+
warnings,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function analyzeDependencies(opts) {
|
|
162
|
+
const pkgEntry = opts.entries.find((e) => e.normalized === 'package.json');
|
|
163
|
+
if (!pkgEntry)
|
|
164
|
+
return [];
|
|
165
|
+
const sourcePkg = readPackageJson(node_path_1.default.join(opts.extractDir, ...pkgEntry.raw.split('/')), '导入包 package.json', opts.warnings);
|
|
166
|
+
const projectPkg = readPackageJson(node_path_1.default.join(opts.projectDir, 'package.json'), '工程 package.json', opts.warnings);
|
|
167
|
+
return (0, index_1.classifyDependencies)({ sourcePkg, projectPkg });
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 暂存目录放在工程 .spark/import/ 下:与工程同一文件系统,rename 不会 EXDEV。
|
|
171
|
+
* 若 .spark/import/ 是本次新建的且导入失败,连目录一起清掉,保证失败时工程原样。
|
|
172
|
+
*/
|
|
173
|
+
function applyWithStaging(opts) {
|
|
174
|
+
const importDir = node_path_1.default.join(opts.projectDir, '.spark', 'import');
|
|
175
|
+
const createdImportDir = !node_fs_1.default.existsSync(importDir);
|
|
176
|
+
const stagingDir = node_path_1.default.join(importDir, `.staging-${String(process.pid)}-${String(Date.now())}`);
|
|
177
|
+
try {
|
|
178
|
+
node_fs_1.default.mkdirSync(stagingDir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
throw new error_1.AppError('WRITE_FAILED', `创建暂存目录失败:${err instanceof Error ? err.message : String(err)}`);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
(0, index_1.applyImportPlan)({ ...opts, stagingDir });
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
if (createdImportDir)
|
|
188
|
+
node_fs_1.default.rmSync(importDir, { recursive: true, force: true });
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PACK_MODES = exports.DEFAULT_PACK_MODE = exports.DEFAULT_PACK_OUT = exports.handleAppPack = exports.SUPPORTED_STACKS = exports.handleAppMigrate = exports.UPGRADE_MANIFEST_FILENAME = exports.DEFAULT_EXPORT_OUT = exports.handleAppExport = exports.handleAppUpgrade = exports.handleAppSync = exports.handleAppInit = exports.handleAppUpdate = exports.handleAppGet = void 0;
|
|
3
|
+
exports.DEFAULT_IMPORT_PLATFORM = exports.IMPORT_PLATFORMS = exports.handleAppImport = exports.PACK_MODES = exports.DEFAULT_PACK_MODE = exports.DEFAULT_PACK_OUT = exports.handleAppPack = exports.SUPPORTED_STACKS = exports.handleAppMigrate = exports.UPGRADE_MANIFEST_FILENAME = exports.DEFAULT_EXPORT_OUT = exports.handleAppExport = exports.handleAppUpgrade = exports.handleAppSync = exports.handleAppInit = exports.handleAppUpdate = exports.handleAppGet = void 0;
|
|
4
4
|
var get_1 = require("./get");
|
|
5
5
|
Object.defineProperty(exports, "handleAppGet", { enumerable: true, get: function () { return get_1.handleAppGet; } });
|
|
6
6
|
var update_1 = require("./update");
|
|
@@ -24,3 +24,9 @@ Object.defineProperty(exports, "handleAppPack", { enumerable: true, get: functio
|
|
|
24
24
|
Object.defineProperty(exports, "DEFAULT_PACK_OUT", { enumerable: true, get: function () { return pack_1.DEFAULT_PACK_OUT; } });
|
|
25
25
|
Object.defineProperty(exports, "DEFAULT_PACK_MODE", { enumerable: true, get: function () { return pack_1.DEFAULT_PACK_MODE; } });
|
|
26
26
|
Object.defineProperty(exports, "PACK_MODES", { enumerable: true, get: function () { return pack_1.PACK_MODES; } });
|
|
27
|
+
var import_1 = require("./import");
|
|
28
|
+
Object.defineProperty(exports, "handleAppImport", { enumerable: true, get: function () { return import_1.handleAppImport; } });
|
|
29
|
+
// commands 层渲染 help 用的枚举,同 SUPPORTED_STACKS 走 handler barrel 转发
|
|
30
|
+
var index_2 = require("../../../services/app/import/index");
|
|
31
|
+
Object.defineProperty(exports, "IMPORT_PLATFORMS", { enumerable: true, get: function () { return index_2.IMPORT_PLATFORMS; } });
|
|
32
|
+
Object.defineProperty(exports, "DEFAULT_IMPORT_PLATFORM", { enumerable: true, get: function () { return index_2.DEFAULT_IMPORT_PLATFORM; } });
|