Files
workbuddy_skills/draw-ui/scripts/credential-ui/src/run.ts
T
admin 237a09a5b0 修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)
一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。

二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。

三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
2026-10-08 22:29:52 +08:00

62 lines
3.3 KiB
TypeScript

import { spawn } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import path from 'node:path';
import { loadManifest, nativeBackend, PublicError } from './config.ts';
type Binding = { file: string; variable: string };
const defaultManifest = fileURLToPath(new URL('../manifests/default.json', import.meta.url));
const reserved = new Set(['PATH', 'HOME', 'SHELL', 'NODE_OPTIONS', 'LD_PRELOAD', 'DYLD_INSERT_LIBRARIES', 'PYTHONPATH', 'PYTHONSTARTUP', 'COMSPEC', 'SYSTEMROOT']);
export function parseBindings(args: string[]) {
const split = args.indexOf('--');
if (split < 0 || !args[split + 1]) throw new PublicError('请指定要启动的程序。');
const bindings: Binding[] = [];
const variables = new Set<string>();
let pending: string | undefined;
for (let i = 0; i < split; i += 2) {
const value = args[i + 1];
if (i + 1 >= split || !value || value.startsWith('--')) throw new PublicError('凭据绑定参数不完整。');
if (args[i] === '--manifest' && pending === undefined) pending = value;
else if (args[i] === '--env') {
if (!/^[A-Z][A-Z0-9_]*$/.test(value) || reserved.has(value) || variables.has(value))
throw new PublicError('环境变量名重复或不适合注入凭据。');
bindings.push({ file: pending ?? defaultManifest, variable: value });
variables.add(value); pending = undefined;
} else throw new PublicError('凭据绑定参数不正确。');
}
if (pending !== undefined || !bindings.length || bindings.length > 16) throw new PublicError('请提供一至十六组完整的凭据绑定。');
return { bindings, command: args[split + 1], args: args.slice(split + 2) };
}
export async function prepareCommand(
args: string[],
readCredential: (ref: string) => Promise<string | undefined> = async ref => (await nativeBackend(ref)).get(),
baseEnv: NodeJS.ProcessEnv = process.env,
) {
const plan = parseBindings(args);
const manifests = await Promise.all(plan.bindings.map(binding => loadManifest(binding.file)));
const env = { ...baseEnv };
try {
for (let i = 0; i < plan.bindings.length; i++) {
const value = await readCredential(manifests[i].credential);
if (!value) throw new PublicError('有凭据尚未配置,任务未启动。');
env[plan.bindings[i].variable] = value;
}
return { ...plan, env };
} catch (error) {
for (const binding of plan.bindings) delete env[binding.variable];
if (error instanceof PublicError) throw error;
throw new PublicError('凭据读取未完成,任务未启动。');
}
}
async function main() {
const prepared = await prepareCommand(process.argv.slice(2));
const child = spawn(prepared.command, prepared.args, { env: prepared.env, stdio: 'inherit', shell: false });
for (const binding of prepared.bindings) delete prepared.env[binding.variable];
child.once('error', () => { process.stderr.write('无法启动目标程序。\n'); process.exitCode = 1; });
child.once('exit', code => { process.exitCode = code ?? 1; });
}
if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url)
main().catch(error => {
process.stderr.write((error instanceof PublicError ? error.message : '无法读取凭据或启动参数不正确。') + '\n');
process.exitCode = 1;
});