arcrun 1.0.9 → 1.3.0

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 (66) hide show
  1. package/dist/commands/auth-recipe.d.ts +11 -0
  2. package/dist/commands/auth-recipe.d.ts.map +1 -0
  3. package/dist/commands/auth-recipe.js +180 -0
  4. package/dist/commands/auth-recipe.js.map +1 -0
  5. package/dist/commands/config.d.ts +4 -0
  6. package/dist/commands/config.d.ts.map +1 -0
  7. package/dist/commands/config.js +39 -0
  8. package/dist/commands/config.js.map +1 -0
  9. package/dist/commands/init.d.ts +5 -2
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +147 -19
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/install-harness.d.ts +2 -0
  14. package/dist/commands/install-harness.d.ts.map +1 -0
  15. package/dist/commands/install-harness.js +122 -0
  16. package/dist/commands/install-harness.js.map +1 -0
  17. package/dist/commands/mcp-setup.d.ts +12 -0
  18. package/dist/commands/mcp-setup.d.ts.map +1 -0
  19. package/dist/commands/mcp-setup.js +42 -0
  20. package/dist/commands/mcp-setup.js.map +1 -0
  21. package/dist/commands/parts.d.ts.map +1 -1
  22. package/dist/commands/parts.js +48 -2
  23. package/dist/commands/parts.js.map +1 -1
  24. package/dist/commands/push.d.ts.map +1 -1
  25. package/dist/commands/push.js +13 -0
  26. package/dist/commands/push.js.map +1 -1
  27. package/dist/commands/recipe.d.ts.map +1 -1
  28. package/dist/commands/recipe.js +63 -1
  29. package/dist/commands/recipe.js.map +1 -1
  30. package/dist/commands/update.d.ts +13 -0
  31. package/dist/commands/update.d.ts.map +1 -0
  32. package/dist/commands/update.js +89 -0
  33. package/dist/commands/update.js.map +1 -0
  34. package/dist/commands/validate.js +2 -2
  35. package/dist/commands/validate.js.map +1 -1
  36. package/dist/index.js +58 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/api-recipe-seeds.d.ts +32 -0
  39. package/dist/lib/api-recipe-seeds.d.ts.map +1 -0
  40. package/dist/lib/api-recipe-seeds.js +108 -0
  41. package/dist/lib/api-recipe-seeds.js.map +1 -0
  42. package/dist/lib/cf-api.d.ts +22 -0
  43. package/dist/lib/cf-api.d.ts.map +1 -1
  44. package/dist/lib/cf-api.js +58 -0
  45. package/dist/lib/cf-api.js.map +1 -1
  46. package/dist/lib/config.d.ts +34 -1
  47. package/dist/lib/config.d.ts.map +1 -1
  48. package/dist/lib/config.js +123 -8
  49. package/dist/lib/config.js.map +1 -1
  50. package/dist/lib/deploy.d.ts +56 -0
  51. package/dist/lib/deploy.d.ts.map +1 -0
  52. package/dist/lib/deploy.js +239 -0
  53. package/dist/lib/deploy.js.map +1 -0
  54. package/dist/lib/exposure-warning.d.ts +25 -0
  55. package/dist/lib/exposure-warning.d.ts.map +1 -0
  56. package/dist/lib/exposure-warning.js +92 -0
  57. package/dist/lib/exposure-warning.js.map +1 -0
  58. package/dist/lib/yaml-parser.d.ts.map +1 -1
  59. package/dist/lib/yaml-parser.js +7 -1
  60. package/dist/lib/yaml-parser.js.map +1 -1
  61. package/harness/CLAUDE.block.md +15 -0
  62. package/harness/commands/arcrun.md +29 -0
  63. package/harness/hooks/arcrun-guard.sh +54 -0
  64. package/harness/settings.fragment.json +16 -0
  65. package/harness/skills/arcrun-mindset/SKILL.md +78 -0
  66. package/package.json +5 -4
@@ -1,22 +1,129 @@
1
1
  /**
2
- * CLI 設定檔管理(~/.arcrun/config.yaml
2
+ * CLI 設定檔管理 — 三層分層解析(SDD: sdk-and-website/config-layering.md
3
+ * 優先序:env 變數 > 專案層 .arcrun.yaml(就近往上找)> 全域 ~/.arcrun/config.yaml
4
+ * 解壓測 #7(AI/CI 非互動)+ #8(接案多帳號),仿 git config / Claude Code MCP 模式。
3
5
  */
4
6
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
7
  import { homedir } from 'node:os';
6
- import { join } from 'node:path';
8
+ import { join, dirname, parse as parsePath } from 'node:path';
7
9
  import yaml from 'js-yaml';
8
10
  const CONFIG_DIR = join(homedir(), '.arcrun');
9
11
  const CONFIG_PATH = join(CONFIG_DIR, 'config.yaml');
