@lark-apaas/miaoda-cli 0.1.4-alpha.9153b4c → 0.1.4-alpha.e76a6d6

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.
Files changed (29) hide show
  1. package/dist/api/deploy/index.js +3 -1
  2. package/dist/api/deploy/plugin-instances-types.js +6 -0
  3. package/dist/api/deploy/plugin-instances.js +30 -0
  4. package/dist/cli/commands/deploy/modern.js +2 -1
  5. package/dist/cli/commands/index.js +2 -0
  6. package/dist/cli/commands/skills/index.js +63 -0
  7. package/dist/cli/handlers/app/init.js +5 -7
  8. package/dist/cli/handlers/skills/index.js +7 -0
  9. package/dist/cli/handlers/skills/status.js +31 -0
  10. package/dist/cli/handlers/skills/sync.js +38 -0
  11. package/dist/services/app/init/index.js +1 -3
  12. package/dist/services/app/init/template.js +5 -4
  13. package/dist/services/deploy/modern/atoms/build.js +26 -7
  14. package/dist/services/deploy/modern/atoms/context.js +5 -5
  15. package/dist/services/deploy/modern/atoms/index.js +3 -1
  16. package/dist/services/deploy/modern/atoms/local-release.js +2 -4
  17. package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
  18. package/dist/services/deploy/modern/atoms/upload.js +134 -19
  19. package/dist/services/deploy/modern/check.js +2 -2
  20. package/dist/services/deploy/modern/constants.js +3 -1
  21. package/dist/services/deploy/modern/pipelines/local.js +20 -11
  22. package/dist/services/deploy/modern/protocol.js +10 -7
  23. package/dist/services/deploy/modern/template-key-map.js +8 -15
  24. package/dist/services/skills/index.js +5 -0
  25. package/dist/services/skills/status.js +37 -0
  26. package/dist/{services/app/init/steering.js → utils/coding-steering.js} +19 -8
  27. package/dist/utils/http.js +5 -9
  28. package/dist/utils/spark-meta.js +2 -8
  29. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.LocalReleaseStatus = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
3
+ exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.batchSavePluginInstances = exports.LocalReleaseStatus = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "createRelease", { enumerable: true, get: function () { return api_1.createRelease; } });
6
6
  Object.defineProperty(exports, "listPipelineInstances", { enumerable: true, get: function () { return api_1.listPipelineInstances; } });
