@the-cascade-protocol/cli 0.2.0 → 0.2.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/dist/commands/pod/helpers.d.ts +1 -1
- package/dist/commands/pod/helpers.d.ts.map +1 -1
- package/dist/commands/pod/helpers.js +5 -20
- package/dist/commands/pod/helpers.js.map +1 -1
- package/package.json +17 -5
- package/.dockerignore +0 -7
- package/.eslintrc.json +0 -23
- package/.prettierrc +0 -7
- package/Dockerfile +0 -18
- package/src/commands/capabilities.ts +0 -235
- package/src/commands/conformance.ts +0 -447
- package/src/commands/convert.ts +0 -164
- package/src/commands/pod/export.ts +0 -85
- package/src/commands/pod/helpers.ts +0 -449
- package/src/commands/pod/index.ts +0 -32
- package/src/commands/pod/info.ts +0 -239
- package/src/commands/pod/init.ts +0 -273
- package/src/commands/pod/query.ts +0 -224
- package/src/commands/serve.ts +0 -92
- package/src/commands/validate.ts +0 -303
- package/src/index.ts +0 -58
- package/src/lib/fhir-converter/cascade-to-fhir.ts +0 -369
- package/src/lib/fhir-converter/converters-clinical.ts +0 -446
- package/src/lib/fhir-converter/converters-demographics.ts +0 -270
- package/src/lib/fhir-converter/fhir-to-cascade.ts +0 -82
- package/src/lib/fhir-converter/index.ts +0 -215
- package/src/lib/fhir-converter/types.ts +0 -318
- package/src/lib/mcp/audit.ts +0 -107
- package/src/lib/mcp/server.ts +0 -192
- package/src/lib/mcp/tools.ts +0 -668
- package/src/lib/output.ts +0 -76
- package/src/lib/shacl-validator.ts +0 -314
- package/src/lib/turtle-parser.ts +0 -277
- package/src/shapes/checkup.shapes.ttl +0 -1459
- package/src/shapes/clinical.shapes.ttl +0 -1350
- package/src/shapes/clinical.ttl +0 -1369
- package/src/shapes/core.shapes.ttl +0 -450
- package/src/shapes/core.ttl +0 -603
- package/src/shapes/coverage.shapes.ttl +0 -214
- package/src/shapes/coverage.ttl +0 -182
- package/src/shapes/health.shapes.ttl +0 -697
- package/src/shapes/health.ttl +0 -859
- package/src/shapes/pots.shapes.ttl +0 -481
- package/test-fixtures/fhir-bundle-example.json +0 -216
- package/test-fixtures/fhir-medication-example.json +0 -18
- package/tests/cli.test.ts +0 -126
- package/tests/fhir-converter.test.ts +0 -874
- package/tests/mcp-server.test.ts +0 -396
- package/tests/pod.test.ts +0 -400
- package/tsconfig.json +0 -24
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for pod subcommands.
|
|
3
|
-
*
|
|
4
|
-
* Includes file-system utilities, the data type registry, parsing helpers,
|
|
5
|
-
* and display-formatting functions used by multiple pod subcommands.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'fs/promises';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
import {
|
|
11
|
-
parseTurtleFile,
|
|
12
|
-
getProperties,
|
|
13
|
-
shortenIRI,
|
|
14
|
-
extractLabel,
|
|
15
|
-
CASCADE_NAMESPACES,
|
|
16
|
-
} from '../../lib/turtle-parser.js';
|
|
17
|
-
|
|
18
|
-
// ─── Data Type Registry ──────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Known data file types and the rdf:type IRIs that identify records in them.
|
|
22
|
-
*/
|
|
23
|
-
export interface DataTypeInfo {
|
|
24
|
-
label: string;
|
|
25
|
-
rdfTypes: string[];
|
|
26
|
-
directory: 'clinical' | 'wellness';
|
|
27
|
-
filename: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const DATA_TYPES: Record<string, DataTypeInfo> = {
|
|
31
|
-
medications: {
|
|
32
|
-
label: 'Medications',
|
|
33
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'MedicationRecord'],
|
|
34
|
-
directory: 'clinical',
|
|
35
|
-
filename: 'medications.ttl',
|
|
36
|
-
},
|
|
37
|
-
conditions: {
|
|
38
|
-
label: 'Conditions',
|
|
39
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'ConditionRecord'],
|
|
40
|
-
directory: 'clinical',
|
|
41
|
-
filename: 'conditions.ttl',
|
|
42
|
-
},
|
|
43
|
-
allergies: {
|
|
44
|
-
label: 'Allergies',
|
|
45
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'AllergyRecord'],
|
|
46
|
-
directory: 'clinical',
|
|
47
|
-
filename: 'allergies.ttl',
|
|
48
|
-
},
|
|
49
|
-
'lab-results': {
|
|
50
|
-
label: 'Lab Results',
|
|
51
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'LabResultRecord'],
|
|
52
|
-
directory: 'clinical',
|
|
53
|
-
filename: 'lab-results.ttl',
|
|
54
|
-
},
|
|
55
|
-
immunizations: {
|
|
56
|
-
label: 'Immunizations',
|
|
57
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'ImmunizationRecord'],
|
|
58
|
-
directory: 'clinical',
|
|
59
|
-
filename: 'immunizations.ttl',
|
|
60
|
-
},
|
|
61
|
-
'vital-signs': {
|
|
62
|
-
label: 'Vital Signs',
|
|
63
|
-
rdfTypes: [CASCADE_NAMESPACES.clinical + 'VitalSign'],
|
|
64
|
-
directory: 'clinical',
|
|
65
|
-
filename: 'vital-signs.ttl',
|
|
66
|
-
},
|
|
67
|
-
insurance: {
|
|
68
|
-
label: 'Insurance',
|
|
69
|
-
rdfTypes: [CASCADE_NAMESPACES.clinical + 'CoverageRecord'],
|
|
70
|
-
directory: 'clinical',
|
|
71
|
-
filename: 'insurance.ttl',
|
|
72
|
-
},
|
|
73
|
-
'patient-profile': {
|
|
74
|
-
label: 'Patient Profile',
|
|
75
|
-
rdfTypes: [CASCADE_NAMESPACES.cascade + 'PatientProfile'],
|
|
76
|
-
directory: 'clinical',
|
|
77
|
-
filename: 'patient-profile.ttl',
|
|
78
|
-
},
|
|
79
|
-
'heart-rate': {
|
|
80
|
-
label: 'Heart Rate',
|
|
81
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'DailyVitalReading', CASCADE_NAMESPACES.health + 'HeartRateData'],
|
|
82
|
-
directory: 'wellness',
|
|
83
|
-
filename: 'heart-rate.ttl',
|
|
84
|
-
},
|
|
85
|
-
'blood-pressure': {
|
|
86
|
-
label: 'Blood Pressure',
|
|
87
|
-
rdfTypes: [
|
|
88
|
-
'http://hl7.org/fhir/Observation',
|
|
89
|
-
CASCADE_NAMESPACES.health + 'BloodPressureData',
|
|
90
|
-
],
|
|
91
|
-
directory: 'wellness',
|
|
92
|
-
filename: 'blood-pressure.ttl',
|
|
93
|
-
},
|
|
94
|
-
activity: {
|
|
95
|
-
label: 'Activity',
|
|
96
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'DailyActivitySnapshot', CASCADE_NAMESPACES.health + 'ActivityData'],
|
|
97
|
-
directory: 'wellness',
|
|
98
|
-
filename: 'activity.ttl',
|
|
99
|
-
},
|
|
100
|
-
sleep: {
|
|
101
|
-
label: 'Sleep',
|
|
102
|
-
rdfTypes: [CASCADE_NAMESPACES.health + 'DailySleepSnapshot', CASCADE_NAMESPACES.health + 'SleepData'],
|
|
103
|
-
directory: 'wellness',
|
|
104
|
-
filename: 'sleep.ttl',
|
|
105
|
-
},
|
|
106
|
-
supplements: {
|
|
107
|
-
label: 'Supplements',
|
|
108
|
-
rdfTypes: [CASCADE_NAMESPACES.clinical + 'Supplement'],
|
|
109
|
-
directory: 'wellness',
|
|
110
|
-
filename: 'supplements.ttl',
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Re-export CASCADE_NAMESPACES for convenience
|
|
115
|
-
export { CASCADE_NAMESPACES };
|
|
116
|
-
|
|
117
|
-
// ─── File-System Helpers ─────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Resolve a pod directory path to an absolute path.
|
|
121
|
-
*/
|
|
122
|
-
export function resolvePodDir(podDir: string): string {
|
|
123
|
-
return path.resolve(process.cwd(), podDir);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Check if a path exists and is a directory.
|
|
128
|
-
*/
|
|
129
|
-
export async function isDirectory(dirPath: string): Promise<boolean> {
|
|
130
|
-
try {
|
|
131
|
-
const stat = await fs.stat(dirPath);
|
|
132
|
-
return stat.isDirectory();
|
|
133
|
-
} catch {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check if a file exists.
|
|
140
|
-
*/
|
|
141
|
-
export async function fileExists(filePath: string): Promise<boolean> {
|
|
142
|
-
try {
|
|
143
|
-
await fs.access(filePath);
|
|
144
|
-
return true;
|
|
145
|
-
} catch {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Discover all TTL files in a pod directory recursively.
|
|
152
|
-
*/
|
|
153
|
-
export async function discoverTtlFiles(podDir: string): Promise<string[]> {
|
|
154
|
-
const files: string[] = [];
|
|
155
|
-
|
|
156
|
-
async function walk(dir: string): Promise<void> {
|
|
157
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
158
|
-
for (const entry of entries) {
|
|
159
|
-
const fullPath = path.join(dir, entry.name);
|
|
160
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
161
|
-
await walk(fullPath);
|
|
162
|
-
} else if (entry.isFile() && entry.name.endsWith('.ttl')) {
|
|
163
|
-
files.push(fullPath);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
await walk(podDir);
|
|
169
|
-
return files.sort();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ─── Parsing Helpers ─────────────────────────────────────────────────────────
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Parse a single TTL file and extract typed records.
|
|
176
|
-
*/
|
|
177
|
-
export async function parseDataFile(filePath: string): Promise<{
|
|
178
|
-
records: Array<{
|
|
179
|
-
id: string;
|
|
180
|
-
type: string;
|
|
181
|
-
label: string | undefined;
|
|
182
|
-
properties: Record<string, string>;
|
|
183
|
-
}>;
|
|
184
|
-
totalQuads: number;
|
|
185
|
-
error?: string;
|
|
186
|
-
}> {
|
|
187
|
-
const result = await parseTurtleFile(filePath);
|
|
188
|
-
if (!result.success) {
|
|
189
|
-
return { records: [], totalQuads: 0, error: result.errors.join('; ') };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const records: Array<{
|
|
193
|
-
id: string;
|
|
194
|
-
type: string;
|
|
195
|
-
label: string | undefined;
|
|
196
|
-
properties: Record<string, string>;
|
|
197
|
-
}> = [];
|
|
198
|
-
|
|
199
|
-
for (const subject of result.subjects) {
|
|
200
|
-
// Skip blank nodes that are just structural (e.g., nested blank nodes for provenance)
|
|
201
|
-
// Keep named subjects (URNs, URIs) and typed blank nodes with meaningful types
|
|
202
|
-
const meaningfulTypes = subject.types.filter(
|
|
203
|
-
(t) =>
|
|
204
|
-
!t.startsWith('http://www.w3.org/ns/prov#') &&
|
|
205
|
-
t !== 'http://www.w3.org/ns/solid/terms#TypeRegistration' &&
|
|
206
|
-
t !== 'http://www.w3.org/ns/solid/terms#TypeIndex' &&
|
|
207
|
-
t !== 'http://www.w3.org/ns/solid/terms#ListedDocument' &&
|
|
208
|
-
t !== 'http://www.w3.org/ns/solid/terms#UnlistedDocument' &&
|
|
209
|
-
t !== 'http://www.w3.org/ns/ldp#BasicContainer',
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
if (meaningfulTypes.length === 0) continue;
|
|
213
|
-
|
|
214
|
-
const props = getProperties(result.store, subject.uri);
|
|
215
|
-
const label = extractLabel(props);
|
|
216
|
-
|
|
217
|
-
// Flatten properties for display (take first value of each, shorten IRIs)
|
|
218
|
-
const flatProps: Record<string, string> = {};
|
|
219
|
-
for (const [pred, values] of Object.entries(props)) {
|
|
220
|
-
const shortPred = shortenIRI(pred);
|
|
221
|
-
// Skip rdf:type since we have it separately
|
|
222
|
-
if (pred === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') continue;
|
|
223
|
-
flatProps[shortPred] = values.length === 1 ? values[0] : values.join(', ');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
records.push({
|
|
227
|
-
id: subject.uri,
|
|
228
|
-
type: shortenIRI(meaningfulTypes[0]),
|
|
229
|
-
label,
|
|
230
|
-
properties: flatProps,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return { records, totalQuads: result.quadCount };
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Read the patient profile from a pod to extract name, age, schema version.
|
|
239
|
-
*/
|
|
240
|
-
export async function readPatientProfile(podDir: string): Promise<{
|
|
241
|
-
name?: string;
|
|
242
|
-
age?: string;
|
|
243
|
-
schemaVersion?: string;
|
|
244
|
-
dateOfBirth?: string;
|
|
245
|
-
}> {
|
|
246
|
-
// Try clinical/patient-profile.ttl first, then profile/card.ttl
|
|
247
|
-
const profilePaths = [
|
|
248
|
-
path.join(podDir, 'clinical', 'patient-profile.ttl'),
|
|
249
|
-
path.join(podDir, 'profile', 'card.ttl'),
|
|
250
|
-
];
|
|
251
|
-
|
|
252
|
-
let name: string | undefined;
|
|
253
|
-
let age: string | undefined;
|
|
254
|
-
let schemaVersion: string | undefined;
|
|
255
|
-
let dateOfBirth: string | undefined;
|
|
256
|
-
|
|
257
|
-
for (const profilePath of profilePaths) {
|
|
258
|
-
if (!(await fileExists(profilePath))) continue;
|
|
259
|
-
|
|
260
|
-
const result = await parseTurtleFile(profilePath);
|
|
261
|
-
if (!result.success) continue;
|
|
262
|
-
|
|
263
|
-
for (const subject of result.subjects) {
|
|
264
|
-
const props = getProperties(result.store, subject.uri);
|
|
265
|
-
if (!name) {
|
|
266
|
-
name = props['http://xmlns.com/foaf/0.1/name']?.[0];
|
|
267
|
-
}
|
|
268
|
-
if (!age) {
|
|
269
|
-
age = props[CASCADE_NAMESPACES.cascade + 'computedAge']?.[0];
|
|
270
|
-
}
|
|
271
|
-
if (!schemaVersion) {
|
|
272
|
-
schemaVersion = props[CASCADE_NAMESPACES.cascade + 'schemaVersion']?.[0];
|
|
273
|
-
}
|
|
274
|
-
if (!dateOfBirth) {
|
|
275
|
-
dateOfBirth = props[CASCADE_NAMESPACES.cascade + 'dateOfBirth']?.[0];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return { name, age, schemaVersion, dateOfBirth };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ─── Display Helpers ─────────────────────────────────────────────────────────
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Normalize provenance label for consistent display.
|
|
287
|
-
* Converts "core:ClinicalGenerated" to "cascade:ClinicalGenerated" since
|
|
288
|
-
* the "core" and "cascade" prefixes map to the same namespace.
|
|
289
|
-
*/
|
|
290
|
-
export function normalizeProvenanceLabel(label: string): string {
|
|
291
|
-
if (label.startsWith('core:')) {
|
|
292
|
-
return 'cascade:' + label.slice(5);
|
|
293
|
-
}
|
|
294
|
-
return label;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Extract a display label from already-shortened property keys.
|
|
299
|
-
*/
|
|
300
|
-
export function extractLabelFromProps(properties: Record<string, string>): string | undefined {
|
|
301
|
-
const labelKeys = [
|
|
302
|
-
'health:medicationName',
|
|
303
|
-
'health:conditionName',
|
|
304
|
-
'health:allergen',
|
|
305
|
-
'clinical:supplementName',
|
|
306
|
-
'clinical:vaccineName',
|
|
307
|
-
'health:vaccineName',
|
|
308
|
-
'health:testName',
|
|
309
|
-
'health:labTestName',
|
|
310
|
-
'foaf:name',
|
|
311
|
-
'dcterms:title',
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
for (const key of labelKeys) {
|
|
315
|
-
if (properties[key]) {
|
|
316
|
-
return properties[key];
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Select the most relevant properties for display based on data type.
|
|
324
|
-
*/
|
|
325
|
-
export function selectKeyProperties(
|
|
326
|
-
typeName: string,
|
|
327
|
-
properties: Record<string, string>,
|
|
328
|
-
): Record<string, string> {
|
|
329
|
-
const result: Record<string, string> = {};
|
|
330
|
-
|
|
331
|
-
// Common properties to always show if present
|
|
332
|
-
const commonKeys = ['cascade:dataProvenance', 'cascade:schemaVersion'];
|
|
333
|
-
|
|
334
|
-
// Type-specific key properties
|
|
335
|
-
const typeKeys: Record<string, string[]> = {
|
|
336
|
-
medications: [
|
|
337
|
-
'health:dose',
|
|
338
|
-
'health:frequency',
|
|
339
|
-
'health:route',
|
|
340
|
-
'health:isActive',
|
|
341
|
-
'health:startDate',
|
|
342
|
-
'health:prescriber',
|
|
343
|
-
'health:rxNormCode',
|
|
344
|
-
'health:medicationClass',
|
|
345
|
-
],
|
|
346
|
-
conditions: [
|
|
347
|
-
'health:status',
|
|
348
|
-
'health:onsetDate',
|
|
349
|
-
'health:icd10Code',
|
|
350
|
-
'health:snomedCode',
|
|
351
|
-
'health:conditionClass',
|
|
352
|
-
],
|
|
353
|
-
allergies: [
|
|
354
|
-
'health:allergyCategory',
|
|
355
|
-
'health:reaction',
|
|
356
|
-
'health:allergySeverity',
|
|
357
|
-
'health:onsetDate',
|
|
358
|
-
],
|
|
359
|
-
'lab-results': [
|
|
360
|
-
'health:value',
|
|
361
|
-
'health:unit',
|
|
362
|
-
'health:referenceRange',
|
|
363
|
-
'health:interpretation',
|
|
364
|
-
'health:effectiveDate',
|
|
365
|
-
],
|
|
366
|
-
immunizations: [
|
|
367
|
-
'health:vaccineDate',
|
|
368
|
-
'health:lotNumber',
|
|
369
|
-
'health:site',
|
|
370
|
-
'health:manufacturer',
|
|
371
|
-
],
|
|
372
|
-
supplements: [
|
|
373
|
-
'clinical:dose',
|
|
374
|
-
'clinical:frequency',
|
|
375
|
-
'clinical:form',
|
|
376
|
-
'clinical:isActive',
|
|
377
|
-
'clinical:evidenceStrength',
|
|
378
|
-
],
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const keysToShow = [...(typeKeys[typeName] ?? []), ...commonKeys];
|
|
382
|
-
|
|
383
|
-
for (const key of keysToShow) {
|
|
384
|
-
if (properties[key]) {
|
|
385
|
-
result[key] = properties[key];
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// If no specific keys matched, show first few properties
|
|
390
|
-
if (Object.keys(result).length === 0) {
|
|
391
|
-
const allKeys = Object.keys(properties);
|
|
392
|
-
for (const key of allKeys.slice(0, 5)) {
|
|
393
|
-
result[key] = properties[key];
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return result;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// ─── Export Helpers ──────────────────────────────────────────────────────────
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Recursively copy a directory.
|
|
404
|
-
*/
|
|
405
|
-
export async function copyDirectory(src: string, dest: string): Promise<void> {
|
|
406
|
-
await fs.mkdir(dest, { recursive: true });
|
|
407
|
-
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
408
|
-
|
|
409
|
-
for (const entry of entries) {
|
|
410
|
-
const srcPath = path.join(src, entry.name);
|
|
411
|
-
const destPath = path.join(dest, entry.name);
|
|
412
|
-
|
|
413
|
-
if (entry.isDirectory()) {
|
|
414
|
-
await copyDirectory(srcPath, destPath);
|
|
415
|
-
} else {
|
|
416
|
-
await fs.copyFile(srcPath, destPath);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Create a ZIP archive of the pod directory using the archiver package.
|
|
423
|
-
*/
|
|
424
|
-
export async function createZipArchive(sourceDir: string, outputPath: string): Promise<void> {
|
|
425
|
-
// Dynamic import of archiver
|
|
426
|
-
let archiverModule: { default: (format: string, options?: Record<string, unknown>) => import('archiver').Archiver };
|
|
427
|
-
try {
|
|
428
|
-
archiverModule = await import('archiver') as typeof archiverModule;
|
|
429
|
-
} catch {
|
|
430
|
-
throw new Error(
|
|
431
|
-
'The "archiver" package is required for ZIP export. ' +
|
|
432
|
-
'Install it with: npm install archiver',
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const { createWriteStream } = await import('fs');
|
|
437
|
-
|
|
438
|
-
return new Promise((resolve, reject) => {
|
|
439
|
-
const output = createWriteStream(outputPath);
|
|
440
|
-
const archive = archiverModule.default('zip', { zlib: { level: 9 } });
|
|
441
|
-
|
|
442
|
-
output.on('close', () => resolve());
|
|
443
|
-
archive.on('error', (err: Error) => reject(err));
|
|
444
|
-
|
|
445
|
-
archive.pipe(output);
|
|
446
|
-
archive.directory(sourceDir, path.basename(sourceDir));
|
|
447
|
-
void archive.finalize();
|
|
448
|
-
});
|
|
449
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cascade pod <subcommand>
|
|
3
|
-
*
|
|
4
|
-
* Manage Cascade Pod structures.
|
|
5
|
-
*
|
|
6
|
-
* Subcommands:
|
|
7
|
-
* init <directory> Initialize a new Cascade Pod
|
|
8
|
-
* query <pod-dir> Query data within a pod
|
|
9
|
-
* export <pod-dir> Export pod data
|
|
10
|
-
* info <pod-dir> Show pod metadata and statistics
|
|
11
|
-
*
|
|
12
|
-
* This module delegates to focused subcommand modules:
|
|
13
|
-
* - init.ts Pod initialization with templates
|
|
14
|
-
* - query.ts Data querying by type
|
|
15
|
-
* - export.ts Pod export (zip/directory)
|
|
16
|
-
* - info.ts Pod metadata and statistics
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { Command } from 'commander';
|
|
20
|
-
import { registerInitSubcommand } from './init.js';
|
|
21
|
-
import { registerQuerySubcommand } from './query.js';
|
|
22
|
-
import { registerExportSubcommand } from './export.js';
|
|
23
|
-
import { registerInfoSubcommand } from './info.js';
|
|
24
|
-
|
|
25
|
-
export function registerPodCommand(program: Command): void {
|
|
26
|
-
const pod = program.command('pod').description('Manage Cascade Pod structures');
|
|
27
|
-
|
|
28
|
-
registerInitSubcommand(pod, program);
|
|
29
|
-
registerQuerySubcommand(pod, program);
|
|
30
|
-
registerExportSubcommand(pod, program);
|
|
31
|
-
registerInfoSubcommand(pod, program);
|
|
32
|
-
}
|