12
+ /** 專案層設定檔名(就近往上找)。含憑證 → 必須 gitignore(見 createCredentialsYamlIfMissing)。*/
13
+ export const PROJECT_CONFIG_NAME = '.arcrun.yaml';
14
+ /** env 變數 → config 欄位映射(最高層覆蓋)。CF 兩個沿用 wrangler 慣用名,CI 設一次兩邊通用。*/
15
+ const ENV_MAP = {
16
+ ARCRUN_MODE: 'mode',
17
+ ARCRUN_API_KEY: 'api_key',
18
+ ARCRUN_ENCRYPTION_KEY: 'encryption_key',
19
+ ARCRUN_CYPHER_EXECUTOR_URL: 'cypher_executor_url',
20
+ ARCRUN_MCP_URL: 'mcp_url',
21
+ CLOUDFLARE_ACCOUNT_ID: 'cloudflare_account_id',
22
+ CLOUDFLARE_API_TOKEN: 'cf_api_token',
23
+ };
24
+ /**
25
+ * 平台預設 MCP URL(mcp_url 未設時的 fallback,SaaS 用戶用)。
26
+ * MCP 搬進 arcrun 主庫後改用 arcrun.dev zone(mcp/wrangler.toml route = mcp.arcrun.dev)。
27
+ */
28
+ export const DEFAULT_MCP_URL = 'https://mcp.arcrun.dev';
10
29
  export function configExists() {
11
- return existsSync(CONFIG_PATH);
30
+ return existsSync(CONFIG_PATH) || findProjectConfig() !== undefined;
31
+ }
32
+ /** 從 startDir 就近往上逐層找專案層 .arcrun.yaml,回傳第一個命中的路徑(停在檔案系統根)。*/
33
+ export function findProjectConfig(startDir = process.cwd()) {
34
+ let dir = startDir;
35
+ const root = parsePath(dir).root;
36
+ // 防呆上界:層數不會無限(root 一定到得了),但仍加保險避免異常路徑死迴圈。
37
+ for (let i = 0; i < 256; i++) {
38
+ const candidate = join(dir, PROJECT_CONFIG_NAME);
39
+ if (existsSync(candidate))
40
+ return candidate;
41
+ if (dir === root)
42
+ break;
43
+ const parent = dirname(dir);
44
+ if (parent === dir)
45
+ break;
46
+ dir = parent;
47
+ }
48
+ return undefined;
49
+ }
50
+ /** 讀全域設定(不分層)。無檔回 undefined。*/
51
+ function readGlobalConfig() {
52
+ if (!existsSync(CONFIG_PATH))
53
+ return undefined;
54
+ return yaml.load(readFileSync(CONFIG_PATH, 'utf8')) ?? undefined;
55
+ }
56
+ /** 讀專案層設定(不分層)。無檔回 undefined。*/
57
+ function readProjectConfig() {
58
+ const path = findProjectConfig();
59
+ if (!path)
60
+ return undefined;
61
+ return yaml.load(readFileSync(path, 'utf8')) ?? undefined;
62
+ }
63
+ /** 蒐集 env 覆蓋(只取有設值的 env,欄位級)。*/
64
+ function readEnvOverrides() {
65
+ const out = {};
66
+ for (const [envName, field] of Object.entries(ENV_MAP)) {
67
+ const v = process.env[envName];
68
+ if (v !== undefined && v !== '') {
69
+ // mode 需窄型別;其餘皆 string 欄位。
70
+ out[field] = v;
71
+ }
72
+ }
73
+ return out;
12
74
  }
