@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.
Files changed (50) hide show
  1. package/dist/commands/pod/helpers.d.ts +1 -1
  2. package/dist/commands/pod/helpers.d.ts.map +1 -1
  3. package/dist/commands/pod/helpers.js +5 -20
  4. package/dist/commands/pod/helpers.js.map +1 -1
  5. package/package.json +17 -5
  6. package/.dockerignore +0 -7
  7. package/.eslintrc.json +0 -23
  8. package/.prettierrc +0 -7
  9. package/Dockerfile +0 -18
  10. package/src/commands/capabilities.ts +0 -235
  11. package/src/commands/conformance.ts +0 -447
  12. package/src/commands/convert.ts +0 -164
  13. package/src/commands/pod/export.ts +0 -85
  14. package/src/commands/pod/helpers.ts +0 -449
  15. package/src/commands/pod/index.ts +0 -32
  16. package/src/commands/pod/info.ts +0 -239
  17. package/src/commands/pod/init.ts +0 -273
  18. package/src/commands/pod/query.ts +0 -224
  19. package/src/commands/serve.ts +0 -92
  20. package/src/commands/validate.ts +0 -303
  21. package/src/index.ts +0 -58
  22. package/src/lib/fhir-converter/cascade-to-fhir.ts +0 -369
  23. package/src/lib/fhir-converter/converters-clinical.ts +0 -446
  24. package/src/lib/fhir-converter/converters-demographics.ts +0 -270
  25. package/src/lib/fhir-converter/fhir-to-cascade.ts +0 -82
  26. package/src/lib/fhir-converter/index.ts +0 -215
  27. package/src/lib/fhir-converter/types.ts +0 -318
  28. package/src/lib/mcp/audit.ts +0 -107
  29. package/src/lib/mcp/server.ts +0 -192
  30. package/src/lib/mcp/tools.ts +0 -668
  31. package/src/lib/output.ts +0 -76
  32. package/src/lib/shacl-validator.ts +0 -314
  33. package/src/lib/turtle-parser.ts +0 -277
  34. package/src/shapes/checkup.shapes.ttl +0 -1459
  35. package/src/shapes/clinical.shapes.ttl +0 -1350
  36. package/src/shapes/clinical.ttl +0 -1369
  37. package/src/shapes/core.shapes.ttl +0 -450
  38. package/src/shapes/core.ttl +0 -603
  39. package/src/shapes/coverage.shapes.ttl +0 -214
  40. package/src/shapes/coverage.ttl +0 -182
  41. package/src/shapes/health.shapes.ttl +0 -697
  42. package/src/shapes/health.ttl +0 -859
  43. package/src/shapes/pots.shapes.ttl +0 -481
  44. package/test-fixtures/fhir-bundle-example.json +0 -216
  45. package/test-fixtures/fhir-medication-example.json +0 -18
  46. package/tests/cli.test.ts +0 -126
  47. package/tests/fhir-converter.test.ts +0 -874
  48. package/tests/mcp-server.test.ts +0 -396
  49. package/tests/pod.test.ts +0 -400
  50. package/tsconfig.json +0 -24
