@tiangong-lca/cli 0.0.1
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 +368 -0
- package/bin/tiangong.d.ts +1 -0
- package/bin/tiangong.js +32 -0
- package/dist/scripts/assert-full-coverage.js +77 -0
- package/dist/scripts/assert-full-coverage.js.map +1 -0
- package/dist/src/cli.js +2740 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/lib/artifacts.js +86 -0
- package/dist/src/lib/artifacts.js.map +1 -0
- package/dist/src/lib/dotenv.js +36 -0
- package/dist/src/lib/dotenv.js.map +1 -0
- package/dist/src/lib/env.js +139 -0
- package/dist/src/lib/env.js.map +1 -0
- package/dist/src/lib/errors.js +38 -0
- package/dist/src/lib/errors.js.map +1 -0
- package/dist/src/lib/flow-build-alias-map.js +299 -0
- package/dist/src/lib/flow-build-alias-map.js.map +1 -0
- package/dist/src/lib/flow-get.js +81 -0
- package/dist/src/lib/flow-get.js.map +1 -0
- package/dist/src/lib/flow-governance.js +164 -0
- package/dist/src/lib/flow-governance.js.map +1 -0
- package/dist/src/lib/flow-list.js +187 -0
- package/dist/src/lib/flow-list.js.map +1 -0
- package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
- package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
- package/dist/src/lib/flow-publish-version.js +541 -0
- package/dist/src/lib/flow-publish-version.js.map +1 -0
- package/dist/src/lib/flow-read.js +310 -0
- package/dist/src/lib/flow-read.js.map +1 -0
- package/dist/src/lib/flow-regen-product.js +1396 -0
- package/dist/src/lib/flow-regen-product.js.map +1 -0
- package/dist/src/lib/flow-remediate.js +821 -0
- package/dist/src/lib/flow-remediate.js.map +1 -0
- package/dist/src/lib/http.js +45 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/io.js +30 -0
- package/dist/src/lib/io.js.map +1 -0
- package/dist/src/lib/kb-search.js +83 -0
- package/dist/src/lib/kb-search.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
- package/dist/src/lib/llm.js +383 -0
- package/dist/src/lib/llm.js.map +1 -0
- package/dist/src/lib/process-auto-build.js +717 -0
- package/dist/src/lib/process-auto-build.js.map +1 -0
- package/dist/src/lib/process-batch-build.js +409 -0
- package/dist/src/lib/process-batch-build.js.map +1 -0
- package/dist/src/lib/process-get.js +66 -0
- package/dist/src/lib/process-get.js.map +1 -0
- package/dist/src/lib/process-publish-build.js +451 -0
- package/dist/src/lib/process-publish-build.js.map +1 -0
- package/dist/src/lib/process-resume-build.js +364 -0
- package/dist/src/lib/process-resume-build.js.map +1 -0
- package/dist/src/lib/publish.js +562 -0
- package/dist/src/lib/publish.js.map +1 -0
- package/dist/src/lib/remote.js +88 -0
- package/dist/src/lib/remote.js.map +1 -0
- package/dist/src/lib/review-flow.js +1110 -0
- package/dist/src/lib/review-flow.js.map +1 -0
- package/dist/src/lib/review-lifecyclemodel.js +727 -0
- package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
- package/dist/src/lib/review-process.js +702 -0
- package/dist/src/lib/review-process.js.map +1 -0
- package/dist/src/lib/run.js +106 -0
- package/dist/src/lib/run.js.map +1 -0
- package/dist/src/lib/state-lock.js +150 -0
- package/dist/src/lib/state-lock.js.map +1 -0
- package/dist/src/lib/supabase-client.js +269 -0
- package/dist/src/lib/supabase-client.js.map +1 -0
- package/dist/src/lib/supabase-json-ordered-write.js +186 -0
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
- package/dist/src/lib/supabase-rest.js +138 -0
- package/dist/src/lib/supabase-rest.js.map +1 -0
- package/dist/src/lib/supabase-session.js +424 -0
- package/dist/src/lib/supabase-session.js.map +1 -0
- package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
- package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
- package/dist/src/lib/unstructured.js +203 -0
- package/dist/src/lib/unstructured.js.map +1 -0
- package/dist/src/lib/user-api-key.js +75 -0
- package/dist/src/lib/user-api-key.js.map +1 -0
- package/dist/src/lib/validation.js +191 -0
- package/dist/src/lib/validation.js.map +1 -0
- package/dist/src/main.js +38 -0
- package/dist/src/main.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function stripQuotes(value) {
|
|
4
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
5
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
6
|
+
return value.slice(1, -1);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export function loadDotEnv(cwd, env) {
|
|
11
|
+
const envPath = path.join(cwd, '.env');
|
|
12
|
+
if (!existsSync(envPath)) {
|
|
13
|
+
return { loaded: false, path: envPath, count: 0 };
|
|
14
|
+
}
|
|
15
|
+
const lines = readFileSync(envPath, 'utf8').split(/\r?\n/u);
|
|
16
|
+
let count = 0;
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
23
|
+
if (separatorIndex <= 0) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
27
|
+
const rawValue = trimmed.slice(separatorIndex + 1).trim();
|
|
28
|
+
if (!key || env[key] !== undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
env[key] = stripQuotes(rawValue);
|
|
32
|
+
count += 1;
|
|
33
|
+
}
|
|
34
|
+
return { loaded: true, path: envPath, count };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=dotenv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotenv.js","sourceRoot":"","sources":["../../../src/lib/dotenv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAQ7B,SAAS,WAAW,CAAC,KAAa;IAChC,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,GAAsB;IAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACpD,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,SAAS;QACX,CAAC;QAED,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACnC,SAAS;QACX,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACjC,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nexport type DotEnvLoadResult = {\n loaded: boolean;\n path: string;\n count: number;\n};\n\nfunction stripQuotes(value: string): string {\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n return value.slice(1, -1);\n }\n return value;\n}\n\nexport function loadDotEnv(cwd: string, env: NodeJS.ProcessEnv): DotEnvLoadResult {\n const envPath = path.join(cwd, '.env');\n if (!existsSync(envPath)) {\n return { loaded: false, path: envPath, count: 0 };\n }\n\n const lines = readFileSync(envPath, 'utf8').split(/\\r?\\n/u);\n let count = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) {\n continue;\n }\n\n const separatorIndex = trimmed.indexOf('=');\n if (separatorIndex <= 0) {\n continue;\n }\n\n const key = trimmed.slice(0, separatorIndex).trim();\n const rawValue = trimmed.slice(separatorIndex + 1).trim();\n if (!key || env[key] !== undefined) {\n continue;\n }\n\n env[key] = stripQuotes(rawValue);\n count += 1;\n }\n\n return { loaded: true, path: envPath, count };\n}\n"]}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export const ENV_KEYS = {
|
|
2
|
+
apiBaseUrl: 'TIANGONG_LCA_API_BASE_URL',
|
|
3
|
+
apiKey: 'TIANGONG_LCA_API_KEY',
|
|
4
|
+
region: 'TIANGONG_LCA_REGION',
|
|
5
|
+
supabasePublishableKey: 'TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY',
|
|
6
|
+
sessionFile: 'TIANGONG_LCA_SESSION_FILE',
|
|
7
|
+
disableSessionCache: 'TIANGONG_LCA_DISABLE_SESSION_CACHE',
|
|
8
|
+
forceReauth: 'TIANGONG_LCA_FORCE_REAUTH',
|
|
9
|
+
};
|
|
10
|
+
export const ENV_SPECS = [
|
|
11
|
+
{
|
|
12
|
+
key: ENV_KEYS.apiBaseUrl,
|
|
13
|
+
required: true,
|
|
14
|
+
description: 'Main TianGong LCA API / Edge Functions base URL',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: ENV_KEYS.apiKey,
|
|
18
|
+
required: true,
|
|
19
|
+
description: 'Main TianGong LCA API key',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: ENV_KEYS.region,
|
|
23
|
+
required: false,
|
|
24
|
+
description: 'Target TianGong LCA API region',
|
|
25
|
+
defaultValue: 'us-east-1',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: ENV_KEYS.supabasePublishableKey,
|
|
29
|
+
required: true,
|
|
30
|
+
description: 'Supabase publishable key used to bootstrap and refresh user sessions',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: ENV_KEYS.sessionFile,
|
|
34
|
+
required: false,
|
|
35
|
+
description: 'Optional local path for the CLI session cache file',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: ENV_KEYS.disableSessionCache,
|
|
39
|
+
required: false,
|
|
40
|
+
description: 'Disable the persistent CLI session cache when set to true',
|
|
41
|
+
defaultValue: 'false',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: ENV_KEYS.forceReauth,
|
|
45
|
+
required: false,
|
|
46
|
+
description: 'Ignore cached sessions and force a fresh login when set to true',
|
|
47
|
+
defaultValue: 'false',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
function parseBooleanEnv(value) {
|
|
51
|
+
if (typeof value !== 'string') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
switch (value.trim().toLowerCase()) {
|
|
55
|
+
case '1':
|
|
56
|
+
case 'true':
|
|
57
|
+
case 'yes':
|
|
58
|
+
case 'on':
|
|
59
|
+
return true;
|
|
60
|
+
default:
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function resolveEnv(spec, env) {
|
|
65
|
+
const envValue = env[spec.key];
|
|
66
|
+
if (envValue) {
|
|
67
|
+
return {
|
|
68
|
+
key: spec.key,
|
|
69
|
+
source: 'env',
|
|
70
|
+
value: envValue,
|
|
71
|
+
present: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (spec.defaultValue !== undefined) {
|
|
75
|
+
return {
|
|
76
|
+
key: spec.key,
|
|
77
|
+
source: 'default',
|
|
78
|
+
value: spec.defaultValue,
|
|
79
|
+
present: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
key: spec.key,
|
|
84
|
+
source: 'missing',
|
|
85
|
+
value: null,
|
|
86
|
+
present: false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function readRuntimeEnv(env) {
|
|
90
|
+
const apiBaseUrl = resolveEnv(ENV_SPECS[0], env).value;
|
|
91
|
+
const apiKey = resolveEnv(ENV_SPECS[1], env).value;
|
|
92
|
+
const region = resolveEnv(ENV_SPECS[2], env).value;
|
|
93
|
+
const supabasePublishableKey = resolveEnv(ENV_SPECS[3], env).value;
|
|
94
|
+
const sessionFile = resolveEnv(ENV_SPECS[4], env).value;
|
|
95
|
+
const disableSessionCache = parseBooleanEnv(resolveEnv(ENV_SPECS[5], env).value);
|
|
96
|
+
const forceReauth = parseBooleanEnv(resolveEnv(ENV_SPECS[6], env).value);
|
|
97
|
+
return {
|
|
98
|
+
apiBaseUrl,
|
|
99
|
+
apiKey,
|
|
100
|
+
region,
|
|
101
|
+
supabasePublishableKey,
|
|
102
|
+
sessionFile,
|
|
103
|
+
disableSessionCache,
|
|
104
|
+
forceReauth,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function maskSecret(value) {
|
|
108
|
+
if (!value) {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
if (value.length <= 8) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
115
|
+
}
|
|
116
|
+
export function buildDoctorReport(env, dotEnvStatus) {
|
|
117
|
+
const checks = ENV_SPECS.map((spec) => {
|
|
118
|
+
const resolved = resolveEnv(spec, env);
|
|
119
|
+
return {
|
|
120
|
+
key: spec.key,
|
|
121
|
+
description: spec.description,
|
|
122
|
+
required: spec.required,
|
|
123
|
+
source: resolved.source,
|
|
124
|
+
present: resolved.present,
|
|
125
|
+
valuePreview: maskSecret(resolved.value),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
ok: checks.every((check) => !check.required || check.present),
|
|
130
|
+
loadedDotEnv: dotEnvStatus.loaded,
|
|
131
|
+
dotEnvPath: dotEnvStatus.path,
|
|
132
|
+
dotEnvKeysLoaded: dotEnvStatus.count,
|
|
133
|
+
checks,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export const __testInternals = {
|
|
137
|
+
parseBooleanEnv,
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/lib/env.ts"],"names":[],"mappings":"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","sourcesContent":["export type EnvSpec = {\n key: string;\n required: boolean;\n description: string;\n defaultValue?: string;\n};\n\nexport type ResolvedEnv = {\n key: string;\n source: 'env' | 'default' | 'missing';\n value: string | null;\n present: boolean;\n};\n\nexport const ENV_KEYS = {\n apiBaseUrl: 'TIANGONG_LCA_API_BASE_URL',\n apiKey: 'TIANGONG_LCA_API_KEY',\n region: 'TIANGONG_LCA_REGION',\n supabasePublishableKey: 'TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY',\n sessionFile: 'TIANGONG_LCA_SESSION_FILE',\n disableSessionCache: 'TIANGONG_LCA_DISABLE_SESSION_CACHE',\n forceReauth: 'TIANGONG_LCA_FORCE_REAUTH',\n} as const;\n\nexport const ENV_SPECS: EnvSpec[] = [\n {\n key: ENV_KEYS.apiBaseUrl,\n required: true,\n description: 'Main TianGong LCA API / Edge Functions base URL',\n },\n {\n key: ENV_KEYS.apiKey,\n required: true,\n description: 'Main TianGong LCA API key',\n },\n {\n key: ENV_KEYS.region,\n required: false,\n description: 'Target TianGong LCA API region',\n defaultValue: 'us-east-1',\n },\n {\n key: ENV_KEYS.supabasePublishableKey,\n required: true,\n description: 'Supabase publishable key used to bootstrap and refresh user sessions',\n },\n {\n key: ENV_KEYS.sessionFile,\n required: false,\n description: 'Optional local path for the CLI session cache file',\n },\n {\n key: ENV_KEYS.disableSessionCache,\n required: false,\n description: 'Disable the persistent CLI session cache when set to true',\n defaultValue: 'false',\n },\n {\n key: ENV_KEYS.forceReauth,\n required: false,\n description: 'Ignore cached sessions and force a fresh login when set to true',\n defaultValue: 'false',\n },\n];\n\nexport type DoctorCheck = {\n key: string;\n description: string;\n required: boolean;\n source: ResolvedEnv['source'];\n present: boolean;\n valuePreview: string | null;\n};\n\nexport type DoctorReport = {\n ok: boolean;\n loadedDotEnv: boolean;\n dotEnvPath: string;\n dotEnvKeysLoaded: number;\n checks: DoctorCheck[];\n};\n\nexport type RuntimeEnv = {\n apiBaseUrl: string | null;\n apiKey: string | null;\n region: string;\n supabasePublishableKey: string | null;\n sessionFile: string | null;\n disableSessionCache: boolean;\n forceReauth: boolean;\n};\n\nfunction parseBooleanEnv(value: string | null): boolean {\n if (typeof value !== 'string') {\n return false;\n }\n\n switch (value.trim().toLowerCase()) {\n case '1':\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n}\n\nexport function resolveEnv(spec: EnvSpec, env: NodeJS.ProcessEnv): ResolvedEnv {\n const envValue = env[spec.key];\n if (envValue) {\n return {\n key: spec.key,\n source: 'env',\n value: envValue,\n present: true,\n };\n }\n\n if (spec.defaultValue !== undefined) {\n return {\n key: spec.key,\n source: 'default',\n value: spec.defaultValue,\n present: true,\n };\n }\n\n return {\n key: spec.key,\n source: 'missing',\n value: null,\n present: false,\n };\n}\n\nexport function readRuntimeEnv(env: NodeJS.ProcessEnv): RuntimeEnv {\n const apiBaseUrl = resolveEnv(ENV_SPECS[0], env).value;\n const apiKey = resolveEnv(ENV_SPECS[1], env).value;\n const region = resolveEnv(ENV_SPECS[2], env).value as string;\n const supabasePublishableKey = resolveEnv(ENV_SPECS[3], env).value;\n const sessionFile = resolveEnv(ENV_SPECS[4], env).value;\n const disableSessionCache = parseBooleanEnv(resolveEnv(ENV_SPECS[5], env).value);\n const forceReauth = parseBooleanEnv(resolveEnv(ENV_SPECS[6], env).value);\n\n return {\n apiBaseUrl,\n apiKey,\n region,\n supabasePublishableKey,\n sessionFile,\n disableSessionCache,\n forceReauth,\n };\n}\n\nexport function maskSecret(value: string | null): string | null {\n if (!value) {\n return value;\n }\n if (value.length <= 8) {\n return value;\n }\n return `${value.slice(0, 4)}...${value.slice(-4)}`;\n}\n\nexport function buildDoctorReport(\n env: NodeJS.ProcessEnv,\n dotEnvStatus: { loaded: boolean; path: string; count: number },\n): DoctorReport {\n const checks = ENV_SPECS.map((spec) => {\n const resolved = resolveEnv(spec, env);\n return {\n key: spec.key,\n description: spec.description,\n required: spec.required,\n source: resolved.source,\n present: resolved.present,\n valuePreview: maskSecret(resolved.value),\n };\n });\n\n return {\n ok: checks.every((check) => !check.required || check.present),\n loadedDotEnv: dotEnvStatus.loaded,\n dotEnvPath: dotEnvStatus.path,\n dotEnvKeysLoaded: dotEnvStatus.count,\n checks,\n };\n}\n\nexport const __testInternals = {\n parseBooleanEnv,\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class CliError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
exitCode;
|
|
4
|
+
details;
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'CliError';
|
|
8
|
+
this.code = options?.code ?? 'CLI_ERROR';
|
|
9
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
10
|
+
this.details = options?.details;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function toErrorPayload(error) {
|
|
14
|
+
if (error instanceof CliError) {
|
|
15
|
+
return {
|
|
16
|
+
error: {
|
|
17
|
+
code: error.code,
|
|
18
|
+
message: error.message,
|
|
19
|
+
...(error.details === undefined ? {} : { details: error.details }),
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return {
|
|
25
|
+
error: {
|
|
26
|
+
code: 'UNEXPECTED_ERROR',
|
|
27
|
+
message: error.message,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
error: {
|
|
33
|
+
code: 'UNKNOWN_THROWN_VALUE',
|
|
34
|
+
message: String(error),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAS;IACb,QAAQ,CAAS;IACjB,OAAO,CAAW;IAE3B,YAAY,OAAe,EAAE,OAAiE;QAC5F,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,WAAW,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;IAClC,CAAC;CACF;AAUD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACL,KAAK,EAAE;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;aACnE;SACF,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO;YACL,KAAK,EAAE;gBACL,IAAI,EAAE,kBAAkB;gBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;SACvB;KACF,CAAC;AACJ,CAAC","sourcesContent":["export class CliError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly details?: unknown;\n\n constructor(message: string, options?: { code?: string; exitCode?: number; details?: unknown }) {\n super(message);\n this.name = 'CliError';\n this.code = options?.code ?? 'CLI_ERROR';\n this.exitCode = options?.exitCode ?? 1;\n this.details = options?.details;\n }\n}\n\nexport type ErrorPayload = {\n error: {\n code: string;\n message: string;\n details?: unknown;\n };\n};\n\nexport function toErrorPayload(error: unknown): ErrorPayload {\n if (error instanceof CliError) {\n return {\n error: {\n code: error.code,\n message: error.message,\n ...(error.details === undefined ? {} : { details: error.details }),\n },\n };\n }\n\n if (error instanceof Error) {\n return {\n error: {\n code: 'UNEXPECTED_ERROR',\n message: error.message,\n },\n };\n }\n\n return {\n error: {\n code: 'UNKNOWN_THROWN_VALUE',\n message: String(error),\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
import { extractFlowRecord, isRecord, loadRowsFromFile, normalizeText, } from './flow-governance.js';
|
|
6
|
+
function assertInputFile(inputFile, requiredCode, missingCode) {
|
|
7
|
+
if (!inputFile) {
|
|
8
|
+
throw new CliError('Missing required input file value.', {
|
|
9
|
+
code: requiredCode,
|
|
10
|
+
exitCode: 2,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const resolved = path.resolve(inputFile);
|
|
14
|
+
if (!existsSync(resolved)) {
|
|
15
|
+
throw new CliError(`Input file not found: ${resolved}`, {
|
|
16
|
+
code: missingCode,
|
|
17
|
+
exitCode: 2,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
function assertInputFiles(inputFiles, requiredCode, missingCode) {
|
|
23
|
+
if (!inputFiles.length) {
|
|
24
|
+
throw new CliError('At least one input file is required.', {
|
|
25
|
+
code: requiredCode,
|
|
26
|
+
exitCode: 2,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return inputFiles.map((inputFile) => assertInputFile(inputFile, requiredCode, missingCode));
|
|
30
|
+
}
|
|
31
|
+
function assertOutDir(outDir) {
|
|
32
|
+
if (!outDir) {
|
|
33
|
+
throw new CliError('Missing required --out-dir value.', {
|
|
34
|
+
code: 'FLOW_BUILD_ALIAS_MAP_OUT_DIR_REQUIRED',
|
|
35
|
+
exitCode: 2,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return path.resolve(outDir);
|
|
39
|
+
}
|
|
40
|
+
function readJsonObjectFile(filePath, requiredCode, missingCode, invalidCode) {
|
|
41
|
+
const resolved = assertInputFile(filePath, requiredCode, missingCode);
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(readFileSync(resolved, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new CliError(`Expected JSON object file: ${resolved}`, {
|
|
48
|
+
code: invalidCode,
|
|
49
|
+
exitCode: 2,
|
|
50
|
+
details: String(error),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!isRecord(parsed)) {
|
|
54
|
+
throw new CliError(`Expected JSON object file: ${resolved}`, {
|
|
55
|
+
code: invalidCode,
|
|
56
|
+
exitCode: 2,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
function buildFlowIndex(rows) {
|
|
62
|
+
const byUuid = {};
|
|
63
|
+
const byUuidVersion = {};
|
|
64
|
+
const byName = {};
|
|
65
|
+
const records = [];
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
const record = extractFlowRecord(row);
|
|
68
|
+
records.push(record);
|
|
69
|
+
byUuid[record.id] ??= [];
|
|
70
|
+
byUuid[record.id].push(record);
|
|
71
|
+
byUuidVersion[`${record.id}@${record.version}`] = record;
|
|
72
|
+
const normalizedName = normalizeText(record.name);
|
|
73
|
+
byName[normalizedName] ??= [];
|
|
74
|
+
byName[normalizedName].push(record);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
records,
|
|
78
|
+
byUuid,
|
|
79
|
+
byUuidVersion,
|
|
80
|
+
byName,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function aliasLookup(aliasMap, flowUuid, flowVersion) {
|
|
84
|
+
const versionedKey = flowVersion ? `${flowUuid}@${flowVersion}` : null;
|
|
85
|
+
for (const key of [versionedKey, flowUuid]) {
|
|
86
|
+
if (!key) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const candidate = aliasMap[key];
|
|
90
|
+
if (isRecord(candidate)) {
|
|
91
|
+
return candidate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function candidateRef(record, includeType = false) {
|
|
97
|
+
return {
|
|
98
|
+
id: record.id,
|
|
99
|
+
version: record.version,
|
|
100
|
+
name: record.name,
|
|
101
|
+
...(includeType ? { flow_type: record.flowType } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function planAlias(oldRecord, newIndex, seedAliasMap) {
|
|
105
|
+
const base = {
|
|
106
|
+
old_flow_id: oldRecord.id,
|
|
107
|
+
old_flow_version: oldRecord.version,
|
|
108
|
+
old_flow_name: oldRecord.name,
|
|
109
|
+
old_flow_type: oldRecord.flowType,
|
|
110
|
+
decision: 'manual_review',
|
|
111
|
+
reason: 'no_deterministic_match',
|
|
112
|
+
};
|
|
113
|
+
const existingTarget = newIndex.byUuidVersion[`${oldRecord.id}@${oldRecord.version}`];
|
|
114
|
+
if (existingTarget) {
|
|
115
|
+
return {
|
|
116
|
+
...base,
|
|
117
|
+
decision: 'no_alias_needed',
|
|
118
|
+
reason: 'already_present_in_target',
|
|
119
|
+
target_flow_id: existingTarget.id,
|
|
120
|
+
target_flow_version: existingTarget.version,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const seededTarget = aliasLookup(seedAliasMap, oldRecord.id, oldRecord.version || null);
|
|
124
|
+
if (seededTarget) {
|
|
125
|
+
const targetRecord = newIndex.byUuidVersion[`${String(seededTarget.id ?? '')}@${String(seededTarget.version ?? '')}`];
|
|
126
|
+
if (targetRecord) {
|
|
127
|
+
return {
|
|
128
|
+
...base,
|
|
129
|
+
decision: 'alias_map_entry',
|
|
130
|
+
reason: 'seed_alias_map',
|
|
131
|
+
target_flow_id: targetRecord.id,
|
|
132
|
+
target_flow_version: targetRecord.version,
|
|
133
|
+
target_flow_name: targetRecord.name,
|
|
134
|
+
target_flow_type: targetRecord.flowType,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const sameUuidCandidates = newIndex.byUuid[oldRecord.id] ?? [];
|
|
139
|
+
if (sameUuidCandidates.length === 1) {
|
|
140
|
+
const candidate = sameUuidCandidates[0];
|
|
141
|
+
return {
|
|
142
|
+
...base,
|
|
143
|
+
decision: 'alias_map_entry',
|
|
144
|
+
reason: 'same_uuid_single_target_version',
|
|
145
|
+
target_flow_id: candidate.id,
|
|
146
|
+
target_flow_version: candidate.version,
|
|
147
|
+
target_flow_name: candidate.name,
|
|
148
|
+
target_flow_type: candidate.flowType,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (sameUuidCandidates.length > 1) {
|
|
152
|
+
return {
|
|
153
|
+
...base,
|
|
154
|
+
decision: 'manual_review',
|
|
155
|
+
reason: 'same_uuid_multiple_target_versions',
|
|
156
|
+
candidate_refs: sameUuidCandidates.map((candidate) => candidateRef(candidate)),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const byName = newIndex.byName[normalizeText(oldRecord.name)] ?? [];
|
|
160
|
+
const nameAndTypeCandidates = byName.filter((candidate) => candidate.flowType === oldRecord.flowType);
|
|
161
|
+
if (nameAndTypeCandidates.length === 1) {
|
|
162
|
+
const candidate = nameAndTypeCandidates[0];
|
|
163
|
+
return {
|
|
164
|
+
...base,
|
|
165
|
+
decision: 'alias_map_entry',
|
|
166
|
+
reason: 'unique_exact_name_and_type_match',
|
|
167
|
+
target_flow_id: candidate.id,
|
|
168
|
+
target_flow_version: candidate.version,
|
|
169
|
+
target_flow_name: candidate.name,
|
|
170
|
+
target_flow_type: candidate.flowType,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (nameAndTypeCandidates.length > 1) {
|
|
174
|
+
return {
|
|
175
|
+
...base,
|
|
176
|
+
decision: 'manual_review',
|
|
177
|
+
reason: 'ambiguous_name_and_type_match',
|
|
178
|
+
candidate_refs: nameAndTypeCandidates.map((candidate) => candidateRef(candidate)),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (byName.length > 0) {
|
|
182
|
+
return {
|
|
183
|
+
...base,
|
|
184
|
+
decision: 'manual_review',
|
|
185
|
+
reason: 'name_match_flow_type_mismatch',
|
|
186
|
+
candidate_refs: byName.map((candidate) => candidateRef(candidate, true)),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return base;
|
|
190
|
+
}
|
|
191
|
+
function buildDecisionCounts(actions) {
|
|
192
|
+
const counts = {};
|
|
193
|
+
actions.forEach((action) => {
|
|
194
|
+
counts[action.decision] = (counts[action.decision] ?? 0) + 1;
|
|
195
|
+
});
|
|
196
|
+
return counts;
|
|
197
|
+
}
|
|
198
|
+
function buildOutputFiles(outDir) {
|
|
199
|
+
return {
|
|
200
|
+
out_dir: outDir,
|
|
201
|
+
alias_plan: path.join(outDir, 'alias-plan.json'),
|
|
202
|
+
alias_plan_jsonl: path.join(outDir, 'alias-plan.jsonl'),
|
|
203
|
+
flow_alias_map: path.join(outDir, 'flow-alias-map.json'),
|
|
204
|
+
manual_review_queue: path.join(outDir, 'manual-review-queue.jsonl'),
|
|
205
|
+
summary: path.join(outDir, 'alias-summary.json'),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
export async function runFlowBuildAliasMap(options, deps = {}) {
|
|
209
|
+
const oldFlowFiles = assertInputFiles(options.oldFlowFiles ?? [], 'FLOW_BUILD_ALIAS_MAP_OLD_FLOW_FILES_REQUIRED', 'FLOW_BUILD_ALIAS_MAP_OLD_FLOW_FILE_NOT_FOUND');
|
|
210
|
+
const newFlowFiles = assertInputFiles(options.newFlowFiles ?? [], 'FLOW_BUILD_ALIAS_MAP_NEW_FLOW_FILES_REQUIRED', 'FLOW_BUILD_ALIAS_MAP_NEW_FLOW_FILE_NOT_FOUND');
|
|
211
|
+
const seedAliasMapFile = options.seedAliasMapFile
|
|
212
|
+
? assertInputFile(options.seedAliasMapFile, 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_REQUIRED', 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_NOT_FOUND')
|
|
213
|
+
: null;
|
|
214
|
+
const outDir = assertOutDir(options.outDir);
|
|
215
|
+
const now = deps.now ?? (() => new Date());
|
|
216
|
+
const files = buildOutputFiles(outDir);
|
|
217
|
+
const oldRows = oldFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
|
|
218
|
+
const newRows = newFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
|
|
219
|
+
const oldIndex = buildFlowIndex(oldRows);
|
|
220
|
+
const newIndex = buildFlowIndex(newRows);
|
|
221
|
+
const seedAliasMap = seedAliasMapFile
|
|
222
|
+
? readJsonObjectFile(seedAliasMapFile, 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_REQUIRED', 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_NOT_FOUND', 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_INVALID')
|
|
223
|
+
: {};
|
|
224
|
+
const aliasPlan = [];
|
|
225
|
+
const manualQueue = [];
|
|
226
|
+
const versionAliasMap = {};
|
|
227
|
+
const uuidTargets = {};
|
|
228
|
+
oldIndex.records.forEach((oldRecord) => {
|
|
229
|
+
const action = planAlias(oldRecord, newIndex, seedAliasMap);
|
|
230
|
+
aliasPlan.push(action);
|
|
231
|
+
if (action.decision === 'alias_map_entry' &&
|
|
232
|
+
action.target_flow_id &&
|
|
233
|
+
action.target_flow_version) {
|
|
234
|
+
const target = {
|
|
235
|
+
id: action.target_flow_id,
|
|
236
|
+
version: action.target_flow_version,
|
|
237
|
+
reason: action.reason,
|
|
238
|
+
};
|
|
239
|
+
versionAliasMap[`${oldRecord.id}@${oldRecord.version}`] = target;
|
|
240
|
+
uuidTargets[oldRecord.id] ??= new Set();
|
|
241
|
+
uuidTargets[oldRecord.id].add(`${action.target_flow_id}@${action.target_flow_version}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (action.decision === 'manual_review') {
|
|
245
|
+
manualQueue.push(action);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
const flowAliasMap = {
|
|
249
|
+
...versionAliasMap,
|
|
250
|
+
};
|
|
251
|
+
Object.entries(uuidTargets).forEach(([oldUuid, targets]) => {
|
|
252
|
+
if (targets.size !== 1) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const [targetId, targetVersion] = [...targets][0].split('@', 2);
|
|
256
|
+
flowAliasMap[oldUuid] = {
|
|
257
|
+
id: targetId,
|
|
258
|
+
version: targetVersion,
|
|
259
|
+
reason: 'all_versions_share_same_target',
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
const summary = {
|
|
263
|
+
old_flow_count: oldIndex.records.length,
|
|
264
|
+
new_flow_count: newIndex.records.length,
|
|
265
|
+
alias_entries_versioned: Object.keys(versionAliasMap).length,
|
|
266
|
+
alias_entries_uuid_only: Object.keys(flowAliasMap).length - Object.keys(versionAliasMap).length,
|
|
267
|
+
manual_review_count: manualQueue.length,
|
|
268
|
+
decision_counts: buildDecisionCounts(aliasPlan),
|
|
269
|
+
};
|
|
270
|
+
writeJsonArtifact(files.alias_plan, aliasPlan);
|
|
271
|
+
writeJsonLinesArtifact(files.alias_plan_jsonl, aliasPlan);
|
|
272
|
+
writeJsonArtifact(files.flow_alias_map, flowAliasMap);
|
|
273
|
+
writeJsonLinesArtifact(files.manual_review_queue, manualQueue);
|
|
274
|
+
writeJsonArtifact(files.summary, summary);
|
|
275
|
+
return {
|
|
276
|
+
schema_version: 1,
|
|
277
|
+
generated_at_utc: now().toISOString(),
|
|
278
|
+
status: 'completed_local_flow_build_alias_map',
|
|
279
|
+
old_flow_files: oldFlowFiles,
|
|
280
|
+
new_flow_files: newFlowFiles,
|
|
281
|
+
seed_alias_map_file: seedAliasMapFile,
|
|
282
|
+
out_dir: outDir,
|
|
283
|
+
summary,
|
|
284
|
+
files,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export const __testInternals = {
|
|
288
|
+
assertInputFile,
|
|
289
|
+
assertInputFiles,
|
|
290
|
+
assertOutDir,
|
|
291
|
+
readJsonObjectFile,
|
|
292
|
+
buildFlowIndex,
|
|
293
|
+
aliasLookup,
|
|
294
|
+
candidateRef,
|
|
295
|
+
planAlias,
|
|
296
|
+
buildDecisionCounts,
|
|
297
|
+
buildOutputFiles,
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=flow-build-alias-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-build-alias-map.js","sourceRoot":"","sources":["../../../src/lib/flow-build-alias-map.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';\nimport { CliError } from './errors.js';\nimport {\n extractFlowRecord,\n isRecord,\n loadRowsFromFile,\n normalizeText,\n type FlowRecord,\n type JsonRecord,\n} from './flow-governance.js';\n\ntype FlowIndex = {\n records: FlowRecord[];\n byUuid: Record<string, FlowRecord[]>;\n byUuidVersion: Record<string, FlowRecord>;\n byName: Record<string, FlowRecord[]>;\n};\n\nexport type FlowBuildAliasMapFiles = {\n out_dir: string;\n alias_plan: string;\n alias_plan_jsonl: string;\n flow_alias_map: string;\n manual_review_queue: string;\n summary: string;\n};\n\nexport type FlowBuildAliasMapSummary = {\n old_flow_count: number;\n new_flow_count: number;\n alias_entries_versioned: number;\n alias_entries_uuid_only: number;\n manual_review_count: number;\n decision_counts: Record<string, number>;\n};\n\nexport type FlowAliasPlanAction = {\n old_flow_id: string;\n old_flow_version: string;\n old_flow_name: string;\n old_flow_type: string;\n decision: 'no_alias_needed' | 'alias_map_entry' | 'manual_review';\n reason: string;\n target_flow_id?: string;\n target_flow_version?: string;\n target_flow_name?: string;\n target_flow_type?: string;\n candidate_refs?: Array<Record<string, string>>;\n};\n\nexport type FlowBuildAliasMapReport = {\n schema_version: 1;\n generated_at_utc: string;\n status: 'completed_local_flow_build_alias_map';\n old_flow_files: string[];\n new_flow_files: string[];\n seed_alias_map_file: string | null;\n out_dir: string;\n summary: FlowBuildAliasMapSummary;\n files: FlowBuildAliasMapFiles;\n};\n\nexport type RunFlowBuildAliasMapOptions = {\n oldFlowFiles: string[];\n newFlowFiles: string[];\n seedAliasMapFile?: string | null;\n outDir: string;\n};\n\ntype FlowBuildAliasMapDeps = {\n now?: () => Date;\n};\n\nfunction assertInputFile(inputFile: string, requiredCode: string, missingCode: string): string {\n if (!inputFile) {\n throw new CliError('Missing required input file value.', {\n code: requiredCode,\n exitCode: 2,\n });\n }\n\n const resolved = path.resolve(inputFile);\n if (!existsSync(resolved)) {\n throw new CliError(`Input file not found: ${resolved}`, {\n code: missingCode,\n exitCode: 2,\n });\n }\n\n return resolved;\n}\n\nfunction assertInputFiles(\n inputFiles: string[],\n requiredCode: string,\n missingCode: string,\n): string[] {\n if (!inputFiles.length) {\n throw new CliError('At least one input file is required.', {\n code: requiredCode,\n exitCode: 2,\n });\n }\n\n return inputFiles.map((inputFile) => assertInputFile(inputFile, requiredCode, missingCode));\n}\n\nfunction assertOutDir(outDir: string): string {\n if (!outDir) {\n throw new CliError('Missing required --out-dir value.', {\n code: 'FLOW_BUILD_ALIAS_MAP_OUT_DIR_REQUIRED',\n exitCode: 2,\n });\n }\n\n return path.resolve(outDir);\n}\n\nfunction readJsonObjectFile(\n filePath: string,\n requiredCode: string,\n missingCode: string,\n invalidCode: string,\n): JsonRecord {\n const resolved = assertInputFile(filePath, requiredCode, missingCode);\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(resolved, 'utf8'));\n } catch (error) {\n throw new CliError(`Expected JSON object file: ${resolved}`, {\n code: invalidCode,\n exitCode: 2,\n details: String(error),\n });\n }\n\n if (!isRecord(parsed)) {\n throw new CliError(`Expected JSON object file: ${resolved}`, {\n code: invalidCode,\n exitCode: 2,\n });\n }\n\n return parsed;\n}\n\nfunction buildFlowIndex(rows: JsonRecord[]): FlowIndex {\n const byUuid: Record<string, FlowRecord[]> = {};\n const byUuidVersion: Record<string, FlowRecord> = {};\n const byName: Record<string, FlowRecord[]> = {};\n const records: FlowRecord[] = [];\n\n for (const row of rows) {\n const record = extractFlowRecord(row);\n records.push(record);\n byUuid[record.id] ??= [];\n byUuid[record.id].push(record);\n byUuidVersion[`${record.id}@${record.version}`] = record;\n const normalizedName = normalizeText(record.name);\n byName[normalizedName] ??= [];\n byName[normalizedName].push(record);\n }\n\n return {\n records,\n byUuid,\n byUuidVersion,\n byName,\n };\n}\n\nfunction aliasLookup(\n aliasMap: JsonRecord,\n flowUuid: string,\n flowVersion: string | null,\n): JsonRecord | null {\n const versionedKey = flowVersion ? `${flowUuid}@${flowVersion}` : null;\n for (const key of [versionedKey, flowUuid]) {\n if (!key) {\n continue;\n }\n const candidate = aliasMap[key];\n if (isRecord(candidate)) {\n return candidate;\n }\n }\n return null;\n}\n\nfunction candidateRef(record: FlowRecord, includeType = false): Record<string, string> {\n return {\n id: record.id,\n version: record.version,\n name: record.name,\n ...(includeType ? { flow_type: record.flowType } : {}),\n };\n}\n\nfunction planAlias(\n oldRecord: FlowRecord,\n newIndex: FlowIndex,\n seedAliasMap: JsonRecord,\n): FlowAliasPlanAction {\n const base: FlowAliasPlanAction = {\n old_flow_id: oldRecord.id,\n old_flow_version: oldRecord.version,\n old_flow_name: oldRecord.name,\n old_flow_type: oldRecord.flowType,\n decision: 'manual_review',\n reason: 'no_deterministic_match',\n };\n\n const existingTarget = newIndex.byUuidVersion[`${oldRecord.id}@${oldRecord.version}`];\n if (existingTarget) {\n return {\n ...base,\n decision: 'no_alias_needed',\n reason: 'already_present_in_target',\n target_flow_id: existingTarget.id,\n target_flow_version: existingTarget.version,\n };\n }\n\n const seededTarget = aliasLookup(seedAliasMap, oldRecord.id, oldRecord.version || null);\n if (seededTarget) {\n const targetRecord =\n newIndex.byUuidVersion[\n `${String(seededTarget.id ?? '')}@${String(seededTarget.version ?? '')}`\n ];\n if (targetRecord) {\n return {\n ...base,\n decision: 'alias_map_entry',\n reason: 'seed_alias_map',\n target_flow_id: targetRecord.id,\n target_flow_version: targetRecord.version,\n target_flow_name: targetRecord.name,\n target_flow_type: targetRecord.flowType,\n };\n }\n }\n\n const sameUuidCandidates = newIndex.byUuid[oldRecord.id] ?? [];\n if (sameUuidCandidates.length === 1) {\n const candidate = sameUuidCandidates[0];\n return {\n ...base,\n decision: 'alias_map_entry',\n reason: 'same_uuid_single_target_version',\n target_flow_id: candidate.id,\n target_flow_version: candidate.version,\n target_flow_name: candidate.name,\n target_flow_type: candidate.flowType,\n };\n }\n\n if (sameUuidCandidates.length > 1) {\n return {\n ...base,\n decision: 'manual_review',\n reason: 'same_uuid_multiple_target_versions',\n candidate_refs: sameUuidCandidates.map((candidate) => candidateRef(candidate)),\n };\n }\n\n const byName = newIndex.byName[normalizeText(oldRecord.name)] ?? [];\n const nameAndTypeCandidates = byName.filter(\n (candidate) => candidate.flowType === oldRecord.flowType,\n );\n if (nameAndTypeCandidates.length === 1) {\n const candidate = nameAndTypeCandidates[0];\n return {\n ...base,\n decision: 'alias_map_entry',\n reason: 'unique_exact_name_and_type_match',\n target_flow_id: candidate.id,\n target_flow_version: candidate.version,\n target_flow_name: candidate.name,\n target_flow_type: candidate.flowType,\n };\n }\n\n if (nameAndTypeCandidates.length > 1) {\n return {\n ...base,\n decision: 'manual_review',\n reason: 'ambiguous_name_and_type_match',\n candidate_refs: nameAndTypeCandidates.map((candidate) => candidateRef(candidate)),\n };\n }\n\n if (byName.length > 0) {\n return {\n ...base,\n decision: 'manual_review',\n reason: 'name_match_flow_type_mismatch',\n candidate_refs: byName.map((candidate) => candidateRef(candidate, true)),\n };\n }\n\n return base;\n}\n\nfunction buildDecisionCounts(actions: FlowAliasPlanAction[]): Record<string, number> {\n const counts: Record<string, number> = {};\n actions.forEach((action) => {\n counts[action.decision] = (counts[action.decision] ?? 0) + 1;\n });\n return counts;\n}\n\nfunction buildOutputFiles(outDir: string): FlowBuildAliasMapFiles {\n return {\n out_dir: outDir,\n alias_plan: path.join(outDir, 'alias-plan.json'),\n alias_plan_jsonl: path.join(outDir, 'alias-plan.jsonl'),\n flow_alias_map: path.join(outDir, 'flow-alias-map.json'),\n manual_review_queue: path.join(outDir, 'manual-review-queue.jsonl'),\n summary: path.join(outDir, 'alias-summary.json'),\n };\n}\n\nexport async function runFlowBuildAliasMap(\n options: RunFlowBuildAliasMapOptions,\n deps: FlowBuildAliasMapDeps = {},\n): Promise<FlowBuildAliasMapReport> {\n const oldFlowFiles = assertInputFiles(\n options.oldFlowFiles ?? [],\n 'FLOW_BUILD_ALIAS_MAP_OLD_FLOW_FILES_REQUIRED',\n 'FLOW_BUILD_ALIAS_MAP_OLD_FLOW_FILE_NOT_FOUND',\n );\n const newFlowFiles = assertInputFiles(\n options.newFlowFiles ?? [],\n 'FLOW_BUILD_ALIAS_MAP_NEW_FLOW_FILES_REQUIRED',\n 'FLOW_BUILD_ALIAS_MAP_NEW_FLOW_FILE_NOT_FOUND',\n );\n const seedAliasMapFile = options.seedAliasMapFile\n ? assertInputFile(\n options.seedAliasMapFile,\n 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_REQUIRED',\n 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_NOT_FOUND',\n )\n : null;\n const outDir = assertOutDir(options.outDir);\n const now = deps.now ?? (() => new Date());\n const files = buildOutputFiles(outDir);\n\n const oldRows = oldFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));\n const newRows = newFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));\n const oldIndex = buildFlowIndex(oldRows);\n const newIndex = buildFlowIndex(newRows);\n const seedAliasMap = seedAliasMapFile\n ? readJsonObjectFile(\n seedAliasMapFile,\n 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_REQUIRED',\n 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_NOT_FOUND',\n 'FLOW_BUILD_ALIAS_MAP_SEED_ALIAS_MAP_INVALID',\n )\n : {};\n\n const aliasPlan: FlowAliasPlanAction[] = [];\n const manualQueue: FlowAliasPlanAction[] = [];\n const versionAliasMap: Record<string, JsonRecord> = {};\n const uuidTargets: Record<string, Set<string>> = {};\n\n oldIndex.records.forEach((oldRecord) => {\n const action = planAlias(oldRecord, newIndex, seedAliasMap);\n aliasPlan.push(action);\n\n if (\n action.decision === 'alias_map_entry' &&\n action.target_flow_id &&\n action.target_flow_version\n ) {\n const target = {\n id: action.target_flow_id,\n version: action.target_flow_version,\n reason: action.reason,\n } satisfies JsonRecord;\n versionAliasMap[`${oldRecord.id}@${oldRecord.version}`] = target;\n uuidTargets[oldRecord.id] ??= new Set<string>();\n uuidTargets[oldRecord.id].add(`${action.target_flow_id}@${action.target_flow_version}`);\n return;\n }\n\n if (action.decision === 'manual_review') {\n manualQueue.push(action);\n }\n });\n\n const flowAliasMap: Record<string, JsonRecord> = {\n ...versionAliasMap,\n };\n Object.entries(uuidTargets).forEach(([oldUuid, targets]) => {\n if (targets.size !== 1) {\n return;\n }\n const [targetId, targetVersion] = [...targets][0].split('@', 2);\n flowAliasMap[oldUuid] = {\n id: targetId,\n version: targetVersion,\n reason: 'all_versions_share_same_target',\n };\n });\n\n const summary: FlowBuildAliasMapSummary = {\n old_flow_count: oldIndex.records.length,\n new_flow_count: newIndex.records.length,\n alias_entries_versioned: Object.keys(versionAliasMap).length,\n alias_entries_uuid_only: Object.keys(flowAliasMap).length - Object.keys(versionAliasMap).length,\n manual_review_count: manualQueue.length,\n decision_counts: buildDecisionCounts(aliasPlan),\n };\n\n writeJsonArtifact(files.alias_plan, aliasPlan);\n writeJsonLinesArtifact(files.alias_plan_jsonl, aliasPlan);\n writeJsonArtifact(files.flow_alias_map, flowAliasMap);\n writeJsonLinesArtifact(files.manual_review_queue, manualQueue);\n writeJsonArtifact(files.summary, summary);\n\n return {\n schema_version: 1,\n generated_at_utc: now().toISOString(),\n status: 'completed_local_flow_build_alias_map',\n old_flow_files: oldFlowFiles,\n new_flow_files: newFlowFiles,\n seed_alias_map_file: seedAliasMapFile,\n out_dir: outDir,\n summary,\n files,\n };\n}\n\nexport const __testInternals = {\n assertInputFile,\n assertInputFiles,\n assertOutDir,\n readJsonObjectFile,\n buildFlowIndex,\n aliasLookup,\n candidateRef,\n planAlias,\n buildDecisionCounts,\n buildOutputFiles,\n};\n"]}
|