@pd4castr/cli 0.0.1 → 0.0.3

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +252 -128
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -20,6 +20,7 @@ yarn link
20
20
  yarn dev
21
21
 
22
22
  # from a model project, link the module
23
+ # ex. https://github.com/pipelabs/pd4castr-model-examples/tree/main/examples/python-demo
23
24
  yarn link @pd4castr/cli
24
25
 
25
26
  # from that project, execute a command
package/dist/index.js CHANGED
@@ -1,4 +1,49 @@
1
1
  #!/usr/bin/env node
2
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
3
+ var __typeError = (msg) => {
4
+ throw TypeError(msg);
5
+ };
6
+ var __using = (stack, value, async) => {
7
+ if (value != null) {
8
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
9
+ var dispose, inner;
10
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
11
+ if (dispose === void 0) {
12
+ dispose = value[__knownSymbol("dispose")];
13
+ if (async) inner = dispose;
14
+ }
15
+ if (typeof dispose !== "function") __typeError("Object not disposable");
16
+ if (inner) dispose = function() {
17
+ try {
18
+ inner.call(this);
19
+ } catch (e) {
20
+ return Promise.reject(e);
21
+ }
22
+ };
23
+ stack.push([async, dispose, value]);
24
+ } else if (async) {
25
+ stack.push([async]);
26
+ }
27
+ return value;
28
+ };
29
+ var __callDispose = (stack, error, hasError) => {
30
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
31
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
32
+ };
33
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
34
+ var next = (it) => {
35
+ while (it = stack.pop()) {
36
+ try {
37
+ var result = it[1] && it[1].call(it[2]);
38
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
39
+ } catch (e) {
40
+ fail(e);
41
+ }
42
+ }
43
+ if (hasError) throw error;
44
+ };
45
+ return next();
46
+ };
2
47
 
3
48
  // src/program.ts
4
49
  import { Command } from "commander";
@@ -6,7 +51,7 @@ import { Command } from "commander";
6
51
  // package.json
7
52
  var package_default = {
8
53
  name: "@pd4castr/cli",
9
- version: "0.0.1",
54
+ version: "0.0.3",
10
55
  description: "CLI tool for creating, testing, and publishing pd4castr models",
11
56
  main: "dist/index.js",
12
57
  type: "module",
@@ -72,6 +117,7 @@ var package_default = {
72
117
  ky: "1.8.2",
73
118
  lilconfig: "3.1.3",
74
119
  ora: "8.2.0",
120
+ slugify: "1.6.6",
75
121
  tiged: "2.12.7",
76
122
  "tiny-invariant": "1.3.3",
77
123
  zod: "4.0.14"
@@ -112,9 +158,9 @@ function getTemplatePath(template) {
112
158
 
113
159
  // src/utils/is-existing-path.ts
114
160
  import fs from "fs/promises";
115
- async function isExistingPath(path8) {
161
+ async function isExistingPath(path12) {
116
162
  try {
117
- await fs.access(path8);
163
+ await fs.access(path12);
118
164
  return true;
119
165
  } catch {
120
166
  return false;
@@ -148,8 +194,11 @@ async function handleAction() {
148
194
  try {
149
195
  await fetchTemplate(template, projectName);
150
196
  spinner.succeed("Template fetched successfully");
151
- } catch {
197
+ } catch (error) {
152
198
  spinner.fail("Error fetching template");
199
+ if (error instanceof Error) {
200
+ console.error(error.message);
201
+ }
153
202
  process.exit(1);
154
203
  }
155
204
  }
@@ -183,8 +232,10 @@ var AUTH0_CLIENT_ID = "Q5tQNF57cQlVXnVsqnU0hhgy92rVb03W";
183
232
  var AUTH0_AUDIENCE = "https://api.pd4castr.com.au";
184
233
  var GLOBAL_CONFIG_FILE = ".pd4castr";
185
234
  var PROJECT_CONFIG_FILE = ".pd4castrrc.json";
186
- var API_URL = "https://api.pd4castr.com.au";
187
- var TEST_INPUT_DATA_DIR = "test_data";
235
+ var API_URL = "https://pd4castr-api.pipelabs.app";
236
+ var TEST_INPUT_DATA_DIR = "test_input";
237
+ var TEST_OUTPUT_DATA_DIR = "test_output";
238
+ var TEST_OUTPUT_FILENAME = `output-${Date.now()}.json`;
188
239
 
189
240
  // src/schemas/global-config-schema.ts
190
241
  import { z } from "zod";
@@ -335,19 +386,20 @@ function registerLoginCommand(program2) {
335
386
  // src/commands/test/handle-action.ts
336
387
  import ora3 from "ora";
337
388
  import express from "express";
338
- import path5 from "path";
389
+ import path8 from "path";
390
+ import slugify from "slugify";
339
391
  import { ExecaError } from "execa";
340
392
  import { ZodError as ZodError2 } from "zod";
341
393
 
342
394
  // src/config/load-project-config.ts
395
+ import path4 from "path";
343
396
  import { lilconfig } from "lilconfig";
344
397
 
345
398
  // src/schemas/project-config-schema.ts
346
399
  import { z as z2 } from "zod";
347
400
  var aemoDataFetcherSchema = z2.object({
348
- key: z2.string(),
349
401
  type: z2.literal("AEMO_MMS"),
350
- checkInterval: z2.int().positive(),
402
+ checkInterval: z2.number().int().min(60),
351
403
  config: z2.object({
352
404
  checkQuery: z2.string(),
353
405
  fetchQuery: z2.string()
@@ -356,13 +408,26 @@ var aemoDataFetcherSchema = z2.object({
356
408
  var dataFetcherSchema = z2.discriminatedUnion("type", [aemoDataFetcherSchema]);
357
409
  var modelInputSchema = z2.object({
358
410
  key: z2.string(),
359
- inputSource: z2.string(),
360
- dataFetcher: z2.string(),
361
- trigger: z2.string()
411
+ inputSource: z2.string().optional(),
412
+ trigger: z2.enum(["WAIT_FOR_LATEST_FILE", "USE_MOST_RECENT_FILE"]),
413
+ fetcher: dataFetcherSchema.optional()
414
+ });
415
+ var modelOutputSchema = z2.object({
416
+ name: z2.string(),
417
+ seriesKey: z2.boolean(),
418
+ colour: z2.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
362
419
  });
363
420
  var projectConfigSchema = z2.object({
364
- dataFetchers: z2.array(dataFetcherSchema).default([]),
365
- modelInputs: z2.array(modelInputSchema).default([])
421
+ $$id: z2.string().nullable(),
422
+ $$modelGroupID: z2.string().nullable(),
423
+ $$revision: z2.number().int().min(0).default(0),
424
+ $$dockerImage: z2.string().nullable(),
425
+ name: z2.string(),
426
+ metadata: z2.record(z2.string(), z2.any()).optional(),
427
+ forecastVariable: z2.enum(["PRICE"]),
428
+ timeHorizon: z2.enum(["ACTUAL", "DAY_AHEAD", "WEEK_AHEAD"]),
429
+ inputs: z2.array(modelInputSchema),
430
+ outputs: z2.array(modelOutputSchema)
366
431
  });
367
432
 
368
433
  // src/config/parse-project-config.ts
@@ -380,23 +445,30 @@ async function loadProjectConfig() {
380
445
  "No config found (docs: https://github.com/pipelabs/pd4castr-model-examples/blob/main/docs/005-config.md)."
381
446
  );
382
447
  }
383
- return parseProjectConfig(result.config);
448
+ const config = parseProjectConfig(result.config);
449
+ const projectRoot = path4.dirname(result.filepath);
450
+ return {
451
+ config,
452
+ projectRoot
453
+ };
384
454
  }
385
455
 
386
- // src/utils/get-cwd.ts
387
- function getCWD() {
388
- if (!process.env.INIT_CWD) {
389
- throw new Error("INIT_CWD environment variable is not set");
390
- }
391
- return process.env.INIT_CWD;
456
+ // src/utils/create-link.ts
457
+ var ESC = "\x1B";
458
+ var OSC = `${ESC}]`;
459
+ var SEP = ";";
460
+ function createLink(text, url) {
461
+ const start = `${OSC}8${SEP}${SEP}${url}${ESC}\\`;
462
+ const end = `${OSC}8${SEP}${SEP}${ESC}\\`;
463
+ return `${start}${text}${end}`;
392
464
  }
393
465
 
394
466
  // src/commands/test/utils/build-docker-image.ts
395
467
  import { execa } from "execa";
396
- async function buildDockerImage(dockerImage) {
468
+ async function buildDockerImage(dockerImage, ctx) {
397
469
  try {
398
470
  await execa("docker", ["build", "-t", dockerImage, "."], {
399
- cwd: getCWD(),
471
+ cwd: ctx.projectRoot,
400
472
  stdio: "pipe"
401
473
  });
402
474
  } catch (error) {
@@ -405,37 +477,60 @@ async function buildDockerImage(dockerImage) {
405
477
  }
406
478
 
407
479
  // src/commands/test/utils/create-input-handler.ts
408
- import path4 from "path";
409
- function createInputHandler(inputFilesPath, modelIOChecks) {
480
+ import path5 from "path";
481
+ function createInputHandler(inputFilesPath, modelIOChecks, ctx) {
410
482
  return (req, res) => {
411
483
  if (!modelIOChecks.isValidInput(req.params.filename)) {
412
484
  return res.status(404).json({ error: "File not found" });
413
485
  }
414
486
  modelIOChecks.trackInputHandled(req.params.filename);
415
- const filePath = path4.join(getCWD(), inputFilesPath, req.params.filename);
487
+ const filePath = path5.join(
488
+ ctx.projectRoot,
489
+ inputFilesPath,
490
+ req.params.filename
491
+ );
416
492
  return res.sendFile(filePath);
417
493
  };
418
494
  }
419
495
 
420
496
  // src/commands/test/utils/create-output-handler.ts
421
- function createOutputHandler(modelIOChecks) {
422
- return (_, res) => {
497
+ import path6 from "path";
498
+ import fs4 from "fs/promises";
499
+ function createOutputHandler(modelIOChecks, ctx) {
500
+ return async (req, res) => {
423
501
  modelIOChecks.trackOutputHandled();
424
- return res.status(200).json({ message: "Output received" });
502
+ const outputPath = path6.join(ctx.projectRoot, TEST_OUTPUT_DATA_DIR);
503
+ await fs4.mkdir(outputPath, { recursive: true });
504
+ const outputFilePath = path6.join(outputPath, TEST_OUTPUT_FILENAME);
505
+ const outputData = JSON.stringify(req.body, null, 2);
506
+ await fs4.writeFile(outputFilePath, outputData, "utf8");
507
+ return res.status(200).json({ success: true });
425
508
  };
426
509
  }
427
510
 
428
- // src/utils/get-test-data-filename.ts
429
- function getTestDataFilename(modelInput) {
430
- return `${modelInput.inputSource}-${modelInput.key}.json`;
511
+ // src/commands/test/utils/check-input-files.ts
512
+ import path7 from "path";
513
+ async function checkInputFiles(inputFiles, inputDataPath, ctx) {
514
+ for (const inputFile of inputFiles) {
515
+ const filePath = path7.join(ctx.projectRoot, inputDataPath, inputFile);
516
+ const exists = await isExistingPath(filePath);
517
+ if (!exists) {
518
+ throw new Error(
519
+ `Input data not found (${inputFile}) - did you need to run \`pd4castr fetch\`?`
520
+ );
521
+ }
522
+ }
523
+ }
524
+
525
+ // src/utils/get-input-filename.ts
526
+ function getInputFilename(modelInput) {
527
+ return `${modelInput.key}.json`;
431
528
  }
432
529
 
433
- // src/commands/test/utils/get-test-data-filenames.ts
434
- function getTestDataFilenames(config) {
435
- const testDataFilenames = config.modelInputs.map(
436
- (modelInput) => getTestDataFilename(modelInput)
437
- );
438
- return testDataFilenames;
530
+ // src/commands/test/utils/get-input-files.ts
531
+ function getInputFiles(config) {
532
+ const inputFiles = config.inputs.map((input2) => getInputFilename(input2));
533
+ return inputFiles;
439
534
  }
440
535
 
441
536
  // src/commands/test/utils/model-io-checks.ts
@@ -477,15 +572,15 @@ import { execa as execa2 } from "execa";
477
572
  // src/commands/test/utils/get-input-env.ts
478
573
  function getInputEnv(modelInput, webserverURL) {
479
574
  const variableName = modelInput.key.toUpperCase();
480
- const filename = getTestDataFilename(modelInput);
575
+ const filename = getInputFilename(modelInput);
481
576
  const inputFileURL = `${webserverURL}/input/${filename}`;
482
577
  return `INPUT_${variableName}_URL=${inputFileURL}`;
483
578
  }
484
579
 
485
580
  // src/commands/test/utils/run-model-container.ts
486
- async function runModelContainer(dockerImage, config, webserverURL) {
487
- const inputEnvs = config.modelInputs.map(
488
- (modelInput) => getInputEnv(modelInput, webserverURL)
581
+ async function runModelContainer(dockerImage, webserverURL, ctx) {
582
+ const inputEnvs = ctx.config.inputs.map(
583
+ (input2) => getInputEnv(input2, webserverURL)
489
584
  );
490
585
  const outputEnv = `OUTPUT_URL=${webserverURL}/output`;
491
586
  const envs = [...inputEnvs, outputEnv];
@@ -497,7 +592,10 @@ async function runModelContainer(dockerImage, config, webserverURL) {
497
592
  ...envs.flatMap((env) => ["--env", env]),
498
593
  dockerImage
499
594
  ];
500
- await execa2("docker", args, { cwd: getCWD(), stdio: "pipe" });
595
+ await execa2("docker", args, {
596
+ cwd: ctx.projectRoot,
597
+ stdio: "pipe"
598
+ });
501
599
  } catch (error) {
502
600
  throw new Error("Failed to run model container", { cause: error });
503
601
  }
@@ -507,57 +605,87 @@ async function runModelContainer(dockerImage, config, webserverURL) {
507
605
  async function startWebServer(app, port) {
508
606
  return new Promise((resolve) => {
509
607
  const server = app.listen(port, () => {
510
- resolve(server);
608
+ resolve({
609
+ server,
610
+ [Symbol.dispose]: () => server.close()
611
+ });
511
612
  });
512
613
  });
513
614
  }
514
615
 
515
616
  // src/commands/test/handle-action.ts
516
617
  async function handleAction3(options) {
517
- const spinner = ora3("Starting model tests...").start();
518
- const app = express();
519
- const server = await startWebServer(app, options.port);
618
+ var _stack = [];
520
619
  try {
521
- const projectConfig = await loadProjectConfig();
522
- spinner.start("Building docker image");
523
- await buildDockerImage(options.dockerImage);
524
- spinner.succeed(`Built docker image (${options.dockerImage})`);
525
- const inputFiles = getTestDataFilenames(projectConfig);
526
- const modelIOChecks = new ModelIOChecks({ inputFiles });
527
- spinner.succeed(`Found ${inputFiles.length} input data files`);
528
- const handleInput = createInputHandler(options.inputData, modelIOChecks);
529
- const handleOutput = createOutputHandler(modelIOChecks);
530
- app.use("/data", express.static(path5.join(getCWD(), options.inputData)));
531
- app.get("/input/:filename", handleInput);
532
- app.put("/output", handleOutput);
533
- spinner.start("Running model container");
534
- const webserverURL = `http://localhost:${options.port}`;
535
- await runModelContainer(options.dockerImage, projectConfig, webserverURL);
536
- spinner.succeed("Model run complete");
537
- for (const file of inputFiles) {
538
- const status = modelIOChecks.isInputHandled(file) ? "\u2714" : "\u2718";
539
- console.log(` ${status} Input Fetched - ${file}`);
540
- }
541
- const outputStatus = modelIOChecks.isOutputHandled() ? "\u2714" : "\u2718";
542
- console.log(` ${outputStatus} Output Uploaded`);
543
- if (modelIOChecks.isInputsHandled() && modelIOChecks.isOutputHandled()) {
544
- spinner.succeed("Model I/O test passed");
545
- } else {
546
- spinner.fail("Model I/O test failed");
547
- }
548
- } catch (error) {
549
- if (error instanceof ZodError2) {
550
- spinner.fail("Config validation failed");
551
- logZodIssues(error);
552
- } else if (error instanceof Error) {
553
- spinner.fail(error.message);
554
- }
555
- if (error instanceof Error && error.cause instanceof ExecaError) {
556
- console.error(error.cause.stderr);
620
+ const spinner = ora3("Starting model tests...").info();
621
+ const app = express();
622
+ const webServer = __using(_stack, await startWebServer(app, options.port));
623
+ try {
624
+ const ctx = await loadProjectConfig();
625
+ const inputFiles = getInputFiles(ctx.config);
626
+ spinner.start("Checking test input data files");
627
+ await checkInputFiles(inputFiles, options.inputDir, ctx);
628
+ spinner.succeed(`Found ${inputFiles.length} test input data files`);
629
+ spinner.start("Building docker image");
630
+ const sluggedName = slugify(ctx.config.name, { lower: true });
631
+ const dockerImage = `pd4castr/${sluggedName}-local:${Date.now()}`;
632
+ await buildDockerImage(dockerImage, ctx);
633
+ spinner.succeed(`Built docker image (${dockerImage})`);
634
+ const modelIOChecks = new ModelIOChecks({ inputFiles });
635
+ const handleInput = createInputHandler(
636
+ options.inputDir,
637
+ modelIOChecks,
638
+ ctx
639
+ );
640
+ const handleOutput = createOutputHandler(modelIOChecks, ctx);
641
+ const inputPath = path8.join(ctx.projectRoot, options.inputDir);
642
+ app.use(express.json());
643
+ app.use("/data", express.static(inputPath));
644
+ app.get("/input/:filename", handleInput);
645
+ app.put("/output", handleOutput);
646
+ spinner.start("Running model container");
647
+ const webserverURL = `http://localhost:${options.port}`;
648
+ await runModelContainer(dockerImage, webserverURL, ctx);
649
+ spinner.succeed("Model run complete");
650
+ for (const inputFile of inputFiles) {
651
+ const status = modelIOChecks.isInputHandled(inputFile) ? "\u2714" : "\u2718";
652
+ console.log(` ${status} Input Fetched - ${inputFile}`);
653
+ }
654
+ const outputStatus = modelIOChecks.isOutputHandled() ? "\u2714" : "\u2718";
655
+ console.log(` ${outputStatus} Output Uploaded`);
656
+ if (modelIOChecks.isInputsHandled() && modelIOChecks.isOutputHandled()) {
657
+ spinner.succeed("Model I/O test passed");
658
+ } else {
659
+ spinner.fail("Model I/O test failed");
660
+ }
661
+ if (modelIOChecks.isOutputHandled()) {
662
+ const outputPath = path8.join(
663
+ ctx.projectRoot,
664
+ TEST_OUTPUT_DATA_DIR,
665
+ TEST_OUTPUT_FILENAME
666
+ );
667
+ const clickHereLink = createLink("Click here", `file://${outputPath}`);
668
+ const fileLink = createLink(TEST_OUTPUT_FILENAME, `file://${outputPath}`);
669
+ console.log(`
670
+ ${clickHereLink} to view output (${fileLink})
671
+ `);
672
+ }
673
+ } catch (error) {
674
+ if (error instanceof ZodError2) {
675
+ spinner.fail("Config validation failed");
676
+ logZodIssues(error);
677
+ } else if (error instanceof Error) {
678
+ spinner.fail(error.message);
679
+ }
680
+ if (error instanceof Error && error.cause instanceof ExecaError) {
681
+ console.error(error.cause.stderr);
682
+ }
683
+ process.exit(1);
557
684
  }
558
- process.exit(1);
685
+ } catch (_) {
686
+ var _error = _, _hasError = true;
559
687
  } finally {
560
- server.close();
688
+ __callDispose(_stack, _error, _hasError);
561
689
  }
562
690
  }
563
691
 
@@ -566,20 +694,15 @@ function registerTestCommand(program2) {
566
694
  program2.command("test").description(
567
695
  "Test a model by verifying input and output is handled correctly."
568
696
  ).option(
569
- "-i, --input-data <path>",
570
- "The path to the input data directory",
697
+ "-i, --input-dir <path>",
698
+ "The input test data directory",
571
699
  TEST_INPUT_DATA_DIR
572
- ).option(
573
- "-d, --docker-image <image>",
574
- "The Docker image to execute for testing",
575
- // TODO: determine a default value here intelligently when we
576
- // implement our image tagging strategy
577
- `pd4castr/my-model:${Date.now()}`
578
700
  ).option("-p, --port <port>", "The port to run the webserver on", "9800").action(handleAction3);
579
701
  }
580
702
 
581
703
  // src/commands/fetch/handle-action.ts
582
704
  import ora4 from "ora";
705
+ import path11 from "path";
583
706
  import { ZodError as ZodError3 } from "zod";
584
707
 
585
708
  // src/utils/get-auth.ts
@@ -598,8 +721,8 @@ async function getAuth() {
598
721
  }
599
722
 
600
723
  // src/commands/fetch/utils/fetch-aemo-data.ts
601
- import path6 from "path";
602
- import fs4 from "fs/promises";
724
+ import path9 from "path";
725
+ import fs5 from "fs/promises";
603
726
 
604
727
  // src/api.ts
605
728
  import ky2 from "ky";
@@ -608,12 +731,15 @@ var api = ky2.create({
608
731
  });
609
732
 
610
733
  // src/commands/fetch/utils/fetch-aemo-data.ts
611
- async function fetchAEMOData(dataFetcher, authCtx) {
612
- const queryPath = path6.resolve(getCWD(), dataFetcher.config.fetchQuery);
613
- const querySQL = await fs4.readFile(queryPath, "utf8");
734
+ async function fetchAEMOData(dataFetcher, authCtx, ctx) {
735
+ const queryPath = path9.resolve(
736
+ ctx.projectRoot,
737
+ dataFetcher.config.fetchQuery
738
+ );
739
+ const querySQL = await fs5.readFile(queryPath, "utf8");
614
740
  const headers = { Authorization: `Bearer ${authCtx.accessToken}` };
615
741
  const payload2 = { query: querySQL, type: "AEMO_MMS" };
616
- const result = await api.post("data-fetcher/run-query", { json: payload2, headers }).json();
742
+ const result = await api.post("data-fetcher/query", { json: payload2, headers }).json();
617
743
  return result;
618
744
  }
619
745
 
@@ -630,47 +756,45 @@ function getFetcher(type) {
630
756
  }
631
757
 
632
758
  // src/commands/fetch/utils/write-test-data.ts
633
- import path7 from "path";
634
- import fs5 from "fs/promises";
635
- async function writeTestData(output, modelInput) {
636
- const outputDir = path7.resolve(getCWD(), TEST_INPUT_DATA_DIR);
637
- await fs5.mkdir(outputDir, { recursive: true });
638
- const testDataFilename = getTestDataFilename(modelInput);
639
- const outputPath = path7.resolve(outputDir, testDataFilename);
640
- await fs5.writeFile(outputPath, JSON.stringify(output, void 0, 2));
759
+ import path10 from "path";
760
+ import fs6 from "fs/promises";
761
+ async function writeTestData(inputData, modelInput, inputDataDir, ctx) {
762
+ const inputDir = path10.resolve(ctx.projectRoot, inputDataDir);
763
+ await fs6.mkdir(inputDir, { recursive: true });
764
+ const inputFilename = getInputFilename(modelInput);
765
+ const inputPath = path10.resolve(inputDir, inputFilename);
766
+ await fs6.writeFile(inputPath, JSON.stringify(inputData, void 0, 2));
641
767
  }
642
768
 
643
769
  // src/commands/fetch/handle-action.ts
644
770
  var FETCHABLE_DATA_FETCHER_TYPES = /* @__PURE__ */ new Set(["AEMO_MMS"]);
645
- async function handleAction4() {
771
+ async function handleAction4(options) {
646
772
  const spinner = ora4("Starting data fetch...").start();
647
773
  try {
648
774
  const authCtx = await getAuth();
649
- const projectConfig = await loadProjectConfig();
650
- for (const modelInput of projectConfig.modelInputs) {
651
- const dataFetcher = projectConfig.dataFetchers.find(
652
- (dataFetcher2) => dataFetcher2.key === modelInput.dataFetcher
653
- );
654
- if (!dataFetcher) {
655
- spinner.warn(
656
- `\`${modelInput.key}\` - input has no data fetcher, skipping`
657
- );
775
+ const ctx = await loadProjectConfig();
776
+ for (const input2 of ctx.config.inputs) {
777
+ if (!input2.fetcher) {
778
+ spinner.warn(`\`${input2.key}\` - no data fetcher defined, skipping`);
658
779
  continue;
659
780
  }
660
- if (!FETCHABLE_DATA_FETCHER_TYPES.has(dataFetcher.type)) {
781
+ if (!FETCHABLE_DATA_FETCHER_TYPES.has(input2.fetcher.type)) {
661
782
  spinner.warn(
662
- `\`${dataFetcher.key}\` (${dataFetcher.type}) - unsupported, skipping`
783
+ `\`${input2.key}\` (${input2.fetcher.type}) - unsupported, skipping`
663
784
  );
664
785
  continue;
665
786
  }
666
- spinner.start(
667
- `\`${dataFetcher.key}\` (${dataFetcher.type}) - fetching...`
668
- );
669
- const fetchData = getFetcher(dataFetcher.type);
670
- const output = await fetchData(dataFetcher, authCtx);
671
- await writeTestData(output, modelInput);
672
- spinner.succeed(`\`${dataFetcher.key}\` (${dataFetcher.type}) - fetched`);
787
+ spinner.start(`\`${input2.key}\` (${input2.fetcher.type}) - fetching...`);
788
+ const fetchData = getFetcher(input2.fetcher.type);
789
+ const output = await fetchData(input2.fetcher, authCtx, ctx);
790
+ await writeTestData(output, input2, options.inputDir, ctx);
791
+ spinner.succeed(`\`${input2.key}\` (${input2.fetcher.type}) - fetched`);
673
792
  }
793
+ const inputPath = path11.resolve(ctx.projectRoot, options.inputDir);
794
+ const link = createLink("Click here", `file://${inputPath}`);
795
+ console.log(`
796
+ ${link} to view fetched data
797
+ `);
674
798
  } catch (error) {
675
799
  if (error instanceof ZodError3) {
676
800
  spinner.fail("Config validation failed");
@@ -685,8 +809,8 @@ async function handleAction4() {
685
809
  // src/commands/fetch/index.ts
686
810
  function registerFetchCommand(program2) {
687
811
  program2.command("fetch").description("Fetches test data from configured data fetchers.").option(
688
- "-i, --input-data <path>",
689
- "The path to the input data directory",
812
+ "-i, --input-dir <path>",
813
+ "The input test data directory",
690
814
  TEST_INPUT_DATA_DIR
691
815
  ).action(handleAction4);
692
816
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pd4castr/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "CLI tool for creating, testing, and publishing pd4castr models",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -66,6 +66,7 @@
66
66
  "ky": "1.8.2",
67
67
  "lilconfig": "3.1.3",
68
68
  "ora": "8.2.0",
69
+ "slugify": "1.6.6",
69
70
  "tiged": "2.12.7",
70
71
  "tiny-invariant": "1.3.3",
71
72
  "zod": "4.0.14"