@@ -15,6 +15,8 @@ Object.defineProperty(exports, "getModernReleaseStatus", { enumerable: true, get
15
15
  Object.defineProperty(exports, "getModernLastPublishedVersion", { enumerable: true, get: function () { return modern_1.getLastPublishedVersion; } });
16
16
  var modern_types_1 = require("./modern-types");
17
17
  Object.defineProperty(exports, "LocalReleaseStatus", { enumerable: true, get: function () { return modern_types_1.LocalReleaseStatus; } });
18
+ var plugin_instances_1 = require("./plugin-instances");
19
+ Object.defineProperty(exports, "batchSavePluginInstances", { enumerable: true, get: function () { return plugin_instances_1.batchSavePluginInstances; } });
18
20
  var schemas_1 = require("./schemas");
19
21
  Object.defineProperty(exports, "deployHistorySchema", { enumerable: true, get: function () { return schemas_1.deployHistorySchema; } });
20
22
  Object.defineProperty(exports, "deployGetSchema", { enumerable: true, get: function () { return schemas_1.deployGetSchema; } });
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // plugin_instances batch_save 接口的请求 / 响应类型。
3
+ //
4
+ // 与 modern deploy 其它接口走的 lark.apaas.devops PSM 不同,
5
+ // 本接口走 lark.apaas.studio_server,响应信封形态也不一样(BaseResp 而非 status_code)。
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ // studio_server 的 plugin_instances 批量保存接口。
3
+ // 与 modern deploy 其余接口(走 devops PSM、InnerEnvelope)信封不同,
4
+ // 这里走 studio_server PSM、BaseResp 信封,故自行解析。
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.batchSavePluginInstances = batchSavePluginInstances;
7
+ const http_1 = require("../../utils/http");
8
+ const error_1 = require("../../utils/error");
9
+ const URL_PATH = '/api/v1/studio/innerapi/plugin_instances/batch_save';
10
+ /**
11
+ * POST /api/v1/studio/innerapi/plugin_instances/batch_save
12
+ *
13
+ * 把发布版本的 capability 集合注册到 studio_server。
14
+ * 失败时抛 AppError("DEPLOY_PLUGIN_BATCH_SAVE_FAILED"),带 BaseResp 错误信息。
15
+ */
16
+ async function batchSavePluginInstances(req) {
17
+ const response = await (0, http_1.getHttpClient)().post(URL_PATH, req);
18
+ if (!response.ok) {
19
+ throw new error_1.HttpError(response.status, URL_PATH, `Failed to save plugin instances: ${String(response.status)} ${response.statusText}`);
20
+ }
21
+ const body = (await response.json());
22
+ const baseResp = body.BaseResp;
23
+ if (baseResp?.StatusCode !== 0) {
24
+ const msg = baseResp?.StatusMessage ?? baseResp?.KStatusMessage ?? 'unknown error';
25
+ const code = baseResp?.KStatusCode !== undefined && baseResp.KStatusCode !== ''
26
+ ? baseResp.KStatusCode
27
+ : String(baseResp?.StatusCode ?? 'unknown');
28
+ throw new error_1.AppError('DEPLOY_PLUGIN_BATCH_SAVE_FAILED', `Failed to save plugin instances: status_code=${code} message=${msg}`);
29
+ }
30
+ }
@@ -28,7 +28,8 @@ function registerDeployCommandsModern(program) {
28
28
  流程概览(stderr 进度提示)
29
29
  1. prepareContext 2. preRelease 3. build
30
30
  4. createLocalRelease 5. uploadArtifacts
31
- 6. finalizeLocalRelease(Success|Failed)
31
+ 6. savePluginInstances(扫描 dist/output_capabilities/*.json 批量注册)
32
+ 7. finalizeLocalRelease(Finished|Failed)
32
33
 
33
34
  JSON 输出(stdout)
34
35
  {"data": {"appId": "...", "version": <n>, "url": "...", "releaseID": "...", "preReleaseID": "..."}}
@@ -8,6 +8,7 @@ const index_3 = require("../../cli/commands/observability/index");
8
8
  const index_4 = require("../../cli/commands/app/index");
9
9
  const index_5 = require("../../cli/commands/deploy/index");
10
10
  const modern_1 = require("../../cli/commands/deploy/modern");
11
+ const index_6 = require("../../cli/commands/skills/index");
11
12
  function resolveScene(appType, _archType) {
12
13
  if (appType === '7')
13
14
  return 'modern';
@@ -24,6 +25,7 @@ const SCENE_REGISTRARS = {
24
25
  modern: (p) => {
25
26
  (0, index_4.registerAppCommands)(p, { includeInit: true });
26
27
  (0, modern_1.registerDeployCommandsModern)(p);
28
+ (0, index_6.registerSkillsCommands)(p);
27
29
  },
28
30
  };
29
31
  function readEnv(name) {
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSkillsCommands = registerSkillsCommands;
4
+ const shared_1 = require("../../../cli/commands/shared");
5
+ const index_1 = require("../../../cli/handlers/skills/index");
6
+ /**
7
+ * miaoda skills:本地 agent skills 维护。
8
+ *
9
+ * 当前仅 steering 一类子集(coding-steering 包内的 _common/skills + <stack>/skills + tech.md);
10
+ * 后续如有更多 skill 类型再加 --type。`app init` 会一次性同步装上,独立 `skills sync` 用于
11
+ * 后续单独升级 / pin 版本,不必走完整 init。
12
+ */
13
+ function registerSkillsCommands(program) {
14
+ const skillsCmd = program.command('skills').description('本地 agent skills(含 steering)维护');
15
+ skillsCmd.action(() => {
16
+ skillsCmd.outputHelp();
17
+ });
18
+ registerSkillsSync(skillsCmd);
19
+ registerSkillsStatus(skillsCmd);
20
+ }
21
+ function registerSkillsSync(parent) {
22
+ const cmd = parent
23
+ .command('sync')
24
+ .description('同步 coding-steering(latest 或指定版本)到 .agent/steering/')
25
+ .argument('[version]', 'coding-steering 包版本或 dist-tag,缺省 latest')
26
+ .option('--dir <path>', '项目目录,默认当前目录', '.')
27
+ .addHelpText('after', `
28
+ 前置要求
29
+ 当前目录(或 --dir)已走过 'miaoda app init'(.spark/meta.json 含 stack)
30
+
31
+ JSON 输出
32
+ {"data": {"stack": "...", "version": "...", "syncedSkills": [...], "techSynced": true|false}}
33
+
34
+ 示例
35
+ $ miaoda skills sync
36
+ $ miaoda skills sync 0.2.0
37
+ $ miaoda skills sync --dir ./my-app
38
+ `);
39
+ cmd.action((0, shared_1.withHelp)(cmd, async (version, rawOpts) => {
40
+ await (0, index_1.handleSkillsSync)({
41
+ dir: rawOpts.dir,
42
+ version,
43
+ });
44
+ }));
45
+ }
46
+ function registerSkillsStatus(parent) {
47
+ const cmd = parent
48
+ .command('status')
49
+ .description('查看本地 .agent/steering/ 的当前状态(不联网)')
50
+ .option('--dir <path>', '项目目录,默认当前目录', '.')
51
+ .addHelpText('after', `
52
+ JSON 输出
53
+ {"data": {"present": true|false, "skills": [...], "techSynced": true|false}}
54
+
55
+ 示例
56
+ $ miaoda skills status
57
+ `);
58
+ cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
59
+ await (0, index_1.handleSkillsStatus)({
60
+ dir: rawOpts.dir,
61
+ });
62
+ }));
63
+ }
@@ -7,6 +7,7 @@ exports.handleAppInit = handleAppInit;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const index_1 = require("../../../services/app/init/index");
10
+ const coding_steering_1 = require("../../../utils/coding-steering");
10
11
  const error_1 = require("../../../utils/error");
11
12
  const output_1 = require("../../../utils/output");
12
13
  /** miaoda app init [--template <stack>] [--conf <json>] */
@@ -43,10 +44,11 @@ async function handleAppInit(opts) {
43
44
  const version = opts.conf?.version;
44
45
  const projectName = node_path_1.default.basename(targetDir);
45
46
  const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
46
- const steeringResult = (0, index_1.syncSteering)({
47
+ const steeringResult = (0, coding_steering_1.syncCodingSteering)({
47
48
  stack,
48
49
  targetDir,
49
50
  version: opts.conf?.steeringVersion,
51
+ logPrefix: 'init',
50
52
  });
51
53
  const installResult = (0, index_1.installDependencies)({
52
54
  targetDir,
@@ -55,13 +57,9 @@ async function handleAppInit(opts) {
55
57
  });
56
58
  (0, index_1.writeSparkMeta)(targetDir, {
57
59
  appId: opts.appId,
58
- template: stack,
59
- templatePackage: tplResult.packageName,
60
- templateVersion: tplResult.version,
61
- steeringVersion: steeringResult.version,
60
+ stack,
61
+ version: tplResult.version,
62
62
  archType: tplResult.archType,
63
- installSource: installResult.source,
64
- installHash: installResult.hash,
65
63
  });
66
64
  if (!(0, output_1.isJsonMode)()) {
67
65
  process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleSkillsStatus = exports.handleSkillsSync = void 0;
4
+ var sync_1 = require("./sync");
5
+ Object.defineProperty(exports, "handleSkillsSync", { enumerable: true, get: function () { return sync_1.handleSkillsSync; } });
6
+ var status_1 = require("./status");
7
+ Object.defineProperty(exports, "handleSkillsStatus", { enumerable: true, get: function () { return status_1.handleSkillsStatus; } });
@@ -0,0 +1,31 @@
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.handleSkillsStatus = handleSkillsStatus;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const index_1 = require("../../../services/skills/index");
9
+ const spark_meta_1 = require("../../../utils/spark-meta");
10
+ const output_1 = require("../../../utils/output");
11
+ /**
12
+ * miaoda skills status [--dir <path>]
13
+ *
14
+ * 从 .spark/meta.json 读 stack,扫描 <dir>/.agent/steering/<stack>/,
15
+ * 给出当前装了哪些 skills、tech.md 是否在。纯本地操作,不联网。
16
+ * meta 缺 stack 时返回 present=false(不抛错,方便和 init 的引导分开)。
17
+ */
18
+ async function handleSkillsStatus(opts) {
19
+ await Promise.resolve();
20
+ const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
21
+ const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
22
+ const status = (0, index_1.readSkillsStatus)({ targetDir, stack: meta.stack });
23
+ (0, output_1.emit)({
24
+ data: {
25
+ stack: meta.stack ?? null,
26
+ present: status.present,
27
+ skills: status.skills,
28
+ techSynced: status.techSynced,
29
+ },
30
+ });
31
+ }
@@ -0,0 +1,38 @@
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.handleSkillsSync = handleSkillsSync;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const coding_steering_1 = require("../../../utils/coding-steering");
9
+ const spark_meta_1 = require("../../../utils/spark-meta");
10
+ const error_1 = require("../../../utils/error");
11
+ const output_1 = require("../../../utils/output");
12
+ /**
13
+ * miaoda skills sync [--dir <path>] [--version <ver>]
14
+ *
15
+ * 从 .spark/meta.json 读 stack,把 coding-steering 包内对应 stack 的 skills + tech.md
16
+ * 同步到 <dir>/.agent/steering/。需要先跑过 `miaoda app init`。
17
+ */
18
+ async function handleSkillsSync(opts) {
19
+ await Promise.resolve();
20
+ const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
21
+ const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
22
+ if (meta.stack === undefined || meta.stack === '') {
23
+ throw new error_1.AppError('SKILLS_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
24
+ }
25
+ const result = (0, coding_steering_1.syncCodingSteering)({
26
+ stack: meta.stack,
27
+ targetDir,
28
+ version: opts.version,
29
+ });
30
+ (0, output_1.emit)({
31
+ data: {
32
+ stack: meta.stack,
33
+ version: result.version,
34
+ syncedSkills: result.syncedSkills,
35
+ techSynced: result.techSynced,
36
+ },
37
+ });
38
+ }
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.syncSteering = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
3
+ 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; } });
7
7
  Object.defineProperty(exports, "TEMPLATE_PACKAGE_BY_STACK", { enumerable: true, get: function () { return template_1.TEMPLATE_PACKAGE_BY_STACK; } });
8
- var steering_1 = require("./steering");
9
- Object.defineProperty(exports, "syncSteering", { enumerable: true, get: function () { return steering_1.syncSteering; } });
10
8
  var spark_meta_1 = require("../../../utils/spark-meta");
11
9
  Object.defineProperty(exports, "readSparkMeta", { enumerable: true, get: function () { return spark_meta_1.readSparkMeta; } });
12
10
  Object.defineProperty(exports, "writeSparkMeta", { enumerable: true, get: function () { return spark_meta_1.writeSparkMeta; } });
@@ -10,7 +10,7 @@ const node_path_1 = __importDefault(require("node:path"));
10
10
  const npm_pack_1 = require("../../../utils/npm-pack");
11
11
  const error_1 = require("../../../utils/error");
12
12
  const logger_1 = require("../../../utils/logger");
13
- /** 短名 → 包名映射。新增 stack 时改这里。 */
13
+ /** 短名 → 包名映射。新增 stack 时改这里。archType 由 template 包自报,CLI 不维护。 */
14
14
  exports.TEMPLATE_PACKAGE_BY_STACK = {
15
15
  'vite-react': '@lark-apaas/coding-template-vite-react',
16
16
  html: '@lark-apaas/coding-template-html',
@@ -37,12 +37,13 @@ function renderTemplate(opts) {
37
37
  throw new error_1.AppError('TEMPLATE_INVALID', `包 ${packageName}@${fetched.version} 缺少 template/ 目录`);
38
38
  }
39
39
  const pkgJsonPath = node_path_1.default.join(fetched.extractDir, 'package.json');
40
- let archType = '';
40
+ let archType;
41
41
  if (node_fs_1.default.existsSync(pkgJsonPath)) {
42
42
  const pkgJson = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
43
- archType = pkgJson.miaodaTemplate?.archType ?? '';
43
+ archType = pkgJson.miaodaTemplate?.archType;
44
44
  }
45
- if (!archType) {
45
+ // 用显式 null/undefined/'' 判,避免把合法值 0 / false 当成"缺失"误抛
46
+ if (archType === undefined || archType === null || archType === '') {
46
47
  throw new error_1.AppError('TEMPLATE_INVALID', `包 ${packageName}@${fetched.version} 缺少 package.json.miaodaTemplate.archType`);
47
48
  }
48
49
  (0, logger_1.log)('init', `Rendering template to ${opts.targetDir}...`);
@@ -5,21 +5,40 @@ const node_child_process_1 = require("node:child_process");
5
5
  const error_1 = require("../../../../utils/error");
6
6
  const logger_1 = require("../../../../utils/logger");
7
7
  const protocol_1 = require("../protocol");
8
+ /**
9
+ * 给 CDN 前缀补 https:// 协议头:
10
+ * - 已带 http:// 或 https:// → 原样返回
11
+ * - 以 / 开头(绝对路径)→ 原样返回
12
+ * - 不含 dot(看起来不像域名,比如纯路径 app_xxx/2)→ 原样返回
13
+ * - 其它(典型:lf-xxx.bytetos.com/...)→ 前缀 https://
14
+ *
15
+ * template 的 build script 直接拿来当 vite base / asset URL 用,必须是合法 URL。
16
+ */
17
+ function ensureHttpsScheme(v) {
18
+ if (/^https?:\/\//i.test(v))
19
+ return v;
20
+ if (v.startsWith('/'))
21
+ return v;
22
+ if (!v.includes('.'))
23
+ return v;
24
+ return `https://${v}`;
25
+ }
8
26
  /**
9
27
  * 跑 `npm run build`,把 preRelease 下发的动态配置注入 env:
10
- * MCLAW_APP_ID / MCLAW_VERSION / MCLAW_STACK
11
- * MCLAW_RESOURCE_CDN_PREFIX / MCLAW_STATIC_CDN_PREFIX
28
+ * MIAODA_APP_ID / MIAODA_VERSION / MIAODA_STACK
29
+ * MIAODA_RESOURCE_CDN_PREFIX / MIAODA_STATIC_CDN_PREFIX
12
30
  *
31
+ * CDN 前缀缺 scheme 时由 CLI 自动补 https://,template 不用关心。
13
32
  * build 失败抛 AppError(execSync 自身会 throw,捕获后包一层加错误码)。
14
33
  */
15
34
  function runBuild(opts) {
16
35
  const buildEnv = {
17
36
  ...process.env,
18
- MCLAW_APP_ID: opts.appId,
19
- MCLAW_VERSION: opts.version,
20
- MCLAW_STACK: opts.templateKey,
21
- MCLAW_RESOURCE_CDN_PREFIX: (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.RESOURCE_CDN_PREFIX),
22
- MCLAW_STATIC_CDN_PREFIX: (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.STATIC_CDN_PREFIX),
37
+ MIAODA_APP_ID: opts.appId,
38
+ MIAODA_VERSION: opts.version,
39
+ MIAODA_STACK: opts.templateKey,
40
+ MIAODA_RESOURCE_CDN_PREFIX: ensureHttpsScheme((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.RESOURCE_CDN_PREFIX)),
41
+ MIAODA_STATIC_CDN_PREFIX: ensureHttpsScheme((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.STATIC_CDN_PREFIX)),
23
42
  };
24
43
  (0, logger_1.log)('deploy', 'Building...');
25
44
  try {
@@ -6,7 +6,7 @@ const spark_meta_1 = require("../../../../utils/spark-meta");
6
6
  const check_1 = require("../check");
7
7
  const template_key_map_1 = require("../template-key-map");
8
8
  /**
9
- * 准备 deploy 上下文:跑前置检查、读 .spark/meta.json、抽出 appId / template
9
+ * 准备 deploy 上下文:跑前置检查、读 .spark/meta.json、抽出 appId / stack
10
10
  * 并把 stack 短名映射成后端 templateKey。
11
11
  *
12
12
  * 任何前置失败统一抛 AppError。返回值是后续 atom 唯一信任的入参源。
@@ -17,14 +17,14 @@ function prepareDeployContext(projectDir) {
17
17
  if (meta.appId === undefined || meta.appId === '') {
18
18
  throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing appId — run `miaoda app init` first');
19
19
  }
20
- if (meta.template === undefined || meta.template === '') {
21
- throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing template — run `miaoda app init` first');
20
+ if (meta.stack === undefined || meta.stack === '') {
21
+ throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
22
22
  }
23
23
  return {
24
24
  projectDir,
25
25
  appId: meta.appId,
26
- template: meta.template,
27
- templateKey: (0, template_key_map_1.resolveTemplateKey)(meta.template),
26
+ stack: meta.stack,
27
+ templateKey: (0, template_key_map_1.resolveTemplateKey)(meta.stack),
28
28
  meta,
29
29
  };
30
30
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = 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");
@@ -13,3 +13,5 @@ var local_release_1 = require("./local-release");
13
13
  Object.defineProperty(exports, "createLocalRelease", { enumerable: true, get: function () { return local_release_1.createLocalRelease; } });
14
14
  Object.defineProperty(exports, "finalizeLocalRelease", { enumerable: true, get: function () { return local_release_1.finalizeLocalRelease; } });
15
15
  Object.defineProperty(exports, "LocalReleaseStatus", { enumerable: true, get: function () { return local_release_1.LocalReleaseStatus; } });
16
+ var save_plugin_instances_1 = require("./save-plugin-instances");
17
+ Object.defineProperty(exports, "savePluginInstances", { enumerable: true, get: function () { return save_plugin_instances_1.savePluginInstances; } });
@@ -15,13 +15,11 @@ async function createLocalRelease(appId, version) {
15
15
  }
16
16
  /**
17
17
  * 把本地发布单翻为终态。Finished / Failed 由 pipeline 视上下游结果决定。
18
- * 后端按 appID 找当前 active release,CLI 无需透传 releaseID。
19
- *
20
- * 失败不抛(避免覆盖上游真错误),但记一行 stderr 让排查可定位。releaseId 仅用于日志。
18
+ * 失败不抛(避免覆盖上游真错误),但记一行 stderr 让排查可定位。
21
19
  */
22
20
  async function finalizeLocalRelease(appId, releaseId, status) {
23
21
  try {
24
- await (0, index_1.updateLocalRelease)({ appID: appId, status });
22
+ await (0, index_1.updateLocalRelease)({ appID: appId, releaseID: releaseId, status });
25
23
  }
26
24
  catch (err) {
27
25
  (0, logger_1.log)('deploy', `finalize release(${releaseId}, status=${String(status)}) failed: ${err.message}`);
@@ -0,0 +1,72 @@
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.savePluginInstances = savePluginInstances;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const index_1 = require("../../../../api/deploy/index");
10
+ const logger_1 = require("../../../../utils/logger");
11
+ const constants_1 = require("../constants");
12
+ function isValidCapability(o) {
13
+ if (typeof o !== 'object' || o === null)
14
+ return false;
15
+ const id = o.id;
16
+ return typeof id === 'string' && id.length > 0;
17
+ }
18
+ /**
19
+ * 扫描 dist/output_capabilities/*.json,过滤出合法 capability,批量上报到 studio_server。
20
+ *
21
+ * 合法判定:能 JSON.parse + 顶层 id 是非空字符串。其它字段后端校验。
22
+ * 目录不存在 / 没有合法 capability → 直接返回 0,pipeline 当作 no-op。
23
+ * batchSave 调用失败 → 透传抛 AppError,pipeline 兜底 finalizeLocalRelease(Failed)。
24
+ */
25
+ async function savePluginInstances(opts) {
26
+ const dir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR, constants_1.OUTPUT_CAPABILITIES_DIR);
27
+ if (!node_fs_1.default.existsSync(dir)) {
28
+ (0, logger_1.debug)(`save-plugin-instances: ${dir} not present, skipping`);
29
+ return { saved: 0, skipped: 0 };
30
+ }
31
+ const files = node_fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.json'));
32
+ const instances = [];
33
+ let skipped = 0;
34
+ for (const f of files) {
35
+ const full = node_path_1.default.join(dir, f);
36
+ let raw;
37
+ try {
38
+ raw = node_fs_1.default.readFileSync(full, 'utf-8');
39
+ }
40
+ catch (err) {
41
+ (0, logger_1.debug)(`save-plugin-instances: read ${f} failed: ${err.message}`);
42
+ skipped++;
43
+ continue;
44
+ }
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(raw);
48
+ }
49
+ catch {
50
+ (0, logger_1.debug)(`save-plugin-instances: ${f} is not valid JSON, skipping`);
51
+ skipped++;
52
+ continue;
53
+ }
54
+ if (!isValidCapability(parsed)) {
55
+ (0, logger_1.debug)(`save-plugin-instances: ${f} missing 'id' string, skipping`);
56
+ skipped++;
57
+ continue;
58
+ }
59
+ instances.push({ id: parsed.id, content: JSON.stringify(parsed) });
60
+ }
61
+ if (instances.length === 0) {
62
+ (0, logger_1.log)('deploy', `No capability to register (${String(files.length)} files scanned, all skipped)`);
63
+ return { saved: 0, skipped };
64
+ }
65
+ (0, logger_1.log)('deploy', `Registering ${String(instances.length)} capability instance(s)...`);
66
+ await (0, index_1.batchSavePluginInstances)({
67
+ appID: opts.appId,
68
+ appVersion: opts.version,
69
+ pluginInstances: instances,
70
+ });
71
+ return { saved: instances.length, skipped };
72
+ }
@@ -5,6 +5,7 @@ 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"));
9
10
  const node_child_process_1 = require("node:child_process");
10
11
  const error_1 = require("../../../../utils/error");
@@ -12,9 +13,17 @@ const logger_1 = require("../../../../utils/logger");
12
13
  const constants_1 = require("../constants");
13
14
  const protocol_1 = require("../protocol");
14
15
  const UPLOAD_PLAN = [
15
- { dir: constants_1.OUTPUT_DIR, dataKey: protocol_1.DataKey.OUTPUT_TOS_UPLOAD_URL, required: true },
16
- { dir: constants_1.OUTPUT_RESOURCE_DIR, dataKey: protocol_1.DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_URL, required: false },
17
- { dir: constants_1.OUTPUT_STATIC_DIR, dataKey: protocol_1.DataKey.OUTPUT_STATIC_TOS_UPLOAD_URL, required: false },
16
+ { dir: constants_1.OUTPUT_DIR, dataKey: protocol_1.DataKey.OUTPUT_TOS_UPLOAD_CREDENTIAL, required: true },
17
+ {
18
+ dir: constants_1.OUTPUT_RESOURCE_DIR,
19
+ dataKey: protocol_1.DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL,
20
+ required: false,
21
+ },
22
+ {
23
+ dir: constants_1.OUTPUT_STATIC_DIR,
24
+ dataKey: protocol_1.DataKey.OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL,
25
+ required: false,
26
+ },
18
27
  ];
19
28
  function resolveTosutilPath() {
20
29
  try {
@@ -27,18 +36,123 @@ function resolveTosutilPath() {
27
36
  }
28
37
  throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
29
38
  }
39
+ /** 把 data 里的凭证字段 JSON.parse 出来;非法 JSON / 缺字段都抛 AppError */
40
+ function parseCredential(raw, dataKey) {
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ }
45
+ catch (err) {
46
+ throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not valid JSON: ${err.message}`);
47
+ }
48
+ if (typeof parsed !== 'object' || parsed === null) {
49
+ throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not an object`);
50
+ }
51
+ const c = parsed;
52
+ const required = [
53
+ 'accessKeyID',
54
+ 'secretAccessKey',
55
+ 'sessionToken',
56
+ 'endpoint',
57
+ 'region',
58
+ 'bucket',
59
+ 'prefix',
60
+ ];
61
+ const missing = required.filter((k) => typeof c[k] !== 'string' || c[k] === '');
62
+ if (missing.length > 0) {
63
+ throw new error_1.AppError('DEPLOY_CRED_INCOMPLETE', `preRelease.data["${dataKey}"] credential missing fields: ${missing.join(', ')}`);
64
+ }
65
+ return c;
66
+ }
67
+ /**
68
+ * 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
69
+ * 返回 config 路径,调用方负责清理。
70
+ */
71
+ function writeTosutilConfig(tosutilPath, cred, label) {
72
+ const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
73
+ node_fs_1.default.writeFileSync(confPath, '');
74
+ (0, node_child_process_1.execFileSync)(tosutilPath, [
75
+ 'config',
76
+ '-conf',
77
+ confPath,
78
+ '-e',
79
+ cred.endpoint,
80
+ '-i',
81
+ cred.accessKeyID,
82
+ '-k',
83
+ cred.secretAccessKey,
84
+ '-t',
85
+ cred.sessionToken,
86
+ '-re',
87
+ cred.region,
88
+ ], { stdio: 'pipe' });
89
+ return confPath;
90
+ }
91
+ /** 把 tosutil 进程的输出按行回放到 stderr,便于线上排查(沙箱里无法 attach 进程)。 */
92
+ function streamTosutilOutput(output) {
93
+ if (!output)
94
+ return;
95
+ for (const line of output.split('\n')) {
96
+ if (line.length === 0)
97
+ continue;
98
+ process.stderr.write(`[tosutil] ${line}\n`);
99
+ }
100
+ }
101
+ function normalizeTosDest(bucket, prefix) {
102
+ const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
103
+ const normalized = prefix.replace(/^\/+/, '');
104
+ return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
105
+ }
30
106
  /**
31
- * 用 tosutil 把目录批量上传到 pre-signed URL。
32
- * 服务端返回的 URL 已带签名凭证,CLI 直接透传给 tosutil,不解析、不拆字段。
107
+ * 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
33
108
  */
34
- function uploadDirToSignedUrl(tosutilPath, sourceDir, signedUrl) {
109
+ function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
35
110
  const entries = node_fs_1.default.readdirSync(sourceDir);
36
111
  if (entries.length === 0) {
37
112
  (0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
38
113
  return;
39
114
  }
40
- (0, logger_1.debug)(`upload: ${sourceDir} -> ${signedUrl} (${String(entries.length)} entries)`);
41
- const output = (0, node_child_process_1.execFileSync)(tosutilPath, ['cp', sourceDir, signedUrl, '-r', '-flat', '-j', '5', '-p', '3', '-ps', '10485760', '-f'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
115
+ const dest = normalizeTosDest(cred.bucket, cred.prefix);
116
+ (0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
117
+ const confPath = writeTosutilConfig(tosutilPath, cred, label);
118
+ let output;
119
+ try {
120
+ output = (0, node_child_process_1.execFileSync)(tosutilPath, [
121
+ 'cp',
122
+ sourceDir,
123
+ dest,
124
+ '-r',
125
+ '-flat',
126
+ '-j',
127
+ '5',
128
+ '-p',
129
+ '3',
130
+ '-ps',
131
+ '10485760',
132
+ '-f',
133
+ '-conf',
134
+ confPath,
135
+ ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
136
+ streamTosutilOutput(output);
137
+ }
138
+ catch (err) {
139
+ const e = err;
140
+ const stderr = (e.stderr ?? '').trim();
141
+ const stdout = (e.stdout ?? '').trim();
142
+ streamTosutilOutput(stdout);
143
+ streamTosutilOutput(stderr);
144
+ throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
145
+ ` stderr: ${stderr || '(empty)'}\n` +
146
+ ` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
147
+ }
148
+ finally {
149
+ try {
150
+ node_fs_1.default.unlinkSync(confPath);
151
+ }
152
+ catch {
153
+ /* ignore */
154
+ }
155
+ }
42
156
  const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
43
157
  const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
44
158
  const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
@@ -47,16 +161,17 @@ function uploadDirToSignedUrl(tosutilPath, sourceDir, signedUrl) {
47
161
  throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
48
162
  }
49
163
  if (succeedCount === 0) {
50
- throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil output and pre-signed URL validity.`);
164
+ throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
51
165
  }
52
166
  }
53
167
  /**
54
- * 上传三类产物到 preRelease 下发的对应 pre-signed URL。
55
- * - dist/output (必传) DataKey.OUTPUT_TOS_UPLOAD_URL
56
- * - dist/output_resource (可选) → DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_URL
57
- * - dist/output_static (可选) → DataKey.OUTPUT_STATIC_TOS_UPLOAD_URL
168
+ * 上传三类产物。每类的凭证来自 preRelease.data 中对应 *_tos_upload_credential 字段
169
+ * (JSON.stringify TosUploadCredential 对象):
170
+ * - dist/output (必传) → OUTPUT_TOS_UPLOAD_CREDENTIAL
171
+ * - dist/output_resource (可选) → OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL
172
+ * - dist/output_static (可选) → OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL
58
173
  *
59
- * 目录不存在的可选项跳过;output 不存在或 url 缺失抛错。
174
+ * 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
60
175
  */
61
176
  function uploadArtifacts(opts) {
62
177
  const tosutilPath = resolveTosutilPath();
@@ -72,16 +187,16 @@ function uploadArtifacts(opts) {
72
187
  (0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
73
188
  continue;
74
189
  }
75
- const signedUrl = target.required
190
+ const credRaw = target.required
76
191
  ? (0, protocol_1.requireDataKey)(opts.data, target.dataKey)
77
192
  : (0, protocol_1.optionalDataKey)(opts.data, target.dataKey);
78
- if (signedUrl === undefined) {
79
- // 目录有但后端没下发 URL 静默跳过(后端语义:这个产物不归该模板管)
80
- (0, logger_1.debug)(`upload: ${target.dir} exists but no upload URL configured, skipping`);
193
+ if (credRaw === undefined) {
194
+ (0, logger_1.debug)(`upload: ${target.dir} exists but no credential configured, skipping`);
81
195
  continue;
82
196
  }
197
+ const cred = parseCredential(credRaw, target.dataKey);
83
198
  (0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
84
- uploadDirToSignedUrl(tosutilPath, localPath, signedUrl);
199
+ uploadDirWithCredential(tosutilPath, localPath, cred, target.dir);
85
200
  uploaded++;
86
201
  }
87
202
  if (uploaded === 0) {
@@ -27,8 +27,8 @@ function runDeployChecks(projectDir) {
27
27
  const missing = [];
28
28
  if (!meta.appId)
29
29
  missing.push('appId');
30
- if (!meta.template)
31
- missing.push('template');
30
+ if (!meta.stack)
31
+ missing.push('stack');
32
32
  if (missing.length > 0) {
33
33
  issues.push(`.spark/meta.json missing fields: ${missing.join(', ')}`);
34
34
  }
@@ -3,9 +3,11 @@
3
3
  // (旧版的 STACK_CONFIGS / archType / FaaS 相关常量在新接口契约下不再需要——
4
4
  // 发布模板差异完全由后端 preRelease.data 下发,CLI 不维护 stack 配置。)
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = void 0;
6
+ exports.OUTPUT_CAPABILITIES_DIR = exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = void 0;
7
7
  /** 构建产物固定目录约定 */
8
8
  exports.DIST_DIR = 'dist';
9
9
  exports.OUTPUT_DIR = 'output';
10
10
  exports.OUTPUT_RESOURCE_DIR = 'output_resource';
11
11
  exports.OUTPUT_STATIC_DIR = 'output_static';
12
+ /** 发布插件能力声明目录:含若干 capability.json 文件,发布时批量注册到 plugin_instances */
13
+ exports.OUTPUT_CAPABILITIES_DIR = 'output_capabilities';
@@ -9,16 +9,17 @@ const index_1 = require("../atoms/index");
9
9
  * 本地构建 + 本地部署 pipeline。
10
10
  *
11
11
  * 步骤顺序:
12
- * 1. prepareDeployContext — 校验项目 + 读 meta
13
- * 2. preRelease — 签发 pre_release_id / version / 动态配置 data
14
- * 3. runBuild — 本地 npm run build(注入 data 中的 CDN 前缀)
15
- * 4. createLocalRelease — 创建发布单(加锁、拿 releaseID)
16
- * 5. uploadArtifacts — 把产物推到 preRelease 下发的 pre-signed URL
17
- * 6. finalizeLocalRelease Success / Failed 翻终态
12
+ * 1. prepareDeployContext — 校验项目 + 读 meta
13
+ * 2. preRelease — 签发 pre_release_id / version / 动态配置 data
14
+ * 3. runBuild — 本地 npm run build(注入 data 中的 CDN 前缀)
15
+ * 4. createLocalRelease — 创建发布单(加锁、拿 releaseID)
16
+ * 5. uploadArtifacts — 把产物推到 preRelease 下发的 pre-signed URL
17
+ * 6. savePluginInstances 扫描 dist/output_capabilities/*.json,批量注册插件能力
18
+ * 7. finalizeLocalRelease — Finished / Failed 翻终态
18
19
  *
19
20
  * createLocalRelease 提前到 upload 之前:发布单先建出来,
20
- * upload 在已知 releaseID 的前提下进行;upload 失败时也能精准把
21
- * 该发布单标记为 Failed,避免遗留挂态。
21
+ * upload / savePluginInstances 在已知 releaseID 的前提下进行;
22
+ * 任一步失败都会精准把该发布单标记为 Failed,避免遗留挂态。
22
23
  */
23
24
  async function localBuildLocalPublishPipeline(opts) {
24
25
  const ctx = (0, index_1.prepareDeployContext)(opts.projectDir);
@@ -46,20 +47,28 @@ async function localBuildLocalPublishPipeline(opts) {
46
47
  try {
47
48
  (0, logger_1.log)('deploy', 'Uploading artifacts...');
48
49
  (0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, data });
50
+ const saveResult = await (0, index_1.savePluginInstances)({
51
+ projectDir: ctx.projectDir,
52
+ appId: ctx.appId,
53
+ version: pre.version,
54
+ });
55
+ if (saveResult.saved > 0) {
56
+ (0, logger_1.log)('deploy', `${String(saveResult.saved)} capability instance(s) registered`);
57
+ }
49
58
  await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
50
59
  }
51
60
  catch (err) {
52
61
  await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
53
62
  throw err;
54
63
  }
55
- if (release.onlineURL !== undefined && release.onlineURL !== '') {
56
- (0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { ...ctx.meta, appUrl: release.onlineURL });
64
+ if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
65
+ (0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { ...ctx.meta, appUrl: release.onlineUrl });
57
66
  }
58
67
  (0, logger_1.log)('deploy', 'Deployed successfully');
59
68
  return {
60
69
  appId: ctx.appId,
61
70
  version: pre.version,
62
- url: release.onlineURL ?? '',
71
+ url: release.onlineUrl ?? '',
63
72
  releaseID: release.releaseID,
64
73
  preReleaseID: pre.preReleaseID,
65
74
  };
@@ -17,13 +17,16 @@ exports.DataKey = {
17
17
  RESOURCE_CDN_PREFIX: 'resource_cdn_prefix',
18
18
  /** dist/output_static 对应的 CDN 域名前缀 */
19
19
  STATIC_CDN_PREFIX: 'static_cdn_prefix',
20
- // ── TOS 上传 URL(与三个产物目录一一对应) ──
21
- /** dist/output 上传地址(必传) */
22
- OUTPUT_TOS_UPLOAD_URL: 'output_tos_upload_url',
23
- /** dist/output_resource 上传地址(仅当目录存在时使用) */
24
- OUTPUT_RESOURCE_TOS_UPLOAD_URL: 'output_resource_tos_upload_url',
25
- /** dist/output_static 上传地址(仅当目录存在时使用) */
26
- OUTPUT_STATIC_TOS_UPLOAD_URL: 'output_static_tos_upload_url',
20
+ // ── TOS 上传凭证(与三个产物目录一一对应,value JSON.stringify 的凭证对象) ──
21
+ // 凭证对象形态(camelCase):
22
+ // { accessKeyID, secretAccessKey, sessionToken, endpoint, region, bucket, prefix }
23
+ // CLI JSON.parse 后写 tosutil 临时 config,cp 到 tos://bucket/prefix。
24
+ /** dist/output 上传凭证(必传) */
25
+ OUTPUT_TOS_UPLOAD_CREDENTIAL: 'output_tos_upload_credential',
26
+ /** dist/output_resource 上传凭证(仅当目录存在时使用) */
27
+ OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL: 'output_resource_tos_upload_credential',
28
+ /** dist/output_static 上传凭证(仅当目录存在时使用) */
29
+ OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL: 'output_static_tos_upload_credential',
27
30
  };
28
31
  /** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
29
32
  function requireDataKey(data, key) {
@@ -1,29 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveTemplateKey = resolveTemplateKey;
4
- const error_1 = require("../../../utils/error");
5
4
  /**
6
5
  * 本地 stack 短名 → 后端发布模板 key 的映射。
7
6
  *
8
- * .spark/meta.json.template 存的是脚手架模板短名(vite-react / vue-react / html ...),
7
+ * .spark/meta.json.stack 存的是脚手架模板短名(vite-react / html ...),
9
8
  * 而服务端 preRelease 接受的 templateKey 是"部署类别"维度,由后端 tcc 注册。
10
- * 同一类别的多个脚手架共享一个部署 key(典型:所有纯前端 stack client_local_deploy)。
11
- *
12
- * 加新 stack:在表里加一行;新增类别同步跟后端 tcc 配置对齐。
9
+ * 当前所有支持的 stack 都属于纯前端类别,默认映射到 client_local_deploy
10
+ * 未来出现需要走别的 templateKey 的 stack,再在表里加一行覆盖默认。
13
11
  */
12
+ const DEFAULT_TEMPLATE_KEY = 'client_local_deploy';
14
13
  const TEMPLATE_KEY_MAP = {
15
- 'vite-react': 'client_local_deploy',
16
- 'vue-react': 'client_local_deploy',
14
+ 'vite-react': DEFAULT_TEMPLATE_KEY,
15
+ html: DEFAULT_TEMPLATE_KEY,
17
16
  };
18
17
  /**
19
- * 把本地 stack 短名映射到后端 templateKey。未配置的 stack AppError,
20
- * 避免拿原始短名直接打过去导致后端报"empty"或"出错了"等含糊错误。
18
+ * 把本地 stack 短名映射到后端 templateKey;未配置的 stack 走默认 client_local_deploy。
21
19
  */
22
20
  function resolveTemplateKey(template) {
23
- const key = TEMPLATE_KEY_MAP[template];
24
- if (key === undefined) {
25
- throw new error_1.AppError('DEPLOY_TEMPLATE_KEY_UNMAPPED', `Template "${template}" has no mapping to backend templateKey. ` +
26
- `Known: ${Object.keys(TEMPLATE_KEY_MAP).join(', ')}`);
27
- }
28
- return key;
21
+ return TEMPLATE_KEY_MAP[template] ?? DEFAULT_TEMPLATE_KEY;
29
22
  }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readSkillsStatus = void 0;
4
+ var status_1 = require("./status");
5
+ Object.defineProperty(exports, "readSkillsStatus", { enumerable: true, get: function () { return status_1.readSkillsStatus; } });
@@ -0,0 +1,37 @@
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.readSkillsStatus = readSkillsStatus;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ /**
10
+ * 扫描 <targetDir>/.agent/skills/steering/<stack>/,给出"当前装了什么"的快照。
11
+ * 不联网、不解析任何 SKILL.md 内容;只列目录名 + tech.md 是否在。
12
+ *
13
+ * stack 缺省(meta.json 缺 stack)时直接返回 present=false,
14
+ * 让 handler 把"先 init"的引导留给用户。
15
+ */
16
+ function readSkillsStatus(opts) {
17
+ if (opts.stack === undefined || opts.stack === '') {
18
+ return { present: false, skills: [], techSynced: false };
19
+ }
20
+ const root = node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack);
21
+ if (!node_fs_1.default.existsSync(root)) {
22
+ return { present: false, skills: [], techSynced: false };
23
+ }
24
+ const skillsDir = node_path_1.default.join(root, 'skills');
25
+ const skills = node_fs_1.default.existsSync(skillsDir)
26
+ ? node_fs_1.default
27
+ .readdirSync(skillsDir, { withFileTypes: true })
28
+ .filter((e) => e.isDirectory())
29
+ .map((e) => e.name)
30
+ .sort()
31
+ : [];
32
+ return {
33
+ present: true,
34
+ skills,
35
+ techSynced: node_fs_1.default.existsSync(node_path_1.default.join(root, 'tech.md')),
36
+ };
37
+ }
@@ -3,14 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.syncSteering = syncSteering;
6
+ exports.syncCodingSteering = syncCodingSteering;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
- const npm_pack_1 = require("../../../utils/npm-pack");
10
- const logger_1 = require("../../../utils/logger");
9
+ const npm_pack_1 = require("../utils/npm-pack");
10
+ const logger_1 = require("../utils/logger");
11
11
  const STEERING_PACKAGE = '@lark-apaas/coding-steering';
12
- function syncSteering(opts) {
13
- (0, logger_1.log)('init', `Fetching ${STEERING_PACKAGE}@${opts.version ?? 'latest'}...`);
12
+ /**
13
+ * coding-steering 包内 _common/skills + <stack>/skills 同步到 <targetDir>/.agent/steering/skills/,
14
+ * 同名 stack 子目录覆盖 _common。tech.md 存在时也同步到 .agent/steering/tech.md。
15
+ *
16
+ * 纯本地 atom(pull npm package + 拷贝文件),不属于任何 cli 域;放在 utils 让
17
+ * app init handler 与独立 skills sync handler 都能复用。
18
+ */
19
+ function syncCodingSteering(opts) {
20
+ const logPrefix = opts.logPrefix ?? 'skills';
21
+ (0, logger_1.log)(logPrefix, `Fetching ${STEERING_PACKAGE}@${opts.version ?? 'latest'}...`);
14
22
  const fetched = (0, npm_pack_1.fetchNpmPackage)({
15
23
  packageName: STEERING_PACKAGE,
16
24
  version: opts.version,
@@ -19,7 +27,10 @@ function syncSteering(opts) {
19
27
  const steeringRoot = node_path_1.default.join(fetched.extractDir, 'steering');
20
28
  const stackDir = node_path_1.default.join(steeringRoot, opts.stack);
21
29
  const commonSkillsDir = node_path_1.default.join(steeringRoot, '_common', 'skills');
22
- const dstRoot = node_path_1.default.join(opts.targetDir, '.agent', 'steering');
30
+ // steering 落到 .agent/skills/steering/<stack>/。.agent/skills/ agent skills 的
31
+ // 公共容器,steering 是其中一个 producer,<stack> 再做命名空间隔离。内部仍保留
32
+ // 包内 steering/<stack>/skills/ 的结构,便于按"包内子目录 → 本地子目录"一一对应排查。
33
+ const dstRoot = node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack);
23
34
  const dstSkillsDir = node_path_1.default.join(dstRoot, 'skills');
24
35
  node_fs_1.default.mkdirSync(dstSkillsDir, { recursive: true });
25
36
  let techSynced = false;
@@ -29,7 +40,7 @@ function syncSteering(opts) {
29
40
  techSynced = true;
30
41
  }
31
42
  const synced = [];
32
- // 先拷 _common/skills/,再拷 <stack>/skills/。stack 同名覆盖 common(设计 §8.4)
43
+ // 先拷 _common/skills/,再拷 <stack>/skills/。stack 同名覆盖 common
33
44
  if (node_fs_1.default.existsSync(commonSkillsDir)) {
34
45
  for (const name of node_fs_1.default.readdirSync(commonSkillsDir)) {
35
46
  const src = node_path_1.default.join(commonSkillsDir, name);
@@ -52,7 +63,7 @@ function syncSteering(opts) {
52
63
  synced.push(`${opts.stack}/skills/${name}`);
53
64
  }
54
65
  }
55
- (0, logger_1.log)('init', `Steering synced: ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'}`);
66
+ (0, logger_1.log)(logPrefix, `Synced ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'}`);
56
67
  return { version: fetched.version, syncedSkills: synced, techSynced };
57
68
  }
58
69
  finally {
@@ -109,14 +109,14 @@ async function handleInnerEnvelope(response, url, opts) {
109
109
  throw new error_1.HttpError(response.status, url, `${opts.errPrefix}: ${String(response.status)} ${response.statusText}`);
110
110
  }
111
111
  const result = await response.json();
112
+ // verbose: 把完整响应体打到 stderr,便于排查(业务错误 / 字段缺失等)
113
+ if ((0, config_1.getConfig)().verbose) {
114
+ (0, logger_1.debug)(` resp: ${truncateForLog(safeStringify(result), 2000)}`);
115
+ }
112
116
  if (typeof result === 'object' && result !== null && 'status_code' in result) {
113
117
  const env = result;
114
118
  if (env.status_code !== undefined && env.status_code !== '0') {
115
119
  const msg = env.message ?? env.error_msg ?? 'unknown error';
116
- // verbose: 把完整业务错误信封打到 stderr,便于追溯
117
- if ((0, config_1.getConfig)().verbose) {
118
- (0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
119
- }
120
120
  const mapped = opts.mapErr?.(env.status_code, msg);
121
121
  if (mapped)
122
122
  throw mapped;
@@ -169,12 +169,8 @@ function logResponseFailure(method, url, err, startMs) {
169
169
  const msgPart = e.message ? ` (${e.message})` : '';
170
170
  (0, logger_1.debug)(`✗ ${method} ${url} ${statusPart} ${String(elapsedMs)}ms${logidPart}${msgPart}`);
171
171
  }
172
- /** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
173
172
  function pickLogid(headers) {
174
- return (headers.get('x-tt-logid') ??
175
- headers.get('x-logid') ??
176
- headers.get('logid') ??
177
- headers.get('x-tt-trace-tag'));
173
+ return headers.get('x-tt-logid');
178
174
  }
179
175
  function safeStringify(v) {
180
176
  try {
@@ -21,7 +21,7 @@ function readSparkMeta(appDir) {
21
21
  }
22
22
  }
23
23
  /**
24
- * merge 写入:保留已有 createdAt,刷新 updatedAt;undefined 字段不覆盖已有值。
24
+ * merge 写入:已有字段保留,undefined 不覆盖;不再维护时间戳。
25
25
  */
26
26
  function writeSparkMeta(appDir, meta) {
27
27
  const sparkDir = node_path_1.default.join(appDir, SPARK_DIR);
@@ -37,12 +37,6 @@ function writeSparkMeta(appDir, meta) {
37
37
  }
38
38
  }
39
39
  const defined = Object.fromEntries(Object.entries(meta).filter(([, v]) => v !== undefined));
40
- const now = new Date().toISOString();
41
- const merged = {
42
- ...existing,
43
- ...defined,
44
- createdAt: existing.createdAt ?? now,
45
- updatedAt: now,
46
- };
40
+ const merged = { ...existing, ...defined };
47
41
  node_fs_1.default.writeFileSync(metaPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
48
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.4-alpha.9153b4c",
3
+ "version": "0.1.4-alpha.e76a6d6",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {