@lark-apaas/miaoda-cli 0.1.4 → 0.1.5-alpha.2f1e0ff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/api/deploy/index.js +13 -1
- package/dist/api/deploy/modern-types.js +23 -0
- package/dist/api/deploy/modern.js +78 -0
- package/dist/api/deploy/plugin-instances-types.js +6 -0
- package/dist/api/deploy/plugin-instances.js +22 -0
- package/dist/api/observability/api.js +10 -0
- package/dist/api/observability/index.js +2 -1
- package/dist/cli/commands/app/index.js +144 -2
- package/dist/cli/commands/deploy/modern.js +50 -0
- package/dist/cli/commands/index.js +68 -5
- package/dist/cli/commands/observability/index.js +62 -6
- package/dist/cli/commands/shared.js +5 -0
- package/dist/cli/commands/skills/index.js +79 -0
- package/dist/cli/handlers/app/index.js +9 -1
- package/dist/cli/handlers/app/init.js +132 -0
- package/dist/cli/handlers/app/sync.js +215 -0
- package/dist/cli/handlers/deploy/modern.js +33 -0
- package/dist/cli/handlers/observability/helpers.js +4 -0
- package/dist/cli/handlers/observability/index.js +3 -1
- package/dist/cli/handlers/observability/log.js +8 -2
- package/dist/cli/handlers/observability/source-stack.js +389 -0
- package/dist/cli/handlers/observability/trace.js +7 -1
- package/dist/cli/handlers/skills/index.js +7 -0
- package/dist/cli/handlers/skills/status.js +31 -0
- package/dist/cli/handlers/skills/sync.js +49 -0
- package/dist/config/fullstack-cli-pin.js +17 -0
- package/dist/config/sync-configs/design-stack.js +98 -0
- package/dist/config/sync-configs/index.js +62 -0
- package/dist/config/sync-configs/nestjs-react-fullstack.js +177 -0
- package/dist/config/sync.js +14 -0
- package/dist/services/app/init/index.js +12 -0
- package/dist/services/app/init/install.js +123 -0
- package/dist/services/app/init/template.js +108 -0
- package/dist/services/deploy/modern/atoms/build.js +59 -0
- package/dist/services/deploy/modern/atoms/context.js +27 -0
- package/dist/services/deploy/modern/atoms/index.js +17 -0
- package/dist/services/deploy/modern/atoms/local-release.js +27 -0
- package/dist/services/deploy/modern/atoms/pre-release.js +13 -0
- package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
- package/dist/services/deploy/modern/atoms/upload.js +246 -0
- package/dist/services/deploy/modern/check.js +53 -0
- package/dist/services/deploy/modern/constants.js +13 -0
- package/dist/services/deploy/modern/index.js +16 -0
- package/dist/services/deploy/modern/pipelines/index.js +5 -0
- package/dist/services/deploy/modern/pipelines/local.js +75 -0
- package/dist/services/deploy/modern/protocol.js +122 -0
- package/dist/services/deploy/modern/run-types.js +4 -0
- package/dist/services/deploy/modern/run.js +13 -0
- package/dist/services/deploy/modern/template-key-map.js +22 -0
- package/dist/services/skills/index.js +5 -0
- package/dist/services/skills/status.js +37 -0
- package/dist/utils/coding-steering.js +169 -0
- package/dist/utils/file-ops.js +45 -0
- package/dist/utils/git.js +22 -0
- package/dist/utils/githooks.js +55 -0
- package/dist/utils/http.js +21 -11
- package/dist/utils/merge-json.js +63 -0
- package/dist/utils/npm-pack.js +55 -0
- package/dist/utils/platform-sync.js +160 -0
- package/dist/utils/spark-meta.js +42 -0
- package/dist/utils/sync-rule.js +295 -0
- package/package.json +5 -3
- package/upgrade/templates/README.md +34 -0
- package/upgrade/templates/design-stack/templates/.githooks/pre-commit +4 -0
- package/upgrade/templates/design-stack/templates/scripts/dev-local.sh +53 -0
- package/upgrade/templates/design-stack/templates/scripts/dev.sh +25 -0
- package/upgrade/templates/design-stack/templates/scripts/hooks/run-precommit.js +37 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.githooks/pre-commit +4 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.gitignore.append +8 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.spark_project +16 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/drizzle.config.ts +55 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/helper/gen-openapi.ts +34 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/nest-cli.json +25 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +207 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.sh +61 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev.js +295 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev.sh +25 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/hooks/run-precommit.js +37 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/lint.js +150 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/prune-smart.js +330 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/run.sh +8 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/server/global.d.ts +19 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/tsconfig.node.json +5 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
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.syncCodingSteering = syncCodingSteering;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
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");
|
|
11
|
+
const STEERING_PACKAGE = '@lark-apaas/coding-steering';
|
|
12
|
+
// 钉死稳定 alpha 版本而非跟 `alpha` dist-tag —— alpha 通道被多个团队共享,外部 prerelease
|
|
13
|
+
// 时常打过去,自动跟 alpha 会把回归版本拉进 user app。维护这个常量比维护 alpha 通道可控。
|
|
14
|
+
// 用户显式传具体版本(如 `miaoda skills sync 0.2.0`)仍 override 这个默认值。
|
|
15
|
+
// 升基线时:跑端到端 smoke(init → skills sync → 看 skills 内容)→ 改这个常量。
|
|
16
|
+
const DEFAULT_PINNED_VERSION = '0.1.6-alpha.10';
|
|
17
|
+
function inferMode() {
|
|
18
|
+
// process.env.SANDBOX_ID 非空 → 沙箱;其他(undefined / 空串)→ 本地
|
|
19
|
+
return process.env.SANDBOX_ID ? 'sandbox' : 'local';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 把 coding-steering 包内 <stack>/skills_common + 专有层(skills 或 skills_local)同步到
|
|
23
|
+
* user app。
|
|
24
|
+
*
|
|
25
|
+
* 包内目录约定(每个 stack 三层):
|
|
26
|
+
* <stack>/skills_common → 沙箱 + 本地都下发的共有层
|
|
27
|
+
* <stack>/skills → 沙箱专有(沙箱端 update-skills.sh / SANDBOX_ID 非空时拿)
|
|
28
|
+
* <stack>/skills_local → 本地专有(本地 dev / agent 环境拿)
|
|
29
|
+
*
|
|
30
|
+
* 下发组合(mode):
|
|
31
|
+
* mode='sandbox' → skills_common + skills
|
|
32
|
+
* mode='local' → skills_common + skills_local
|
|
33
|
+
*
|
|
34
|
+
* 目标路径形态(outputLayout):
|
|
35
|
+
* 'nested' (默认,向后兼容):拷到 .agent/skills/steering/<stack>/skills/,tech.md 同目录
|
|
36
|
+
* 'flat':拷到 .agents/skills/(平铺),tech.md → .agents/tech.md,并创建 .claude/skills
|
|
37
|
+
* 软链指向 ../.agents/skills 让 Claude Code 识别同一份
|
|
38
|
+
*
|
|
39
|
+
* 同名 skill 覆盖顺序:common < 专有(专有覆盖 common 同名)。mode 缺省按 SANDBOX_ID env
|
|
40
|
+
* 推断(沙箱平台运行时注入应用所属沙箱 ID;空 / undefined 则视为本地)。
|
|
41
|
+
*
|
|
42
|
+
* 纯本地 atom(pull npm package + 拷贝文件),不属于任何 cli 域;放在 utils 让
|
|
43
|
+
* app init handler 与独立 skills sync handler 都能复用。
|
|
44
|
+
*/
|
|
45
|
+
function syncCodingSteering(opts) {
|
|
46
|
+
const logPrefix = opts.logPrefix ?? 'skills';
|
|
47
|
+
const effectiveVersion = opts.version ?? DEFAULT_PINNED_VERSION;
|
|
48
|
+
const mode = opts.mode ?? inferMode();
|
|
49
|
+
const layout = opts.outputLayout ?? 'nested';
|
|
50
|
+
(0, logger_1.log)(logPrefix, `Fetching ${STEERING_PACKAGE}@${effectiveVersion} (mode=${mode}, layout=${layout})...`);
|
|
51
|
+
const fetched = (0, npm_pack_1.fetchNpmPackage)({
|
|
52
|
+
packageName: STEERING_PACKAGE,
|
|
53
|
+
version: effectiveVersion,
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
const steeringRoot = node_path_1.default.join(fetched.extractDir, 'steering');
|
|
57
|
+
const stackDir = node_path_1.default.join(steeringRoot, opts.stack);
|
|
58
|
+
// 目标路径按 layout 切:
|
|
59
|
+
// nested → .agent/skills/steering/<stack>/{skills, tech.md}(老链路、stack 命名空间)
|
|
60
|
+
// flat → .agents/{skills, tech.md}(平铺,无 stack 命名空间,跟 .claude/skills 软链对齐)
|
|
61
|
+
const { dstSkillsDir, dstTechPath } = layout === 'flat'
|
|
62
|
+
? {
|
|
63
|
+
dstSkillsDir: node_path_1.default.join(opts.targetDir, '.agents', 'skills'),
|
|
64
|
+
dstTechPath: node_path_1.default.join(opts.targetDir, '.agents', 'tech.md'),
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
dstSkillsDir: node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack, 'skills'),
|
|
68
|
+
dstTechPath: node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack, 'tech.md'),
|
|
69
|
+
};
|
|
70
|
+
node_fs_1.default.mkdirSync(dstSkillsDir, { recursive: true });
|
|
71
|
+
let techSynced = false;
|
|
72
|
+
const techSrc = node_path_1.default.join(stackDir, 'tech.md');
|
|
73
|
+
if (node_fs_1.default.existsSync(techSrc)) {
|
|
74
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dstTechPath), { recursive: true });
|
|
75
|
+
node_fs_1.default.copyFileSync(techSrc, dstTechPath);
|
|
76
|
+
techSynced = true;
|
|
77
|
+
}
|
|
78
|
+
const synced = [];
|
|
79
|
+
// 顺序拷贝,后者覆盖前者:skills_common 铺底,专有层(skills 或 skills_local)覆盖同名
|
|
80
|
+
const exclusiveLayer = mode === 'sandbox' ? 'skills' : 'skills_local';
|
|
81
|
+
const layers = [
|
|
82
|
+
{ dir: node_path_1.default.join(stackDir, 'skills_common'), label: `${opts.stack}/skills_common` },
|
|
83
|
+
{ dir: node_path_1.default.join(stackDir, exclusiveLayer), label: `${opts.stack}/${exclusiveLayer}` },
|
|
84
|
+
];
|
|
85
|
+
for (const layer of layers) {
|
|
86
|
+
if (!node_fs_1.default.existsSync(layer.dir))
|
|
87
|
+
continue;
|
|
88
|
+
for (const name of node_fs_1.default.readdirSync(layer.dir)) {
|
|
89
|
+
const src = node_path_1.default.join(layer.dir, name);
|
|
90
|
+
if (!node_fs_1.default.statSync(src).isDirectory())
|
|
91
|
+
continue;
|
|
92
|
+
const dst = node_path_1.default.join(dstSkillsDir, name);
|
|
93
|
+
if (node_fs_1.default.existsSync(dst))
|
|
94
|
+
node_fs_1.default.rmSync(dst, { recursive: true, force: true });
|
|
95
|
+
copyDir(src, dst);
|
|
96
|
+
synced.push(`${layer.label}/${name}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
let claudeSkillsLink;
|
|
100
|
+
if (layout === 'flat') {
|
|
101
|
+
claudeSkillsLink = ensureClaudeSkillsSymlink(opts.targetDir, logPrefix);
|
|
102
|
+
}
|
|
103
|
+
(0, logger_1.log)(logPrefix, `Synced ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'} (mode=${mode}, layout=${layout})`);
|
|
104
|
+
return {
|
|
105
|
+
version: fetched.version,
|
|
106
|
+
syncedSkills: synced,
|
|
107
|
+
techSynced,
|
|
108
|
+
...(claudeSkillsLink !== undefined ? { claudeSkillsLink } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
fetched.cleanup();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* flat layout 下创建 `<targetDir>/.claude/skills` 软链 → `../.agents/skills`,让 Claude Code
|
|
117
|
+
* 默认的 skills 加载路径跟 user app 的 .agents/skills 是同一份内容。
|
|
118
|
+
*
|
|
119
|
+
* 用相对路径软链(不是 absolute)—— user app 移动目录 / clone 到别处不会断链。
|
|
120
|
+
*
|
|
121
|
+
* 处理 4 种已有状态:
|
|
122
|
+
* - 已是软链且指向对的:noop
|
|
123
|
+
* - 已是软链但指错地方:删旧的,建新的(updated)
|
|
124
|
+
* - 已是普通目录 / 文件:不覆盖,警告并跳过(conflict)—— 用户可能手动放了别的 skills
|
|
125
|
+
* - 不存在:建新的(created)
|
|
126
|
+
*/
|
|
127
|
+
function ensureClaudeSkillsSymlink(targetDir, logPrefix) {
|
|
128
|
+
const linkPath = node_path_1.default.join(targetDir, '.claude', 'skills');
|
|
129
|
+
const linkTarget = node_path_1.default.join('..', '.agents', 'skills');
|
|
130
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(linkPath), { recursive: true });
|
|
131
|
+
let existing = null;
|
|
132
|
+
try {
|
|
133
|
+
existing = node_fs_1.default.lstatSync(linkPath);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err.code !== 'ENOENT')
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
if (existing === null) {
|
|
140
|
+
node_fs_1.default.symlinkSync(linkTarget, linkPath, 'dir');
|
|
141
|
+
(0, logger_1.log)(logPrefix, ` ✓ .claude/skills → ${linkTarget} (symlink created)`);
|
|
142
|
+
return 'created';
|
|
143
|
+
}
|
|
144
|
+
if (existing.isSymbolicLink()) {
|
|
145
|
+
const current = node_fs_1.default.readlinkSync(linkPath);
|
|
146
|
+
if (current === linkTarget) {
|
|
147
|
+
return 'noop';
|
|
148
|
+
}
|
|
149
|
+
node_fs_1.default.unlinkSync(linkPath);
|
|
150
|
+
node_fs_1.default.symlinkSync(linkTarget, linkPath, 'dir');
|
|
151
|
+
(0, logger_1.log)(logPrefix, ` ✓ .claude/skills → ${linkTarget} (symlink updated, was → ${current})`);
|
|
152
|
+
return 'updated';
|
|
153
|
+
}
|
|
154
|
+
(0, logger_1.log)(logPrefix, ` ⚠ .claude/skills 已是普通目录/文件,跳过软链创建`);
|
|
155
|
+
return 'conflict';
|
|
156
|
+
}
|
|
157
|
+
function copyDir(src, dest) {
|
|
158
|
+
node_fs_1.default.mkdirSync(dest, { recursive: true });
|
|
159
|
+
for (const entry of node_fs_1.default.readdirSync(src, { withFileTypes: true })) {
|
|
160
|
+
const srcPath = node_path_1.default.join(src, entry.name);
|
|
161
|
+
const destPath = node_path_1.default.join(dest, entry.name);
|
|
162
|
+
if (entry.isDirectory()) {
|
|
163
|
+
copyDir(srcPath, destPath);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
node_fs_1.default.copyFileSync(srcPath, destPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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.removeLineFromFile = removeLineFromFile;
|
|
7
|
+
exports.removeLineFromContent = removeLineFromContent;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const logger_1 = require("./logger");
|
|
11
|
+
/**
|
|
12
|
+
* 从文件中移除匹配的行。
|
|
13
|
+
* 搬自 @lark-apaas/fullstack-cli/src/utils/file-ops.ts,行为完全对齐:精确匹配(trim 后)。
|
|
14
|
+
*
|
|
15
|
+
* @returns 是否真的移除过内容
|
|
16
|
+
*/
|
|
17
|
+
function removeLineFromFile(filePath, pattern, logPrefix = 'sync') {
|
|
18
|
+
if (!node_fs_1.default.existsSync(filePath)) {
|
|
19
|
+
(0, logger_1.log)(logPrefix, ` ○ ${node_path_1.default.basename(filePath)} (not found)`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf-8');
|
|
23
|
+
const trimmed = pattern.trim();
|
|
24
|
+
const lines = content.split('\n');
|
|
25
|
+
const filtered = lines.filter((line) => line.trim() !== trimmed);
|
|
26
|
+
if (filtered.length === lines.length) {
|
|
27
|
+
(0, logger_1.log)(logPrefix, ` ○ ${node_path_1.default.basename(filePath)} (pattern not found: ${pattern})`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
node_fs_1.default.writeFileSync(filePath, filtered.join('\n'));
|
|
31
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${node_path_1.default.basename(filePath)} (removed: ${pattern})`);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 纯函数版本:便于单测,不读写文件。
|
|
36
|
+
*/
|
|
37
|
+
function removeLineFromContent(content, pattern) {
|
|
38
|
+
const trimmed = pattern.trim();
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
const filtered = lines.filter((line) => line.trim() !== trimmed);
|
|
41
|
+
return {
|
|
42
|
+
result: filtered.join('\n'),
|
|
43
|
+
changed: filtered.length !== lines.length,
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/utils/git.js
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCurrentGitBranch = getCurrentGitBranch;
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const GIT_LOCAL_ENV_KEYS = new Set([
|
|
6
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
7
|
+
'GIT_CONFIG',
|
|
8
|
+
'GIT_CONFIG_PARAMETERS',
|
|
9
|
+
'GIT_CONFIG_COUNT',
|
|
10
|
+
'GIT_OBJECT_DIRECTORY',
|
|
11
|
+
'GIT_DIR',
|
|
12
|
+
'GIT_WORK_TREE',
|
|
13
|
+
'GIT_IMPLICIT_WORK_TREE',
|
|
14
|
+
'GIT_GRAFT_FILE',
|
|
15
|
+
'GIT_INDEX_FILE',
|
|
16
|
+
'GIT_NO_REPLACE_OBJECTS',
|
|
17
|
+
'GIT_REPLACE_REF_BASE',
|
|
18
|
+
'GIT_PREFIX',
|
|
19
|
+
'GIT_INTERNAL_SUPER_PREFIX',
|
|
20
|
+
'GIT_SHALLOW_FILE',
|
|
21
|
+
'GIT_COMMON_DIR',
|
|
22
|
+
]);
|
|
23
|
+
function withoutGitLocalEnv() {
|
|
24
|
+
return Object.fromEntries(Object.entries(process.env).filter(([key]) => !GIT_LOCAL_ENV_KEYS.has(key)));
|
|
25
|
+
}
|
|
5
26
|
/**
|
|
6
27
|
* 读取 cwd 所在仓库的当前分支名。
|
|
7
28
|
*
|
|
@@ -13,6 +34,7 @@ function getCurrentGitBranch(cwd = process.cwd()) {
|
|
|
13
34
|
try {
|
|
14
35
|
result = (0, node_child_process_1.spawnSync)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
15
36
|
cwd,
|
|
37
|
+
env: withoutGitLocalEnv(),
|
|
16
38
|
encoding: 'utf8',
|
|
17
39
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
18
40
|
});
|
|
@@ -0,0 +1,55 @@
|
|
|
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.activateGitHooks = activateGitHooks;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
/**
|
|
12
|
+
* 把 `.githooks/` 注册为本仓库的 git hooks 目录,让 pre-commit 等 hook 立即生效。
|
|
13
|
+
*
|
|
14
|
+
* 三件事,都先检查再写(幂等):
|
|
15
|
+
* 1. `.git` 存在(非 git 仓库直接跳)
|
|
16
|
+
* 2. `.githooks/pre-commit` 存在(没 hook 文件直接跳,且不写 git config 防止误指空目录)
|
|
17
|
+
* 3. `core.hooksPath` 是 `.githooks`(已经是就短路)
|
|
18
|
+
*
|
|
19
|
+
* 镜像 fullstack-cli `src/commands/sync/activate-hooks.ts`,逻辑保持一致;本地用户
|
|
20
|
+
* 通过 `miaoda app upgrade` 走的这一份,沙箱用户走 `npm run upgrade`(fullstack-cli sync)
|
|
21
|
+
* 那一份,两条通道结果应等价。
|
|
22
|
+
*/
|
|
23
|
+
function activateGitHooks(targetDir) {
|
|
24
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, '.git'))) {
|
|
25
|
+
return { action: 'skipped-no-git' };
|
|
26
|
+
}
|
|
27
|
+
const hookFile = node_path_1.default.join(targetDir, '.githooks', 'pre-commit');
|
|
28
|
+
if (!node_fs_1.default.existsSync(hookFile)) {
|
|
29
|
+
return { action: 'skipped-no-hook-file' };
|
|
30
|
+
}
|
|
31
|
+
// copyFileSync 不保留 mode,确保 +x(重要:没 +x 时 git 不会执行 hook)
|
|
32
|
+
const currentMode = node_fs_1.default.statSync(hookFile).mode & 0o777;
|
|
33
|
+
if ((currentMode & 0o111) !== 0o111) {
|
|
34
|
+
node_fs_1.default.chmodSync(hookFile, 0o755);
|
|
35
|
+
}
|
|
36
|
+
const probe = (0, node_child_process_1.spawnSync)('git', ['config', '--get', 'core.hooksPath'], {
|
|
37
|
+
cwd: targetDir,
|
|
38
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
39
|
+
});
|
|
40
|
+
const currentHooksPath = probe.stdout.toString().trim();
|
|
41
|
+
if (currentHooksPath === '.githooks') {
|
|
42
|
+
return { action: 'already-active' };
|
|
43
|
+
}
|
|
44
|
+
const res = (0, node_child_process_1.spawnSync)('git', ['config', 'core.hooksPath', '.githooks'], {
|
|
45
|
+
cwd: targetDir,
|
|
46
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
47
|
+
});
|
|
48
|
+
if (res.status !== 0) {
|
|
49
|
+
const stderr = res.stderr.toString().trim();
|
|
50
|
+
(0, logger_1.log)('githooks', `⚠️ failed to set core.hooksPath: ${stderr}`);
|
|
51
|
+
return { action: 'skipped-no-git' };
|
|
52
|
+
}
|
|
53
|
+
(0, logger_1.log)('githooks', `activated: core.hooksPath = .githooks`);
|
|
54
|
+
return { action: 'activated' };
|
|
55
|
+
}
|
package/dist/utils/http.js
CHANGED
|
@@ -60,13 +60,27 @@ function createClient(opts) {
|
|
|
60
60
|
instance.interceptors.request.use(applyCanaryHeader);
|
|
61
61
|
return instance;
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* 默认灰度泳道。当前 miaoda CLI 接入的新接口(modern deploy 等)只在该泳道生效,
|
|
65
|
+
* 缺省走这个。MIAODA_CANARY_HEADER 可显式覆盖;设为空字符串则去掉头。
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_CANARY_HEADER = 'boe_miaoda_doubao';
|
|
68
|
+
/**
|
|
69
|
+
* 注入 x-tt-env 小流量头:env 显式设值 > DEFAULT_CANARY_HEADER。
|
|
70
|
+
*
|
|
71
|
+
* 当泳道形如 `ppe_xxx`(PPE 预发环境)时,额外加 `x-use-ppe: 1`;
|
|
72
|
+
* BOE 泳道(`boe_xxx`)只带 x-tt-env,不加该头。
|
|
73
|
+
*/
|
|
64
74
|
function applyCanaryHeader(reqConfig) {
|
|
65
|
-
|
|
75
|
+
// env 显式设为空字符串视为"去掉灰度头";未设置时用 DEFAULT_CANARY_HEADER
|
|
76
|
+
const canary = process.env.MIAODA_CANARY_HEADER ?? DEFAULT_CANARY_HEADER;
|
|
66
77
|
if (!canary)
|
|
67
78
|
return reqConfig;
|
|
68
79
|
const headers = new Headers(reqConfig.headers);
|
|
69
80
|
headers.set('x-tt-env', canary);
|
|
81
|
+
if (canary.startsWith('ppe_')) {
|
|
82
|
+
headers.set('x-use-ppe', '1');
|
|
83
|
+
}
|
|
70
84
|
reqConfig.headers = headers;
|
|
71
85
|
return reqConfig;
|
|
72
86
|
}
|
|
@@ -103,14 +117,14 @@ async function handleInnerEnvelope(response, url, opts) {
|
|
|
103
117
|
throw new error_1.HttpError(response.status, url, `${opts.errPrefix}: ${String(response.status)} ${response.statusText}`);
|
|
104
118
|
}
|
|
105
119
|
const result = await response.json();
|
|
120
|
+
// verbose: 把完整响应体打到 stderr,便于排查(业务错误 / 字段缺失等)
|
|
121
|
+
if ((0, config_1.getConfig)().verbose) {
|
|
122
|
+
(0, logger_1.debug)(` resp: ${truncateForLog(safeStringify(result), 2000)}`);
|
|
123
|
+
}
|
|
106
124
|
if (typeof result === 'object' && result !== null && 'status_code' in result) {
|
|
107
125
|
const env = result;
|
|
108
126
|
if (env.status_code !== undefined && env.status_code !== '0') {
|
|
109
127
|
const msg = env.message ?? env.error_msg ?? 'unknown error';
|
|
110
|
-
// verbose: 把完整业务错误信封打到 stderr,便于追溯
|
|
111
|
-
if ((0, config_1.getConfig)().verbose) {
|
|
112
|
-
(0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
|
|
113
|
-
}
|
|
114
128
|
const mapped = opts.mapErr?.(env.status_code, msg);
|
|
115
129
|
if (mapped)
|
|
116
130
|
throw mapped;
|
|
@@ -163,12 +177,8 @@ function logResponseFailure(method, url, err, startMs) {
|
|
|
163
177
|
const msgPart = e.message ? ` (${e.message})` : '';
|
|
164
178
|
(0, logger_1.debug)(`✗ ${method} ${url} ${statusPart} ${String(elapsedMs)}ms${logidPart}${msgPart}`);
|
|
165
179
|
}
|
|
166
|
-
/** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
|
|
167
180
|
function pickLogid(headers) {
|
|
168
|
-
return
|
|
169
|
-
headers.get('x-logid') ??
|
|
170
|
-
headers.get('logid') ??
|
|
171
|
-
headers.get('x-tt-trace-tag'));
|
|
181
|
+
return headers.get('x-tt-logid');
|
|
172
182
|
}
|
|
173
183
|
function safeStringify(v) {
|
|
174
184
|
try {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deep merge JSON 对象,支持按 key 去重合并数组。
|
|
4
|
+
*
|
|
5
|
+
* 搬自 @lark-apaas/fullstack-cli/src/utils/merge-json.ts,行为完全对齐:
|
|
6
|
+
* - scalar(string / number / boolean / null):template 覆盖
|
|
7
|
+
* - object:递归 merge(template 字段覆盖,user 多出的字段保留)
|
|
8
|
+
* - 在 arrayMerge 配置里的 array:按指定 key 去重合并(template item 覆盖同 key 的 user item,
|
|
9
|
+
* user 多出的 item 保留)
|
|
10
|
+
* - 不在 arrayMerge 配置里的 array:template 覆盖
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.deepMergeJson = deepMergeJson;
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function mergeArrayByKey(userArr, templateArr, key) {
|
|
18
|
+
const result = [...userArr];
|
|
19
|
+
for (const templateItem of templateArr) {
|
|
20
|
+
if (!isPlainObject(templateItem))
|
|
21
|
+
continue;
|
|
22
|
+
const templateKey = templateItem[key];
|
|
23
|
+
const existingIndex = result.findIndex((item) => isPlainObject(item) && item[key] === templateKey);
|
|
24
|
+
if (existingIndex >= 0) {
|
|
25
|
+
result[existingIndex] = templateItem;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
result.push(templateItem);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function deepMergeJson(user, template, arrayMerge = {}, currentPath = '') {
|
|
34
|
+
const result = { ...user };
|
|
35
|
+
for (const key of Object.keys(template)) {
|
|
36
|
+
const fullPath = currentPath ? `${currentPath}.${key}` : key;
|
|
37
|
+
const templateValue = template[key];
|
|
38
|
+
const userValue = user[key];
|
|
39
|
+
if (Array.isArray(templateValue)) {
|
|
40
|
+
const cfg = Object.prototype.hasOwnProperty.call(arrayMerge, fullPath)
|
|
41
|
+
? arrayMerge[fullPath]
|
|
42
|
+
: undefined;
|
|
43
|
+
if (cfg !== undefined && Array.isArray(userValue)) {
|
|
44
|
+
result[key] = mergeArrayByKey(userValue, templateValue, cfg.key);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
result[key] = templateValue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (isPlainObject(templateValue)) {
|
|
51
|
+
if (isPlainObject(userValue)) {
|
|
52
|
+
result[key] = deepMergeJson(userValue, templateValue, arrayMerge, fullPath);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
result[key] = templateValue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result[key] = templateValue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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.fetchNpmPackage = fetchNpmPackage;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const error_1 = require("./error");
|
|
12
|
+
const logger_1 = require("./logger");
|
|
13
|
+
const DEFAULT_REGISTRY = 'https://registry.npmmirror.com/';
|
|
14
|
+
/**
|
|
15
|
+
* `npm pack <pkg>@<ver> --registry <r>` + `tar -xzf`,解到临时目录。
|
|
16
|
+
* extractDir 是包内容根(含 package.json)。使用完必须 cleanup()。
|
|
17
|
+
*/
|
|
18
|
+
function fetchNpmPackage(opts) {
|
|
19
|
+
const registry = opts.registry ?? process.env.MIAODA_NPM_REGISTRY ?? DEFAULT_REGISTRY;
|
|
20
|
+
const version = opts.version ?? 'latest';
|
|
21
|
+
const pkgSpec = `${opts.packageName}@${version}`;
|
|
22
|
+
const tmpDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'miaoda-pack-'));
|
|
23
|
+
try {
|
|
24
|
+
(0, logger_1.debug)(`npm pack ${pkgSpec} --registry ${registry} → ${tmpDir}`);
|
|
25
|
+
const stdout = (0, node_child_process_1.execFileSync)('npm', ['pack', pkgSpec, '--pack-destination', tmpDir, '--json', '--registry', registry], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
26
|
+
const packInfo = JSON.parse(stdout);
|
|
27
|
+
const tgzFilename = packInfo[0]?.filename;
|
|
28
|
+
const pkgVersion = packInfo[0]?.version ?? version;
|
|
29
|
+
if (!tgzFilename) {
|
|
30
|
+
throw new error_1.AppError('NPM_PACK_FAILED', `npm pack 未返回 filename:${pkgSpec}`);
|
|
31
|
+
}
|
|
32
|
+
const tgzPath = node_path_1.default.join(tmpDir, tgzFilename);
|
|
33
|
+
const extractDir = node_path_1.default.join(tmpDir, 'extracted');
|
|
34
|
+
node_fs_1.default.mkdirSync(extractDir, { recursive: true });
|
|
35
|
+
(0, node_child_process_1.execFileSync)('tar', ['-xzf', tgzPath, '-C', extractDir], {
|
|
36
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
extractDir: node_path_1.default.join(extractDir, 'package'),
|
|
40
|
+
version: pkgVersion,
|
|
41
|
+
cleanup: () => {
|
|
42
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
48
|
+
if (err instanceof error_1.AppError)
|
|
49
|
+
throw err;
|
|
50
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
51
|
+
throw new error_1.AppError('NPM_PACK_FAILED', `抓取 npm 包失败 (${pkgSpec}): ${msg}`, {
|
|
52
|
+
next_actions: [`确认包已发布到 registry:${registry}`, `检查版本号 / dist-tag 拼写`],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|