75
+ /**
76
+ * 三層分層解析:全域 → 疊專案層 → 疊 env(欄位級 merge,高層只覆蓋它提供的欄位)。
77
+ * 任一層都沒有 mode 時 fallback 'local',讓 validate --offline 等在無設定下可運作。
78
+ */
13
79
  export function loadConfig() {
14
- if (!existsSync(CONFIG_PATH)) {
15
- // 未初始化時回傳 local 模式預設值,讓 validate --offline 等指令能在無設定下運作
16
- return { mode: 'local' };
80
+ const merged = {
81
+ ...(readGlobalConfig() ?? {}),
82
+ ...(readProjectConfig() ?? {}),
83
+ ...readEnvOverrides(),
84
+ };
85
+ if (!merged.mode)
86
+ merged.mode = 'local';
87
+ return merged;
88
+ }
89
+ /** 解析每個關鍵欄位的最終值與來源層(acr config --where 用)。*/
90
+ export function resolveConfigSources() {
91
+ const global = readGlobalConfig() ?? {};
92
+ const project = readProjectConfig() ?? {};
93
+ const env = readEnvOverrides();
94
+ const fields = [
95
+ 'mode', 'api_key', 'encryption_key', 'cloudflare_account_id',
96
+ 'cf_api_token', 'cypher_executor_url', 'mcp_url',
97
+ ];
98
+ const rows = [];
99
+ for (const f of fields) {
100
+ let value;
101
+ let source = 'default';
102
+ if (f in env) {
103
+ value = env[f];
104
+ source = 'env';
105
+ }
106
+ else if (f in project) {
107
+ value = project[f];
108
+ source = 'project';
109
+ }
110
+ else if (f in global) {
111
+ value = global[f];
112
+ source = 'global';
113
+ }
114
+ else if (f === 'mode') {
115
+ value = 'local';
116
+ source = 'default';
117
+ }
118
+ else
119
+ continue;
120
+ rows.push({ field: f, value: String(value), source });
17
121
  }
18
- const raw = readFileSync(CONFIG_PATH, 'utf8');
19
- return yaml.load(raw);
122
+ return rows;
123
+ }
124
+ /** 回傳本次解析實際採用的專案層設定檔路徑(無則 undefined)。acr config --where 顯示用。*/
125
+ export function activeProjectConfigPath() {
126
+ return findProjectConfig();
20
127
  }
21
128
  export function saveConfig(config) {
22
129
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -28,4 +135,12 @@ export function getCypherExecutorUrl(config) {
28
135
  }
29
136
  return 'https://cypher.arcrun.dev';
30
137
  }
138
+ /**
139
+ * 取得 MCP server URL(薄殼原則:與 cypher_url 同一份 config 解析)。
140
+ * config 有 mcp_url(env/專案/全域 任一層)→ 用它;否則 fallback 平台預設。
141
+ * acr mcp-setup 用此決定要寫進專案 .mcp.json 的 URL → 切資料夾自動切 MCP。
142
+ */
143
+ export function getMcpUrl(config) {
144
+ return config.mcp_url && config.mcp_url.trim() !== '' ? config.mcp_url : DEFAULT_MCP_URL;
145
+ }
31
146
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,SAAS,CAAC;AAmB3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,uDAAuD;QACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAiB,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC7C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAChE,OAAO,MAAM,CAAC,mBAAmB,CAAC;IACpC,CAAC;IACD,OAAO,2BAA2B,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * deploy.ts — self-hosted Worker 部署(installer 的「下載 repo tarball + wrangler deploy」段)
3
+ *
4
+ * 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §6(commit wasm + codeload)
5
+ *
6
+ * 策略(richblack 2026-06-02):repo 自帶預編譯 wasm(.component-builds 下各 component.wasm,
7
+ * 見 rule 05 慣例變更)→ CLI 從 GitHub codeload tarball 拿完整部署物 → 注入用戶的 KV id
8
+ * → 用用戶自己的 CF token wrangler deploy。用戶不需 git / tinygo,只需 wrangler。
9
+ */
10
+ /**
11
+ * init 要建立的 KV namespace(title)。
12
+ * 前 7 個權威來源:.claude/rules/01-tech-stack.md 資料儲存表(cypher-executor 用)。
13
+ * SUBMISSIONS_KV:registry worker 用(component 投稿)。漏建會讓 registry deploy 失敗 →
14
+ * 壓測 §2.6/#11「20/21」根因(registry/wrangler.toml 綁 SUBMISSIONS_KV,但注入清單沒有它,
15
+ * 殘留官方舊 id → wrangler deploy 因 KV 不存在而失敗)。補進來後回到 21/21。
16
+ */
17
+ export declare const REQUIRED_KV_NAMESPACES: readonly ["WEBHOOKS", "CREDENTIALS_KV", "RECIPES", "USERS_KV", "SESSIONS_KV", "ANALYTICS_KV", "EXEC_CONTEXT", "SUBMISSIONS_KV"];
18
+ /** 部署後要提示用戶手動 `wrangler secret put ENCRYPTION_KEY` 的 Worker。*/
19
+ export declare const SECRET_TARGET_WORKERS: readonly ["arcrun-cypher-executor", "arcrun-auth-static-key", "arcrun-auth-service-account"];
20
+ export interface DeployContext {
21
+ accountId: string;
22
+ apiToken: string;
23
+ workerSubdomain: string;
24
+ kvNamespaceIds: Record<string, string>;
25
+ }
26
+ export interface DeployResult {
27
+ implemented: boolean;
28
+ cypherExecutorUrl?: string;
29
+ message: string;
30
+ }
31
+ /** 偵測 wrangler 是否已安裝(用戶前置:裝 CF CLI)。*/
32
+ export declare function wranglerAvailable(): boolean;
33
+ /**
34
+ * 下載 repo codeload tarball(含預編譯 wasm)→ 注入用戶 KV id → wrangler deploy 全部 Worker。
35
+ *
36
+ * SDD self-hosted-init.md §6.4:
37
+ * 1. 下載 codeload tarball(ref 預設 main)→ 解壓到暫存目錄
38
+ * 2. 各 wrangler.toml 注入 ctx.kvNamespaceIds + cypher-executor WORKER_SUBDOMAIN
39
+ * 3. tier1=.component-builds/* 先 → tier2=cypher-executor/registry 後,逐一 wrangler deploy
40
+ * 4. 回 cypherExecutorUrl = https://arcrun-cypher-executor.<subdomain>.workers.dev
41
+ *
42
+ * 誠實(mindset §7):任一 worker deploy 失敗會收集進 message 回報,不假裝全綠。
43
+ *
44
+ * @param ctx 部署上下文
45
+ * @param ref git ref(branch / tag),預設 main;acr update 可帶 tag
46
+ */
47
+ export declare function downloadAndDeploy(ctx: DeployContext, ref?: string): Promise<DeployResult>;
48
+ /**
49
+ * 移除 self-hosted fork 帳號沒有、會導致 wrangler deploy 失敗的官方專屬 TOML 區塊:
50
+ * - `[[routes]]`(含 pattern/zone_name):fork 沒有 arcrun.dev zone
51
+ * - `[[r2_buckets]]`:dead storage + 綁卡違背開源免費(registry-canon 1.5)
52
+ * - `[ai]`(Workers AI binding):免費帳號未必啟用,且自架預設不需要
53
+ * 純文字行級移除(TOML table 以空行 / 下一個 `[` 區塊結束)。worker 仍靠 `workers_dev = true` 對外。
54
+ */
55
+ export declare function stripOfficialOnlyBindings(toml: string): string;
56
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/lib/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,iIASzB,CAAC;AAEX,+DAA+D;AAC/D,eAAO,MAAM,qBAAqB,8FAIxB,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAmD/F;AAuFD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA2B9D"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * deploy.ts — self-hosted Worker 部署(installer 的「下載 repo tarball + wrangler deploy」段)
3
+ *
4
+ * 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §6(commit wasm + codeload)
5
+ *
6
+ * 策略(richblack 2026-06-02):repo 自帶預編譯 wasm(.component-builds 下各 component.wasm,
7
+ * 見 rule 05 慣例變更)→ CLI 從 GitHub codeload tarball 拿完整部署物 → 注入用戶的 KV id
8
+ * → 用用戶自己的 CF token wrangler deploy。用戶不需 git / tinygo,只需 wrangler。
9
+ */
10
+ import { execFileSync } from 'node:child_process';
11
+ import { mkdtempSync, readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { join } from 'node:path';
14
+ /** GitHub repo(codeload tarball 來源)。fork 者改這裡或用 ARCRUN_REPO env。
15
+ * 注意:repo 名大小寫敏感(codeload 路徑需完全一致)。*/
16
+ const ARCRUN_REPO = process.env.ARCRUN_REPO ?? 'uncle6me-web/Arcrun';
17
+ /**
18
+ * init 要建立的 KV namespace(title)。
19
+ * 前 7 個權威來源:.claude/rules/01-tech-stack.md 資料儲存表(cypher-executor 用)。
20
+ * SUBMISSIONS_KV:registry worker 用(component 投稿)。漏建會讓 registry deploy 失敗 →
21
+ * 壓測 §2.6/#11「20/21」根因(registry/wrangler.toml 綁 SUBMISSIONS_KV,但注入清單沒有它,
22
+ * 殘留官方舊 id → wrangler deploy 因 KV 不存在而失敗)。補進來後回到 21/21。
23
+ */
24
+ export const REQUIRED_KV_NAMESPACES = [
25
+ 'WEBHOOKS',
26
+ 'CREDENTIALS_KV',
27
+ 'RECIPES',
28
+ 'USERS_KV',
29
+ 'SESSIONS_KV',
30
+ 'ANALYTICS_KV',
31
+ 'EXEC_CONTEXT',
32
+ 'SUBMISSIONS_KV',
33
+ ];
34
+ /** 部署後要提示用戶手動 `wrangler secret put ENCRYPTION_KEY` 的 Worker。*/
35
+ export const SECRET_TARGET_WORKERS = [
36
+ 'arcrun-cypher-executor',
37
+ 'arcrun-auth-static-key',
38
+ 'arcrun-auth-service-account',
39
+ ];
40
+ /** 偵測 wrangler 是否已安裝(用戶前置:裝 CF CLI)。*/
41
+ export function wranglerAvailable() {
42
+ try {
43
+ execFileSync('wrangler', ['--version'], { stdio: 'ignore' });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ /**
51
+ * 下載 repo codeload tarball(含預編譯 wasm)→ 注入用戶 KV id → wrangler deploy 全部 Worker。
52
+ *
53
+ * SDD self-hosted-init.md §6.4:
54
+ * 1. 下載 codeload tarball(ref 預設 main)→ 解壓到暫存目錄
55
+ * 2. 各 wrangler.toml 注入 ctx.kvNamespaceIds + cypher-executor WORKER_SUBDOMAIN
56
+ * 3. tier1=.component-builds/* 先 → tier2=cypher-executor/registry 後,逐一 wrangler deploy
57
+ * 4. 回 cypherExecutorUrl = https://arcrun-cypher-executor.<subdomain>.workers.dev
58
+ *
59
+ * 誠實(mindset §7):任一 worker deploy 失敗會收集進 message 回報,不假裝全綠。
60
+ *
61
+ * @param ctx 部署上下文
62
+ * @param ref git ref(branch / tag),預設 main;acr update 可帶 tag
63
+ */
64
+ export async function downloadAndDeploy(ctx, ref = 'main') {
65
+ // 1. 下載 + 解壓 codeload tarball
66
+ let root;
67
+ try {
68
+ root = await downloadRepoTarball(ref);
69
+ }
70
+ catch (e) {
71
+ return {
72
+ implemented: true,
73
+ message: `下載部署物失敗(${e instanceof Error ? e.message : e})。確認網路 + ARCRUN_REPO=${ARCRUN_REPO} 可達。`,
74
+ };
75
+ }
76
+ // 2. 列出要部署的 worker 目錄(含 wrangler.toml),分 tier
77
+ const { tier1, tier2 } = discoverWorkerDirs(root);
78
+ if (tier1.length === 0 && tier2.length === 0) {
79
+ return { implemented: true, message: `部署物中找不到任何 wrangler.toml(root=${root})。` };
80
+ }
81
+ // 3. 對每個 worker:注入 KV id(+ cypher WORKER_SUBDOMAIN)→ wrangler deploy。tier1 先 tier2 後。
82
+ const failures = [];
83
+ let deployed = 0;
84
+ for (const dir of [...tier1, ...tier2]) {
85
+ const tomlPath = join(dir, 'wrangler.toml');
86
+ try {
87
+ injectWranglerConfig(tomlPath, ctx);
88
+ runWranglerDeploy(dir, ctx);
89
+ deployed++;
90
+ }
91
+ catch (e) {
92
+ failures.push(`${dir}: ${e instanceof Error ? e.message : String(e)}`);
93
+ }
94
+ }
95
+ const cypherExecutorUrl = ctx.workerSubdomain
96
+ ? `https://arcrun-cypher-executor.${ctx.workerSubdomain}.workers.dev`
97
+ : undefined;
98
+ if (failures.length > 0) {
99
+ return {
100
+ implemented: true,
101
+ cypherExecutorUrl,
102
+ message: `部署 ${deployed}/${tier1.length + tier2.length} 成功,${failures.length} 失敗(誠實回報,未假綠):\n` +
103
+ failures.map(f => ` ✗ ${f}`).join('\n'),
104
+ };
105
+ }
106
+ return {
107
+ implemented: true,
108
+ cypherExecutorUrl,
109
+ message: `部署完成:${deployed} 個 Worker 全部成功。`,
110
+ };
111
+ }
112
+ /** 下載 codeload tarball 解壓到暫存目錄,回傳解壓出的 repo root 路徑。*/
113
+ async function downloadRepoTarball(ref) {
114
+ const url = `https://codeload.github.com/${ARCRUN_REPO}/tar.gz/${ref}`;
115
+ const res = await fetch(url, { signal: AbortSignal.timeout(120_000) });
116
+ if (!res.ok)
117
+ throw new Error(`codeload HTTP ${res.status}(${url})`);
118
+ const buf = Buffer.from(await res.arrayBuffer());
119
+ const dir = mkdtempSync(join(tmpdir(), 'arcrun-deploy-'));
120
+ const tarPath = join(dir, 'repo.tar.gz');
121
+ writeFileSync(tarPath, buf);
122
+ // 用系統 tar 解壓(macOS/Linux 內建)。tarball 解出單一頂層目錄 {repo}-{ref}/。
123
+ execFileSync('tar', ['-xzf', tarPath, '-C', dir], { stdio: 'ignore' });
124
+ const entries = readdirSync(dir).filter(n => n !== 'repo.tar.gz');
125
+ const top = entries.find(n => statSync(join(dir, n)).isDirectory());
126
+ if (!top)
127
+ throw new Error('tarball 解壓後找不到頂層目錄');
128
+ return join(dir, top);
129
+ }
130
+ /** 掃解壓出的部署物,回傳 tier1(.component-builds/*)與 tier2(cypher-executor/registry)目錄清單。*/
131
+ function discoverWorkerDirs(root) {
132
+ const tier1 = [];
133
+ const tier2 = [];
134
+ const cbRoot = join(root, '.component-builds');
135
+ if (existsSync(cbRoot)) {
136
+ for (const name of readdirSync(cbRoot)) {
137
+ const dir = join(cbRoot, name);
138
+ // 需同時有 wrangler.toml 且有 component.wasm 才部署。
139
+ // 「錯做成零件」的(claude_api / km_writer / kbdb_upsert_block)wasm 沒 commit 進 repo
140
+ // (.gitignore 排除,待降級成工作流/recipe)→ codeload 拿到的目錄缺 wasm → 自然跳過,
141
+ // 不讓 wrangler deploy 因缺檔失敗。
142
+ if (existsSync(join(dir, 'wrangler.toml')) && existsSync(join(dir, 'component.wasm'))) {
143
+ tier1.push(dir);
144
+ }
145
+ }
146
+ }
147
+ for (const name of ['cypher-executor', 'registry']) {
148
+ const dir = join(root, name);
149
+ if (existsSync(join(dir, 'wrangler.toml')))
150
+ tier2.push(dir);
151
+ }
152
+ return { tier1, tier2 };
153
+ }
154
+ /**
155
+ * 注入用戶的 KV namespace id(取代 wrangler.toml 中各 binding 的 id)+ cypher WORKER_SUBDOMAIN,
156
+ * 並 strip 掉只有 arcrun 官方帳號才有的綁定(self-hosted fork 帳號沒有)。
157
+ *
158
+ * 為何 strip 而非刪 repo 內 toml(壓測 2026-06-04 阻斷項 #1#2#3#4):
159
+ * - repo 內各 worker toml 的 `[[routes]] zone_name="arcrun.dev"` 是**官方 prod CI 部署**需要的
160
+ * (對外開放零件)。直接從 repo 刪會破壞官方部署。
161
+ * - 但 fork 用戶**沒有 arcrun.dev zone** → wrangler deploy 找不到 zone 而失敗。
162
+ * - deploy.ts 只在 self-hosted 路徑跑,且改的是「暫存目錄副本」(SDD self-hosted-init.md §3 step 4),
163
+ * 不碰用戶 repo。所以在注入時 strip 掉這些官方專屬綁定 = 對的層級。
164
+ * - 每個 worker toml 都有 `workers_dev = true` → strip routes 後純靠 workers.dev URL,自架可達。
165
+ * - R2(`[[r2_buckets]]`)是 dead storage(registry-canon Phase 1.5),且綁卡違背開源免費 → 一併移除。
166
+ */
167
+ function injectWranglerConfig(tomlPath, ctx) {
168
+ if (!existsSync(tomlPath))
169
+ return;
170
+ let toml = readFileSync(tomlPath, 'utf8');
171
+ // 對每個已建立的 KV namespace:把對應 binding 的 id 換成用戶的。
172
+ // 匹配 `[[kv_namespaces]] ... binding = "NAME" ... id = "OLD"` 的 id 行。
173
+ for (const [binding, id] of Object.entries(ctx.kvNamespaceIds)) {
174
+ if (!id)
175
+ continue;
176
+ const re = new RegExp(`(binding\\s*=\\s*"${binding}"\\s*\\n\\s*id\\s*=\\s*")[^"]*(")`, 'g');
177
+ toml = toml.replace(re, `$1${id}$2`);
178
+ }
179
+ // cypher-executor 的 WORKER_SUBDOMAIN(vars)換成用戶帳號 subdomain
180
+ if (ctx.workerSubdomain && /WORKER_SUBDOMAIN/.test(toml)) {
181
+ toml = toml.replace(/(WORKER_SUBDOMAIN\s*=\s*")[^"]*(")/, `$1${ctx.workerSubdomain}$2`);
182
+ }
183
+ toml = stripOfficialOnlyBindings(toml);
184
+ writeFileSync(tomlPath, toml, 'utf8');
185
+ }
186
+ /**
187
+ * 移除 self-hosted fork 帳號沒有、會導致 wrangler deploy 失敗的官方專屬 TOML 區塊:
188
+ * - `[[routes]]`(含 pattern/zone_name):fork 沒有 arcrun.dev zone
189
+ * - `[[r2_buckets]]`:dead storage + 綁卡違背開源免費(registry-canon 1.5)
190
+ * - `[ai]`(Workers AI binding):免費帳號未必啟用,且自架預設不需要
191
+ * 純文字行級移除(TOML table 以空行 / 下一個 `[` 區塊結束)。worker 仍靠 `workers_dev = true` 對外。
192
+ */
193
+ export function stripOfficialOnlyBindings(toml) {
194
+ const lines = toml.split('\n');
195
+ const out = [];
196
+ let skipping = false;
197
+ const isBlockHeader = (l) => /^\s*\[\[?(routes|r2_buckets|ai)\]?\]\s*$/.test(l);
198
+ for (const line of lines) {
199
+ if (isBlockHeader(line)) {
200
+ skipping = true; // 進入要移除的區塊,連同 header 一起丟
201
+ continue;
202
+ }
203
+ if (skipping) {
204
+ // 區塊結束條件:遇到下一個 table header(`[...]`)或空行
205
+ if (/^\s*\[/.test(line)) {
206
+ skipping = false; // 這行是新區塊的開頭,保留並由下方邏輯處理
207
+ }
208
+ else if (line.trim() === '') {
209
+ skipping = false; // 空行結束區塊;空行本身丟掉避免堆疊空白
210
+ continue;
211
+ }
212
+ else {
213
+ continue; // 仍在被移除區塊內(pattern/zone_name/binding/bucket_name 等)
214
+ }
215
+ }
216
+ out.push(line);
217
+ }
218
+ return out.join('\n');
219
+ }
220
+ /** 在 worker 目錄跑 wrangler deploy(用用戶的 CF token + account)。*/
221
+ function runWranglerDeploy(dir, ctx) {
222
+ // 先裝依賴(cypher-executor/registry 是 TS,wrangler 內建 esbuild bundle 需 node_modules)
223
+ if (existsSync(join(dir, 'package.json'))) {
224
+ const installer = existsSync(join(dir, 'pnpm-lock.yaml'))
225
+ ? ['pnpm', 'install', '--frozen-lockfile']
226
+ : ['npm', 'install', '--no-audit', '--no-fund'];
227
+ execFileSync(installer[0], installer.slice(1), { cwd: dir, stdio: 'ignore' });
228
+ }
229
+ execFileSync('wrangler', ['deploy'], {
230
+ cwd: dir,
231
+ stdio: 'ignore',
232
+ env: {
233
+ ...process.env,
234
+ CLOUDFLARE_API_TOKEN: ctx.apiToken,
235
+ CLOUDFLARE_ACCOUNT_ID: ctx.accountId,
236
+ },
237
+ });
238
+ }
239
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/lib/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface ExposureConsent {
2
+ confirmed_by_human: true;
3
+ understood: string;
4
+ confirmed_at: string;
5
+ suppress_future?: boolean;
6
+ }
7
+ export interface ExposureWarningOptions {
8
+ _reserved?: never;
9
+ }
10
+ export interface ExposureContext {
11
+ /** 動作種類,顯示用:'webhook' | 'recipe' */
12
+ kind: string;
13
+ /** 資源名(用戶要打這個字確認)*/
14
+ resourceName: string;
15
+ /** 暴露後的 URL / 去向(顯示用,可選) */
16
+ destination?: string;
17
+ /** 這個資源讀取/送出什麼(盡力盤,盤不出傳 undefined) */
18
+ dataSummary?: string;
19
+ }
20
+ /**
21
+ * 取得暴露同意。回傳 ExposureConsent(放進 push 請求 body)。
22
+ * 未取得同意 → 印訊息並 return null(呼叫端應中止)。
23
+ */
24
+ export declare function obtainExposureConsent(ctx: ExposureContext, opts?: ExposureWarningOptions): Promise<ExposureConsent | null>;
25
+ //# sourceMappingURL=exposure-warning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposure-warning.d.ts","sourceRoot":"","sources":["../../src/lib/exposure-warning.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,EAAE,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAKD,MAAM,WAAW,sBAAsB;IAErC,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,eAAe,EACpB,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmDjC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * 資料外流警示 — CLI 互動(data-exfil-warning SDD §1a / B)
3
+ *
4
+ * 觸發策略:只在「資料變成可被外部呼叫」時警示(webhook 部署 / recipe push)。
5
+ * 互動形式(richblack):仿 GCP 刪 project —— 要用戶打資源名證明讀了警示(比 y/n 硬,不用打一大串)。
6
+ * 同意 = 法律憑證:回傳的 ExposureConsent 帶 understood(用戶打的內容)+ 時間,server 端 log。
7
+ * 誠實限制:非 TTY(AI 直跑)無 --confirm-exposure → 拒絕(AI 不該替人類確認暴露)。
8
+ */
9
+ import { createInterface } from 'node:readline/promises';
10
+ import chalk from 'chalk';
11
+ import { loadConfig, saveConfig } from './config.js';
12
+ /**
13
+ * 取得暴露同意。回傳 ExposureConsent(放進 push 請求 body)。
14
+ * 未取得同意 → 印訊息並 return null(呼叫端應中止)。
15
+ */
16
+ export async function obtainExposureConsent(ctx, opts = {}) {
17
+ const nowIso = new Date().toISOString();
18
+ const memKey = `${ctx.kind}:${ctx.resourceName}`;
19
+ // §3 首次問記住:本機已記錄同意此資源 → 不重問(server 端仍存法律憑證並強制)。
20
+ const cfg = loadConfig();
21
+ const prior = cfg.exposure_consented?.[memKey];
22
+ if (prior) {
23
+ return {
24
+ confirmed_by_human: true,
25
+ understood: `先前已同意暴露 ${ctx.resourceName}(${prior.confirmed_at}${prior.suppress_future ? ',已選不再警示' : ''})`,
26
+ confirmed_at: prior.confirmed_at,
27
+ suppress_future: prior.suppress_future,
28
+ };
29
+ }
30
+ // 非 TTY(AI 直跑)→ 一律拒絕,無捷徑。AI 不該、也不能替人類確認暴露。
31
+ // (移除了 --confirm-exposure 旗標:那是 AI 自己能加的後門,等於自己批准自己。)
32
+ if (!process.stdin.isTTY) {
33
+ console.error(chalk.red('\n⚠️ 此動作會把資源變成可被外部呼叫(暴露/送出資料),需人類明示同意。'));
34
+ console.error(chalk.gray(' 你(AI)無法確認暴露——這必須由人類在終端機親自執行、輸入資源名確認。'));
35
+ console.error(chalk.gray(' 請把這件事交給人類做。\n'));
36
+ return null;
37
+ }
38
+ // 互動式警示 + 打資源名確認(唯一通過路徑,AI 生不出這個輸入)
39
+ printWarning(ctx);
40
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
41
+ try {
42
+ const answer = (await rl.question(chalk.bold(` 確認暴露?請輸入資源名 "${ctx.resourceName}" 以繼續(或 Ctrl-C 取消):`))).trim();
43
+ if (answer !== ctx.resourceName) {
44
+ console.error(chalk.red(`\n 輸入不符(需輸入 "${ctx.resourceName}")。已取消,未暴露。\n`));
45
+ return null;
46
+ }
47
+ // 互動中詢問「以後不再問」(人類選,不是 AI 加旗標)
48
+ const suppressAns = (await rl.question(chalk.gray(` 以後此資源(${ctx.resourceName})的暴露不再提醒?(y/N):`))).trim().toLowerCase();
49
+ const suppress = suppressAns === 'y' || suppressAns === 'yes';
50
+ rememberConsent(memKey, nowIso, suppress);
51
+ return {
52
+ confirmed_by_human: true,
53
+ understood: `用戶輸入資源名 "${ctx.resourceName}" 確認暴露${ctx.destination ? `(去向:${ctx.destination})` : ''}${suppress ? ';並選擇以後不再提醒' : ''}`,
54
+ confirmed_at: nowIso,
55
+ suppress_future: suppress,
56
+ };
57
+ }
58
+ finally {
59
+ rl.close();
60
+ }
61
+ }
62
+ /** 本機記住此資源已同意(避免下次重問;server 端仍獨立存法律憑證並強制) */
63
+ function rememberConsent(memKey, confirmedAt, suppressFuture) {
64
+ try {
65
+ const cfg = loadConfig();
66
+ cfg.exposure_consented = cfg.exposure_consented ?? {};
67
+ cfg.exposure_consented[memKey] = { confirmed_at: confirmedAt, suppress_future: suppressFuture };
68
+ saveConfig(cfg);
69
+ }
70
+ catch {
71
+ // 記不住不影響本次同意(server 端仍會擋首次)
72
+ }
73
+ }
74
+ function printWarning(ctx) {
75
+ console.log(chalk.yellow.bold(`\n⚠️ 資料外流警示`));
76
+ console.log(chalk.yellow(` 這個動作會把 ${ctx.kind} "${ctx.resourceName}" 變成可被外部呼叫。`));
77
+ if (ctx.destination) {
78
+ console.log(chalk.gray(` 去向:${ctx.destination}`));
79
+ }
80
+ if (ctx.dataSummary) {
81
+ console.log(chalk.gray(` 涉及資料:${ctx.dataSummary}`));
82
+ }
83
+ else {
84
+ console.log(chalk.gray(` 涉及資料:無法自動判斷,請自行確認此資源是否含敏感資料。`));
85
+ }
86
+ console.log(chalk.gray(` 任何能呼叫它的人都能取得它的輸出/能力。`));
87
+ console.log('');
88
+ console.log(chalk.cyan(` arcrun 可幫你保護它:要求呼叫者帶 API Key/設權限/限流(一個動作就能加)。`));
89
+ console.log(chalk.gray(` 若這是要公開的資料(如公開 API),可直接確認。`));
90
+ console.log('');
91
+ }
92
+ //# sourceMappingURL=exposure-warning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposure-warning.js","sourceRoot":"","sources":["../../src/lib/exposure-warning.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"yaml-parser.d.ts","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAU/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAgB7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,IAAI,CAejE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAOhE"}
1
+ {"version":3,"file":"yaml-parser.d.ts","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAU/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAgB7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,IAAI,CAqBjE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAOhE"}
@@ -38,9 +38,15 @@ export function validateRelations(triplets) {
38
38
  throw new Error(`不允許使用關係詞「${t.relation}」。\n` +
39
39
  `「PIPE」已棄用,請改用「完成後」或「ON_SUCCESS」。`);
40
40
  }
41
+ // 容許 FOREACH iterator 命名變體:「對每個 paragraph」/「FOREACH item」
42
+ // graph-builder.ts 已支援這個 regex(commit e8fca33 2026-05-07)
43
+ const foreachMatch = t.relation.match(/^(?:對每個|FOREACH)\s+\w+$/i);
44
+ if (foreachMatch)
45
+ continue;
41
46
  if (!VALID_RELATIONS.has(t.relation)) {
42
47
  throw new Error(`未知關係詞「${t.relation}」。\n` +
43
- `合法關係詞:${[...VALID_RELATIONS].join('、')}`);
48
+ `合法關係詞:${[...VALID_RELATIONS].join('、')}\n` +
49
+ `(FOREACH 支援 iterator 命名:「對每個 X」/「FOREACH X」)`);
44
50
  }
45
51
  }
46
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"yaml-parser.js","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAevC,oBAAoB;AACpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO;IAC5B,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe;CACtE,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAiB,CAAC;IAE3C,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,YAAY,IAAI,KAAK;gBACrB,sBAAsB,CACvB,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,YAAY,CAAC,CAAC,QAAQ,MAAM;gBAC5B,kCAAkC,CACnC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,SAAS,CAAC,CAAC,QAAQ,MAAM;gBACzB,SAAS,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC1C,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAyB;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACrB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"yaml-parser.js","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAevC,oBAAoB;AACpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO;IAC5B,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe;CACtE,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAiB,CAAC;IAE3C,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,YAAY,IAAI,KAAK;gBACrB,sBAAsB,CACvB,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,YAAY,CAAC,CAAC,QAAQ,MAAM;gBAC5B,kCAAkC,CACnC,CAAC;QACJ,CAAC;QACD,0DAA0D;QAC1D,0DAA0D;QAC1D,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAClE,IAAI,YAAY;YAAE,SAAS;QAE3B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,SAAS,CAAC,CAAC,QAAQ,MAAM;gBACzB,SAAS,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI;gBAC3C,8CAA8C,CAC/C,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAyB;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACrB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC"}