@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,203 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolveEnv } from './env.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
export const UNSTRUCTURED_ENV_KEYS = {
|
|
6
|
+
apiBaseUrl: 'TIANGONG_LCA_UNSTRUCTURED_API_BASE_URL',
|
|
7
|
+
apiKey: 'TIANGONG_LCA_UNSTRUCTURED_API_KEY',
|
|
8
|
+
provider: 'TIANGONG_LCA_UNSTRUCTURED_PROVIDER',
|
|
9
|
+
model: 'TIANGONG_LCA_UNSTRUCTURED_MODEL',
|
|
10
|
+
chunkType: 'TIANGONG_LCA_UNSTRUCTURED_CHUNK_TYPE',
|
|
11
|
+
returnTxt: 'TIANGONG_LCA_UNSTRUCTURED_RETURN_TXT',
|
|
12
|
+
};
|
|
13
|
+
export const UNSTRUCTURED_ENV_SPECS = [
|
|
14
|
+
{
|
|
15
|
+
key: UNSTRUCTURED_ENV_KEYS.apiBaseUrl,
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'TianGong unstructured service base URL',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: UNSTRUCTURED_ENV_KEYS.apiKey,
|
|
21
|
+
required: true,
|
|
22
|
+
description: 'TianGong unstructured service API key',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: UNSTRUCTURED_ENV_KEYS.provider,
|
|
26
|
+
required: false,
|
|
27
|
+
description: 'Preferred unstructured vision provider',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: UNSTRUCTURED_ENV_KEYS.model,
|
|
31
|
+
required: false,
|
|
32
|
+
description: 'Preferred unstructured vision model',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: UNSTRUCTURED_ENV_KEYS.chunkType,
|
|
36
|
+
required: false,
|
|
37
|
+
description: 'Default chunk_type query flag for unstructured parsing',
|
|
38
|
+
defaultValue: 'false',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: UNSTRUCTURED_ENV_KEYS.returnTxt,
|
|
42
|
+
required: false,
|
|
43
|
+
description: 'Default return_txt query flag for unstructured parsing',
|
|
44
|
+
defaultValue: 'true',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
function parseBooleanFlag(value, fallback) {
|
|
48
|
+
const normalized = value.trim().toLowerCase();
|
|
49
|
+
if (!normalized) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return fallback;
|
|
59
|
+
}
|
|
60
|
+
function requireUnstructuredRuntime(env) {
|
|
61
|
+
if (!env.apiBaseUrl) {
|
|
62
|
+
throw new CliError(`Missing unstructured API base URL. Set ${UNSTRUCTURED_ENV_KEYS.apiBaseUrl}.`, {
|
|
63
|
+
code: 'UNSTRUCTURED_API_BASE_URL_REQUIRED',
|
|
64
|
+
exitCode: 2,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (!env.apiKey) {
|
|
68
|
+
throw new CliError(`Missing unstructured API key. Set ${UNSTRUCTURED_ENV_KEYS.apiKey}.`, {
|
|
69
|
+
code: 'UNSTRUCTURED_API_KEY_REQUIRED',
|
|
70
|
+
exitCode: 2,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
apiBaseUrl: env.apiBaseUrl,
|
|
75
|
+
apiKey: env.apiKey,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function buildUnstructuredUrl(apiBaseUrl, options) {
|
|
79
|
+
const url = new URL(`${apiBaseUrl.replace(/\/+$/u, '')}/mineru_with_images`);
|
|
80
|
+
url.searchParams.set('chunk_type', options.chunkType ? 'true' : 'false');
|
|
81
|
+
url.searchParams.set('return_txt', options.returnTxt ? 'true' : 'false');
|
|
82
|
+
return url.toString();
|
|
83
|
+
}
|
|
84
|
+
function normalizeOptionalText(value) {
|
|
85
|
+
if (typeof value !== 'string') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const normalized = value.trim();
|
|
89
|
+
return normalized || null;
|
|
90
|
+
}
|
|
91
|
+
function guessContentType(filePath) {
|
|
92
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
93
|
+
switch (extension) {
|
|
94
|
+
case '.pdf':
|
|
95
|
+
return 'application/pdf';
|
|
96
|
+
case '.png':
|
|
97
|
+
return 'image/png';
|
|
98
|
+
case '.jpg':
|
|
99
|
+
case '.jpeg':
|
|
100
|
+
return 'image/jpeg';
|
|
101
|
+
case '.md':
|
|
102
|
+
case '.markdown':
|
|
103
|
+
return 'text/markdown';
|
|
104
|
+
default:
|
|
105
|
+
return 'application/octet-stream';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function parseJsonResponse(response) {
|
|
109
|
+
const rawText = await response.text();
|
|
110
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
111
|
+
if (!contentType.toLowerCase().includes('application/json')) {
|
|
112
|
+
throw new CliError('Unstructured service response was not valid JSON.', {
|
|
113
|
+
code: 'UNSTRUCTURED_INVALID_JSON',
|
|
114
|
+
exitCode: 1,
|
|
115
|
+
details: rawText,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(rawText);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw new CliError('Unstructured service response was not valid JSON.', {
|
|
123
|
+
code: 'UNSTRUCTURED_INVALID_JSON',
|
|
124
|
+
exitCode: 1,
|
|
125
|
+
details: String(error),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function readUnstructuredRuntimeEnv(env) {
|
|
130
|
+
const chunkTypeValue = resolveEnv(UNSTRUCTURED_ENV_SPECS[4], env).value;
|
|
131
|
+
const returnTxtValue = resolveEnv(UNSTRUCTURED_ENV_SPECS[5], env).value;
|
|
132
|
+
return {
|
|
133
|
+
apiBaseUrl: resolveEnv(UNSTRUCTURED_ENV_SPECS[0], env).value,
|
|
134
|
+
apiKey: resolveEnv(UNSTRUCTURED_ENV_SPECS[1], env).value,
|
|
135
|
+
provider: resolveEnv(UNSTRUCTURED_ENV_SPECS[2], env).value,
|
|
136
|
+
model: resolveEnv(UNSTRUCTURED_ENV_SPECS[3], env).value,
|
|
137
|
+
chunkType: parseBooleanFlag(chunkTypeValue, false),
|
|
138
|
+
returnTxt: parseBooleanFlag(returnTxtValue, true),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export async function parseUnstructuredDocument(options) {
|
|
142
|
+
const runtime = requireUnstructuredRuntime(options.env);
|
|
143
|
+
const filePath = normalizeOptionalText(options.filePath);
|
|
144
|
+
if (!filePath) {
|
|
145
|
+
throw new CliError('Missing unstructured input file path.', {
|
|
146
|
+
code: 'UNSTRUCTURED_INPUT_REQUIRED',
|
|
147
|
+
exitCode: 2,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const fileName = path.basename(filePath);
|
|
151
|
+
let fileContents;
|
|
152
|
+
try {
|
|
153
|
+
fileContents = readFileSync(filePath);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new CliError(`Unstructured input file not found: ${filePath}`, {
|
|
157
|
+
code: 'UNSTRUCTURED_INPUT_NOT_FOUND',
|
|
158
|
+
exitCode: 2,
|
|
159
|
+
details: String(error),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const chunkType = options.chunkType ?? options.env.chunkType;
|
|
163
|
+
const returnTxt = options.returnTxt ?? options.env.returnTxt;
|
|
164
|
+
const provider = normalizeOptionalText(options.provider) ?? options.env.provider;
|
|
165
|
+
const model = normalizeOptionalText(options.model) ?? options.env.model;
|
|
166
|
+
const prompt = normalizeOptionalText(options.prompt);
|
|
167
|
+
const url = buildUnstructuredUrl(runtime.apiBaseUrl, {
|
|
168
|
+
chunkType,
|
|
169
|
+
returnTxt,
|
|
170
|
+
});
|
|
171
|
+
const body = new FormData();
|
|
172
|
+
const fileBytes = new Uint8Array(fileContents);
|
|
173
|
+
body.set('file', new File([fileBytes], fileName, {
|
|
174
|
+
type: guessContentType(filePath),
|
|
175
|
+
}));
|
|
176
|
+
if (provider) {
|
|
177
|
+
body.set('provider', provider);
|
|
178
|
+
}
|
|
179
|
+
if (model) {
|
|
180
|
+
body.set('model', model);
|
|
181
|
+
}
|
|
182
|
+
if (prompt) {
|
|
183
|
+
body.set('prompt', prompt);
|
|
184
|
+
}
|
|
185
|
+
const response = await options.fetchImpl(url, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: {
|
|
188
|
+
Authorization: `Bearer ${runtime.apiKey}`,
|
|
189
|
+
},
|
|
190
|
+
body,
|
|
191
|
+
signal: AbortSignal.timeout(options.timeoutMs),
|
|
192
|
+
});
|
|
193
|
+
const payload = await parseJsonResponse(response);
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
throw new CliError(`Unstructured request failed with HTTP ${response.status}.`, {
|
|
196
|
+
code: 'UNSTRUCTURED_REQUEST_FAILED',
|
|
197
|
+
exitCode: 1,
|
|
198
|
+
details: payload,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return payload;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=unstructured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unstructured.js","sourceRoot":"","sources":["../../../src/lib/unstructured.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { readFileSync } from 'node:fs';\nimport type { EnvSpec } from './env.js';\nimport { resolveEnv } from './env.js';\nimport { CliError } from './errors.js';\nimport type { FetchLike } from './http.js';\n\nexport const UNSTRUCTURED_ENV_KEYS = {\n apiBaseUrl: 'TIANGONG_LCA_UNSTRUCTURED_API_BASE_URL',\n apiKey: 'TIANGONG_LCA_UNSTRUCTURED_API_KEY',\n provider: 'TIANGONG_LCA_UNSTRUCTURED_PROVIDER',\n model: 'TIANGONG_LCA_UNSTRUCTURED_MODEL',\n chunkType: 'TIANGONG_LCA_UNSTRUCTURED_CHUNK_TYPE',\n returnTxt: 'TIANGONG_LCA_UNSTRUCTURED_RETURN_TXT',\n} as const;\n\nexport const UNSTRUCTURED_ENV_SPECS: EnvSpec[] = [\n {\n key: UNSTRUCTURED_ENV_KEYS.apiBaseUrl,\n required: true,\n description: 'TianGong unstructured service base URL',\n },\n {\n key: UNSTRUCTURED_ENV_KEYS.apiKey,\n required: true,\n description: 'TianGong unstructured service API key',\n },\n {\n key: UNSTRUCTURED_ENV_KEYS.provider,\n required: false,\n description: 'Preferred unstructured vision provider',\n },\n {\n key: UNSTRUCTURED_ENV_KEYS.model,\n required: false,\n description: 'Preferred unstructured vision model',\n },\n {\n key: UNSTRUCTURED_ENV_KEYS.chunkType,\n required: false,\n description: 'Default chunk_type query flag for unstructured parsing',\n defaultValue: 'false',\n },\n {\n key: UNSTRUCTURED_ENV_KEYS.returnTxt,\n required: false,\n description: 'Default return_txt query flag for unstructured parsing',\n defaultValue: 'true',\n },\n] as const;\n\nexport type UnstructuredRuntimeEnv = {\n apiBaseUrl: string | null;\n apiKey: string | null;\n provider: string | null;\n model: string | null;\n chunkType: boolean;\n returnTxt: boolean;\n};\n\nexport type ParseUnstructuredDocumentOptions = {\n env: UnstructuredRuntimeEnv;\n filePath: string;\n prompt?: string | null;\n provider?: string | null;\n model?: string | null;\n chunkType?: boolean;\n returnTxt?: boolean;\n timeoutMs: number;\n fetchImpl: FetchLike;\n};\n\nfunction parseBooleanFlag(value: string, fallback: boolean): boolean {\n const normalized = value.trim().toLowerCase();\n if (!normalized) {\n return fallback;\n }\n if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') {\n return true;\n }\n if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') {\n return false;\n }\n\n return fallback;\n}\n\nfunction requireUnstructuredRuntime(env: UnstructuredRuntimeEnv): {\n apiBaseUrl: string;\n apiKey: string;\n} {\n if (!env.apiBaseUrl) {\n throw new CliError(\n `Missing unstructured API base URL. Set ${UNSTRUCTURED_ENV_KEYS.apiBaseUrl}.`,\n {\n code: 'UNSTRUCTURED_API_BASE_URL_REQUIRED',\n exitCode: 2,\n },\n );\n }\n\n if (!env.apiKey) {\n throw new CliError(`Missing unstructured API key. Set ${UNSTRUCTURED_ENV_KEYS.apiKey}.`, {\n code: 'UNSTRUCTURED_API_KEY_REQUIRED',\n exitCode: 2,\n });\n }\n\n return {\n apiBaseUrl: env.apiBaseUrl,\n apiKey: env.apiKey,\n };\n}\n\nfunction buildUnstructuredUrl(\n apiBaseUrl: string,\n options: {\n chunkType: boolean;\n returnTxt: boolean;\n },\n): string {\n const url = new URL(`${apiBaseUrl.replace(/\\/+$/u, '')}/mineru_with_images`);\n url.searchParams.set('chunk_type', options.chunkType ? 'true' : 'false');\n url.searchParams.set('return_txt', options.returnTxt ? 'true' : 'false');\n return url.toString();\n}\n\nfunction normalizeOptionalText(value: string | null | undefined): string | null {\n if (typeof value !== 'string') {\n return null;\n }\n\n const normalized = value.trim();\n return normalized || null;\n}\n\nfunction guessContentType(filePath: string): string {\n const extension = path.extname(filePath).toLowerCase();\n switch (extension) {\n case '.pdf':\n return 'application/pdf';\n case '.png':\n return 'image/png';\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg';\n case '.md':\n case '.markdown':\n return 'text/markdown';\n default:\n return 'application/octet-stream';\n }\n}\n\nasync function parseJsonResponse(response: Awaited<ReturnType<FetchLike>>): Promise<unknown> {\n const rawText = await response.text();\n const contentType = response.headers.get('content-type') ?? '';\n\n if (!contentType.toLowerCase().includes('application/json')) {\n throw new CliError('Unstructured service response was not valid JSON.', {\n code: 'UNSTRUCTURED_INVALID_JSON',\n exitCode: 1,\n details: rawText,\n });\n }\n\n try {\n return JSON.parse(rawText);\n } catch (error) {\n throw new CliError('Unstructured service response was not valid JSON.', {\n code: 'UNSTRUCTURED_INVALID_JSON',\n exitCode: 1,\n details: String(error),\n });\n }\n}\n\nexport function readUnstructuredRuntimeEnv(env: NodeJS.ProcessEnv): UnstructuredRuntimeEnv {\n const chunkTypeValue = resolveEnv(UNSTRUCTURED_ENV_SPECS[4], env).value as string;\n const returnTxtValue = resolveEnv(UNSTRUCTURED_ENV_SPECS[5], env).value as string;\n\n return {\n apiBaseUrl: resolveEnv(UNSTRUCTURED_ENV_SPECS[0], env).value,\n apiKey: resolveEnv(UNSTRUCTURED_ENV_SPECS[1], env).value,\n provider: resolveEnv(UNSTRUCTURED_ENV_SPECS[2], env).value,\n model: resolveEnv(UNSTRUCTURED_ENV_SPECS[3], env).value,\n chunkType: parseBooleanFlag(chunkTypeValue, false),\n returnTxt: parseBooleanFlag(returnTxtValue, true),\n };\n}\n\nexport async function parseUnstructuredDocument(\n options: ParseUnstructuredDocumentOptions,\n): Promise<unknown> {\n const runtime = requireUnstructuredRuntime(options.env);\n const filePath = normalizeOptionalText(options.filePath);\n if (!filePath) {\n throw new CliError('Missing unstructured input file path.', {\n code: 'UNSTRUCTURED_INPUT_REQUIRED',\n exitCode: 2,\n });\n }\n\n const fileName = path.basename(filePath);\n let fileContents: Uint8Array;\n try {\n fileContents = readFileSync(filePath);\n } catch (error) {\n throw new CliError(`Unstructured input file not found: ${filePath}`, {\n code: 'UNSTRUCTURED_INPUT_NOT_FOUND',\n exitCode: 2,\n details: String(error),\n });\n }\n\n const chunkType = options.chunkType ?? options.env.chunkType;\n const returnTxt = options.returnTxt ?? options.env.returnTxt;\n const provider = normalizeOptionalText(options.provider) ?? options.env.provider;\n const model = normalizeOptionalText(options.model) ?? options.env.model;\n const prompt = normalizeOptionalText(options.prompt);\n const url = buildUnstructuredUrl(runtime.apiBaseUrl, {\n chunkType,\n returnTxt,\n });\n\n const body = new FormData();\n const fileBytes = new Uint8Array(fileContents);\n body.set(\n 'file',\n new File([fileBytes], fileName, {\n type: guessContentType(filePath),\n }),\n );\n if (provider) {\n body.set('provider', provider);\n }\n if (model) {\n body.set('model', model);\n }\n if (prompt) {\n body.set('prompt', prompt);\n }\n\n const response = await options.fetchImpl(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${runtime.apiKey}`,\n },\n body,\n signal: AbortSignal.timeout(options.timeoutMs),\n });\n\n const payload = await parseJsonResponse(response);\n if (!response.ok) {\n throw new CliError(`Unstructured request failed with HTTP ${response.status}.`, {\n code: 'UNSTRUCTURED_REQUEST_FAILED',\n exitCode: 1,\n details: payload,\n });\n }\n\n return payload;\n}\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
function normalizeToken(value) {
|
|
4
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
5
|
+
}
|
|
6
|
+
function normalizeCredentialValue(value) {
|
|
7
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
export function decodeUserApiKey(apiKey) {
|
|
13
|
+
const normalizedApiKey = normalizeToken(apiKey);
|
|
14
|
+
if (!normalizedApiKey) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const jsonText = Buffer.from(normalizedApiKey, 'base64').toString('utf8');
|
|
19
|
+
const parsed = JSON.parse(jsonText);
|
|
20
|
+
if (!isRecord(parsed)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const email = normalizeCredentialValue(parsed.email);
|
|
24
|
+
const password = normalizeCredentialValue(parsed.password);
|
|
25
|
+
if (!email || !password) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
email,
|
|
30
|
+
password,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function requireUserApiKeyCredentials(apiKey) {
|
|
38
|
+
const credentials = decodeUserApiKey(apiKey);
|
|
39
|
+
if (!credentials) {
|
|
40
|
+
throw new CliError('TIANGONG_LCA_API_KEY is invalid. Generate a new user API key from the TianGong account page.', {
|
|
41
|
+
code: 'USER_API_KEY_INVALID',
|
|
42
|
+
exitCode: 2,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return credentials;
|
|
46
|
+
}
|
|
47
|
+
export function fingerprintSecret(value) {
|
|
48
|
+
const normalized = normalizeToken(value);
|
|
49
|
+
if (!normalized) {
|
|
50
|
+
throw new CliError('Cannot fingerprint an empty secret value.', {
|
|
51
|
+
code: 'SECRET_FINGERPRINT_VALUE_REQUIRED',
|
|
52
|
+
exitCode: 2,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return `sha256:${createHash('sha256').update(normalized).digest('hex')}`;
|
|
56
|
+
}
|
|
57
|
+
export function fingerprintUserApiKey(apiKey) {
|
|
58
|
+
return fingerprintSecret(apiKey);
|
|
59
|
+
}
|
|
60
|
+
export function redactEmail(email) {
|
|
61
|
+
const normalized = normalizeCredentialValue(email);
|
|
62
|
+
const [localPart, domainPart] = normalized.split('@');
|
|
63
|
+
if (!localPart || !domainPart) {
|
|
64
|
+
return '****';
|
|
65
|
+
}
|
|
66
|
+
if (localPart.length <= 2) {
|
|
67
|
+
return `****@${domainPart}`;
|
|
68
|
+
}
|
|
69
|
+
return `${localPart.slice(0, 2)}****@${domainPart}`;
|
|
70
|
+
}
|
|
71
|
+
export const __testInternals = {
|
|
72
|
+
normalizeCredentialValue,
|
|
73
|
+
normalizeToken,
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=user-api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-api-key.js","sourceRoot":"","sources":["../../../src/lib/user-api-key.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { CliError } from './errors.js';\n\nexport type UserApiKeyCredentials = {\n email: string;\n password: string;\n};\n\nfunction normalizeToken(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction normalizeCredentialValue(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function decodeUserApiKey(apiKey: string): UserApiKeyCredentials | null {\n const normalizedApiKey = normalizeToken(apiKey);\n if (!normalizedApiKey) {\n return null;\n }\n\n try {\n const jsonText = Buffer.from(normalizedApiKey, 'base64').toString('utf8');\n const parsed = JSON.parse(jsonText);\n if (!isRecord(parsed)) {\n return null;\n }\n\n const email = normalizeCredentialValue(parsed.email);\n const password = normalizeCredentialValue(parsed.password);\n if (!email || !password) {\n return null;\n }\n\n return {\n email,\n password,\n };\n } catch {\n return null;\n }\n}\n\nexport function requireUserApiKeyCredentials(apiKey: string): UserApiKeyCredentials {\n const credentials = decodeUserApiKey(apiKey);\n if (!credentials) {\n throw new CliError(\n 'TIANGONG_LCA_API_KEY is invalid. Generate a new user API key from the TianGong account page.',\n {\n code: 'USER_API_KEY_INVALID',\n exitCode: 2,\n },\n );\n }\n\n return credentials;\n}\n\nexport function fingerprintSecret(value: string): string {\n const normalized = normalizeToken(value);\n if (!normalized) {\n throw new CliError('Cannot fingerprint an empty secret value.', {\n code: 'SECRET_FINGERPRINT_VALUE_REQUIRED',\n exitCode: 2,\n });\n }\n\n return `sha256:${createHash('sha256').update(normalized).digest('hex')}`;\n}\n\nexport function fingerprintUserApiKey(apiKey: string): string {\n return fingerprintSecret(apiKey);\n}\n\nexport function redactEmail(email: string): string {\n const normalized = normalizeCredentialValue(email);\n const [localPart, domainPart] = normalized.split('@');\n if (!localPart || !domainPart) {\n return '****';\n }\n\n if (localPart.length <= 2) {\n return `****@${domainPart}`;\n }\n\n return `${localPart.slice(0, 2)}****@${domainPart}`;\n}\n\nexport const __testInternals = {\n normalizeCredentialValue,\n normalizeToken,\n};\n"]}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
import { writeJsonArtifact } from './artifacts.js';
|
|
6
|
+
import { createTidasSdkPackageValidator } from './tidas-sdk-package-validator.js';
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function summarizeIssues(issues) {
|
|
11
|
+
const errorCount = issues.filter((issue) => issue.severity === 'error').length;
|
|
12
|
+
const warningCount = issues.filter((issue) => issue.severity === 'warning').length;
|
|
13
|
+
const infoCount = issues.filter((issue) => issue.severity === 'info').length;
|
|
14
|
+
return {
|
|
15
|
+
issue_count: issues.length,
|
|
16
|
+
error_count: errorCount,
|
|
17
|
+
warning_count: warningCount,
|
|
18
|
+
info_count: infoCount,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normalizeIssue(raw) {
|
|
22
|
+
if (!isRecord(raw)) {
|
|
23
|
+
return {
|
|
24
|
+
issue_code: 'validation_issue',
|
|
25
|
+
severity: 'error',
|
|
26
|
+
category: 'unknown',
|
|
27
|
+
file_path: '<unknown>',
|
|
28
|
+
message: String(raw),
|
|
29
|
+
location: '<root>',
|
|
30
|
+
context: {},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const severity = raw.severity === 'warning' || raw.severity === 'info' || raw.severity === 'error'
|
|
34
|
+
? raw.severity
|
|
35
|
+
: 'error';
|
|
36
|
+
return {
|
|
37
|
+
issue_code: typeof raw.issue_code === 'string' ? raw.issue_code : 'validation_issue',
|
|
38
|
+
severity,
|
|
39
|
+
category: typeof raw.category === 'string' ? raw.category : 'unknown',
|
|
40
|
+
file_path: typeof raw.file_path === 'string' ? raw.file_path : '<unknown>',
|
|
41
|
+
message: typeof raw.message === 'string' ? raw.message : JSON.stringify(raw),
|
|
42
|
+
location: typeof raw.location === 'string' ? raw.location : '<root>',
|
|
43
|
+
context: isRecord(raw.context) ? raw.context : {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function normalizeCategoryReport(raw) {
|
|
47
|
+
const record = isRecord(raw) ? raw : {};
|
|
48
|
+
const issues = Array.isArray(record.issues) ? record.issues.map(normalizeIssue) : [];
|
|
49
|
+
return {
|
|
50
|
+
category: typeof record.category === 'string' ? record.category : 'unknown',
|
|
51
|
+
ok: issues.length === 0,
|
|
52
|
+
summary: summarizeIssues(issues),
|
|
53
|
+
issues,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizePackageReport(raw, inputDir) {
|
|
57
|
+
const record = isRecord(raw) ? raw : {};
|
|
58
|
+
const categories = Array.isArray(record.categories)
|
|
59
|
+
? record.categories.map(normalizeCategoryReport)
|
|
60
|
+
: [];
|
|
61
|
+
const issues = categories.flatMap((category) => category.issues);
|
|
62
|
+
return {
|
|
63
|
+
input_dir: typeof record.input_dir === 'string' ? record.input_dir : inputDir,
|
|
64
|
+
ok: issues.length === 0,
|
|
65
|
+
summary: {
|
|
66
|
+
category_count: categories.length,
|
|
67
|
+
...summarizeIssues(issues),
|
|
68
|
+
},
|
|
69
|
+
categories,
|
|
70
|
+
issues,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function resolveRepoRootFrom(startDir) {
|
|
74
|
+
let currentDir = startDir;
|
|
75
|
+
while (true) {
|
|
76
|
+
if (existsSync(path.join(currentDir, 'package.json'))) {
|
|
77
|
+
return currentDir;
|
|
78
|
+
}
|
|
79
|
+
const parentDir = path.dirname(currentDir);
|
|
80
|
+
if (parentDir === currentDir) {
|
|
81
|
+
return process.cwd();
|
|
82
|
+
}
|
|
83
|
+
currentDir = parentDir;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function build_sdk_candidates() {
|
|
87
|
+
return ['@tiangong-lca/tidas-sdk'];
|
|
88
|
+
}
|
|
89
|
+
function normalizeValidationMode(value) {
|
|
90
|
+
if (!value) {
|
|
91
|
+
return 'auto';
|
|
92
|
+
}
|
|
93
|
+
if (value === 'auto' || value === 'sdk') {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
throw new CliError('Expected --engine to be one of auto or sdk.', {
|
|
97
|
+
code: 'VALIDATION_INVALID_ENGINE',
|
|
98
|
+
exitCode: 2,
|
|
99
|
+
details: value,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function assert_input_dir(inputDir) {
|
|
103
|
+
if (!inputDir) {
|
|
104
|
+
throw new CliError('Missing required --input-dir value.', {
|
|
105
|
+
code: 'VALIDATION_INPUT_DIR_REQUIRED',
|
|
106
|
+
exitCode: 2,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const resolved = path.resolve(inputDir);
|
|
110
|
+
if (!existsSync(resolved)) {
|
|
111
|
+
throw new CliError(`Validation input directory not found: ${resolved}`, {
|
|
112
|
+
code: 'VALIDATION_INPUT_DIR_NOT_FOUND',
|
|
113
|
+
exitCode: 2,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (!statSync(resolved).isDirectory()) {
|
|
117
|
+
throw new CliError(`Validation input path is not a directory: ${resolved}`, {
|
|
118
|
+
code: 'VALIDATION_INPUT_NOT_DIRECTORY',
|
|
119
|
+
exitCode: 2,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return resolved;
|
|
123
|
+
}
|
|
124
|
+
export function resolveSdkModuleFromCandidates(requireFn, candidates) {
|
|
125
|
+
const details = [];
|
|
126
|
+
for (const candidate of candidates) {
|
|
127
|
+
try {
|
|
128
|
+
const loaded = requireFn(candidate);
|
|
129
|
+
if (typeof loaded.validatePackageDir === 'function') {
|
|
130
|
+
return {
|
|
131
|
+
location: candidate,
|
|
132
|
+
validatePackageDir: loaded.validatePackageDir,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const packageValidator = createTidasSdkPackageValidator(loaded, candidate);
|
|
136
|
+
if (packageValidator) {
|
|
137
|
+
return packageValidator;
|
|
138
|
+
}
|
|
139
|
+
details.push(`Candidate missing direct package validation exports: ${candidate}`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
details.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw new CliError('Unable to resolve the direct tidas-sdk package validator.', {
|
|
146
|
+
code: 'VALIDATION_SDK_UNAVAILABLE',
|
|
147
|
+
exitCode: 2,
|
|
148
|
+
details,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export function resolveLocalSdkModule() {
|
|
152
|
+
return resolveSdkModuleFromCandidates(createRequire(import.meta.url), build_sdk_candidates());
|
|
153
|
+
}
|
|
154
|
+
async function runSdkValidation(inputDir, deps) {
|
|
155
|
+
const startedAt = Date.now();
|
|
156
|
+
const sdkModule = (deps.loadSdkModule ?? resolveLocalSdkModule)();
|
|
157
|
+
const report = normalizePackageReport(sdkModule.validatePackageDir(inputDir, false), inputDir);
|
|
158
|
+
return {
|
|
159
|
+
engine: 'sdk',
|
|
160
|
+
ok: report.ok,
|
|
161
|
+
duration_ms: Date.now() - startedAt,
|
|
162
|
+
location: sdkModule.location,
|
|
163
|
+
report,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export async function runValidation(options, deps = {}) {
|
|
167
|
+
const inputDir = assert_input_dir(options.inputDir);
|
|
168
|
+
const mode = normalizeValidationMode(options.engine);
|
|
169
|
+
const reports = [await runSdkValidation(inputDir, deps)];
|
|
170
|
+
const reportFile = options.reportFile ? path.resolve(options.reportFile) : null;
|
|
171
|
+
const finalReport = {
|
|
172
|
+
input_dir: inputDir,
|
|
173
|
+
mode,
|
|
174
|
+
ok: reports.every((report) => report.ok),
|
|
175
|
+
summary: {
|
|
176
|
+
engine_count: reports.length,
|
|
177
|
+
ok_count: reports.filter((report) => report.ok).length,
|
|
178
|
+
failed_count: reports.filter((report) => !report.ok).length,
|
|
179
|
+
},
|
|
180
|
+
files: {
|
|
181
|
+
report: reportFile,
|
|
182
|
+
},
|
|
183
|
+
reports,
|
|
184
|
+
comparison: null,
|
|
185
|
+
};
|
|
186
|
+
if (reportFile) {
|
|
187
|
+
writeJsonArtifact(reportFile, finalReport);
|
|
188
|
+
}
|
|
189
|
+
return finalReport;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/lib/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { CliError } from './errors.js';\nimport { writeJsonArtifact } from './artifacts.js';\nimport { createTidasSdkPackageValidator } from './tidas-sdk-package-validator.js';\n\ntype JsonObject = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction summarizeIssues(issues: ValidationIssue[]): ValidationSummary {\n const errorCount = issues.filter((issue) => issue.severity === 'error').length;\n const warningCount = issues.filter((issue) => issue.severity === 'warning').length;\n const infoCount = issues.filter((issue) => issue.severity === 'info').length;\n\n return {\n issue_count: issues.length,\n error_count: errorCount,\n warning_count: warningCount,\n info_count: infoCount,\n };\n}\n\nfunction normalizeIssue(raw: unknown): ValidationIssue {\n if (!isRecord(raw)) {\n return {\n issue_code: 'validation_issue',\n severity: 'error',\n category: 'unknown',\n file_path: '<unknown>',\n message: String(raw),\n location: '<root>',\n context: {},\n };\n }\n\n const severity =\n raw.severity === 'warning' || raw.severity === 'info' || raw.severity === 'error'\n ? raw.severity\n : 'error';\n\n return {\n issue_code: typeof raw.issue_code === 'string' ? raw.issue_code : 'validation_issue',\n severity,\n category: typeof raw.category === 'string' ? raw.category : 'unknown',\n file_path: typeof raw.file_path === 'string' ? raw.file_path : '<unknown>',\n message: typeof raw.message === 'string' ? raw.message : JSON.stringify(raw),\n location: typeof raw.location === 'string' ? raw.location : '<root>',\n context: isRecord(raw.context) ? raw.context : {},\n };\n}\n\nfunction normalizeCategoryReport(raw: unknown): CategoryValidationReport {\n const record = isRecord(raw) ? raw : {};\n const issues = Array.isArray(record.issues) ? record.issues.map(normalizeIssue) : [];\n\n return {\n category: typeof record.category === 'string' ? record.category : 'unknown',\n ok: issues.length === 0,\n summary: summarizeIssues(issues),\n issues,\n };\n}\n\nfunction normalizePackageReport(raw: unknown, inputDir: string): PackageValidationReport {\n const record = isRecord(raw) ? raw : {};\n const categories = Array.isArray(record.categories)\n ? record.categories.map(normalizeCategoryReport)\n : [];\n const issues = categories.flatMap((category) => category.issues);\n\n return {\n input_dir: typeof record.input_dir === 'string' ? record.input_dir : inputDir,\n ok: issues.length === 0,\n summary: {\n category_count: categories.length,\n ...summarizeIssues(issues),\n },\n categories,\n issues,\n };\n}\n\nexport function resolveRepoRootFrom(startDir: string): string {\n let currentDir = startDir;\n\n while (true) {\n if (existsSync(path.join(currentDir, 'package.json'))) {\n return currentDir;\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return process.cwd();\n }\n currentDir = parentDir;\n }\n}\n\nfunction build_sdk_candidates(): string[] {\n return ['@tiangong-lca/tidas-sdk'];\n}\n\nfunction normalizeValidationMode(value: string | undefined): ValidationMode {\n if (!value) {\n return 'auto';\n }\n if (value === 'auto' || value === 'sdk') {\n return value;\n }\n throw new CliError('Expected --engine to be one of auto or sdk.', {\n code: 'VALIDATION_INVALID_ENGINE',\n exitCode: 2,\n details: value,\n });\n}\n\nfunction assert_input_dir(inputDir: string): string {\n if (!inputDir) {\n throw new CliError('Missing required --input-dir value.', {\n code: 'VALIDATION_INPUT_DIR_REQUIRED',\n exitCode: 2,\n });\n }\n\n const resolved = path.resolve(inputDir);\n if (!existsSync(resolved)) {\n throw new CliError(`Validation input directory not found: ${resolved}`, {\n code: 'VALIDATION_INPUT_DIR_NOT_FOUND',\n exitCode: 2,\n });\n }\n\n if (!statSync(resolved).isDirectory()) {\n throw new CliError(`Validation input path is not a directory: ${resolved}`, {\n code: 'VALIDATION_INPUT_NOT_DIRECTORY',\n exitCode: 2,\n });\n }\n\n return resolved;\n}\nexport type ValidationSeverity = 'error' | 'warning' | 'info';\n\nexport type ValidationIssue = {\n issue_code: string;\n severity: ValidationSeverity;\n category: string;\n file_path: string;\n message: string;\n location: string;\n context: Record<string, unknown>;\n};\n\nexport type ValidationSummary = {\n issue_count: number;\n error_count: number;\n warning_count: number;\n info_count: number;\n};\n\nexport type CategoryValidationReport = {\n category: string;\n ok: boolean;\n summary: ValidationSummary;\n issues: ValidationIssue[];\n};\n\nexport type PackageValidationReport = {\n input_dir: string;\n ok: boolean;\n summary: ValidationSummary & { category_count: number };\n categories: CategoryValidationReport[];\n issues: ValidationIssue[];\n};\n\nexport type ValidationEngine = 'sdk';\nexport type ValidationMode = 'auto' | 'sdk';\n\nexport type ValidationExecutionReport = {\n engine: ValidationEngine;\n ok: boolean;\n duration_ms: number;\n location: string;\n report: PackageValidationReport;\n command?: string[];\n command_exit_code?: number | null;\n};\n\nexport type ValidationComparison = {\n equivalent: boolean;\n differences: string[];\n};\n\nexport type ValidationRunReport = {\n input_dir: string;\n mode: ValidationMode;\n ok: boolean;\n summary: {\n engine_count: number;\n ok_count: number;\n failed_count: number;\n };\n files: {\n report: string | null;\n };\n reports: ValidationExecutionReport[];\n comparison: ValidationComparison | null;\n};\n\nexport type RunValidationOptions = {\n inputDir: string;\n engine?: string;\n reportFile?: string | null;\n};\n\nexport type ValidationDeps = {\n loadSdkModule?: () => {\n location: string;\n validatePackageDir: (inputDir: string, emitLogs?: boolean) => unknown;\n };\n};\n\nexport function resolveSdkModuleFromCandidates(\n requireFn: (candidate: string) => unknown,\n candidates: string[],\n): {\n location: string;\n validatePackageDir: (inputDir: string, emitLogs?: boolean) => unknown;\n} {\n const details: string[] = [];\n\n for (const candidate of candidates) {\n try {\n const loaded = requireFn(candidate) as Record<string, unknown> & {\n validatePackageDir?: unknown;\n };\n if (typeof loaded.validatePackageDir === 'function') {\n return {\n location: candidate,\n validatePackageDir: loaded.validatePackageDir as (\n inputDir: string,\n emitLogs?: boolean,\n ) => unknown,\n };\n }\n\n const packageValidator = createTidasSdkPackageValidator(\n loaded as Parameters<typeof createTidasSdkPackageValidator>[0],\n candidate,\n );\n if (packageValidator) {\n return packageValidator;\n }\n\n details.push(`Candidate missing direct package validation exports: ${candidate}`);\n } catch (error) {\n details.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n throw new CliError('Unable to resolve the direct tidas-sdk package validator.', {\n code: 'VALIDATION_SDK_UNAVAILABLE',\n exitCode: 2,\n details,\n });\n}\n\nexport function resolveLocalSdkModule(): {\n location: string;\n validatePackageDir: (inputDir: string, emitLogs?: boolean) => unknown;\n} {\n return resolveSdkModuleFromCandidates(createRequire(import.meta.url), build_sdk_candidates());\n}\n\nasync function runSdkValidation(\n inputDir: string,\n deps: ValidationDeps,\n): Promise<ValidationExecutionReport> {\n const startedAt = Date.now();\n const sdkModule = (deps.loadSdkModule ?? resolveLocalSdkModule)();\n const report = normalizePackageReport(sdkModule.validatePackageDir(inputDir, false), inputDir);\n\n return {\n engine: 'sdk',\n ok: report.ok,\n duration_ms: Date.now() - startedAt,\n location: sdkModule.location,\n report,\n };\n}\n\nexport async function runValidation(\n options: RunValidationOptions,\n deps: ValidationDeps = {},\n): Promise<ValidationRunReport> {\n const inputDir = assert_input_dir(options.inputDir);\n const mode = normalizeValidationMode(options.engine);\n const reports: ValidationExecutionReport[] = [await runSdkValidation(inputDir, deps)];\n const reportFile = options.reportFile ? path.resolve(options.reportFile) : null;\n const finalReport: ValidationRunReport = {\n input_dir: inputDir,\n mode,\n ok: reports.every((report) => report.ok),\n summary: {\n engine_count: reports.length,\n ok_count: reports.filter((report) => report.ok).length,\n failed_count: reports.filter((report) => !report.ok).length,\n },\n files: {\n report: reportFile,\n },\n reports,\n comparison: null,\n };\n\n if (reportFile) {\n writeJsonArtifact(reportFile, finalReport);\n }\n\n return finalReport;\n}\n"]}
|
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { executeCli } from './cli.js';
|
|
6
|
+
import { loadDotEnv } from './lib/dotenv.js';
|
|
7
|
+
export async function main(argv, env = process.env) {
|
|
8
|
+
const dotEnvStatus = loadDotEnv(process.cwd(), env);
|
|
9
|
+
const result = await executeCli(argv, {
|
|
10
|
+
env,
|
|
11
|
+
dotEnvStatus,
|
|
12
|
+
fetchImpl: fetch,
|
|
13
|
+
});
|
|
14
|
+
if (result.stdout) {
|
|
15
|
+
process.stdout.write(result.stdout);
|
|
16
|
+
}
|
|
17
|
+
if (result.stderr) {
|
|
18
|
+
process.stderr.write(result.stderr);
|
|
19
|
+
}
|
|
20
|
+
return result.exitCode;
|
|
21
|
+
}
|
|
22
|
+
export function isDirectEntry(importMetaUrl, argv1) {
|
|
23
|
+
if (!argv1) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return importMetaUrl === pathToFileURL(path.resolve(argv1)).href;
|
|
27
|
+
}
|
|
28
|
+
export async function maybeRunFromProcess(argv = process.argv, env = process.env, importMetaUrl = import.meta.url) {
|
|
29
|
+
const entryPath = argv[1];
|
|
30
|
+
if (!isDirectEntry(importMetaUrl, entryPath)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const exitCode = await main(argv.slice(2), env);
|
|
34
|
+
process.exitCode = exitCode;
|
|
35
|
+
return exitCode;
|
|
36
|
+
}
|
|
37
|
+
await maybeRunFromProcess();
|
|
38
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC7E,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE;QACpC,GAAG;QACH,YAAY;QACZ,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,aAAqB,EAAE,KAAyB;IAC5E,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,aAAa,KAAK,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAiB,OAAO,CAAC,IAAI,EAC7B,MAAyB,OAAO,CAAC,GAAG,EACpC,gBAAwB,MAAM,CAAC,IAAI,CAAC,GAAG;IAEvC,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,SAAS,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,mBAAmB,EAAE,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport process from 'node:process';\nimport { pathToFileURL } from 'node:url';\nimport { executeCli } from './cli.js';\nimport { loadDotEnv } from './lib/dotenv.js';\n\nexport async function main(argv: string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {\n const dotEnvStatus = loadDotEnv(process.cwd(), env);\n const result = await executeCli(argv, {\n env,\n dotEnvStatus,\n fetchImpl: fetch,\n });\n\n if (result.stdout) {\n process.stdout.write(result.stdout);\n }\n if (result.stderr) {\n process.stderr.write(result.stderr);\n }\n return result.exitCode;\n}\n\nexport function isDirectEntry(importMetaUrl: string, argv1: string | undefined): boolean {\n if (!argv1) {\n return false;\n }\n return importMetaUrl === pathToFileURL(path.resolve(argv1)).href;\n}\n\nexport async function maybeRunFromProcess(\n argv: string[] = process.argv,\n env: NodeJS.ProcessEnv = process.env,\n importMetaUrl: string = import.meta.url,\n): Promise<number | null> {\n const entryPath = argv[1];\n if (!isDirectEntry(importMetaUrl, entryPath)) {\n return null;\n }\n\n const exitCode = await main(argv.slice(2), env);\n process.exitCode = exitCode;\n return exitCode;\n}\n\nawait maybeRunFromProcess();\n"]}
|