@tiangong-lca/cli 0.0.18 → 0.0.21
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 +2 -2
- package/assets/tidas-schemas/tidas_processes.json +29 -9
- package/dist/src/cli.js +140 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-source-upload-attachments.js +522 -0
- package/dist/src/lib/dataset-source-upload-attachments.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
import { buildSupabaseAuthHeaders, deriveSupabaseProjectBaseUrl, requireSupabaseRestRuntime, } from './supabase-client.js';
|
|
6
|
+
import { resolveSupabaseUserSession } from './supabase-session.js';
|
|
7
|
+
const DEFAULT_BUCKET = 'external_docs';
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
|
+
const MIME_TYPES = {
|
|
10
|
+
'.pdf': 'application/pdf',
|
|
11
|
+
'.jpg': 'image/jpeg',
|
|
12
|
+
'.jpeg': 'image/jpeg',
|
|
13
|
+
'.png': 'image/png',
|
|
14
|
+
'.gif': 'image/gif',
|
|
15
|
+
'.bmp': 'image/bmp',
|
|
16
|
+
'.webp': 'image/webp',
|
|
17
|
+
'.svg': 'image/svg+xml',
|
|
18
|
+
'.csv': 'text/csv',
|
|
19
|
+
'.txt': 'text/plain',
|
|
20
|
+
'.xml': 'application/xml',
|
|
21
|
+
'.json': 'application/json',
|
|
22
|
+
'.zip': 'application/zip',
|
|
23
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
24
|
+
};
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function trimToken(value) {
|
|
29
|
+
if (typeof value !== 'string') {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
return trimmed ? trimmed : null;
|
|
34
|
+
}
|
|
35
|
+
function caughtErrorMessage(error) {
|
|
36
|
+
return error instanceof Error ? error.message : String(error);
|
|
37
|
+
}
|
|
38
|
+
function readFileBytes(filePath) {
|
|
39
|
+
// Copy into a fresh ArrayBuffer-backed view so the bytes are a valid BlobPart
|
|
40
|
+
// (readFileSync's Buffer is typed over ArrayBufferLike, which Blob rejects).
|
|
41
|
+
return new Uint8Array(readFileSync(filePath));
|
|
42
|
+
}
|
|
43
|
+
function normalizeTimeoutMs(value) {
|
|
44
|
+
if (value === undefined) {
|
|
45
|
+
return DEFAULT_TIMEOUT_MS;
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
48
|
+
throw new CliError('--timeout-ms must be a positive integer.', {
|
|
49
|
+
code: 'DATASET_SOURCE_UPLOAD_TIMEOUT_INVALID',
|
|
50
|
+
exitCode: 2,
|
|
51
|
+
details: value,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
export function classifyDigitalFileUri(uri) {
|
|
57
|
+
const trimmed = typeof uri === 'string' ? uri.trim() : '';
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
return 'empty';
|
|
60
|
+
}
|
|
61
|
+
if (/^https?:\/\//iu.test(trimmed)) {
|
|
62
|
+
return 'remote';
|
|
63
|
+
}
|
|
64
|
+
return 'local';
|
|
65
|
+
}
|
|
66
|
+
export function digitalFileBasename(uri) {
|
|
67
|
+
const normalized = uri.trim().replace(/\\/gu, '/');
|
|
68
|
+
const slash = normalized.lastIndexOf('/');
|
|
69
|
+
return slash >= 0 ? normalized.slice(slash + 1) : normalized;
|
|
70
|
+
}
|
|
71
|
+
export function mimeTypeForFile(fileName) {
|
|
72
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
73
|
+
return MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
74
|
+
}
|
|
75
|
+
function buildExternalDocsIndex(externalDocsDir) {
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = readdirSync(externalDocsDir, { withFileTypes: true })
|
|
79
|
+
.filter((entry) => entry.isFile())
|
|
80
|
+
.map((entry) => entry.name);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new CliError(`Cannot read external docs directory: ${externalDocsDir}`, {
|
|
84
|
+
code: 'DATASET_SOURCE_UPLOAD_EXTERNAL_DOCS_DIR_UNREADABLE',
|
|
85
|
+
exitCode: 2,
|
|
86
|
+
details: caughtErrorMessage(error),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const index = new Map();
|
|
90
|
+
for (const name of entries) {
|
|
91
|
+
index.set(name.toLowerCase(), name);
|
|
92
|
+
}
|
|
93
|
+
return index;
|
|
94
|
+
}
|
|
95
|
+
function digitalFileNode(row) {
|
|
96
|
+
const root = isRecord(row.sourceDataSet) ? row.sourceDataSet : row;
|
|
97
|
+
const sourceInformation = isRecord(root.sourceInformation)
|
|
98
|
+
? root.sourceInformation
|
|
99
|
+
: null;
|
|
100
|
+
if (!sourceInformation) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const dataSetInformation = isRecord(sourceInformation.dataSetInformation)
|
|
104
|
+
? sourceInformation.dataSetInformation
|
|
105
|
+
: null;
|
|
106
|
+
return dataSetInformation;
|
|
107
|
+
}
|
|
108
|
+
function sourceIdentity(row) {
|
|
109
|
+
const dataSetInformation = digitalFileNode(row);
|
|
110
|
+
const id = dataSetInformation ? trimToken(dataSetInformation['common:UUID']) : null;
|
|
111
|
+
const root = isRecord(row.sourceDataSet) ? row.sourceDataSet : row;
|
|
112
|
+
const administrative = isRecord(root.administrativeInformation)
|
|
113
|
+
? root.administrativeInformation
|
|
114
|
+
: null;
|
|
115
|
+
const publication = administrative && isRecord(administrative.publicationAndOwnership)
|
|
116
|
+
? administrative.publicationAndOwnership
|
|
117
|
+
: null;
|
|
118
|
+
const version = publication ? trimToken(publication['common:dataSetVersion']) : null;
|
|
119
|
+
return { id, version };
|
|
120
|
+
}
|
|
121
|
+
function entryUri(entry) {
|
|
122
|
+
if (typeof entry === 'string') {
|
|
123
|
+
return entry;
|
|
124
|
+
}
|
|
125
|
+
if (isRecord(entry)) {
|
|
126
|
+
const uri = entry['@uri'];
|
|
127
|
+
return typeof uri === 'string' ? uri : '';
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
function withRewrittenUri(entry, rewrittenUri) {
|
|
132
|
+
if (typeof entry === 'string') {
|
|
133
|
+
return rewrittenUri;
|
|
134
|
+
}
|
|
135
|
+
if (isRecord(entry)) {
|
|
136
|
+
return { ...entry, '@uri': rewrittenUri };
|
|
137
|
+
}
|
|
138
|
+
return entry;
|
|
139
|
+
}
|
|
140
|
+
function resolveReferences(identity, entries, fileIndex, bucket) {
|
|
141
|
+
return entries.map((entry, entryIndex) => {
|
|
142
|
+
const original = entryUri(entry);
|
|
143
|
+
const kind = classifyDigitalFileUri(original);
|
|
144
|
+
if (kind !== 'local') {
|
|
145
|
+
return {
|
|
146
|
+
entryIndex,
|
|
147
|
+
reference: {
|
|
148
|
+
source_id: identity.id,
|
|
149
|
+
source_version: identity.version,
|
|
150
|
+
original_uri: original,
|
|
151
|
+
kind,
|
|
152
|
+
resolved_file: null,
|
|
153
|
+
bucket_key: null,
|
|
154
|
+
rewritten_uri: original,
|
|
155
|
+
status: 'left_as_is',
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const basename = digitalFileBasename(original);
|
|
160
|
+
const resolved = fileIndex.get(basename.toLowerCase()) ?? null;
|
|
161
|
+
if (!resolved) {
|
|
162
|
+
return {
|
|
163
|
+
entryIndex,
|
|
164
|
+
reference: {
|
|
165
|
+
source_id: identity.id,
|
|
166
|
+
source_version: identity.version,
|
|
167
|
+
original_uri: original,
|
|
168
|
+
kind,
|
|
169
|
+
resolved_file: null,
|
|
170
|
+
bucket_key: null,
|
|
171
|
+
rewritten_uri: original,
|
|
172
|
+
status: 'unresolved',
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
entryIndex,
|
|
178
|
+
reference: {
|
|
179
|
+
source_id: identity.id,
|
|
180
|
+
source_version: identity.version,
|
|
181
|
+
original_uri: original,
|
|
182
|
+
kind,
|
|
183
|
+
resolved_file: resolved,
|
|
184
|
+
bucket_key: resolved,
|
|
185
|
+
rewritten_uri: `../${bucket}/${resolved}`,
|
|
186
|
+
status: 'rewritten',
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function digitalFileEntries(value) {
|
|
192
|
+
if (value === undefined || value === null) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return Array.isArray(value) ? value : [value];
|
|
196
|
+
}
|
|
197
|
+
function rewriteDigitalFileValue(value, resolved) {
|
|
198
|
+
const entries = digitalFileEntries(value);
|
|
199
|
+
const rewritten = entries.map((entry, entryIndex) => {
|
|
200
|
+
const match = resolved.find((item) => item.entryIndex === entryIndex);
|
|
201
|
+
if (match && match.reference.status === 'rewritten' && match.reference.rewritten_uri) {
|
|
202
|
+
return withRewrittenUri(entry, match.reference.rewritten_uri);
|
|
203
|
+
}
|
|
204
|
+
return entry;
|
|
205
|
+
});
|
|
206
|
+
return Array.isArray(value) ? rewritten : (rewritten[0] ?? value);
|
|
207
|
+
}
|
|
208
|
+
function loadSourceRows(inputPath) {
|
|
209
|
+
const resolved = path.resolve(inputPath);
|
|
210
|
+
let stats;
|
|
211
|
+
try {
|
|
212
|
+
stats = statSync(resolved);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
throw new CliError(`Cannot read --input path: ${inputPath}`, {
|
|
216
|
+
code: 'DATASET_SOURCE_UPLOAD_INPUT_UNREADABLE',
|
|
217
|
+
exitCode: 2,
|
|
218
|
+
details: caughtErrorMessage(error),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const files = [];
|
|
222
|
+
if (stats.isDirectory()) {
|
|
223
|
+
const walk = (dir) => {
|
|
224
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
225
|
+
const entryPath = path.join(dir, entry.name);
|
|
226
|
+
if (entry.isDirectory()) {
|
|
227
|
+
walk(entryPath);
|
|
228
|
+
}
|
|
229
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
|
|
230
|
+
files.push(entryPath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
walk(resolved);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
files.push(resolved);
|
|
238
|
+
}
|
|
239
|
+
const rows = [];
|
|
240
|
+
for (const file of files) {
|
|
241
|
+
const text = readFileSync(file, 'utf8');
|
|
242
|
+
if (file.toLowerCase().endsWith('.jsonl')) {
|
|
243
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
244
|
+
const trimmed = line.trim();
|
|
245
|
+
if (!trimmed) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
rows.push({ path: file, row: parseRowObject(trimmed, file) });
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const parsed = parseJson(text, file);
|
|
253
|
+
if (Array.isArray(parsed)) {
|
|
254
|
+
for (const item of parsed) {
|
|
255
|
+
if (isRecord(item)) {
|
|
256
|
+
rows.push({ path: file, row: item });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (isRecord(parsed)) {
|
|
261
|
+
rows.push({ path: file, row: parsed });
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
throw new CliError(`Source row is not a JSON object: ${file}`, {
|
|
265
|
+
code: 'DATASET_SOURCE_UPLOAD_INPUT_NOT_OBJECT',
|
|
266
|
+
exitCode: 2,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return rows;
|
|
271
|
+
}
|
|
272
|
+
function parseJson(text, file) {
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(text);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
throw new CliError(`Cannot parse source JSON: ${file}`, {
|
|
278
|
+
code: 'DATASET_SOURCE_UPLOAD_INPUT_INVALID_JSON',
|
|
279
|
+
exitCode: 2,
|
|
280
|
+
details: caughtErrorMessage(error),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function parseRowObject(text, file) {
|
|
285
|
+
const parsed = parseJson(text, file);
|
|
286
|
+
if (!isRecord(parsed)) {
|
|
287
|
+
throw new CliError(`Source row is not a JSON object: ${file}`, {
|
|
288
|
+
code: 'DATASET_SOURCE_UPLOAD_INPUT_NOT_OBJECT',
|
|
289
|
+
exitCode: 2,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return parsed;
|
|
293
|
+
}
|
|
294
|
+
async function uploadObject(options) {
|
|
295
|
+
const encodedKey = options.key
|
|
296
|
+
.split('/')
|
|
297
|
+
.map((segment) => encodeURIComponent(segment))
|
|
298
|
+
.join('/');
|
|
299
|
+
const url = `${options.storageBaseUrl}/object/${options.bucket}/${encodedKey}`;
|
|
300
|
+
const response = await options.fetchImpl(url, {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: {
|
|
303
|
+
...buildSupabaseAuthHeaders(options.publishableKey, options.accessToken),
|
|
304
|
+
'content-type': options.contentType,
|
|
305
|
+
'x-upsert': 'true',
|
|
306
|
+
},
|
|
307
|
+
body: new Blob([options.body], { type: options.contentType }),
|
|
308
|
+
signal: AbortSignal.timeout(options.timeoutMs),
|
|
309
|
+
});
|
|
310
|
+
if (!response.ok) {
|
|
311
|
+
const detail = await response.text();
|
|
312
|
+
throw new CliError(`HTTP ${response.status} returned uploading ${options.key}`, {
|
|
313
|
+
code: 'DATASET_SOURCE_UPLOAD_OBJECT_FAILED',
|
|
314
|
+
exitCode: 1,
|
|
315
|
+
details: { url, body: detail },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function verifyObject(options) {
|
|
320
|
+
const encodedKey = options.key
|
|
321
|
+
.split('/')
|
|
322
|
+
.map((segment) => encodeURIComponent(segment))
|
|
323
|
+
.join('/');
|
|
324
|
+
const url = `${options.storageBaseUrl}/object/sign/${options.bucket}/${encodedKey}`;
|
|
325
|
+
const response = await options.fetchImpl(url, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: {
|
|
328
|
+
...buildSupabaseAuthHeaders(options.publishableKey, options.accessToken),
|
|
329
|
+
'content-type': 'application/json',
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({ expiresIn: 60 }),
|
|
332
|
+
signal: AbortSignal.timeout(options.timeoutMs),
|
|
333
|
+
});
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
const detail = await response.text();
|
|
336
|
+
throw new CliError(`HTTP ${response.status} returned verifying ${options.key}`, {
|
|
337
|
+
code: 'DATASET_SOURCE_UPLOAD_VERIFY_FAILED',
|
|
338
|
+
exitCode: 1,
|
|
339
|
+
details: { url, body: detail },
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export async function runDatasetSourceUploadAttachments(options) {
|
|
344
|
+
const now = options.now ?? new Date();
|
|
345
|
+
const generatedAtUtc = now.toISOString();
|
|
346
|
+
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
|
347
|
+
const commit = Boolean(options.commit);
|
|
348
|
+
const verify = Boolean(options.verify);
|
|
349
|
+
const bucket = trimToken(options.bucket) ?? DEFAULT_BUCKET;
|
|
350
|
+
const outDir = path.resolve(options.outDir ?? 'dataset-source-upload-attachments');
|
|
351
|
+
const externalDocsDir = path.resolve(options.externalDocsDir);
|
|
352
|
+
const fileIndex = buildExternalDocsIndex(externalDocsDir);
|
|
353
|
+
const sourceRows = loadSourceRows(options.inputPath);
|
|
354
|
+
const references = [];
|
|
355
|
+
const filesByKey = new Map();
|
|
356
|
+
const rewrittenRows = [];
|
|
357
|
+
let sourcesRewritten = 0;
|
|
358
|
+
for (const { row } of sourceRows) {
|
|
359
|
+
const identity = sourceIdentity(row);
|
|
360
|
+
const node = digitalFileNode(row);
|
|
361
|
+
const rawValue = node ? node.referenceToDigitalFile : undefined;
|
|
362
|
+
const entries = digitalFileEntries(rawValue);
|
|
363
|
+
if (entries.length === 0 || !node) {
|
|
364
|
+
rewrittenRows.push(row);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const resolved = resolveReferences(identity, entries, fileIndex, bucket);
|
|
368
|
+
let rowChanged = false;
|
|
369
|
+
for (const item of resolved) {
|
|
370
|
+
references.push(item.reference);
|
|
371
|
+
if (item.reference.status === 'rewritten' && item.reference.bucket_key) {
|
|
372
|
+
rowChanged = rowChanged || item.reference.rewritten_uri !== item.reference.original_uri;
|
|
373
|
+
const key = item.reference.bucket_key;
|
|
374
|
+
const existing = filesByKey.get(key);
|
|
375
|
+
const sourceLabel = `${identity.id ?? 'unknown'}@${identity.version ?? 'unknown'}`;
|
|
376
|
+
if (existing) {
|
|
377
|
+
existing.referenced_by.add(sourceLabel);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const filePath = path.join(externalDocsDir, item.reference.resolved_file);
|
|
381
|
+
filesByKey.set(key, {
|
|
382
|
+
source_path: filePath,
|
|
383
|
+
size_bytes: statSync(filePath).size,
|
|
384
|
+
content_type: mimeTypeForFile(item.reference.resolved_file),
|
|
385
|
+
referenced_by: new Set([sourceLabel]),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (rowChanged) {
|
|
391
|
+
sourcesRewritten += 1;
|
|
392
|
+
node.referenceToDigitalFile = rewriteDigitalFileValue(rawValue, resolved);
|
|
393
|
+
}
|
|
394
|
+
rewrittenRows.push(row);
|
|
395
|
+
}
|
|
396
|
+
const runtime = requireSupabaseRestRuntime(options.env);
|
|
397
|
+
const projectBaseUrl = deriveSupabaseProjectBaseUrl(runtime.apiBaseUrl);
|
|
398
|
+
const storageBaseUrl = `${projectBaseUrl}/storage/v1`;
|
|
399
|
+
const files = [];
|
|
400
|
+
let filesUploaded = 0;
|
|
401
|
+
let filesFailed = 0;
|
|
402
|
+
if (commit && filesByKey.size > 0) {
|
|
403
|
+
const session = await resolveSupabaseUserSession({
|
|
404
|
+
runtime,
|
|
405
|
+
fetchImpl: options.fetchImpl,
|
|
406
|
+
timeoutMs,
|
|
407
|
+
now,
|
|
408
|
+
});
|
|
409
|
+
for (const [key, info] of filesByKey) {
|
|
410
|
+
const base = {
|
|
411
|
+
bucket_key: key,
|
|
412
|
+
source_path: info.source_path,
|
|
413
|
+
size_bytes: info.size_bytes,
|
|
414
|
+
content_type: info.content_type,
|
|
415
|
+
referenced_by: [...info.referenced_by].sort(),
|
|
416
|
+
status: 'planned',
|
|
417
|
+
error: null,
|
|
418
|
+
};
|
|
419
|
+
try {
|
|
420
|
+
await uploadObject({
|
|
421
|
+
storageBaseUrl,
|
|
422
|
+
bucket,
|
|
423
|
+
key,
|
|
424
|
+
body: readFileBytes(info.source_path),
|
|
425
|
+
contentType: info.content_type,
|
|
426
|
+
publishableKey: runtime.publishableKey,
|
|
427
|
+
accessToken: session.accessToken,
|
|
428
|
+
fetchImpl: options.fetchImpl,
|
|
429
|
+
timeoutMs,
|
|
430
|
+
});
|
|
431
|
+
if (verify) {
|
|
432
|
+
await verifyObject({
|
|
433
|
+
storageBaseUrl,
|
|
434
|
+
bucket,
|
|
435
|
+
key,
|
|
436
|
+
publishableKey: runtime.publishableKey,
|
|
437
|
+
accessToken: session.accessToken,
|
|
438
|
+
fetchImpl: options.fetchImpl,
|
|
439
|
+
timeoutMs,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
filesUploaded += 1;
|
|
443
|
+
files.push({ ...base, status: verify ? 'verified' : 'uploaded' });
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
filesFailed += 1;
|
|
447
|
+
files.push({ ...base, status: 'failed', error: caughtErrorMessage(error) });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
for (const [key, info] of filesByKey) {
|
|
453
|
+
files.push({
|
|
454
|
+
bucket_key: key,
|
|
455
|
+
source_path: info.source_path,
|
|
456
|
+
size_bytes: info.size_bytes,
|
|
457
|
+
content_type: info.content_type,
|
|
458
|
+
referenced_by: [...info.referenced_by].sort(),
|
|
459
|
+
status: 'planned',
|
|
460
|
+
error: null,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const localRefs = references.filter((ref) => ref.kind === 'local').length;
|
|
465
|
+
const remoteRefs = references.filter((ref) => ref.kind === 'remote').length;
|
|
466
|
+
const unresolvedRefs = references.filter((ref) => ref.status === 'unresolved').length;
|
|
467
|
+
const artifacts = {
|
|
468
|
+
report: path.join(outDir, 'attachments-report.json'),
|
|
469
|
+
rewritten_sources: path.join(outDir, 'rewritten-sources.jsonl'),
|
|
470
|
+
};
|
|
471
|
+
const status = commit
|
|
472
|
+
? filesFailed > 0
|
|
473
|
+
? 'completed_with_failures'
|
|
474
|
+
: 'uploaded_attachments'
|
|
475
|
+
: unresolvedRefs > 0
|
|
476
|
+
? 'completed_with_unresolved_refs'
|
|
477
|
+
: 'planned_attachment_upload';
|
|
478
|
+
const report = {
|
|
479
|
+
schema_version: 1,
|
|
480
|
+
generated_at_utc: generatedAtUtc,
|
|
481
|
+
status,
|
|
482
|
+
mode: commit ? 'commit' : 'dry-run',
|
|
483
|
+
bucket,
|
|
484
|
+
external_docs_dir: externalDocsDir,
|
|
485
|
+
summary: {
|
|
486
|
+
sources_scanned: sourceRows.length,
|
|
487
|
+
local_refs: localRefs,
|
|
488
|
+
remote_refs: remoteRefs,
|
|
489
|
+
unresolved_refs: unresolvedRefs,
|
|
490
|
+
files_planned: filesByKey.size,
|
|
491
|
+
files_uploaded: filesUploaded,
|
|
492
|
+
files_failed: filesFailed,
|
|
493
|
+
sources_rewritten: sourcesRewritten,
|
|
494
|
+
},
|
|
495
|
+
files,
|
|
496
|
+
references,
|
|
497
|
+
artifacts,
|
|
498
|
+
};
|
|
499
|
+
writeJsonArtifact(artifacts.report, report);
|
|
500
|
+
writeJsonLinesArtifact(artifacts.rewritten_sources, rewrittenRows);
|
|
501
|
+
return report;
|
|
502
|
+
}
|
|
503
|
+
export const __testInternals = {
|
|
504
|
+
buildExternalDocsIndex,
|
|
505
|
+
caughtErrorMessage,
|
|
506
|
+
classifyDigitalFileUri,
|
|
507
|
+
digitalFileBasename,
|
|
508
|
+
digitalFileEntries,
|
|
509
|
+
digitalFileNode,
|
|
510
|
+
entryUri,
|
|
511
|
+
loadSourceRows,
|
|
512
|
+
mimeTypeForFile,
|
|
513
|
+
normalizeTimeoutMs,
|
|
514
|
+
parseJson,
|
|
515
|
+
parseRowObject,
|
|
516
|
+
resolveReferences,
|
|
517
|
+
rewriteDigitalFileValue,
|
|
518
|
+
sourceIdentity,
|
|
519
|
+
trimToken,
|
|
520
|
+
withRewrittenUri,
|
|
521
|
+
};
|
|
522
|
+
//# sourceMappingURL=dataset-source-upload-attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-source-upload-attachments.js","sourceRoot":"","sources":["../../../src/lib/dataset-source-upload-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, readFileSync, statSync } from 'node:fs';\nimport path from 'node:path';\nimport { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';\nimport { CliError } from './errors.js';\nimport type { FetchLike } from './http.js';\nimport {\n buildSupabaseAuthHeaders,\n deriveSupabaseProjectBaseUrl,\n requireSupabaseRestRuntime,\n} from './supabase-client.js';\nimport { resolveSupabaseUserSession } from './supabase-session.js';\n\ntype JsonObject = Record<string, unknown>;\n\nconst DEFAULT_BUCKET = 'external_docs';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nconst MIME_TYPES: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.gif': 'image/gif',\n '.bmp': 'image/bmp',\n '.webp': 'image/webp',\n '.svg': 'image/svg+xml',\n '.csv': 'text/csv',\n '.txt': 'text/plain',\n '.xml': 'application/xml',\n '.json': 'application/json',\n '.zip': 'application/zip',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n};\n\nexport type DigitalFileRefKind = 'local' | 'remote' | 'empty';\n\nexport type DigitalFileReference = {\n source_id: string | null;\n source_version: string | null;\n original_uri: string;\n kind: DigitalFileRefKind;\n resolved_file: string | null;\n bucket_key: string | null;\n rewritten_uri: string | null;\n status: 'rewritten' | 'left_as_is' | 'unresolved';\n};\n\nexport type AttachmentFileReport = {\n bucket_key: string;\n source_path: string;\n size_bytes: number;\n content_type: string;\n referenced_by: string[];\n status: 'planned' | 'uploaded' | 'verified' | 'failed';\n error: string | null;\n};\n\nexport type DatasetSourceUploadAttachmentsReport = {\n schema_version: 1;\n generated_at_utc: string;\n status:\n | 'planned_attachment_upload'\n | 'uploaded_attachments'\n | 'completed_with_failures'\n | 'completed_with_unresolved_refs';\n mode: 'dry-run' | 'commit';\n bucket: string;\n external_docs_dir: string;\n summary: {\n sources_scanned: number;\n local_refs: number;\n remote_refs: number;\n unresolved_refs: number;\n files_planned: number;\n files_uploaded: number;\n files_failed: number;\n sources_rewritten: number;\n };\n files: AttachmentFileReport[];\n references: DigitalFileReference[];\n artifacts: {\n report: string;\n rewritten_sources: string;\n };\n};\n\nexport type RunDatasetSourceUploadAttachmentsOptions = {\n inputPath: string;\n externalDocsDir: string;\n outDir?: string | null;\n bucket?: string | null;\n commit?: boolean;\n verify?: boolean;\n timeoutMs?: number;\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n now?: Date;\n};\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction trimToken(value: unknown): string | null {\n if (typeof value !== 'string') {\n return null;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed : null;\n}\n\nfunction caughtErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction readFileBytes(filePath: string): Uint8Array<ArrayBuffer> {\n // Copy into a fresh ArrayBuffer-backed view so the bytes are a valid BlobPart\n // (readFileSync's Buffer is typed over ArrayBufferLike, which Blob rejects).\n return new Uint8Array(readFileSync(filePath));\n}\n\nfunction normalizeTimeoutMs(value: number | undefined): number {\n if (value === undefined) {\n return DEFAULT_TIMEOUT_MS;\n }\n if (!Number.isInteger(value) || value < 1) {\n throw new CliError('--timeout-ms must be a positive integer.', {\n code: 'DATASET_SOURCE_UPLOAD_TIMEOUT_INVALID',\n exitCode: 2,\n details: value,\n });\n }\n return value;\n}\n\nexport function classifyDigitalFileUri(uri: unknown): DigitalFileRefKind {\n const trimmed = typeof uri === 'string' ? uri.trim() : '';\n if (!trimmed) {\n return 'empty';\n }\n if (/^https?:\\/\\//iu.test(trimmed)) {\n return 'remote';\n }\n return 'local';\n}\n\nexport function digitalFileBasename(uri: string): string {\n const normalized = uri.trim().replace(/\\\\/gu, '/');\n const slash = normalized.lastIndexOf('/');\n return slash >= 0 ? normalized.slice(slash + 1) : normalized;\n}\n\nexport function mimeTypeForFile(fileName: string): string {\n const ext = path.extname(fileName).toLowerCase();\n return MIME_TYPES[ext] ?? 'application/octet-stream';\n}\n\nfunction buildExternalDocsIndex(externalDocsDir: string): Map<string, string> {\n let entries: string[];\n try {\n entries = readdirSync(externalDocsDir, { withFileTypes: true })\n .filter((entry) => entry.isFile())\n .map((entry) => entry.name);\n } catch (error) {\n throw new CliError(`Cannot read external docs directory: ${externalDocsDir}`, {\n code: 'DATASET_SOURCE_UPLOAD_EXTERNAL_DOCS_DIR_UNREADABLE',\n exitCode: 2,\n details: caughtErrorMessage(error),\n });\n }\n\n const index = new Map<string, string>();\n for (const name of entries) {\n index.set(name.toLowerCase(), name);\n }\n return index;\n}\n\nfunction digitalFileNode(row: JsonObject): JsonObject | null {\n const root = isRecord(row.sourceDataSet) ? (row.sourceDataSet as JsonObject) : row;\n const sourceInformation = isRecord(root.sourceInformation)\n ? (root.sourceInformation as JsonObject)\n : null;\n if (!sourceInformation) {\n return null;\n }\n const dataSetInformation = isRecord(sourceInformation.dataSetInformation)\n ? (sourceInformation.dataSetInformation as JsonObject)\n : null;\n return dataSetInformation;\n}\n\nfunction sourceIdentity(row: JsonObject): { id: string | null; version: string | null } {\n const dataSetInformation = digitalFileNode(row);\n const id = dataSetInformation ? trimToken(dataSetInformation['common:UUID']) : null;\n\n const root = isRecord(row.sourceDataSet) ? (row.sourceDataSet as JsonObject) : row;\n const administrative = isRecord(root.administrativeInformation)\n ? (root.administrativeInformation as JsonObject)\n : null;\n const publication =\n administrative && isRecord(administrative.publicationAndOwnership)\n ? (administrative.publicationAndOwnership as JsonObject)\n : null;\n const version = publication ? trimToken(publication['common:dataSetVersion']) : null;\n\n return { id, version };\n}\n\nfunction entryUri(entry: unknown): string {\n if (typeof entry === 'string') {\n return entry;\n }\n if (isRecord(entry)) {\n const uri = entry['@uri'];\n return typeof uri === 'string' ? uri : '';\n }\n return '';\n}\n\nfunction withRewrittenUri(entry: unknown, rewrittenUri: string): unknown {\n if (typeof entry === 'string') {\n return rewrittenUri;\n }\n if (isRecord(entry)) {\n return { ...entry, '@uri': rewrittenUri };\n }\n return entry;\n}\n\nexport type ResolvedReference = {\n reference: DigitalFileReference;\n entryIndex: number;\n};\n\nfunction resolveReferences(\n identity: { id: string | null; version: string | null },\n entries: unknown[],\n fileIndex: Map<string, string>,\n bucket: string,\n): ResolvedReference[] {\n return entries.map((entry, entryIndex) => {\n const original = entryUri(entry);\n const kind = classifyDigitalFileUri(original);\n\n if (kind !== 'local') {\n return {\n entryIndex,\n reference: {\n source_id: identity.id,\n source_version: identity.version,\n original_uri: original,\n kind,\n resolved_file: null,\n bucket_key: null,\n rewritten_uri: original,\n status: 'left_as_is',\n },\n };\n }\n\n const basename = digitalFileBasename(original);\n const resolved = fileIndex.get(basename.toLowerCase()) ?? null;\n if (!resolved) {\n return {\n entryIndex,\n reference: {\n source_id: identity.id,\n source_version: identity.version,\n original_uri: original,\n kind,\n resolved_file: null,\n bucket_key: null,\n rewritten_uri: original,\n status: 'unresolved',\n },\n };\n }\n\n return {\n entryIndex,\n reference: {\n source_id: identity.id,\n source_version: identity.version,\n original_uri: original,\n kind,\n resolved_file: resolved,\n bucket_key: resolved,\n rewritten_uri: `../${bucket}/${resolved}`,\n status: 'rewritten',\n },\n };\n });\n}\n\nexport function digitalFileEntries(value: unknown): unknown[] {\n if (value === undefined || value === null) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\nfunction rewriteDigitalFileValue(value: unknown, resolved: ResolvedReference[]): unknown {\n const entries = digitalFileEntries(value);\n const rewritten = entries.map((entry, entryIndex) => {\n const match = resolved.find((item) => item.entryIndex === entryIndex);\n if (match && match.reference.status === 'rewritten' && match.reference.rewritten_uri) {\n return withRewrittenUri(entry, match.reference.rewritten_uri);\n }\n return entry;\n });\n\n return Array.isArray(value) ? rewritten : (rewritten[0] ?? value);\n}\n\nfunction loadSourceRows(inputPath: string): { path: string; row: JsonObject }[] {\n const resolved = path.resolve(inputPath);\n let stats;\n try {\n stats = statSync(resolved);\n } catch (error) {\n throw new CliError(`Cannot read --input path: ${inputPath}`, {\n code: 'DATASET_SOURCE_UPLOAD_INPUT_UNREADABLE',\n exitCode: 2,\n details: caughtErrorMessage(error),\n });\n }\n\n const files: string[] = [];\n if (stats.isDirectory()) {\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const entryPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(entryPath);\n } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {\n files.push(entryPath);\n }\n }\n };\n walk(resolved);\n } else {\n files.push(resolved);\n }\n\n const rows: { path: string; row: JsonObject }[] = [];\n for (const file of files) {\n const text = readFileSync(file, 'utf8');\n if (file.toLowerCase().endsWith('.jsonl')) {\n for (const line of text.split(/\\r?\\n/u)) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n rows.push({ path: file, row: parseRowObject(trimmed, file) });\n }\n continue;\n }\n\n const parsed = parseJson(text, file);\n if (Array.isArray(parsed)) {\n for (const item of parsed) {\n if (isRecord(item)) {\n rows.push({ path: file, row: item });\n }\n }\n } else if (isRecord(parsed)) {\n rows.push({ path: file, row: parsed });\n } else {\n throw new CliError(`Source row is not a JSON object: ${file}`, {\n code: 'DATASET_SOURCE_UPLOAD_INPUT_NOT_OBJECT',\n exitCode: 2,\n });\n }\n }\n\n return rows;\n}\n\nfunction parseJson(text: string, file: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new CliError(`Cannot parse source JSON: ${file}`, {\n code: 'DATASET_SOURCE_UPLOAD_INPUT_INVALID_JSON',\n exitCode: 2,\n details: caughtErrorMessage(error),\n });\n }\n}\n\nfunction parseRowObject(text: string, file: string): JsonObject {\n const parsed = parseJson(text, file);\n if (!isRecord(parsed)) {\n throw new CliError(`Source row is not a JSON object: ${file}`, {\n code: 'DATASET_SOURCE_UPLOAD_INPUT_NOT_OBJECT',\n exitCode: 2,\n });\n }\n return parsed;\n}\n\nasync function uploadObject(options: {\n storageBaseUrl: string;\n bucket: string;\n key: string;\n body: Uint8Array<ArrayBuffer>;\n contentType: string;\n publishableKey: string;\n accessToken: string;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<void> {\n const encodedKey = options.key\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n const url = `${options.storageBaseUrl}/object/${options.bucket}/${encodedKey}`;\n const response = await options.fetchImpl(url, {\n method: 'POST',\n headers: {\n ...buildSupabaseAuthHeaders(options.publishableKey, options.accessToken),\n 'content-type': options.contentType,\n 'x-upsert': 'true',\n },\n body: new Blob([options.body], { type: options.contentType }),\n signal: AbortSignal.timeout(options.timeoutMs),\n });\n\n if (!response.ok) {\n const detail = await response.text();\n throw new CliError(`HTTP ${response.status} returned uploading ${options.key}`, {\n code: 'DATASET_SOURCE_UPLOAD_OBJECT_FAILED',\n exitCode: 1,\n details: { url, body: detail },\n });\n }\n}\n\nasync function verifyObject(options: {\n storageBaseUrl: string;\n bucket: string;\n key: string;\n publishableKey: string;\n accessToken: string;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<void> {\n const encodedKey = options.key\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n const url = `${options.storageBaseUrl}/object/sign/${options.bucket}/${encodedKey}`;\n const response = await options.fetchImpl(url, {\n method: 'POST',\n headers: {\n ...buildSupabaseAuthHeaders(options.publishableKey, options.accessToken),\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ expiresIn: 60 }),\n signal: AbortSignal.timeout(options.timeoutMs),\n });\n\n if (!response.ok) {\n const detail = await response.text();\n throw new CliError(`HTTP ${response.status} returned verifying ${options.key}`, {\n code: 'DATASET_SOURCE_UPLOAD_VERIFY_FAILED',\n exitCode: 1,\n details: { url, body: detail },\n });\n }\n}\n\nexport async function runDatasetSourceUploadAttachments(\n options: RunDatasetSourceUploadAttachmentsOptions,\n): Promise<DatasetSourceUploadAttachmentsReport> {\n const now = options.now ?? new Date();\n const generatedAtUtc = now.toISOString();\n const timeoutMs = normalizeTimeoutMs(options.timeoutMs);\n const commit = Boolean(options.commit);\n const verify = Boolean(options.verify);\n const bucket = trimToken(options.bucket) ?? DEFAULT_BUCKET;\n const outDir = path.resolve(options.outDir ?? 'dataset-source-upload-attachments');\n const externalDocsDir = path.resolve(options.externalDocsDir);\n\n const fileIndex = buildExternalDocsIndex(externalDocsDir);\n const sourceRows = loadSourceRows(options.inputPath);\n\n const references: DigitalFileReference[] = [];\n const filesByKey = new Map<\n string,\n { source_path: string; size_bytes: number; content_type: string; referenced_by: Set<string> }\n >();\n const rewrittenRows: JsonObject[] = [];\n let sourcesRewritten = 0;\n\n for (const { row } of sourceRows) {\n const identity = sourceIdentity(row);\n const node = digitalFileNode(row);\n const rawValue = node ? node.referenceToDigitalFile : undefined;\n const entries = digitalFileEntries(rawValue);\n\n if (entries.length === 0 || !node) {\n rewrittenRows.push(row);\n continue;\n }\n\n const resolved = resolveReferences(identity, entries, fileIndex, bucket);\n let rowChanged = false;\n\n for (const item of resolved) {\n references.push(item.reference);\n if (item.reference.status === 'rewritten' && item.reference.bucket_key) {\n rowChanged = rowChanged || item.reference.rewritten_uri !== item.reference.original_uri;\n const key = item.reference.bucket_key;\n const existing = filesByKey.get(key);\n const sourceLabel = `${identity.id ?? 'unknown'}@${identity.version ?? 'unknown'}`;\n if (existing) {\n existing.referenced_by.add(sourceLabel);\n } else {\n const filePath = path.join(externalDocsDir, item.reference.resolved_file!);\n filesByKey.set(key, {\n source_path: filePath,\n size_bytes: statSync(filePath).size,\n content_type: mimeTypeForFile(item.reference.resolved_file!),\n referenced_by: new Set([sourceLabel]),\n });\n }\n }\n }\n\n if (rowChanged) {\n sourcesRewritten += 1;\n node.referenceToDigitalFile = rewriteDigitalFileValue(rawValue, resolved);\n }\n rewrittenRows.push(row);\n }\n\n const runtime = requireSupabaseRestRuntime(options.env);\n const projectBaseUrl = deriveSupabaseProjectBaseUrl(runtime.apiBaseUrl);\n const storageBaseUrl = `${projectBaseUrl}/storage/v1`;\n\n const files: AttachmentFileReport[] = [];\n let filesUploaded = 0;\n let filesFailed = 0;\n\n if (commit && filesByKey.size > 0) {\n const session = await resolveSupabaseUserSession({\n runtime,\n fetchImpl: options.fetchImpl,\n timeoutMs,\n now,\n });\n\n for (const [key, info] of filesByKey) {\n const base: AttachmentFileReport = {\n bucket_key: key,\n source_path: info.source_path,\n size_bytes: info.size_bytes,\n content_type: info.content_type,\n referenced_by: [...info.referenced_by].sort(),\n status: 'planned',\n error: null,\n };\n try {\n await uploadObject({\n storageBaseUrl,\n bucket,\n key,\n body: readFileBytes(info.source_path),\n contentType: info.content_type,\n publishableKey: runtime.publishableKey,\n accessToken: session.accessToken,\n fetchImpl: options.fetchImpl,\n timeoutMs,\n });\n if (verify) {\n await verifyObject({\n storageBaseUrl,\n bucket,\n key,\n publishableKey: runtime.publishableKey,\n accessToken: session.accessToken,\n fetchImpl: options.fetchImpl,\n timeoutMs,\n });\n }\n filesUploaded += 1;\n files.push({ ...base, status: verify ? 'verified' : 'uploaded' });\n } catch (error) {\n filesFailed += 1;\n files.push({ ...base, status: 'failed', error: caughtErrorMessage(error) });\n }\n }\n } else {\n for (const [key, info] of filesByKey) {\n files.push({\n bucket_key: key,\n source_path: info.source_path,\n size_bytes: info.size_bytes,\n content_type: info.content_type,\n referenced_by: [...info.referenced_by].sort(),\n status: 'planned',\n error: null,\n });\n }\n }\n\n const localRefs = references.filter((ref) => ref.kind === 'local').length;\n const remoteRefs = references.filter((ref) => ref.kind === 'remote').length;\n const unresolvedRefs = references.filter((ref) => ref.status === 'unresolved').length;\n\n const artifacts = {\n report: path.join(outDir, 'attachments-report.json'),\n rewritten_sources: path.join(outDir, 'rewritten-sources.jsonl'),\n };\n\n const status: DatasetSourceUploadAttachmentsReport['status'] = commit\n ? filesFailed > 0\n ? 'completed_with_failures'\n : 'uploaded_attachments'\n : unresolvedRefs > 0\n ? 'completed_with_unresolved_refs'\n : 'planned_attachment_upload';\n\n const report: DatasetSourceUploadAttachmentsReport = {\n schema_version: 1,\n generated_at_utc: generatedAtUtc,\n status,\n mode: commit ? 'commit' : 'dry-run',\n bucket,\n external_docs_dir: externalDocsDir,\n summary: {\n sources_scanned: sourceRows.length,\n local_refs: localRefs,\n remote_refs: remoteRefs,\n unresolved_refs: unresolvedRefs,\n files_planned: filesByKey.size,\n files_uploaded: filesUploaded,\n files_failed: filesFailed,\n sources_rewritten: sourcesRewritten,\n },\n files,\n references,\n artifacts,\n };\n\n writeJsonArtifact(artifacts.report, report);\n writeJsonLinesArtifact(artifacts.rewritten_sources, rewrittenRows);\n\n return report;\n}\n\nexport const __testInternals = {\n buildExternalDocsIndex,\n caughtErrorMessage,\n classifyDigitalFileUri,\n digitalFileBasename,\n digitalFileEntries,\n digitalFileNode,\n entryUri,\n loadSourceRows,\n mimeTypeForFile,\n normalizeTimeoutMs,\n parseJson,\n parseRowObject,\n resolveReferences,\n rewriteDigitalFileValue,\n sourceIdentity,\n trimToken,\n withRewrittenUri,\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiangong-lca/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Unified TianGong LCA CLI with direct REST adapters and low-entropy command surface.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -77,6 +77,6 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@supabase/supabase-js": "^2.101.1",
|
|
80
|
-
"@tiangong-lca/tidas-sdk": "^0.1.
|
|
80
|
+
"@tiangong-lca/tidas-sdk": "^0.1.45"
|
|
81
81
|
}
|
|
82
82
|
}
|