@pd4castr/cli 0.0.1 → 0.0.2
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 +1 -0
- package/dist/index.js +251 -127
- package/package.json +2 -1
package/README.md
CHANGED
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.
|
|
54
|
+
version: "0.0.2",
|
|
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(
|
|
161
|
+
async function isExistingPath(path12) {
|
|
116
162
|
try {
|
|
117
|
-
await fs.access(
|
|
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
|
}
|
|
@@ -184,7 +233,9 @@ var AUTH0_AUDIENCE = "https://api.pd4castr.com.au";
|
|
|
184
233
|
var GLOBAL_CONFIG_FILE = ".pd4castr";
|
|
185
234
|
var PROJECT_CONFIG_FILE = ".pd4castrrc.json";
|
|
186
235
|
var API_URL = "https://api.pd4castr.com.au";
|
|
187
|
-
var TEST_INPUT_DATA_DIR = "
|
|
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
|
|
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().
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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/
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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/
|
|
429
|
-
|
|
430
|
-
|
|
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-
|
|
434
|
-
function
|
|
435
|
-
const
|
|
436
|
-
|
|
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 =
|
|
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,
|
|
487
|
-
const inputEnvs = config.
|
|
488
|
-
(
|
|
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, {
|
|
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(
|
|
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
|
-
|
|
518
|
-
const app = express();
|
|
519
|
-
const server = await startWebServer(app, options.port);
|
|
618
|
+
var _stack = [];
|
|
520
619
|
try {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
await
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
spinner.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
console.
|
|
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
|
-
|
|
685
|
+
} catch (_) {
|
|
686
|
+
var _error = _, _hasError = true;
|
|
559
687
|
} finally {
|
|
560
|
-
|
|
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-
|
|
570
|
-
"The
|
|
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
|
|
602
|
-
import
|
|
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 =
|
|
613
|
-
|
|
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/
|
|
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
|
|
634
|
-
import
|
|
635
|
-
async function writeTestData(
|
|
636
|
-
const
|
|
637
|
-
await
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
await
|
|
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
|
|
650
|
-
for (const
|
|
651
|
-
|
|
652
|
-
(
|
|
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(
|
|
781
|
+
if (!FETCHABLE_DATA_FETCHER_TYPES.has(input2.fetcher.type)) {
|
|
661
782
|
spinner.warn(
|
|
662
|
-
`\`${
|
|
783
|
+
`\`${input2.key}\` (${input2.fetcher.type}) - unsupported, skipping`
|
|
663
784
|
);
|
|
664
785
|
continue;
|
|
665
786
|
}
|
|
666
|
-
spinner.start(
|
|
667
|
-
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
|
|
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-
|
|
689
|
-
"The
|
|
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.
|
|
3
|
+
"version": "0.0.2",
|
|
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"
|