@lark-apaas/miaoda-cli 0.1.18-alpha.5fd4656 → 0.1.18-beta.9194d39

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.
@@ -198,7 +198,7 @@ function registerAppInit(parent) {
198
198
  异步安装(--async-install)
199
199
  跳过同步 npm install,派发一个 detached 后台进程装依赖后立即返回。
200
200
  .spark/meta.json 在返回前写出(= 脚手架就绪),install 真实结果由 event marker 表达。
201
- 仅沙箱(SANDBOX_ID 非空)写 marker(presence 定成败,content 给排障):
201
+ 仅沙箱(MIAODA_DEP_CACHE_DIR 非空)写 marker(presence 定成败,content 给排障):
202
202
  成功 → /tmp/event/WORKSPACE_READY
203
203
  失败 → /tmp/event/WORKSPACE_FAILED
204
204
  安装日志:/tmp/async_install_dep.std.log
@@ -14,7 +14,6 @@ const index_6 = require("../../cli/commands/skills/index");
14
14
  // 3 = AppType_APPLICATION(全栈应用,当前仅 nestjs-react-fullstack 一个 stack)
15
15
  // 4 = AppType_DESIGN(design-stack,SSR 渲染、无业务逻辑、无数据库)
16
16
  // 7 = miaoda-cli 自定义 modern 占位(后端枚举无 7,沙箱目前不传)
17
- // 8 = AppType_DESIGN_HTML(design-html,buildless 静态 HTML,命令集同 modern)
18
17
  // 其它(0/1/2/5/6 / 未设)→ default(命令全开,本地 dev / CI / 兼容回退)
19
18
  // stack 维度(nestjs-react-fullstack / vite-react / ...)是正交的,
20
19
  // skills sync 内部按 .spark/meta.json.stack 拉对应 coding-steering/steering/<stack>/ 子目录。
@@ -25,8 +24,6 @@ function resolveScene(appType, _archType) {
25
24
  return 'application';
26
25
  if (appType === '4')
27
26
  return 'design';
28
- if (appType === '8')
29
- return 'design-html';
30
27
  return 'default';
31
28
  }
32
29
  const SCENE_REGISTRARS = {
@@ -65,13 +62,6 @@ const SCENE_REGISTRARS = {
65
62
  (0, index_3.registerObservabilityCommands)(p);
66
63
  (0, index_6.registerSkillsCommands)(p);
67
64
  },
68
- // design-html scene(AppType_DESIGN_HTML=8):buildless 静态 HTML,
69
- // 命令集与 modern 一致(app init/sync + modern 拆分版 deploy + skills),不挂 db/file/observability。
70
- 'design-html': (p) => {
71
- (0, index_4.registerAppCommands)(p, { includeInit: true });
72
- (0, modern_1.registerDeployCommandsModern)(p);
73
- (0, index_6.registerSkillsCommands)(p);
74
- },
75
65
  };
76
66
  function readEnv(name) {
77
67
  const v = process.env[name]?.trim();
@@ -11,6 +11,7 @@ const coding_steering_1 = require("../../../utils/coding-steering");
11
11
  const logger_1 = require("../../../utils/logger");
12
12
  const githooks_1 = require("../../../utils/githooks");
13
13
  const logs_dir_1 = require("../../../utils/logs-dir");
14
+ const env_1 = require("../../../utils/env");
14
15
  const error_1 = require("../../../utils/error");
15
16
  const output_1 = require("../../../utils/output");
16
17
  /**
@@ -72,12 +73,12 @@ async function handleAppInit(opts) {
72
73
  (0, logs_dir_1.ensureLogsDir)(targetDir);
73
74
  // skills 同步软失败:拉不到 coding-steering 包不该阻断 writeSparkMeta /
74
75
  // activateGitHooks(之前会让 .spark/meta.json 没写,下次 init 半渲染状态又得重跑全套)。
75
- // 按运行环境(SANDBOX_ID)分流 outputLayout,不绑 stack:
76
- // - 沙箱内(SANDBOX_ID 非空)→ nested (.agent/skills/steering/<stack>/skills/),
76
+ // 按运行环境(isSandboxEnv:MIAODA_DEP_CACHE_DIR 非空)分流 outputLayout,不绑 stack:
77
+ // - 沙箱内 nested (.agent/skills/steering/<stack>/skills/),
77
78
  // 跟沙箱端 update-skills.sh 链路对齐
78
- // - 本地(SANDBOX_ID 空)→ flat (.agents/skills + .claude/skills 软链),
79
+ // - 本地 flat (.agents/skills + .claude/skills 软链),
79
80
  // Agent / Claude Code 用同一份;nrf 在本地是主战场,vite-react 等也一样适用
80
- const outputLayout = process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '' ? 'nested' : 'flat';
81
+ const outputLayout = (0, env_1.isSandboxEnv)() ? 'nested' : 'flat';
81
82
  let steeringResult;
82
83
  let steeringError;
83
84
  try {
@@ -14,6 +14,7 @@ const migrate_rule_1 = require("../../../utils/migrate-rule");
14
14
  const spark_meta_1 = require("../../../utils/spark-meta");
15
15
  const error_1 = require("../../../utils/error");
16
16
  const output_1 = require("../../../utils/output");
17
+ const env_1 = require("../../../utils/env");
17
18
  const logger_1 = require("../../../utils/logger");
18
19
  /**
19
20
  * miaoda app migrate --to <stack> [--from <stack>] [--dir <path>]
@@ -138,14 +139,12 @@ async function handleAppMigrate(opts) {
138
139
  // 跑的 node 进程仍按老逻辑工作(只起 vite,没起 nest),整体没切到 fullstack 模式。
139
140
  // 通过 pkill 杀掉 dev.js 主进程,依赖沙箱平台 supervisor 自动重新 exec dev.sh → dev.js,
140
141
  // 新进程用新 deps + 新 scripts,正常进入 fullstack 模式。
141
- // - 只在 SANDBOX_ID 非空时做(本地环境用户进程混杂,主动 pkill 风险大)
142
+ // - 只在沙箱内做(isSandboxEnv;本地环境用户进程混杂,主动 pkill 风险大)
142
143
  // - install 挂了不做 —— 新依赖不全, 杀旧 dev 后新 dev 起不来反而更糟,
143
144
  // 留着旧进程让用户先处理 installError
144
145
  // - 软失败:pkill 无匹配进程退出 1,catch 吞掉
145
146
  let devRestarted = false;
146
- if (installError === undefined &&
147
- process.env.SANDBOX_ID !== undefined &&
148
- process.env.SANDBOX_ID !== '') {
147
+ if (installError === undefined && (0, env_1.isSandboxEnv)()) {
149
148
  (0, logger_1.log)('migrate', '沙箱环境,重启 dev process(平台 supervisor 会自动拉起)...');
150
149
  try {
151
150
  (0, node_child_process_1.execFileSync)('pkill', ['-f', 'node.*scripts/dev\\.js'], { stdio: 'ignore' });
@@ -170,7 +169,7 @@ async function handleAppMigrate(opts) {
170
169
  followLatestPackages: followLatest,
171
170
  installError,
172
171
  devRestarted,
173
- nextActions: process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== ''
172
+ nextActions: (0, env_1.isSandboxEnv)()
174
173
  ? [
175
174
  'git status / git diff 评估改动并 commit',
176
175
  'miaoda skills sync 同步到新 stack 的 agent skills',
@@ -23,7 +23,6 @@ const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
24
  const nestjs_react_fullstack_1 = __importDefault(require("./nestjs-react-fullstack"));
25
25
  const design_stack_1 = __importDefault(require("./design-stack"));
26
- const design_html_1 = __importDefault(require("./design-html"));
27
26
  /**
28
27
  * 已纳入新 sync 机制的 stack 注册表。未在表里的 stack(如老 vite-react / html)走旧的
29
28
  * `upgrade/templates/<stack>/{files,patches}` 机制,由 sync handler 兜底。
@@ -31,7 +30,6 @@ const design_html_1 = __importDefault(require("./design-html"));
31
30
  const STACK_REGISTRY = {
32
31
  'nestjs-react-fullstack': nestjs_react_fullstack_1.default,
33
32
  'design-stack': design_stack_1.default,
34
- 'design-html': design_html_1.default,
35
33
  };
36
34
  /** 返回该 stack 的 SyncConfig;表里没有时返回 null。 */
37
35
  function getSyncConfig(stack) {
@@ -4,12 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ASYNC_INSTALL_LOG = void 0;
7
- exports.isSandboxEnv = isSandboxEnv;
8
7
  exports.runAsyncInstallWorker = runAsyncInstallWorker;
9
8
  exports.dispatchAsyncInstall = dispatchAsyncInstall;
10
9
  const node_child_process_1 = require("node:child_process");
11
10
  const node_fs_1 = __importDefault(require("node:fs"));
12
11
  const node_path_1 = __importDefault(require("node:path"));
12
+ const env_1 = require("../../../utils/env");
13
13
  const install_1 = require("./install");
14
14
  /** event marker 目录,写死;写前 mkdir -p。沙箱里 code server 轮询此目录。 */
15
15
  const EVENT_DIR = '/tmp/event';
@@ -19,10 +19,6 @@ const MARKER_READY = 'WORKSPACE_READY';
19
19
  const MARKER_FAILED = 'WORKSPACE_FAILED';
20
20
  /** 后台 worker stdout/stderr 落盘路径 */
21
21
  exports.ASYNC_INSTALL_LOG = '/tmp/async_install_dep.std.log';
22
- /** 沙箱判定:SANDBOX_ID 非空。与 init handler 的 outputLayout 分流口径一致。 */
23
- function isSandboxEnv() {
24
- return process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '';
25
- }
26
22
  function nowIso() {
27
23
  return new Date().toISOString();
28
24
  }
@@ -44,7 +40,7 @@ function writeMarker(eventDir, name, content) {
44
40
  */
45
41
  function runAsyncInstallWorker(opts) {
46
42
  const eventDir = opts.eventDir ?? EVENT_DIR;
47
- const sandbox = isSandboxEnv();
43
+ const sandbox = (0, env_1.isSandboxEnv)();
48
44
  if (sandbox)
49
45
  clearStaleMarkers(eventDir);
50
46
  const start = Date.now();
@@ -89,7 +85,7 @@ function runAsyncInstallWorker(opts) {
89
85
  function dispatchAsyncInstall(opts) {
90
86
  const eventDir = opts.eventDir ?? EVENT_DIR;
91
87
  const logPath = opts.logPath ?? exports.ASYNC_INSTALL_LOG;
92
- const sandbox = isSandboxEnv();
88
+ const sandbox = (0, env_1.isSandboxEnv)();
93
89
  if (sandbox)
94
90
  clearStaleMarkers(eventDir);
95
91
  // targetDir 走 argv 传,不拼进代码串(免转义);只把本模块路径 JSON 内联进去。
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ASYNC_INSTALL_LOG = exports.isSandboxEnv = exports.runAsyncInstallWorker = exports.dispatchAsyncInstall = exports.resolveNpmInstallRegistry = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
3
+ exports.ASYNC_INSTALL_LOG = exports.runAsyncInstallWorker = exports.dispatchAsyncInstall = exports.resolveNpmInstallRegistry = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
4
4
  var template_1 = require("./template");
5
5
  Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
6
6
  Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return template_1.SUPPORTED_STACKS; } });
@@ -14,5 +14,4 @@ Object.defineProperty(exports, "resolveNpmInstallRegistry", { enumerable: true,
14
14
  var async_install_1 = require("./async-install");
15
15
  Object.defineProperty(exports, "dispatchAsyncInstall", { enumerable: true, get: function () { return async_install_1.dispatchAsyncInstall; } });
16
16
  Object.defineProperty(exports, "runAsyncInstallWorker", { enumerable: true, get: function () { return async_install_1.runAsyncInstallWorker; } });
17
- Object.defineProperty(exports, "isSandboxEnv", { enumerable: true, get: function () { return async_install_1.isSandboxEnv; } });
18
17
  Object.defineProperty(exports, "ASYNC_INSTALL_LOG", { enumerable: true, get: function () { return async_install_1.ASYNC_INSTALL_LOG; } });
@@ -15,7 +15,6 @@ exports.TEMPLATE_PACKAGE_BY_STACK = {
15
15
  'vite-react': '@lark-apaas/coding-template-vite-react',
16
16
  html: '@lark-apaas/coding-template-html',
17
17
  'nestjs-react-fullstack': '@lark-apaas/coding-template-nestjs-react-fullstack',
18
- 'design-html': '@lark-apaas/coding-template-design-html',
19
18
  };
20
19
  /**
21
20
  * 短名 → template 包钉版表。renderTemplate 默认按这张表取版本号,表外 stack 跟 npm latest。
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.uploadDesignArtifacts = exports.runDesignBuild = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
3
+ exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
4
4
  var context_1 = require("./context");
5
5
  Object.defineProperty(exports, "prepareDeployContext", { enumerable: true, get: function () { return context_1.prepareDeployContext; } });
6
6
  var pre_release_1 = require("./pre-release");
7
7
  Object.defineProperty(exports, "preRelease", { enumerable: true, get: function () { return pre_release_1.preRelease; } });
8
8
  var build_1 = require("./build");
9
9
  Object.defineProperty(exports, "runBuild", { enumerable: true, get: function () { return build_1.runBuild; } });
10
- var design_build_1 = require("./design-build");
11
- Object.defineProperty(exports, "runDesignBuild", { enumerable: true, get: function () { return design_build_1.runDesignBuild; } });
12
- var design_upload_1 = require("./design-upload");
13
- Object.defineProperty(exports, "uploadDesignArtifacts", { enumerable: true, get: function () { return design_upload_1.uploadDesignArtifacts; } });
14
10
  var upload_1 = require("./upload");
15
11
  Object.defineProperty(exports, "uploadArtifacts", { enumerable: true, get: function () { return upload_1.uploadArtifacts; } });
16
12
  var local_release_1 = require("./local-release");
@@ -5,13 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.uploadArtifacts = uploadArtifacts;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_os_1 = __importDefault(require("node:os"));
8
9
  const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
9
11
  const index_1 = require("../../../../api/deploy/index");
10
12
  const error_1 = require("../../../../utils/error");
11
13
  const logger_1 = require("../../../../utils/logger");
12
14
  const constants_1 = require("../constants");
13
15
  const protocol_1 = require("../protocol");
14
- const tosutil_1 = require("./tosutil");
15
16
  const UPLOAD_PLAN = [
16
17
  {
17
18
  kind: 'tos',
@@ -31,6 +32,28 @@ const UPLOAD_PLAN = [
31
32
  dataKey: protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL,
32
33
  },
33
34
  ];
35
+ function resolveTosutilPath() {
36
+ try {
37
+ const resolved = (0, node_child_process_1.execFileSync)('which', ['tosutil'], { encoding: 'utf-8' }).trim();
38
+ if (resolved)
39
+ return resolved;
40
+ }
41
+ catch {
42
+ /* fallthrough */
43
+ }
44
+ throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
45
+ }
46
+ function tosutilUploadFromTos(cred) {
47
+ return {
48
+ accessKeyID: cred.accessKeyID,
49
+ secretAccessKey: cred.secretAccessKey,
50
+ sessionToken: cred.sessionToken,
51
+ endpoint: cred.endpoint,
52
+ region: cred.region,
53
+ bucket: cred.bucket,
54
+ prefix: cred.prefix,
55
+ };
56
+ }
34
57
  function tosutilUploadFromPaasStorage(cred, dataKey) {
35
58
  const { bucket, prefix } = (0, protocol_1.parseTosUploadPrefix)(cred.uploadPrefix, dataKey);
36
59
  return {
@@ -43,6 +66,106 @@ function tosutilUploadFromPaasStorage(cred, dataKey) {
43
66
  prefix,
44
67
  };
45
68
  }
69
+ /**
70
+ * 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
71
+ * 返回 config 路径,调用方负责清理。
72
+ */
73
+ function writeTosutilConfig(tosutilPath, cred, label) {
74
+ const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
75
+ node_fs_1.default.writeFileSync(confPath, '');
76
+ (0, node_child_process_1.execFileSync)(tosutilPath, [
77
+ 'config',
78
+ '-conf',
79
+ confPath,
80
+ '-e',
81
+ cred.endpoint,
82
+ '-i',
83
+ cred.accessKeyID,
84
+ '-k',
85
+ cred.secretAccessKey,
86
+ '-t',
87
+ cred.sessionToken,
88
+ '-re',
89
+ cred.region,
90
+ ], { stdio: 'pipe' });
91
+ return confPath;
92
+ }
93
+ /** 把 tosutil 进程的输出按行回放到 stderr,便于线上排查(沙箱里无法 attach 进程)。 */
94
+ function streamTosutilOutput(output) {
95
+ if (!output)
96
+ return;
97
+ for (const line of output.split('\n')) {
98
+ if (line.length === 0)
99
+ continue;
100
+ process.stderr.write(`[tosutil] ${line}\n`);
101
+ }
102
+ }
103
+ function normalizeTosDest(bucket, prefix) {
104
+ const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
105
+ const normalized = prefix.replace(/^\/+/, '');
106
+ return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
107
+ }
108
+ /**
109
+ * 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
110
+ */
111
+ function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
112
+ const entries = node_fs_1.default.readdirSync(sourceDir);
113
+ if (entries.length === 0) {
114
+ (0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
115
+ return;
116
+ }
117
+ const dest = normalizeTosDest(cred.bucket, cred.prefix);
118
+ (0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
119
+ const confPath = writeTosutilConfig(tosutilPath, cred, label);
120
+ let output;
121
+ try {
122
+ output = (0, node_child_process_1.execFileSync)(tosutilPath, [
123
+ 'cp',
124
+ sourceDir,
125
+ dest,
126
+ '-r',
127
+ '-flat',
128
+ '-j',
129
+ '5',
130
+ '-p',
131
+ '3',
132
+ '-ps',
133
+ '10485760',
134
+ '-f',
135
+ '-conf',
136
+ confPath,
137
+ ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
138
+ streamTosutilOutput(output);
139
+ }
140
+ catch (err) {
141
+ const e = err;
142
+ const stderr = (e.stderr ?? '').trim();
143
+ const stdout = (e.stdout ?? '').trim();
144
+ streamTosutilOutput(stdout);
145
+ streamTosutilOutput(stderr);
146
+ throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
147
+ ` stderr: ${stderr || '(empty)'}\n` +
148
+ ` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
149
+ }
150
+ finally {
151
+ try {
152
+ node_fs_1.default.unlinkSync(confPath);
153
+ }
154
+ catch {
155
+ /* ignore */
156
+ }
157
+ }
158
+ const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
159
+ const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
160
+ const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
161
+ const failedCount = failedMatch ? Number.parseInt(failedMatch[1], 10) : 0;
162
+ if (failedCount > 0) {
163
+ throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
164
+ }
165
+ if (succeedCount === 0) {
166
+ throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
167
+ }
168
+ }
46
169
  function uploadTosTarget(tosutilPath, target, localPath, data) {
47
170
  const credRaw = target.required
48
171
  ? (0, protocol_1.requireDataKey)(data, target.dataKey)
@@ -53,7 +176,7 @@ function uploadTosTarget(tosutilPath, target, localPath, data) {
53
176
  }
54
177
  const cred = (0, protocol_1.parseTosUploadCredential)(credRaw, target.dataKey);
55
178
  (0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
56
- (0, tosutil_1.uploadDirWithCredential)(tosutilPath, localPath, (0, tosutil_1.tosutilUploadFromTos)(cred), target.dir);
179
+ uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromTos(cred), target.dir);
57
180
  return true;
58
181
  }
59
182
  function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
@@ -65,7 +188,7 @@ function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
65
188
  }
66
189
  const cred = (0, protocol_1.parsePaasStorageCredential)((0, protocol_1.requireDataKey)(data, target.dataKey), target.dataKey);
67
190
  (0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
68
- (0, tosutil_1.uploadDirWithCredential)(tosutilPath, localPath, tosutilUploadFromPaasStorage(cred, target.dataKey), target.dir);
191
+ uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromPaasStorage(cred, target.dataKey), target.dir);
69
192
  return cred;
70
193
  }
71
194
  /**
@@ -79,7 +202,7 @@ function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
79
202
  * 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
80
203
  */
81
204
  async function uploadArtifacts(opts) {
82
- const tosutilPath = (0, tosutil_1.resolveTosutilPath)();
205
+ const tosutilPath = resolveTosutilPath();
83
206
  const distDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR);
84
207
  const outcome = { uploaded: 0 };
85
208
  for (const target of UPLOAD_PLAN) {
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.designLocalPublishPipeline = exports.localBuildLocalPublishPipeline = void 0;
3
+ exports.localBuildLocalPublishPipeline = void 0;
4
4
  var local_1 = require("./local");
5
5
  Object.defineProperty(exports, "localBuildLocalPublishPipeline", { enumerable: true, get: function () { return local_1.localBuildLocalPublishPipeline; } });
6
- var design_local_1 = require("./design-local");
7
- Object.defineProperty(exports, "designLocalPublishPipeline", { enumerable: true, get: function () { return design_local_1.designLocalPublishPipeline; } });
@@ -33,13 +33,6 @@ exports.DataKey = {
33
33
  // downloadURLPrefix 替代原 static_cdn_prefix 注入 build env。
34
34
  /** dist/output_static 上传凭证(仅当目录存在时使用) */
35
35
  OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL: 'output_static_paas_storage_credential',
36
- // ── design 部署专用(design_local_deploy) ──
37
- /** 覆盖整个 {app_id} 文件夹的上传凭证(形态同 TosUploadCredential) */
38
- OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL: 'output_all_tos_upload_credential',
39
- /** version/备份份真实路径(后端已替换 {app_id}/{version}) */
40
- OUTPUT_BACKUP_PATH: 'output_backup_path',
41
- /** latest 份真实路径(后端已替换 {app_id}/latest) */
42
- OUTPUT_LATEST_PATH: 'output_latest_path',
43
36
  };
44
37
  /** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
45
38
  function requireDataKey(data, key) {
@@ -1,19 +1,13 @@
1
1
  "use strict";
2
2
  // modern deploy 主入口(dispatch 层)。
3
3
  //
4
- // templateKey pipeline:design_local_deploy → design pipeline,其余 → client pipeline。
5
- // 仅为路由轻量解析 templateKey(读 meta.stack + 映射),不跑前置检查——前置检查在各
6
- // pipeline 内部的 prepareDeployContext 里做。client 分支与原行为完全一致。
7
- // pipeline 之间彼此独立、不共享中间状态。
4
+ // 当前阶段(Scope A)只接入"本地构建 + 本地部署"一条 pipeline
5
+ // 后续接入"本地构建 + 远端部署 / 远端构建 + 远端部署"时,在此处按 template_key
6
+ // 或显式 flag 选不同 pipeline。pipeline 之间彼此独立、不共享中间状态。
8
7
  Object.defineProperty(exports, "__esModule", { value: true });
9
8
  exports.runModernDeploy = runModernDeploy;
10
- const spark_meta_1 = require("../../../utils/spark-meta");
11
- const template_key_map_1 = require("./template-key-map");
12
9
  const index_1 = require("./pipelines/index");
13
10
  async function runModernDeploy(opts) {
14
- const stack = (0, spark_meta_1.readSparkMeta)(opts.projectDir).stack ?? '';
15
- if ((0, template_key_map_1.resolveTemplateKey)(stack) === template_key_map_1.DESIGN_LOCAL_DEPLOY) {
16
- return (0, index_1.designLocalPublishPipeline)(opts);
17
- }
11
+ // 暂只支持本地构建 + 本地部署
18
12
  return (0, index_1.localBuildLocalPublishPipeline)(opts);
19
13
  }
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DESIGN_LOCAL_DEPLOY = void 0;
4
3
  exports.resolveTemplateKey = resolveTemplateKey;
5
4
  /**
6
5
  * 本地 stack 短名 → 后端发布模板 key 的映射。
@@ -11,12 +10,9 @@ exports.resolveTemplateKey = resolveTemplateKey;
11
10
  * 未来出现需要走别的 templateKey 的 stack,再在表里加一行覆盖默认。
12
11
  */
13
12
  const DEFAULT_TEMPLATE_KEY = 'client_local_deploy';
14
- /** design 部署 templateKey(run.ts 据此分流到 design pipeline) */
15
- exports.DESIGN_LOCAL_DEPLOY = 'design_local_deploy';
16
13
  const TEMPLATE_KEY_MAP = {
17
14
  'vite-react': DEFAULT_TEMPLATE_KEY,
18
15
  html: DEFAULT_TEMPLATE_KEY,
19
- 'design-html': exports.DESIGN_LOCAL_DEPLOY,
20
16
  };
21
17
  /**
22
18
  * 把本地 stack 短名映射到后端 templateKey;未配置的 stack 走默认 client_local_deploy。
@@ -8,10 +8,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const npm_pack_1 = require("../utils/npm-pack");
10
10
  const logger_1 = require("../utils/logger");
11
+ const env_1 = require("../utils/env");
11
12
  const STEERING_PACKAGE = '@lark-apaas/coding-steering';
12
13
  function inferMode() {
13
- // process.env.SANDBOX_ID 非空沙箱;其他(undefined / 空串)→ 本地
14
- return process.env.SANDBOX_ID ? 'sandbox' : 'local';
14
+ // 沙箱(MIAODA_DEP_CACHE_DIR 非空)→ sandbox;本地 local
15
+ return (0, env_1.isSandboxEnv)() ? 'sandbox' : 'local';
15
16
  }
16
17
  /**
17
18
  * 把 coding-steering 包内 <stack>/skills_common + 专有层(skills 或 skills_local)同步到
@@ -19,7 +20,7 @@ function inferMode() {
19
20
  *
20
21
  * 包内目录约定(每个 stack 三层):
21
22
  * <stack>/skills_common → 沙箱 + 本地都下发的共有层
22
- * <stack>/skills → 沙箱专有(沙箱端 update-skills.sh / SANDBOX_ID 非空时拿)
23
+ * <stack>/skills → 沙箱专有(沙箱端 update-skills.sh / mode=sandbox 时拿)
23
24
  * <stack>/skills_local → 本地专有(本地 dev / agent 环境拿)
24
25
  *
25
26
  * 下发组合(mode):
@@ -31,8 +32,8 @@ function inferMode() {
31
32
  * 'flat':拷到 .agents/skills/(平铺),tech.md → .agents/tech.md,并创建 .claude/skills
32
33
  * 软链指向 ../.agents/skills 让 Claude Code 识别同一份
33
34
  *
34
- * 同名 skill 覆盖顺序:common < 专有(专有覆盖 common 同名)。mode 缺省按 SANDBOX_ID env
35
- * 推断(沙箱平台运行时注入应用所属沙箱 ID;空 / undefined 则视为本地)。
35
+ * 同名 skill 覆盖顺序:common < 专有(专有覆盖 common 同名)。mode 缺省按 isSandboxEnv()
36
+ * 推断(MIAODA_DEP_CACHE_DIR 非空 → sandbox;空 / undefined → local)。
36
37
  *
37
38
  * 纯本地 atom(pull npm package + 拷贝文件),不属于任何 cli 域;放在 utils 让
38
39
  * app init handler 与独立 skills sync handler 都能复用。
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isSandboxEnv = isSandboxEnv;
4
+ /**
5
+ * 运行环境判定:当前进程是否跑在妙搭沙箱里。
6
+ *
7
+ * 口径:`process.env.MIAODA_DEP_CACHE_DIR` 非空(既非 undefined 也非空串)即为沙箱。
8
+ * 沙箱平台运行时会给应用进程注入依赖缓存目录;本地 dev / agent 环境拿不到该变量。
9
+ *
10
+ * 历史上用 `SANDBOX_ID` 判断,但该变量在部分沙箱场景下并不会注入,判断不可靠,已换成
11
+ * `MIAODA_DEP_CACHE_DIR`。注意:下发给 user app 的 `scripts/dev.sh` 仍按 `SANDBOX_ID`
12
+ * 判断(独立 bash 链路,不复用本函数)。
13
+ *
14
+ * 这是 CLI 运行时沙箱判定的 SSOT —— init outputLayout 分流、migrate 重启 dev process、
15
+ * coding-steering 同步 mode 推断、async-install 写 marker 都走这里,别再各处内联读环境变量。
16
+ */
17
+ function isSandboxEnv() {
18
+ return process.env.MIAODA_DEP_CACHE_DIR !== undefined && process.env.MIAODA_DEP_CACHE_DIR !== '';
19
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pollUntilDone = exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = exports.getInnerApi = exports.postInnerApi = exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.fmt = exports.isJsonMode = exports.emitPaged = exports.emitOk = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
3
+ exports.pollUntilDone = exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = exports.isSandboxEnv = exports.getInnerApi = exports.postInnerApi = exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.fmt = exports.isJsonMode = exports.emitPaged = exports.emitOk = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
4
4
  var error_1 = require("./error");
5
5
  Object.defineProperty(exports, "AppError", { enumerable: true, get: function () { return error_1.AppError; } });
6
6
  Object.defineProperty(exports, "HttpError", { enumerable: true, get: function () { return error_1.HttpError; } });
@@ -30,6 +30,8 @@ Object.defineProperty(exports, "setHttpClient", { enumerable: true, get: functio
30
30
  Object.defineProperty(exports, "setRuntimeHttpClient", { enumerable: true, get: function () { return http_1.setRuntimeHttpClient; } });
31
31
  Object.defineProperty(exports, "postInnerApi", { enumerable: true, get: function () { return http_1.postInnerApi; } });
32
32
  Object.defineProperty(exports, "getInnerApi", { enumerable: true, get: function () { return http_1.getInnerApi; } });
33
+ var env_1 = require("./env");
34
+ Object.defineProperty(exports, "isSandboxEnv", { enumerable: true, get: function () { return env_1.isSandboxEnv; } });
33
35
  var time_1 = require("./time");
34
36
  Object.defineProperty(exports, "parseTimeToMs", { enumerable: true, get: function () { return time_1.parseTimeToMs; } });
35
37
  Object.defineProperty(exports, "parseToMs", { enumerable: true, get: function () { return time_1.parseToMs; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.18-alpha.5fd4656",
3
+ "version": "0.1.18-beta.9194d39",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,25 +0,0 @@
1
- "use strict";
2
- /**
3
- * design-html 的 sync 规则。
4
- *
5
- * design-html 是 buildless 静态 HTML stack(design-html scene,MIAODA_APP_TYPE=8),
6
- * 无依赖、无 nest 后端、无数据库、无 dev 链路,只有一个打包脚本 scripts/build.sh
7
- * (源码即产物:rsync 到 dist/output + 生成 routes.json)。
8
- *
9
- * 所以 sync 只做一件事:把 cli 自带的 scripts/build.sh 派生覆盖到 user app,
10
- * 让 build 脚本可独立于模板版本更新。不同步 .githooks、不加 dev:local、
11
- * 不动 .gitignore(卫生项直接在 coding 仓模板 _gitignore 里维护)。
12
- */
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.SYNC_CONFIG = void 0;
15
- exports.SYNC_CONFIG = {
16
- sync: [
17
- {
18
- type: 'directory',
19
- from: 'scripts',
20
- to: 'scripts',
21
- overwrite: true,
22
- },
23
- ],
24
- };
25
- exports.default = exports.SYNC_CONFIG;
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runDesignBuild = runDesignBuild;
4
- const node_child_process_1 = require("node:child_process");
5
- const error_1 = require("../../../../utils/error");
6
- const logger_1 = require("../../../../utils/logger");
7
- /**
8
- * design-html 本地构建:跑 `npm run build`(模板里即 bash scripts/build.sh)。
9
- * build.sh 读 process.env.CLIENT_BASE_PATH(平台预置),子进程继承 env 即透传,
10
- * 不需要 CDN 凭证、不注入 MIAODA_*。
11
- */
12
- function runDesignBuild(opts) {
13
- (0, logger_1.log)('deploy', 'Building (design)...');
14
- try {
15
- (0, node_child_process_1.execSync)('npm run build', { cwd: opts.projectDir, stdio: 'inherit', env: process.env });
16
- }
17
- catch (err) {
18
- throw new error_1.AppError('DEPLOY_BUILD_FAILED', `npm run build failed: ${err.message}`);
19
- }
20
- }
@@ -1,73 +0,0 @@
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.uploadDesignArtifacts = uploadDesignArtifacts;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const error_1 = require("../../../../utils/error");
10
- const logger_1 = require("../../../../utils/logger");
11
- const constants_1 = require("../constants");
12
- const protocol_1 = require("../protocol");
13
- const tosutil_1 = require("./tosutil");
14
- /** 递归收集 dir 下相对路径(posix 分隔),与 -flat 上传后远端 key 对齐。 */
15
- function listLocalRelKeys(dir) {
16
- const out = new Set();
17
- const walk = (cur, rel) => {
18
- for (const entry of node_fs_1.default.readdirSync(cur, { withFileTypes: true })) {
19
- const childRel = rel ? `${rel}/${entry.name}` : entry.name;
20
- if (entry.isDirectory())
21
- walk(node_path_1.default.join(cur, entry.name), childRel);
22
- else
23
- out.add(childRel);
24
- }
25
- };
26
- walk(dir, '');
27
- return out;
28
- }
29
- /**
30
- * design 部署上传(省带宽):
31
- * 1. 本地 dist/output ──cp(local→tos)──▶ output_backup_path/ (唯一一次上传)
32
- * 2. output_backup_path ──cp(tos→tos,同凭证同桶)──▶ output_latest_path (服务端复制)
33
- * 3. ls latest → 跟本地 diff → rm latest 里源端已无的对象 (精确镜像)
34
- * design-html 只产 dist/output,无 output_resource / output_static / capability。
35
- */
36
- async function uploadDesignArtifacts(opts) {
37
- await Promise.resolve();
38
- const tosutilPath = (0, tosutil_1.resolveTosutilPath)();
39
- const outputDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR, constants_1.OUTPUT_DIR);
40
- if (!node_fs_1.default.existsSync(outputDir)) {
41
- throw new error_1.AppError('DEPLOY_NO_BUILD_OUTPUT', `Required directory missing: ${constants_1.DIST_DIR}/${constants_1.OUTPUT_DIR}`);
42
- }
43
- // 空产物防呆:本地一个文件都没有时,绝不继续(否则 latest 会被整体 prune 清空)。
44
- const localKeys = listLocalRelKeys(outputDir);
45
- if (localKeys.size === 0) {
46
- throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `No files under ${constants_1.DIST_DIR}/${constants_1.OUTPUT_DIR} to upload — check build output.`);
47
- }
48
- const cred = (0, protocol_1.parseTosUploadCredential)((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL), protocol_1.DataKey.OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL);
49
- const backupPath = (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_BACKUP_PATH);
50
- const latestPath = (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_LATEST_PATH);
51
- const base = (0, tosutil_1.tosutilUploadFromTos)(cred); // 鉴权 + bucket
52
- // 1. 本地 → backup(version):唯一一次本地上传
53
- (0, logger_1.log)('deploy', `Uploading ${constants_1.OUTPUT_DIR} → ${backupPath}...`);
54
- (0, tosutil_1.uploadDirWithCredential)(tosutilPath, outputDir, { ...base, prefix: backupPath }, 'output_backup');
55
- // 2. backup → latest:服务端 tos→tos cp(不占带宽)
56
- (0, logger_1.log)('deploy', `Server-side copy ${backupPath} → ${latestPath}...`);
57
- (0, tosutil_1.copyTosToTos)(tosutilPath, base, backupPath, latestPath);
58
- // 3. latest 精确镜像:删源端已不存在的对象。
59
- // 安全阀:远端有对象但跟本地零重合时,判定为 key 形态/ls 解析不匹配,跳过 prune——
60
- // 宁可漏删(latest 残留旧文件)也绝不误删整目录(数据丢失)。design-html 的 build.sh
61
- // 必产稳定名的 routes.json / *.html,正常重合不会为空,零重合即异常信号。
62
- const remoteKeys = (0, tosutil_1.listRemoteKeys)(tosutilPath, { ...base, prefix: latestPath });
63
- const overlap = remoteKeys.filter((k) => localKeys.has(k));
64
- if (remoteKeys.length > 0 && overlap.length === 0) {
65
- (0, logger_1.log)('deploy', `⚠ latest 远端 ${String(remoteKeys.length)} 个对象与本地零重合,疑似 key 形态不匹配,跳过 prune(避免误删)`);
66
- return;
67
- }
68
- const staleKeys = remoteKeys.filter((k) => !localKeys.has(k));
69
- if (staleKeys.length > 0) {
70
- (0, logger_1.log)('deploy', `Pruning ${String(staleKeys.length)} stale object(s) from latest...`);
71
- (0, tosutil_1.removeRemoteKeys)(tosutilPath, { ...base, prefix: latestPath }, staleKeys);
72
- }
73
- }
@@ -1,234 +0,0 @@
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.resolveTosutilPath = resolveTosutilPath;
7
- exports.tosutilUploadFromTos = tosutilUploadFromTos;
8
- exports.writeTosutilConfig = writeTosutilConfig;
9
- exports.streamTosutilOutput = streamTosutilOutput;
10
- exports.normalizeTosDest = normalizeTosDest;
11
- exports.uploadDirWithCredential = uploadDirWithCredential;
12
- exports.copyTosToTos = copyTosToTos;
13
- exports.listRemoteKeys = listRemoteKeys;
14
- exports.removeRemoteKeys = removeRemoteKeys;
15
- const node_fs_1 = __importDefault(require("node:fs"));
16
- const node_os_1 = __importDefault(require("node:os"));
17
- const node_path_1 = __importDefault(require("node:path"));
18
- const node_child_process_1 = require("node:child_process");
19
- const error_1 = require("../../../../utils/error");
20
- const logger_1 = require("../../../../utils/logger");
21
- function resolveTosutilPath() {
22
- try {
23
- const resolved = (0, node_child_process_1.execFileSync)('which', ['tosutil'], { encoding: 'utf-8' }).trim();
24
- if (resolved)
25
- return resolved;
26
- }
27
- catch {
28
- /* fallthrough */
29
- }
30
- throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
31
- }
32
- function tosutilUploadFromTos(cred) {
33
- return {
34
- accessKeyID: cred.accessKeyID,
35
- secretAccessKey: cred.secretAccessKey,
36
- sessionToken: cred.sessionToken,
37
- endpoint: cred.endpoint,
38
- region: cred.region,
39
- bucket: cred.bucket,
40
- prefix: cred.prefix,
41
- };
42
- }
43
- /**
44
- * 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
45
- * 返回 config 路径,调用方负责清理。
46
- */
47
- function writeTosutilConfig(tosutilPath, cred, label) {
48
- const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
49
- node_fs_1.default.writeFileSync(confPath, '');
50
- (0, node_child_process_1.execFileSync)(tosutilPath, [
51
- 'config',
52
- '-conf',
53
- confPath,
54
- '-e',
55
- cred.endpoint,
56
- '-i',
57
- cred.accessKeyID,
58
- '-k',
59
- cred.secretAccessKey,
60
- '-t',
61
- cred.sessionToken,
62
- '-re',
63
- cred.region,
64
- ], { stdio: 'pipe' });
65
- return confPath;
66
- }
67
- /** 把 tosutil 进程的输出按行回放到 stderr,便于线上排查(沙箱里无法 attach 进程)。 */
68
- function streamTosutilOutput(output) {
69
- if (!output)
70
- return;
71
- for (const line of output.split('\n')) {
72
- if (line.length === 0)
73
- continue;
74
- process.stderr.write(`[tosutil] ${line}\n`);
75
- }
76
- }
77
- function normalizeTosDest(bucket, prefix) {
78
- const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
79
- const normalized = prefix.replace(/^\/+/, '');
80
- return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
81
- }
82
- /**
83
- * 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
84
- */
85
- function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
86
- const entries = node_fs_1.default.readdirSync(sourceDir);
87
- if (entries.length === 0) {
88
- (0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
89
- return;
90
- }
91
- const dest = normalizeTosDest(cred.bucket, cred.prefix);
92
- (0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
93
- const confPath = writeTosutilConfig(tosutilPath, cred, label);
94
- let output;
95
- try {
96
- output = (0, node_child_process_1.execFileSync)(tosutilPath, [
97
- 'cp',
98
- sourceDir,
99
- dest,
100
- '-r',
101
- '-flat',
102
- '-j',
103
- '5',
104
- '-p',
105
- '3',
106
- '-ps',
107
- '10485760',
108
- '-f',
109
- '-conf',
110
- confPath,
111
- ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
112
- streamTosutilOutput(output);
113
- }
114
- catch (err) {
115
- const e = err;
116
- const stderr = (e.stderr ?? '').trim();
117
- const stdout = (e.stdout ?? '').trim();
118
- streamTosutilOutput(stdout);
119
- streamTosutilOutput(stderr);
120
- throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
121
- ` stderr: ${stderr || '(empty)'}\n` +
122
- ` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
123
- }
124
- finally {
125
- try {
126
- node_fs_1.default.unlinkSync(confPath);
127
- }
128
- catch {
129
- /* ignore */
130
- }
131
- }
132
- const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
133
- const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
134
- const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
135
- const failedCount = failedMatch ? Number.parseInt(failedMatch[1], 10) : 0;
136
- if (failedCount > 0) {
137
- throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
138
- }
139
- if (succeedCount === 0) {
140
- throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
141
- }
142
- }
143
- /** version→latest 同桶同凭证服务端复制:tosutil cp tos://src tos://dst -r -flat -f */
144
- function copyTosToTos(tosutilPath, cred, srcPrefix, dstPrefix) {
145
- const src = normalizeTosDest(cred.bucket, srcPrefix);
146
- const dst = normalizeTosDest(cred.bucket, dstPrefix);
147
- const confPath = writeTosutilConfig(tosutilPath, cred, 'tos2tos');
148
- try {
149
- const out = (0, node_child_process_1.execFileSync)(tosutilPath, ['cp', src, dst, '-r', '-flat', '-f', '-j', '5', '-conf', confPath], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
150
- streamTosutilOutput(out);
151
- }
152
- catch (err) {
153
- const e = err;
154
- streamTosutilOutput((e.stdout ?? '').trim());
155
- streamTosutilOutput((e.stderr ?? '').trim());
156
- throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp(tos→tos) exited status ${String(e.status ?? -1)}: ${src} → ${dst}`);
157
- }
158
- finally {
159
- try {
160
- node_fs_1.default.unlinkSync(confPath);
161
- }
162
- catch {
163
- /* ignore */
164
- }
165
- }
166
- }
167
- /** 列 tos://bucket/prefix 下所有对象,返回相对 prefix 的 key 集合(用 ls -r -s 精简格式)。 */
168
- function listRemoteKeys(tosutilPath, cred) {
169
- const base = normalizeTosDest(cred.bucket, cred.prefix); // tos://bucket/prefix
170
- const confPath = writeTosutilConfig(tosutilPath, cred, 'ls');
171
- let out;
172
- try {
173
- out = (0, node_child_process_1.execFileSync)(tosutilPath, ['ls', base, '-r', '-s', '-conf', confPath], {
174
- encoding: 'utf-8',
175
- stdio: ['pipe', 'pipe', 'pipe'],
176
- });
177
- }
178
- catch (err) {
179
- const e = err;
180
- throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil ls exited status ${String(e.status ?? -1)} for ${base}: ${(e.stderr ?? '').trim()}`);
181
- }
182
- finally {
183
- try {
184
- node_fs_1.default.unlinkSync(confPath);
185
- }
186
- catch {
187
- /* ignore */
188
- }
189
- }
190
- const prefixUri = base.replace(/\/+$/, '') + '/';
191
- const keys = [];
192
- for (const line of out.split('\n')) {
193
- const t = line.trim();
194
- if (!t.startsWith(prefixUri))
195
- continue;
196
- const rel = t.slice(prefixUri.length);
197
- if (rel.length > 0 && !rel.endsWith('/'))
198
- keys.push(rel);
199
- }
200
- return keys;
201
- }
202
- /** 删除 tos://bucket/prefix 下指定相对 key(逐个 rm -f)。 */
203
- function removeRemoteKeys(tosutilPath, cred, relKeys) {
204
- if (relKeys.length === 0)
205
- return;
206
- const confPath = writeTosutilConfig(tosutilPath, cred, 'rm');
207
- const prefix = cred.prefix.replace(/\/+$/, '');
208
- const failed = [];
209
- try {
210
- for (const rel of relKeys) {
211
- // 用与 cp/ls 同一份 normalizeTosDest 拼目标 URI,避免删除路径与其它操作漂移。
212
- const dest = normalizeTosDest(cred.bucket, `${prefix}/${rel}`);
213
- try {
214
- (0, node_child_process_1.execFileSync)(tosutilPath, ['rm', dest, '-f', '-conf', confPath], {
215
- stdio: ['pipe', 'pipe', 'pipe'],
216
- });
217
- }
218
- catch {
219
- failed.push(rel);
220
- }
221
- }
222
- }
223
- finally {
224
- try {
225
- node_fs_1.default.unlinkSync(confPath);
226
- }
227
- catch {
228
- /* ignore */
229
- }
230
- }
231
- if (failed.length > 0) {
232
- throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `prune latest failed for: ${failed.join(', ')}`);
233
- }
234
- }
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.designLocalPublishPipeline = designLocalPublishPipeline;
4
- const spark_meta_1 = require("../../../../utils/spark-meta");
5
- const error_1 = require("../../../../utils/error");
6
- const logger_1 = require("../../../../utils/logger");
7
- const index_1 = require("../atoms/index");
8
- /**
9
- * design(design_local_deploy)本地构建 + 本地部署 pipeline。
10
- *
11
- * 与 client 版差异:build 走 runDesignBuild、upload 走 uploadDesignArtifacts(version+latest
12
- * 双写、latest 精确镜像)、不注册插件能力(design-html 无 capability)。其余复用同一套 atom。
13
- */
14
- async function designLocalPublishPipeline(opts) {
15
- const ctx = (0, index_1.prepareDeployContext)(opts.projectDir, opts.appId);
16
- (0, logger_1.log)('deploy', 'Pre-releasing...');
17
- const pre = await (0, index_1.preRelease)(ctx.appId, ctx.templateKey);
18
- (0, logger_1.log)('deploy', `pre_release_id=${pre.preReleaseID} version=${pre.version}`);
19
- if (pre.data === undefined) {
20
- throw new error_1.AppError('DEPLOY_PRE_RELEASE_DATA_MISSING', 'preRelease did not return a `data` map — backend tcc may not be configured for current template_key');
21
- }
22
- const data = pre.data;
23
- if (!opts.skipBuild) {
24
- (0, index_1.runDesignBuild)({ projectDir: ctx.projectDir });
25
- }
26
- (0, logger_1.log)('deploy', 'Creating local release...');
27
- const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
28
- try {
29
- await (0, index_1.uploadDesignArtifacts)({ projectDir: ctx.projectDir, data });
30
- await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
31
- }
32
- catch (err) {
33
- await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
34
- throw err;
35
- }
36
- if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
37
- (0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { appUrl: release.onlineUrl });
38
- }
39
- (0, logger_1.log)('deploy', 'Deployed successfully');
40
- return {
41
- appId: ctx.appId,
42
- version: pre.version,
43
- url: release.onlineUrl ?? '',
44
- releaseID: release.releaseID,
45
- preReleaseID: pre.preReleaseID,
46
- };
47
- }
@@ -1,70 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
-
4
- # design-html 的「构建」= 打包,不是编译。
5
- # 1. 把源文件原样拷进 dist/output/(保留目录层级),排除工具/公共文件;
6
- # 2. 扫描 .html 生成 dist/output/routes.json(schema 与 vite-react 对齐:[{ path }])。
7
- # 无 bundler、无 transpile —— 源码即产物。
8
-
9
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
10
- OUTPUT="$ROOT/dist/output"
11
-
12
- # basePath:平台注入 CLIENT_BASE_PATH 作为路由前缀(如 /app/<id>);
13
- # 本地裸跑不设则为空(routes 退到 '/')。
14
- BASE_PATH="${CLIENT_BASE_PATH:-}"
15
-
16
- # 不进产物的公共 / 工具文件(rsync 模式,相对 ROOT)
17
- EXCLUDES=(
18
- ".git" "node_modules" "dist" "scripts"
19
- "package.json" "package-lock.json" "pnpm-lock.yaml" "yarn.lock"
20
- ".gitignore" ".npmrc" ".agent" ".env" ".env.local"
21
- "README.md" ".DS_Store" ".spark"
22
- )
23
-
24
- # 清理 & 拷贝
25
- rm -rf "$ROOT/dist"
26
- mkdir -p "$OUTPUT"
27
-
28
- rsync_args=(-a)
29
- for e in "${EXCLUDES[@]}"; do rsync_args+=(--exclude "$e"); done
30
- rsync "${rsync_args[@]}" "$ROOT/" "$OUTPUT/"
31
-
32
- # 生成 routes.json
33
- OUTPUT="$OUTPUT" BASE_PATH="$BASE_PATH" node --input-type=module <<'NODE'
34
- import { readdirSync, statSync, writeFileSync } from 'node:fs';
35
- import { join, relative, sep } from 'node:path';
36
-
37
- const out = process.env.OUTPUT;
38
- const base = (process.env.BASE_PATH || '').replace(/\/+$/, '');
39
-
40
- const htmls = [];
41
- (function walk(dir) {
42
- for (const name of readdirSync(dir)) {
43
- const p = join(dir, name);
44
- if (statSync(p).isDirectory()) walk(p);
45
- else if (name.toLowerCase().endsWith('.html')) htmls.push(relative(out, p));
46
- }
47
- })(out);
48
-
49
- function toRoute(rel) {
50
- let r = '/' + rel.split(sep).join('/'); // '/about.html' | '/blog/index.html'
51
- r = r.replace(/\.html$/i, ''); // '/about' | '/blog/index'
52
- r = r.replace(/(^|\/)index$/, '$1'); // '/about' | '/blog/'
53
- if (r.length > 1) r = r.replace(/\/$/, ''); // 去掉非根的尾斜杠 → '/blog'
54
- const full = base + r;
55
- return full === '' ? '/' : full;
56
- }
57
-
58
- const routes = [...new Set(htmls.map(toRoute))]
59
- .sort()
60
- .map((path) => ({ path }));
61
-
62
- // 空白画布或无 html 时给一个默认根路由,保证 routes.json 始终可用
63
- if (routes.length === 0) routes.push({ path: base ? `${base}/` : '/' });
64
-
65
- writeFileSync(join(out, 'routes.json'), JSON.stringify(routes, null, 2) + '\n');
66
- console.log(`Generated routes.json (${routes.length} routes)`);
67
- NODE
68
-
69
- echo "Build complete"
70
- echo " Output → dist/output/ (+ routes.json)"