@@ -1,447 +0,0 @@
1
- /**
2
- * cascade conformance run
3
- *
4
- * Run conformance test suite against a CLI command or self-test.
5
- *
6
- * Options:
7
- * --suite <fixtures-dir> Path to test fixtures directory
8
- * --command "<cmd>" External command to test
9
- * --self Run self-conformance tests
10
- * --json Output results as JSON
11
- * --verbose Show detailed test output
12
- */
13
-
14
- import fs from 'node:fs';
15
- import path from 'node:path';
16
- import { Command } from 'commander';
17
- import { loadShapes, validateTurtle } from '../lib/shacl-validator.js';
18
- import { parseTurtle } from '../lib/turtle-parser.js';
19
- import { printResult, printError, printVerbose, type OutputOptions } from '../lib/output.js';
20
- import type { ValidationResult } from '../lib/shacl-validator.js';
21
-
22
- // ---------------------------------------------------------------------------
23
- // Types
24
- // ---------------------------------------------------------------------------
25
-
26
- /** Shape of a single conformance fixture JSON file. */
27
- interface ConformanceFixture {
28
- id: string;
29
- description: string;
30
- dataType: string;
31
- vocabulary: string;
32
- input: Record<string, unknown>;
33
- expectedOutput: {
34
- turtle: string;
35
- validationMode: 'shacl-valid' | 'exact-match';
36
- };
37
- shouldAccept: boolean;
38
- tags: string[];
39
- shaclConstraintViolated?: string;
40
- notes?: string;
41
- }
42
-
43
- /** Result of running a single fixture. */
44
- interface FixtureResult {
45
- id: string;
46
- description: string;
47
- dataType: string;
48
- status: 'passed' | 'failed' | 'error';
49
- negative: boolean;
50
- details?: string;
51
- validationDetails?: ValidationResult;
52
- }
53
-
54
- /** Aggregate report for the full suite run. */
55
- interface SuiteReport {
56
- suite: string;
57
- mode: string;
58
- total: number;
59
- passed: number;
60
- failed: number;
61
- errors: number;
62
- results: FixtureResult[];
63
- byDataType: Record<string, { total: number; passed: number; failed: number; errors: number }>;
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // Fixture loading
68
- // ---------------------------------------------------------------------------
69
-
70
- /**
71
- * Load and parse all `.json` fixture files from the given directory.
72
- * Files are sorted by id so output order is deterministic.
73
- */
74
- function loadFixtures(suiteDir: string): ConformanceFixture[] {
75
- if (!fs.existsSync(suiteDir)) {
76
- throw new Error(`Fixtures directory not found: ${suiteDir}`);
77
- }
78
-
79
- const files = fs
80
- .readdirSync(suiteDir)
81
- .filter((f) => f.endsWith('.json'))
82
- .sort();
83
-
84
- if (files.length === 0) {
85
- throw new Error(`No .json fixture files found in ${suiteDir}`);
86
- }
87
-
88
- const fixtures: ConformanceFixture[] = [];
89
-
90
- for (const file of files) {
91
- const filePath = path.join(suiteDir, file);
92
- const raw = fs.readFileSync(filePath, 'utf-8');
93
- const fixture = JSON.parse(raw) as ConformanceFixture;
94
- fixtures.push(fixture);
95
- }
96
-
97
- return fixtures;
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // Self-conformance runner
102
- // ---------------------------------------------------------------------------
103
-
104
- /**
105
- * Run a single fixture in self-conformance mode.
106
- *
107
- * Positive fixtures (shouldAccept === true):
108
- * - Parse the expected turtle
109
- * - Run SHACL validation
110
- * - PASS if no violations; FAIL otherwise
111
- *
112
- * Negative fixtures (shouldAccept === false):
113
- * - If turtle is empty: PASS (rejection before serialization)
114
- * - If turtle is non-empty: SHACL validate — PASS if violations found, FAIL if clean
115
- */
116
- function runSelfFixture(
117
- fixture: ConformanceFixture,
118
- shapesStore: ReturnType<typeof loadShapes>['store'],
119
- shapeFiles: string[],
120
- opts: OutputOptions,
121
- ): FixtureResult {
122
- const base: Pick<FixtureResult, 'id' | 'description' | 'dataType' | 'negative'> = {
123
- id: fixture.id,
124
- description: fixture.description,
125
- dataType: fixture.dataType,
126
- negative: !fixture.shouldAccept,
127
- };
128
-
129
- const turtle = fixture.expectedOutput.turtle;
130
- const validationMode = fixture.expectedOutput.validationMode;
131
-
132
- try {
133
- if (fixture.shouldAccept) {
134
- // ---- Positive fixture ----
135
-
136
- // Step 1: Parse the turtle
137
- const parseResult = parseTurtle(turtle);
138
- if (!parseResult.success) {
139
- return {
140
- ...base,
141
- status: 'failed',
142
- details: `Turtle parse error: ${parseResult.errors.join('; ')}`,
143
- };
144
- }
145
-
146
- // Step 2: SHACL validation
147
- const validation = validateTurtle(turtle, shapesStore, shapeFiles, fixture.id);
148
-
149
- if (validationMode === 'shacl-valid') {
150
- if (validation.valid) {
151
- printVerbose(` [${fixture.id}] SHACL valid (${validation.quadCount} quads)`, opts);
152
- return { ...base, status: 'passed', validationDetails: validation };
153
- } else {
154
- const violations = validation.results
155
- .filter((r) => r.severity === 'violation')
156
- .map((r) => `${r.shape}.${r.property}: ${r.message}`)
157
- .join('; ');
158
- return {
159
- ...base,
160
- status: 'failed',
161
- details: `SHACL violations: ${violations}`,
162
- validationDetails: validation,
163
- };
164
- }
165
- } else if (validationMode === 'exact-match') {
166
- // Exact-match: for now, verify it parses and is SHACL-valid
167
- // Full normalized triple-by-triple equivalence is deferred
168
- if (validation.valid) {
169
- printVerbose(
170
- ` [${fixture.id}] exact-match: parsed & SHACL valid (full normalization deferred)`,
171
- opts,
172
- );
173
- return { ...base, status: 'passed', validationDetails: validation };
174
- } else {
175
- const violations = validation.results
176
- .filter((r) => r.severity === 'violation')
177
- .map((r) => `${r.shape}.${r.property}: ${r.message}`)
178
- .join('; ');
179
- return {
180
- ...base,
181
- status: 'failed',
182
- details: `SHACL violations (exact-match mode): ${violations}`,
183
- validationDetails: validation,
184
- };
185
- }
186
- } else {
187
- return {
188
- ...base,
189
- status: 'error',
190
- details: `Unknown validationMode: ${validationMode as string}`,
191
- };
192
- }
193
- } else {
194
- // ---- Negative fixture ----
195
-
196
- if (turtle === '') {
197
- // Empty turtle means the SDK should reject before serialization — PASS
198
- printVerbose(` [${fixture.id}] negative: empty turtle (pre-serialization rejection)`, opts);
199
- return { ...base, status: 'passed' };
200
- }
201
-
202
- // Non-empty turtle: validate and expect violations
203
- const parseResult = parseTurtle(turtle);
204
- if (!parseResult.success) {
205
- // Parse failure on negative fixture = PASS (malformed is invalid)
206
- printVerbose(` [${fixture.id}] negative: turtle parse error (expected)`, opts);
207
- return { ...base, status: 'passed', details: 'Turtle parse error (expected for negative fixture)' };
208
- }
209
-
210
- const validation = validateTurtle(turtle, shapesStore, shapeFiles, fixture.id);
211
-
212
- if (!validation.valid) {
213
- // Violations found — PASS for negative fixture
214
- const violations = validation.results
215
- .filter((r) => r.severity === 'violation')
216
- .map((r) => `${r.shape}.${r.property}: ${r.message}`)
217
- .join('; ');
218
- printVerbose(` [${fixture.id}] negative: SHACL violations found (expected): ${violations}`, opts);
219
- return { ...base, status: 'passed', validationDetails: validation };
220
- } else {
221
- // No violations — FAIL for negative fixture
222
- return {
223
- ...base,
224
- status: 'failed',
225
- details: `Expected SHACL violations but data validated clean. Expected: ${fixture.shaclConstraintViolated ?? 'unspecified'}`,
226
- validationDetails: validation,
227
- };
228
- }
229
- }
230
- } catch (err: unknown) {
231
- const message = err instanceof Error ? err.message : String(err);
232
- return {
233
- ...base,
234
- status: 'error',
235
- details: `Unexpected error: ${message}`,
236
- };
237
- }
238
- }
239
-
240
- // ---------------------------------------------------------------------------
241
- // Report generation
242
- // ---------------------------------------------------------------------------
243
-
244
- /**
245
- * Build the aggregate suite report from individual fixture results.
246
- */
247
- function buildReport(
248
- suitePath: string,
249
- mode: string,
250
- results: FixtureResult[],
251
- ): SuiteReport {
252
- const byDataType: SuiteReport['byDataType'] = {};
253
-
254
- for (const r of results) {
255
- if (!byDataType[r.dataType]) {
256
- byDataType[r.dataType] = { total: 0, passed: 0, failed: 0, errors: 0 };
257
- }
258
- const group = byDataType[r.dataType];
259
- group.total++;
260
- if (r.status === 'passed') group.passed++;
261
- else if (r.status === 'failed') group.failed++;
262
- else group.errors++;
263
- }
264
-
265
- return {
266
- suite: suitePath,
267
- mode,
268
- total: results.length,
269
- passed: results.filter((r) => r.status === 'passed').length,
270
- failed: results.filter((r) => r.status === 'failed').length,
271
- errors: results.filter((r) => r.status === 'error').length,
272
- results,
273
- byDataType,
274
- };
275
- }
276
-
277
- /**
278
- * Print a human-readable report to stdout.
279
- */
280
- function printHumanReport(report: SuiteReport, opts: OutputOptions): void {
281
- console.log('');
282
- console.log('Cascade Protocol Conformance Test Suite');
283
- console.log('========================================');
284
- console.log(`Suite: ${report.suite}`);
285
- console.log(`Mode: ${report.mode === 'self' ? 'self-conformance' : report.mode}`);
286
- console.log(`Fixtures: ${report.total}`);
287
- console.log('');
288
- console.log('Running tests...');
289
- console.log('');
290
-
291
- // Group results by dataType, preserving insertion order
292
- const dataTypes: string[] = [];
293
- const grouped: Record<string, FixtureResult[]> = {};
294
-
295
- for (const r of report.results) {
296
- if (!grouped[r.dataType]) {
297
- grouped[r.dataType] = [];
298
- dataTypes.push(r.dataType);
299
- }
300
- grouped[r.dataType].push(r);
301
- }
302
-
303
- for (const dt of dataTypes) {
304
- const fixtures = grouped[dt];
305
- console.log(` ${dt} (${fixtures.length} fixtures)`);
306
-
307
- for (const r of fixtures) {
308
- const icon = r.status === 'passed' ? '\u2713' : r.status === 'failed' ? '\u2717' : '!';
309
- const negativeTag = r.negative ? '[negative] ' : '';
310
- const statusLine = ` ${icon} ${r.id}: ${negativeTag}${r.description}`;
311
- console.log(statusLine);
312
-
313
- if (r.status !== 'passed' && r.details) {
314
- console.log(` ${r.details}`);
315
- }
316
-
317
- // In verbose mode, show validation details even for passing tests
318
- if (opts.verbose && r.validationDetails) {
319
- const vd = r.validationDetails;
320
- console.log(` Quads: ${vd.quadCount}, Shapes: [${vd.shapesUsed.join(', ')}]`);
321
- if (vd.results.length > 0) {
322
- for (const issue of vd.results) {
323
- console.log(` - [${issue.severity}] ${issue.shape}.${issue.property}: ${issue.message}`);
324
- }
325
- }
326
- }
327
- }
328
-
329
- console.log('');
330
- }
331
-
332
- // Summary line
333
- const parts: string[] = [];
334
- parts.push(`${report.passed} passed`);
335
- if (report.failed > 0) parts.push(`${report.failed} failed`);
336
- else parts.push('0 failed');
337
- if (report.errors > 0) parts.push(`${report.errors} errors`);
338
- else parts.push('0 errors');
339
-
340
- console.log(`Results: ${parts.join(', ')}`);
341
-
342
- const exitCode = report.failed > 0 || report.errors > 0 ? 1 : 0;
343
- console.log(`Exit code: ${exitCode}`);
344
- }
345
-
346
- /**
347
- * Print the JSON report using the output library.
348
- */
349
- function printJsonReport(report: SuiteReport, opts: OutputOptions): void {
350
- // Strip validationDetails from results for cleaner JSON output
351
- const cleanResults = report.results.map((r) => {
352
- const { validationDetails: _vd, ...rest } = r;
353
- return rest;
354
- });
355
-
356
- printResult(
357
- {
358
- suite: report.suite,
359
- mode: report.mode,
360
- total: report.total,
361
- passed: report.passed,
362
- failed: report.failed,
363
- errors: report.errors,
364
- results: cleanResults,
365
- byDataType: report.byDataType,
366
- },
367
- opts,
368
- );
369
- }
370
-
371
- // ---------------------------------------------------------------------------
372
- // Command registration
373
- // ---------------------------------------------------------------------------
374
-
375
- export function registerConformanceCommand(program: Command): void {
376
- const conformance = program
377
- .command('conformance')
378
- .description('Run conformance test suite');
379
-
380
- conformance
381
- .command('run')
382
- .description('Execute conformance tests')
383
- .requiredOption('--suite <fixtures-dir>', 'Path to test fixtures directory')
384
- .option('--command <cmd>', 'External command to test against')
385
- .option('--self', 'Run self-conformance tests')
386
- .action(
387
- async (options: {
388
- suite: string;
389
- command?: string;
390
- self?: boolean;
391
- }) => {
392
- const globalOpts = program.opts() as OutputOptions;
393
-
394
- if (!options.command && !options.self) {
395
- printError('Either --command or --self must be specified', globalOpts);
396
- process.exitCode = 1;
397
- return;
398
- }
399
-
400
- // External command mode: not yet supported
401
- if (options.command) {
402
- printError('External command mode not yet supported', globalOpts);
403
- process.exitCode = 1;
404
- return;
405
- }
406
-
407
- // Resolve suite directory
408
- const suitePath = path.resolve(options.suite);
409
- printVerbose(`Conformance suite: ${suitePath}`, globalOpts);
410
- printVerbose('Running self-conformance tests', globalOpts);
411
-
412
- try {
413
- // Load fixtures
414
- const fixtures = loadFixtures(suitePath);
415
- printVerbose(`Loaded ${fixtures.length} fixture(s)`, globalOpts);
416
-
417
- // Load SHACL shapes once
418
- const { store: shapesStore, shapeFiles } = loadShapes();
419
- printVerbose(`Loaded shapes: ${shapeFiles.join(', ')}`, globalOpts);
420
-
421
- // Run each fixture
422
- const results: FixtureResult[] = [];
423
-
424
- for (const fixture of fixtures) {
425
- const result = runSelfFixture(fixture, shapesStore, shapeFiles, globalOpts);
426
- results.push(result);
427
- }
428
-
429
- // Build and output report
430
- const report = buildReport(options.suite, 'self', results);
431
-
432
- if (globalOpts.json) {
433
- printJsonReport(report, globalOpts);
434
- } else {
435
- printHumanReport(report, globalOpts);
436
- }
437
-
438
- // Set exit code
439
- process.exitCode = report.failed > 0 || report.errors > 0 ? 1 : 0;
440
- } catch (err: unknown) {
441
- const message = err instanceof Error ? err.message : String(err);
442
- printError(`Conformance suite failed: ${message}`, globalOpts);
443
- process.exitCode = 1;
444
- }
445
- },
446
- );
447
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * cascade convert --from <format> --to <format> [file]
3
- *
4
- * Convert between health data formats.
5
- * Supports FHIR R4, Cascade Protocol Turtle, and JSON-LD.
6
- *
7
- * Options:
8
- * --from <format> Source format (fhir|cascade|c-cda)
9
- * --to <format> Target format (turtle|jsonld|fhir|cascade)
10
- * --format <output> Output serialization format (turtle|jsonld) [default: turtle]
11
- * --json Output results as JSON envelope (machine-readable)
12
- * --verbose Show detailed conversion information
13
- *
14
- * Supports stdin piping:
15
- * cat patient.json | cascade convert --from fhir --to cascade
16
- *
17
- * Zero network calls. All conversion is local.
18
- */
19
-
20
- import { Command } from 'commander';
21
- import { readFileSync } from 'node:fs';
22
- import { printResult, printError, printVerbose, type OutputOptions } from '../lib/output.js';
23
- import { convert, detectFormat, type InputFormat, type OutputFormat } from '../lib/fhir-converter/index.js';
24
-
25
- /**
26
- * Read input from file or stdin.
27
- * If file is provided, reads from disk.
28
- * Otherwise reads all of stdin synchronously.
29
- */
30
- function readInput(file: string | undefined): string {
31
- if (file) {
32
- return readFileSync(file, 'utf-8');
33
- }
34
- // Read from stdin
35
- return readFileSync(0, 'utf-8');
36
- }
37
-
38
- export function registerConvertCommand(program: Command): void {
39
- program
40
- .command('convert')
41
- .description('Convert between health data formats')
42
- .argument('[file]', 'Input file (reads from stdin if omitted)')
43
- .requiredOption('--from <format>', 'Source format (fhir|cascade|c-cda)')
44
- .requiredOption('--to <format>', 'Target format (turtle|jsonld|fhir|cascade)')
45
- .option('--format <output>', 'Output serialization format (turtle|jsonld)', 'turtle')
46
- .action(
47
- async (
48
- file: string | undefined,
49
- options: { from: string; to: string; format: string },
50
- ) => {
51
- const globalOpts = program.opts() as OutputOptions;
52
-
53
- printVerbose(`Converting from ${options.from} to ${options.to}`, globalOpts);
54
- if (file) {
55
- printVerbose(`Input file: ${file}`, globalOpts);
56
- } else {
57
- printVerbose('Reading from stdin', globalOpts);
58
- }
59
- printVerbose(`Output format: ${options.format}`, globalOpts);
60
-
61
- // 1. Read input
62
- let input: string;
63
- try {
64
- input = readInput(file);
65
- } catch (err: any) {
66
- printError(`Failed to read input: ${err.message}`, globalOpts);
67
- process.exitCode = 1;
68
- return;
69
- }
70
-
71
- if (!input.trim()) {
72
- printError('Empty input', globalOpts);
73
- process.exitCode = 1;
74
- return;
75
- }
76
-
77
- // 2. Validate source/target formats
78
- const validInputFormats = ['fhir', 'cascade', 'c-cda'];
79
- const validOutputFormats = ['turtle', 'jsonld', 'fhir', 'cascade'];
80
-
81
- if (!validInputFormats.includes(options.from)) {
82
- printError(`Invalid source format: ${options.from}. Valid: ${validInputFormats.join(', ')}`, globalOpts);
83
- process.exitCode = 1;
84
- return;
85
- }
86
-
87
- if (!validOutputFormats.includes(options.to)) {
88
- printError(`Invalid target format: ${options.to}. Valid: ${validOutputFormats.join(', ')}`, globalOpts);
89
- process.exitCode = 1;
90
- return;
91
- }
92
-
93
- // 3. Auto-detect format if helpful (validate matches declared)
94
- const detected = detectFormat(input);
95
- if (detected && detected !== options.from) {
96
- printVerbose(
97
- `Note: Input appears to be ${detected} but --from says ${options.from}. Proceeding with declared format.`,
98
- globalOpts,
99
- );
100
- }
101
-
102
- // 4. Run conversion
103
- const outputSerialization = (options.format === 'jsonld' ? 'jsonld' : 'turtle') as 'turtle' | 'jsonld';
104
- const result = await convert(
105
- input,
106
- options.from as InputFormat,
107
- options.to as OutputFormat,
108
- outputSerialization,
109
- );
110
-
111
- // 5. Output
112
- if (!result.success) {
113
- for (const err of result.errors) {
114
- printError(err, globalOpts);
115
- }
116
- for (const warn of result.warnings) {
117
- printVerbose(`Warning: ${warn}`, globalOpts);
118
- }
119
- process.exitCode = 1;
120
- return;
121
- }
122
-
123
- // Print warnings in verbose mode
124
- for (const warn of result.warnings) {
125
- printVerbose(`Warning: ${warn}`, globalOpts);
126
- }
127
-
128
- if (globalOpts.json) {
129
- // JSON envelope for machine-readable output
130
- printResult(
131
- {
132
- success: true,
133
- from: options.from,
134
- to: options.to,
135
- format: result.format,
136
- resourceCount: result.resourceCount,
137
- warnings: result.warnings,
138
- output: result.output,
139
- resources: result.results.map(r => ({
140
- resourceType: r.resourceType,
141
- cascadeType: r.cascadeType,
142
- warnings: r.warnings,
143
- })),
144
- },
145
- globalOpts,
146
- );
147
- } else {
148
- // Direct output (Turtle, JSON-LD, or FHIR JSON)
149
- console.log(result.output);
150
-
151
- // Print summary to stderr so it does not pollute piped output
152
- if (result.resourceCount > 0) {
153
- console.error(
154
- `Converted ${result.resourceCount} resource${result.resourceCount > 1 ? 's' : ''} ` +
155
- `(${options.from} -> ${result.format})`,
156
- );
157
- }
158
- if (result.warnings.length > 0) {
159
- console.error(`${result.warnings.length} warning${result.warnings.length > 1 ? 's' : ''}`);
160
- }
161
- }
162
- },
163
- );
164
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * cascade pod export <pod-dir>
3
- *
4
- * Export pod data as a ZIP archive or directory copy.
5
- */
6
-
7
- import type { Command } from 'commander';
8
- import * as path from 'path';
9
- import { printResult, printError, printVerbose, type OutputOptions } from '../../lib/output.js';
10
- import { resolvePodDir, isDirectory, copyDirectory, createZipArchive } from './helpers.js';
11
-
12
- export function registerExportSubcommand(pod: Command, program: Command): void {
13
- pod
14
- .command('export')
15
- .description('Export pod data')
16
- .argument('<pod-dir>', 'Path to the Cascade Pod')
17
- .option('--format <fmt>', 'Export format (zip|directory)', 'zip')
18
- .option('--output <path>', 'Output path for export')
19
- .action(async (podDir: string, options: { format: string; output?: string }) => {
20
- const globalOpts = program.opts() as OutputOptions;
21
- const absDir = resolvePodDir(podDir);
22
-
23
- printVerbose(`Exporting pod: ${absDir} as ${options.format}`, globalOpts);
24
-
25
- // Validate pod exists
26
- if (!(await isDirectory(absDir))) {
27
- printError(`Pod directory not found: ${absDir}`, globalOpts);
28
- process.exitCode = 1;
29
- return;
30
- }
31
-
32
- try {
33
- if (options.format === 'directory') {
34
- // Copy to new directory
35
- const outputDir = options.output ?? `${absDir}-export`;
36
- await copyDirectory(absDir, outputDir);
37
-
38
- if (globalOpts.json) {
39
- printResult(
40
- {
41
- status: 'exported',
42
- format: 'directory',
43
- source: absDir,
44
- output: outputDir,
45
- },
46
- globalOpts,
47
- );
48
- } else {
49
- console.log(`Pod exported to directory: ${outputDir}`);
50
- }
51
- } else if (options.format === 'zip') {
52
- // Create ZIP archive
53
- const outputZip =
54
- options.output ?? `${path.basename(absDir)}.zip`;
55
- const absOutputZip = path.resolve(process.cwd(), outputZip);
56
-
57
- await createZipArchive(absDir, absOutputZip);
58
-
59
- if (globalOpts.json) {
60
- printResult(
61
- {
62
- status: 'exported',
63
- format: 'zip',
64
- source: absDir,
65
- output: absOutputZip,
66
- },
67
- globalOpts,
68
- );
69
- } else {
70
- console.log(`Pod exported to ZIP: ${absOutputZip}`);
71
- }
72
- } else {
73
- printError(
74
- `Unknown export format: ${options.format}. Use 'zip' or 'directory'.`,
75
- globalOpts,
76
- );
77
- process.exitCode = 1;
78
- }
79
- } catch (err: unknown) {
80
- const message = err instanceof Error ? err.message : String(err);
81
- printError(`Failed to export pod: ${message}`, globalOpts);
82
- process.exitCode = 1;
83
- }
84
- });
85
- }