@caleuche/cli 0.1.3 → 0.2.0
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/CHANGELOG.md +12 -0
- package/README.md +102 -103
- package/dist/batch.js +98 -0
- package/dist/common.js +62 -0
- package/dist/compile.js +19 -0
- package/dist/index.js +7 -39
- package/dist/interfaces.js +1 -0
- package/dist/utils.js +13 -1
- package/package.json +6 -6
- package/src/batch.ts +112 -0
- package/src/common.ts +67 -0
- package/src/compile.ts +24 -0
- package/src/index.ts +21 -67
- package/src/interfaces.ts +22 -0
- package/src/utils.ts +71 -50
- package/test/bach.test.ts +285 -0
- package/test/common.test.ts +69 -0
- package/test/compile.test.ts +255 -0
- package/test/utils.test.ts +267 -0
- package/tsconfig.json +12 -11
package/src/common.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { compileSample, Sample } from "@caleuche/core";
|
|
2
|
+
import {
|
|
3
|
+
createOutputDirectory,
|
|
4
|
+
parse,
|
|
5
|
+
resolveSampleFile,
|
|
6
|
+
resolveTemplate,
|
|
7
|
+
} from "./utils";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
export function resolveAndParseSample(samplePath: string): Sample | null {
|
|
12
|
+
const sampleFilePath = resolveSampleFile(samplePath);
|
|
13
|
+
if (!fs.existsSync(sampleFilePath)) {
|
|
14
|
+
console.error(`Sample file not found: ${sampleFilePath}`);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const sample = parse<Sample>(sampleFilePath);
|
|
18
|
+
if (!sample) {
|
|
19
|
+
console.error(`Failed to parse sample file: ${sampleFilePath}`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const resolvedTemplate = resolveTemplate(samplePath, sample);
|
|
23
|
+
if (!resolvedTemplate) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
sample.template = resolvedTemplate;
|
|
27
|
+
return sample;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function compileAndWriteOutput(
|
|
31
|
+
sample: Sample,
|
|
32
|
+
input: object,
|
|
33
|
+
outputPath: string,
|
|
34
|
+
options: { project?: boolean },
|
|
35
|
+
) {
|
|
36
|
+
const output = (() => {
|
|
37
|
+
try {
|
|
38
|
+
return compileSample(sample, input, {
|
|
39
|
+
project: options.project || false,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof Error) {
|
|
43
|
+
console.error(`Error during compilation: ${error.message}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.error("An unknown error occurred during compilation.");
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
|
|
51
|
+
if (!output) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
createOutputDirectory(outputPath);
|
|
57
|
+
|
|
58
|
+
for (const { fileName, content } of output.items) {
|
|
59
|
+
const itemOutputPath = path.join(outputPath, fileName);
|
|
60
|
+
fs.writeFileSync(itemOutputPath, content);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
console.error(`Failed to write output to ${outputPath}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
package/src/compile.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isObject, parse } from "./utils";
|
|
2
|
+
import { compileAndWriteOutput, resolveAndParseSample } from "./common";
|
|
3
|
+
|
|
4
|
+
export function compile(
|
|
5
|
+
samplePath: string,
|
|
6
|
+
dataPath: string,
|
|
7
|
+
outputPath: string,
|
|
8
|
+
options: { project?: boolean },
|
|
9
|
+
) {
|
|
10
|
+
const sample = resolveAndParseSample(samplePath);
|
|
11
|
+
if (!sample) {
|
|
12
|
+
return process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const inputData = parse<Record<string, any>>(dataPath);
|
|
16
|
+
if (!inputData || !isObject(inputData)) {
|
|
17
|
+
console.error(`Failed to parse input data file: ${dataPath}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!compileAndWriteOutput(sample, inputData, outputPath, options)) {
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,67 +1,21 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { program } from "commander";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
dataPath: string,
|
|
23
|
-
outputPath: string,
|
|
24
|
-
options: { project?: boolean },
|
|
25
|
-
) {
|
|
26
|
-
const sampleFilePath = resolveSampleFile(samplePath);
|
|
27
|
-
if (!fs.existsSync(sampleFilePath)) {
|
|
28
|
-
console.error(`Sample file not found: ${sampleFilePath}`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const sample = parse<Sample>(sampleFilePath);
|
|
33
|
-
if (!sample) {
|
|
34
|
-
console.error(`Failed to parse sample file: ${sampleFilePath}`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
sample.template = resolveTemplate(samplePath, sample);
|
|
38
|
-
|
|
39
|
-
const inputData = parse<Record<string, any>>(dataPath);
|
|
40
|
-
if (!inputData || !isObject(inputData)) {
|
|
41
|
-
console.error(`Failed to parse input data file: ${dataPath}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const output = compileSample(sample, inputData, {
|
|
46
|
-
project: options.project || false,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!isObject(inputData)) {
|
|
50
|
-
console.error("Input data must be an object.");
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
createOutputDirectory(outputPath);
|
|
55
|
-
|
|
56
|
-
for (const { fileName, content } of output.items) {
|
|
57
|
-
const itemOutputPath = path.join(outputPath, fileName);
|
|
58
|
-
fs.writeFileSync(itemOutputPath, content);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
program
|
|
63
|
-
.command("compile <sample-directory> <data-file> <output-directory>")
|
|
64
|
-
.option("-p, --project", "Generate project file")
|
|
65
|
-
.action(compile);
|
|
66
|
-
|
|
67
|
-
program.parse();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { compile } from "./compile";
|
|
5
|
+
import { version } from "../package.json";
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name("@caleuche/cli")
|
|
9
|
+
.description("Caleuche CLI for compiling samples")
|
|
10
|
+
.version(version);
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command("compile <sample-directory> <data-file> <output-directory>")
|
|
14
|
+
.option("-p, --project", "Generate project file")
|
|
15
|
+
.action(compile);
|
|
16
|
+
|
|
17
|
+
program.command("batch <batch-file>").action((batchFile) => {
|
|
18
|
+
console.log(`Batch compiling samples from ${batchFile}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
program.parse();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type SampleVariantDefinition = Record<string, any>;
|
|
2
|
+
type SampleVariantReference = string;
|
|
3
|
+
type SampleVariantPath = string;
|
|
4
|
+
type SampleVariant =
|
|
5
|
+
| SampleVariantDefinition
|
|
6
|
+
| SampleVariantReference
|
|
7
|
+
| SampleVariantPath;
|
|
8
|
+
|
|
9
|
+
interface SampleVariantConfig {
|
|
10
|
+
output: string;
|
|
11
|
+
data: SampleVariant;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SampleDefinition {
|
|
15
|
+
templatePath: string;
|
|
16
|
+
variants: SampleVariantConfig[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface BatchCompileOptions {
|
|
20
|
+
variants?: Record<string, SampleVariantDefinition | SampleVariantPath>;
|
|
21
|
+
samples: SampleDefinition[];
|
|
22
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,50 +1,71 @@
|
|
|
1
|
-
import { parse as parseYaml } from "yaml";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { Sample } from "@caleuche/core";
|
|
5
|
-
|
|
6
|
-
export function parse<T>(filePath: string): T | null {
|
|
7
|
-
try {
|
|
8
|
-
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
9
|
-
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
|
10
|
-
return parseYaml(fileContent) as T;
|
|
11
|
-
}
|
|
12
|
-
return JSON.parse(fileContent) as T;
|
|
13
|
-
} catch (error) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function resolveSampleFile(samplePath: string): string {
|
|
19
|
-
if (isDirectory(samplePath)) {
|
|
20
|
-
return path.join(samplePath, "sample.yaml");
|
|
21
|
-
}
|
|
22
|
-
return samplePath;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isDirectory(path: string): boolean {
|
|
26
|
-
return fs.existsSync(path) && fs.lstatSync(path).isDirectory();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createOutputDirectory(outputPath: string) {
|
|
30
|
-
if (!fs.existsSync(outputPath)) {
|
|
31
|
-
fs.mkdirSync(outputPath, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function resolveTemplate(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
import { parse as parseYaml } from "yaml";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Sample } from "@caleuche/core";
|
|
5
|
+
|
|
6
|
+
export function parse<T>(filePath: string): T | null {
|
|
7
|
+
try {
|
|
8
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
9
|
+
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
|
10
|
+
return parseYaml(fileContent) as T;
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(fileContent) as T;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveSampleFile(samplePath: string): string {
|
|
19
|
+
if (isDirectory(samplePath)) {
|
|
20
|
+
return path.join(samplePath, "sample.yaml");
|
|
21
|
+
}
|
|
22
|
+
return samplePath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isDirectory(path: string): boolean {
|
|
26
|
+
return fs.existsSync(path) && fs.lstatSync(path).isDirectory();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createOutputDirectory(outputPath: string) {
|
|
30
|
+
if (!fs.existsSync(outputPath)) {
|
|
31
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveTemplate(
|
|
36
|
+
samplePath: string,
|
|
37
|
+
sample: Sample,
|
|
38
|
+
): string | null {
|
|
39
|
+
try {
|
|
40
|
+
const templatePath = path.join(samplePath, sample.template);
|
|
41
|
+
if (!fs.existsSync(templatePath)) {
|
|
42
|
+
return sample.template;
|
|
43
|
+
}
|
|
44
|
+
return fs.readFileSync(templatePath, "utf-8");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("Error reading template file.");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isObject(value: any): value is Record<string, any> {
|
|
52
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isVariantDefinition(
|
|
56
|
+
variant: SampleVariant,
|
|
57
|
+
): variant is SampleVariantDefinition {
|
|
58
|
+
return isObject(variant) && !Array.isArray(variant);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isVariantPath(
|
|
62
|
+
variant: SampleVariant,
|
|
63
|
+
): variant is SampleVariantPath {
|
|
64
|
+
return typeof variant === "string" && fs.existsSync(variant);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isVariantReference(
|
|
68
|
+
variant: SampleVariant,
|
|
69
|
+
): variant is SampleVariantReference {
|
|
70
|
+
return typeof variant === "string" && !fs.existsSync(variant);
|
|
71
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
vi.mock("fs");
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
const mockFs = vi.mocked(fs);
|
|
7
|
+
|
|
8
|
+
vi.mock("@caleuche/core");
|
|
9
|
+
import { compileSample, Sample } from "@caleuche/core";
|
|
10
|
+
const mockCompileSample = vi.mocked(compileSample);
|
|
11
|
+
|
|
12
|
+
vi.mock("../src/utils");
|
|
13
|
+
import {
|
|
14
|
+
parse,
|
|
15
|
+
resolveSampleFile,
|
|
16
|
+
createOutputDirectory,
|
|
17
|
+
resolveTemplate,
|
|
18
|
+
isVariantDefinition,
|
|
19
|
+
isVariantPath,
|
|
20
|
+
isVariantReference,
|
|
21
|
+
} from "../src/utils";
|
|
22
|
+
const mockParse = vi.mocked(parse);
|
|
23
|
+
const mockResolveSampleFile = vi.mocked(resolveSampleFile);
|
|
24
|
+
const mockCreateOutputDirectory = vi.mocked(createOutputDirectory);
|
|
25
|
+
const mockResolveTemplate = vi.mocked(resolveTemplate);
|
|
26
|
+
const mockIsVariantDefinition = vi.mocked(isVariantDefinition);
|
|
27
|
+
const mockIsVariantPath = vi.mocked(isVariantPath);
|
|
28
|
+
const mockIsVariantReference = vi.mocked(isVariantReference);
|
|
29
|
+
|
|
30
|
+
import { batchCompile } from "../src/batch";
|
|
31
|
+
|
|
32
|
+
describe("batchCompile", () => {
|
|
33
|
+
let mockExit: any;
|
|
34
|
+
let mockConsoleError: any;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
vi.clearAllMocks();
|
|
38
|
+
mockExit = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
39
|
+
throw new Error("process.exit");
|
|
40
|
+
});
|
|
41
|
+
mockConsoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should exit if batch file does not exist", () => {
|
|
49
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
50
|
+
expect(() => {
|
|
51
|
+
batchCompile("batch.yaml");
|
|
52
|
+
}).toThrow("process.exit");
|
|
53
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
54
|
+
'Batch file "batch.yaml" does not exist.',
|
|
55
|
+
);
|
|
56
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should exit if batch file is not a file", () => {
|
|
60
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
61
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => false } as any);
|
|
62
|
+
expect(() => {
|
|
63
|
+
batchCompile("batch.yaml");
|
|
64
|
+
}).toThrow("process.exit");
|
|
65
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
66
|
+
'"batch.yaml" is not a file.',
|
|
67
|
+
);
|
|
68
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should exit if batch file cannot be parsed", () => {
|
|
72
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
73
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
74
|
+
mockParse.mockReturnValueOnce(null);
|
|
75
|
+
expect(() => {
|
|
76
|
+
batchCompile("batch.yaml");
|
|
77
|
+
}).toThrow("process.exit");
|
|
78
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
79
|
+
"Failed to parse batch file: batch.yaml",
|
|
80
|
+
);
|
|
81
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should exit if variant definitions cannot be loaded", () => {
|
|
85
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
86
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
87
|
+
mockParse
|
|
88
|
+
.mockImplementationOnce(() => ({
|
|
89
|
+
variants: { foo: "badvariant.yaml" },
|
|
90
|
+
samples: [],
|
|
91
|
+
}))
|
|
92
|
+
.mockImplementationOnce(() => null);
|
|
93
|
+
expect(() => {
|
|
94
|
+
batchCompile("batch.yaml");
|
|
95
|
+
}).toThrow("process.exit");
|
|
96
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
97
|
+
`Failed to load variant definition for key "foo": badvariant.yaml`,
|
|
98
|
+
);
|
|
99
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should exit if sample file not found", () => {
|
|
103
|
+
mockFs.existsSync.mockReturnValueOnce(true);
|
|
104
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
105
|
+
mockParse.mockReturnValueOnce({
|
|
106
|
+
variants: {},
|
|
107
|
+
samples: [{ templatePath: "sample.yaml", variants: [], output: "out" }],
|
|
108
|
+
});
|
|
109
|
+
mockResolveSampleFile.mockReturnValue("sample.yaml");
|
|
110
|
+
mockFs.existsSync.mockReturnValueOnce(false);
|
|
111
|
+
expect(() => {
|
|
112
|
+
batchCompile("batch.yaml");
|
|
113
|
+
}).toThrow("process.exit");
|
|
114
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
115
|
+
"Sample file not found: sample.yaml",
|
|
116
|
+
);
|
|
117
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should exit if sample file cannot be parsed", () => {
|
|
121
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
122
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
123
|
+
mockParse.mockReturnValueOnce({
|
|
124
|
+
variants: {},
|
|
125
|
+
samples: [
|
|
126
|
+
{ templatePath: "sample.yaml", variants: ["v1"], output: "out" },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
130
|
+
mockParse.mockReturnValueOnce(null);
|
|
131
|
+
expect(() => {
|
|
132
|
+
batchCompile("batch.yaml");
|
|
133
|
+
}).toThrow("process.exit");
|
|
134
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
135
|
+
"Failed to parse sample file: /path/to/sample.yaml",
|
|
136
|
+
);
|
|
137
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should exit if variant cannot be resolved", () => {
|
|
141
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
142
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
143
|
+
mockParse.mockReturnValueOnce({
|
|
144
|
+
variants: {},
|
|
145
|
+
samples: [
|
|
146
|
+
{ templatePath: "sample.yaml", variants: ["v1"], output: "out" },
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
150
|
+
mockParse.mockReturnValueOnce({
|
|
151
|
+
template: "t",
|
|
152
|
+
type: "js",
|
|
153
|
+
dependencies: [],
|
|
154
|
+
input: [],
|
|
155
|
+
});
|
|
156
|
+
mockResolveTemplate.mockReturnValue("resolved template");
|
|
157
|
+
// variant resolution fails
|
|
158
|
+
mockIsVariantPath.mockReturnValue(false);
|
|
159
|
+
mockIsVariantDefinition.mockReturnValue(false);
|
|
160
|
+
mockIsVariantReference.mockReturnValue(false);
|
|
161
|
+
expect(() => {
|
|
162
|
+
batchCompile("batch.yaml");
|
|
163
|
+
}).toThrow("process.exit");
|
|
164
|
+
expect(mockConsoleError).toHaveBeenCalledWith('Invalid variant type: "v1"');
|
|
165
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should exit if compilation throws error", () => {
|
|
169
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
170
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
171
|
+
mockParse.mockReturnValueOnce({
|
|
172
|
+
variants: {},
|
|
173
|
+
samples: [
|
|
174
|
+
{ templatePath: "sample.yaml", variants: ["v1"], output: "out" },
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
178
|
+
mockParse.mockReturnValueOnce({
|
|
179
|
+
template: "t",
|
|
180
|
+
type: "js",
|
|
181
|
+
dependencies: [],
|
|
182
|
+
input: [],
|
|
183
|
+
});
|
|
184
|
+
mockResolveTemplate.mockReturnValue("resolved template");
|
|
185
|
+
mockIsVariantPath.mockReturnValue(false);
|
|
186
|
+
mockIsVariantDefinition.mockReturnValue(true);
|
|
187
|
+
mockCompileSample.mockImplementation(() => {
|
|
188
|
+
throw new Error("Compilation error");
|
|
189
|
+
});
|
|
190
|
+
expect(() => {
|
|
191
|
+
batchCompile("batch.yaml");
|
|
192
|
+
}).toThrow("process.exit");
|
|
193
|
+
expect(mockConsoleError).toHaveBeenNthCalledWith(
|
|
194
|
+
1,
|
|
195
|
+
"Error during compilation: Compilation error",
|
|
196
|
+
);
|
|
197
|
+
expect(mockConsoleError).toHaveBeenNthCalledWith(
|
|
198
|
+
2,
|
|
199
|
+
'Sample: sample.yaml, Variant: "v1"',
|
|
200
|
+
);
|
|
201
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should exit if compilation throws unknown error", () => {
|
|
205
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
206
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
207
|
+
mockParse.mockReturnValueOnce({
|
|
208
|
+
variants: {},
|
|
209
|
+
samples: [
|
|
210
|
+
{ templatePath: "sample.yaml", variants: ["v1"], output: "out" },
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
214
|
+
mockParse.mockReturnValueOnce({
|
|
215
|
+
template: "t",
|
|
216
|
+
type: "js",
|
|
217
|
+
dependencies: [],
|
|
218
|
+
input: [],
|
|
219
|
+
});
|
|
220
|
+
mockResolveTemplate.mockReturnValue("resolved template");
|
|
221
|
+
// variant resolution
|
|
222
|
+
mockIsVariantPath.mockReturnValue(false);
|
|
223
|
+
mockIsVariantDefinition.mockReturnValue(true);
|
|
224
|
+
// compileSample throws unknown error
|
|
225
|
+
mockCompileSample.mockImplementation(() => {
|
|
226
|
+
throw "Unknown error";
|
|
227
|
+
});
|
|
228
|
+
expect(() => {
|
|
229
|
+
batchCompile("batch.yaml");
|
|
230
|
+
}).toThrow("process.exit");
|
|
231
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
232
|
+
"An unknown error occurred during compilation.",
|
|
233
|
+
);
|
|
234
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should compile and write output files for each variant", () => {
|
|
238
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
239
|
+
mockFs.lstatSync.mockReturnValue({ isFile: () => true } as any);
|
|
240
|
+
mockParse.mockReturnValueOnce({
|
|
241
|
+
variants: {},
|
|
242
|
+
samples: [
|
|
243
|
+
{
|
|
244
|
+
templatePath: "sample.yaml",
|
|
245
|
+
variants: [
|
|
246
|
+
{ output: "v1.output", foo: "bar" },
|
|
247
|
+
{ output: "v2.output", foo: "baz" },
|
|
248
|
+
],
|
|
249
|
+
output: "out",
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
254
|
+
mockParse.mockReturnValueOnce({
|
|
255
|
+
template: "t",
|
|
256
|
+
type: "js",
|
|
257
|
+
dependencies: [],
|
|
258
|
+
input: [],
|
|
259
|
+
});
|
|
260
|
+
mockResolveTemplate.mockReturnValue("resolved template");
|
|
261
|
+
// variant resolution
|
|
262
|
+
mockIsVariantPath.mockReturnValue(false);
|
|
263
|
+
mockIsVariantDefinition.mockReturnValue(true);
|
|
264
|
+
// compileSample returns output
|
|
265
|
+
mockCompileSample.mockReturnValue({
|
|
266
|
+
items: [
|
|
267
|
+
{ fileName: "file1.js", content: "console.log('1');" },
|
|
268
|
+
{ fileName: "file2.js", content: "console.log('2');" },
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
mockCreateOutputDirectory.mockImplementation(() => {});
|
|
272
|
+
mockFs.writeFileSync.mockImplementation(() => {});
|
|
273
|
+
batchCompile("batch.yaml");
|
|
274
|
+
expect(mockCreateOutputDirectory).toHaveBeenCalledWith("v1.output");
|
|
275
|
+
expect(mockCreateOutputDirectory).toHaveBeenCalledWith("v2.output");
|
|
276
|
+
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
|
|
277
|
+
path.join("v1.output", "file1.js"),
|
|
278
|
+
"console.log('1');",
|
|
279
|
+
);
|
|
280
|
+
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
|
|
281
|
+
path.join("v1.output", "file2.js"),
|
|
282
|
+
"console.log('2');",
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
vi.mock("fs");
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
const mockFs = vi.mocked(fs);
|
|
7
|
+
|
|
8
|
+
vi.mock("@caleuche/core");
|
|
9
|
+
import { compileSample, Sample } from "@caleuche/core";
|
|
10
|
+
const mockCompileSample = vi.mocked(compileSample);
|
|
11
|
+
|
|
12
|
+
vi.mock("../src/utils");
|
|
13
|
+
import {
|
|
14
|
+
parse,
|
|
15
|
+
resolveSampleFile,
|
|
16
|
+
createOutputDirectory,
|
|
17
|
+
resolveTemplate,
|
|
18
|
+
isVariantDefinition,
|
|
19
|
+
isVariantPath,
|
|
20
|
+
isVariantReference,
|
|
21
|
+
} from "../src/utils";
|
|
22
|
+
import { resolveAndParseSample } from "../src/common";
|
|
23
|
+
const mockParse = vi.mocked(parse);
|
|
24
|
+
const mockResolveSampleFile = vi.mocked(resolveSampleFile);
|
|
25
|
+
const mockCreateOutputDirectory = vi.mocked(createOutputDirectory);
|
|
26
|
+
const mockResolveTemplate = vi.mocked(resolveTemplate);
|
|
27
|
+
const mockIsVariantDefinition = vi.mocked(isVariantDefinition);
|
|
28
|
+
const mockIsVariantPath = vi.mocked(isVariantPath);
|
|
29
|
+
const mockIsVariantReference = vi.mocked(isVariantReference);
|
|
30
|
+
|
|
31
|
+
describe("common", () => {
|
|
32
|
+
describe("resolveAndParseSample", () => {
|
|
33
|
+
let mockConsoleError: any;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
mockConsoleError = vi
|
|
38
|
+
.spyOn(console, "error")
|
|
39
|
+
.mockImplementation(() => {});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
vi.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return null and log an error when sample file does not exist", () => {
|
|
47
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
48
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
49
|
+
|
|
50
|
+
const sample = resolveAndParseSample("sample");
|
|
51
|
+
expect(sample).toBeNull();
|
|
52
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
53
|
+
"Sample file not found: /path/to/sample.yaml",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should return null and log an error when sample file cannot be parsed", () => {
|
|
58
|
+
mockResolveSampleFile.mockReturnValue("/path/to/sample.yaml");
|
|
59
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
60
|
+
mockParse.mockReturnValueOnce(null);
|
|
61
|
+
|
|
62
|
+
const sample = resolveAndParseSample("sample");
|
|
63
|
+
expect(sample).toBeNull();
|
|
64
|
+
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
65
|
+
"Failed to parse sample file: /path/to/sample.yaml",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|