@funkai/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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +55 -0
- package/dist/index.mjs +7591 -9666
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
- package/src/commands/generate.ts +1 -1
- package/src/commands/prompts/create.ts +3 -2
- package/src/commands/prompts/generate.ts +39 -25
- package/src/commands/prompts/lint.ts +34 -24
- package/src/commands/prompts/setup.ts +64 -21
- package/src/commands/validate.ts +1 -1
- package/src/lib/prompts/__tests__/extract-variables.test.ts +1 -1
- package/src/lib/prompts/__tests__/flatten.test.ts +30 -28
- package/src/lib/prompts/__tests__/frontmatter.test.ts +18 -16
- package/src/lib/prompts/__tests__/lint.test.ts +5 -5
- package/src/lib/prompts/codegen.ts +47 -20
- package/src/lib/prompts/extract-variables.ts +20 -3
- package/src/lib/prompts/flatten.ts +65 -14
- package/src/lib/prompts/frontmatter.ts +102 -44
- package/src/lib/prompts/lint.ts +18 -23
- package/src/lib/prompts/paths.ts +25 -11
- package/src/lib/prompts/pipeline.ts +26 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funkai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI for the funkai AI SDK framework",
|
|
6
6
|
"keywords": [
|
|
@@ -25,19 +25,23 @@
|
|
|
25
25
|
"funkai": "./bin/funkai.mjs"
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
28
31
|
"dependencies": {
|
|
29
|
-
"@kidd-cli/core": "^0.
|
|
32
|
+
"@kidd-cli/core": "^0.10.0",
|
|
33
|
+
"es-toolkit": "^1.45.1",
|
|
30
34
|
"liquidjs": "^10.25.0",
|
|
31
35
|
"ts-pattern": "^5.9.0",
|
|
32
36
|
"yaml": "^2.8.2",
|
|
33
37
|
"zod": "^4.3.6",
|
|
34
|
-
"@funkai/prompts": "0.
|
|
38
|
+
"@funkai/prompts": "0.3.0"
|
|
35
39
|
},
|
|
36
40
|
"devDependencies": {
|
|
37
|
-
"@kidd-cli/cli": "^0.4.
|
|
41
|
+
"@kidd-cli/cli": "^0.4.9",
|
|
38
42
|
"@types/node": "^25.5.0",
|
|
39
43
|
"@vitest/coverage-v8": "^4.1.0",
|
|
40
|
-
"tsdown": "^0.21.
|
|
44
|
+
"tsdown": "^0.21.4",
|
|
41
45
|
"typescript": "^5.9.3",
|
|
42
46
|
"vitest": "^4.1.0"
|
|
43
47
|
},
|
package/src/commands/generate.ts
CHANGED
|
@@ -13,7 +13,7 @@ export default command({
|
|
|
13
13
|
ctx.logger.info("Running prompts code generation...");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
handleGenerate(ctx.args, ctx.logger, ctx.fail);
|
|
16
|
+
handleGenerate({ args: ctx.args, logger: ctx.logger, fail: ctx.fail });
|
|
17
17
|
|
|
18
18
|
// --- Future: agents codegen ---
|
|
19
19
|
},
|
|
@@ -5,7 +5,8 @@ import { command } from "@kidd-cli/core";
|
|
|
5
5
|
import { match, P } from "ts-pattern";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/** @private */
|
|
9
|
+
const createTemplate = (name: string) => `---
|
|
9
10
|
name: ${name}
|
|
10
11
|
---
|
|
11
12
|
|
|
@@ -34,7 +35,7 @@ export default command({
|
|
|
34
35
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: directory derived from user CLI path argument
|
|
35
36
|
mkdirSync(dir, { recursive: true });
|
|
36
37
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: file path derived from user CLI path argument
|
|
37
|
-
writeFileSync(filePath,
|
|
38
|
+
writeFileSync(filePath, createTemplate(name), "utf8");
|
|
38
39
|
|
|
39
40
|
ctx.logger.success(`Created ${filePath}`);
|
|
40
41
|
},
|
|
@@ -2,12 +2,14 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { command } from "@kidd-cli/core";
|
|
5
|
+
import { match } from "ts-pattern";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
|
|
7
8
|
import { generatePromptModule, generateRegistry } from "@/lib/prompts/codegen.js";
|
|
8
9
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
9
10
|
import { runGeneratePipeline } from "@/lib/prompts/pipeline.js";
|
|
10
11
|
|
|
12
|
+
/** Zod schema for the `prompts generate` CLI arguments. */
|
|
11
13
|
export const generateArgs = z.object({
|
|
12
14
|
out: z.string().describe("Output directory for generated files"),
|
|
13
15
|
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
@@ -15,31 +17,35 @@ export const generateArgs = z.object({
|
|
|
15
17
|
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
16
18
|
});
|
|
17
19
|
|
|
20
|
+
/** Inferred type of the `prompts generate` CLI arguments. */
|
|
18
21
|
export type GenerateArgs = z.infer<typeof generateArgs>;
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param args - Parsed CLI arguments.
|
|
24
|
-
* @param logger - Logger instance from the command context.
|
|
25
|
-
* @param fail - Failure callback from the command context.
|
|
24
|
+
* Parameters for the shared generate handler.
|
|
26
25
|
*/
|
|
27
|
-
export
|
|
28
|
-
args: {
|
|
26
|
+
export interface HandleGenerateParams {
|
|
27
|
+
readonly args: {
|
|
29
28
|
readonly out: string;
|
|
30
29
|
readonly roots: readonly string[];
|
|
31
30
|
readonly partials?: string;
|
|
32
31
|
readonly silent: boolean;
|
|
33
|
-
}
|
|
34
|
-
logger: {
|
|
32
|
+
};
|
|
33
|
+
readonly logger: {
|
|
35
34
|
info: (msg: string) => void;
|
|
36
35
|
step: (msg: string) => void;
|
|
37
36
|
error: (msg: string) => void;
|
|
38
37
|
warn: (msg: string) => void;
|
|
39
38
|
success: (msg: string) => void;
|
|
40
|
-
}
|
|
41
|
-
fail: (msg: string) => never
|
|
42
|
-
|
|
39
|
+
};
|
|
40
|
+
readonly fail: (msg: string) => never;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Shared handler for prompts code generation.
|
|
45
|
+
*
|
|
46
|
+
* @param params - Handler context with args, logger, and fail callback.
|
|
47
|
+
*/
|
|
48
|
+
export function handleGenerate({ args, logger, fail }: HandleGenerateParams): void {
|
|
43
49
|
const { out, roots, partials, silent } = args;
|
|
44
50
|
|
|
45
51
|
const { discovered, lintResults, prompts } = runGeneratePipeline({ roots, out, partials });
|
|
@@ -50,20 +56,20 @@ export function handleGenerate(
|
|
|
50
56
|
|
|
51
57
|
if (!silent) {
|
|
52
58
|
for (const prompt of prompts) {
|
|
53
|
-
const varList =
|
|
54
|
-
prompt.schema.length > 0 ? ` (${prompt.schema.map((v) => v.name).join(", ")})` : "";
|
|
59
|
+
const varList = formatVarList(prompt.schema);
|
|
55
60
|
logger.step(`${prompt.name}${varList}`);
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
for (const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
for (const diag of lintResults.flatMap((result) => result.diagnostics)) {
|
|
65
|
+
match(diag.level)
|
|
66
|
+
.with("error", () => logger.error(diag.message))
|
|
67
|
+
.with("warn", () => {
|
|
68
|
+
if (!silent) {
|
|
69
|
+
logger.warn(diag.message);
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.exhaustive();
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
if (hasLintErrors(lintResults)) {
|
|
@@ -77,22 +83,30 @@ export function handleGenerate(
|
|
|
77
83
|
for (const prompt of prompts) {
|
|
78
84
|
const content = generatePromptModule(prompt);
|
|
79
85
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated module to output directory
|
|
80
|
-
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "
|
|
86
|
+
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "utf8");
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
const registryContent = generateRegistry(prompts);
|
|
84
90
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated registry to output directory
|
|
85
|
-
writeFileSync(resolve(outDir, "index.ts"), registryContent, "
|
|
91
|
+
writeFileSync(resolve(outDir, "index.ts"), registryContent, "utf8");
|
|
86
92
|
|
|
87
93
|
if (!silent) {
|
|
88
94
|
logger.success(`Generated ${prompts.length} prompt module(s) + registry → ${outDir}`);
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
/** @private */
|
|
99
|
+
function formatVarList(schema: readonly { readonly name: string }[]): string {
|
|
100
|
+
if (schema.length > 0) {
|
|
101
|
+
return ` (${schema.map((v) => v.name).join(", ")})`;
|
|
102
|
+
}
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
|
|
92
106
|
export default command({
|
|
93
107
|
description: "Generate TypeScript modules from .prompt files",
|
|
94
108
|
options: generateArgs,
|
|
95
109
|
handler(ctx) {
|
|
96
|
-
handleGenerate(ctx.args, ctx.logger, ctx.fail);
|
|
110
|
+
handleGenerate({ args: ctx.args, logger: ctx.logger, fail: ctx.fail });
|
|
97
111
|
},
|
|
98
112
|
});
|
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
import { command } from "@kidd-cli/core";
|
|
2
|
+
import { match } from "ts-pattern";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
|
|
4
5
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
5
6
|
import { runLintPipeline } from "@/lib/prompts/pipeline.js";
|
|
6
7
|
|
|
8
|
+
/** Zod schema for the `prompts lint` CLI arguments. */
|
|
7
9
|
export const lintArgs = z.object({
|
|
8
10
|
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
9
11
|
partials: z.string().optional().describe("Custom partials directory"),
|
|
10
12
|
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
11
13
|
});
|
|
12
14
|
|
|
15
|
+
/** Inferred type of the `prompts lint` CLI arguments. */
|
|
13
16
|
export type LintArgs = z.infer<typeof lintArgs>;
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param args - Parsed CLI arguments.
|
|
19
|
-
* @param logger - Logger instance from the command context.
|
|
20
|
-
* @param fail - Failure callback from the command context.
|
|
19
|
+
* Parameters for the shared lint handler.
|
|
21
20
|
*/
|
|
22
|
-
export
|
|
23
|
-
args: {
|
|
24
|
-
|
|
21
|
+
export interface HandleLintParams {
|
|
22
|
+
readonly args: {
|
|
23
|
+
readonly roots: readonly string[];
|
|
24
|
+
readonly partials?: string;
|
|
25
|
+
readonly silent: boolean;
|
|
26
|
+
};
|
|
27
|
+
readonly logger: {
|
|
25
28
|
info: (msg: string) => void;
|
|
26
29
|
error: (msg: string) => void;
|
|
27
30
|
warn: (msg: string) => void;
|
|
28
|
-
}
|
|
29
|
-
fail: (msg: string) => never
|
|
30
|
-
|
|
31
|
+
};
|
|
32
|
+
readonly fail: (msg: string) => never;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Shared handler for prompts lint/validation.
|
|
37
|
+
*
|
|
38
|
+
* @param params - Handler context with args, logger, and fail callback.
|
|
39
|
+
*/
|
|
40
|
+
export function handleLint({ args, logger, fail }: HandleLintParams): void {
|
|
31
41
|
const { roots, partials, silent } = args;
|
|
32
42
|
|
|
33
43
|
const { discovered, results } = runLintPipeline({ roots, partials });
|
|
@@ -39,24 +49,24 @@ export function handleLint(
|
|
|
39
49
|
const diagnostics = results.flatMap((result) => result.diagnostics);
|
|
40
50
|
|
|
41
51
|
for (const diag of diagnostics) {
|
|
42
|
-
|
|
43
|
-
logger.error(diag.message)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
52
|
+
match(diag.level)
|
|
53
|
+
.with("error", () => logger.error(diag.message))
|
|
54
|
+
.with("warn", () => logger.warn(diag.message))
|
|
55
|
+
.exhaustive();
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
const errorCount = diagnostics.filter((d) => d.level === "error").length;
|
|
50
59
|
const warnCount = diagnostics.filter((d) => d.level !== "error").length;
|
|
51
60
|
|
|
52
61
|
if (!silent) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
|
|
62
|
+
const summaryParts: string[] = [`${discovered} prompt(s) linted`];
|
|
63
|
+
if (errorCount > 0) {
|
|
64
|
+
summaryParts.push(`${errorCount} error(s)`);
|
|
65
|
+
}
|
|
66
|
+
if (warnCount > 0) {
|
|
67
|
+
summaryParts.push(`${warnCount} warning(s)`);
|
|
68
|
+
}
|
|
69
|
+
const summary = summaryParts.join(", ");
|
|
60
70
|
|
|
61
71
|
logger.info(summary);
|
|
62
72
|
}
|
|
@@ -70,6 +80,6 @@ export default command({
|
|
|
70
80
|
description: "Validate .prompt files for schema/template mismatches",
|
|
71
81
|
options: lintArgs,
|
|
72
82
|
handler(ctx) {
|
|
73
|
-
handleLint(ctx.args, ctx.logger, ctx.fail);
|
|
83
|
+
handleLint({ args: ctx.args, logger: ctx.logger, fail: ctx.fail });
|
|
74
84
|
},
|
|
75
85
|
});
|
|
@@ -38,7 +38,7 @@ export default command({
|
|
|
38
38
|
"liquid.engine": "standard",
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2)
|
|
41
|
+
writeFileSync(settingsPath, `${JSON.stringify(updatedSettings, null, 2)}\n`, "utf8");
|
|
42
42
|
ctx.logger.success(`Updated ${settingsPath}`);
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -57,14 +57,13 @@ export default command({
|
|
|
57
57
|
const currentRecs = (extensions.recommendations ?? []) as string[];
|
|
58
58
|
const extensionId = "sissel.shopify-liquid";
|
|
59
59
|
|
|
60
|
+
const recommendations = ensureRecommendation(currentRecs, extensionId);
|
|
60
61
|
const updatedExtensions = {
|
|
61
62
|
...extensions,
|
|
62
|
-
recommendations
|
|
63
|
-
? currentRecs
|
|
64
|
-
: [...currentRecs, extensionId],
|
|
63
|
+
recommendations,
|
|
65
64
|
};
|
|
66
65
|
|
|
67
|
-
writeFileSync(extensionsPath, JSON.stringify(updatedExtensions, null, 2)
|
|
66
|
+
writeFileSync(extensionsPath, `${JSON.stringify(updatedExtensions, null, 2)}\n`, "utf8");
|
|
68
67
|
ctx.logger.success(`Updated ${extensionsPath}`);
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -75,15 +74,15 @@ export default command({
|
|
|
75
74
|
|
|
76
75
|
if (shouldGitignore) {
|
|
77
76
|
const gitignorePath = resolve(GITIGNORE_FILE);
|
|
78
|
-
const existing =
|
|
77
|
+
const existing = readFileOrEmpty(gitignorePath);
|
|
79
78
|
|
|
80
|
-
if (
|
|
81
|
-
|
|
79
|
+
if (existing.includes(GITIGNORE_ENTRY)) {
|
|
80
|
+
ctx.logger.info(`${GITIGNORE_ENTRY} already in ${gitignorePath}`);
|
|
81
|
+
} else {
|
|
82
|
+
const separator = trailingSeparator(existing);
|
|
82
83
|
const block = `${separator}\n# Generated prompt client (created by \`funkai prompts generate\`)\n${GITIGNORE_ENTRY}\n`;
|
|
83
|
-
writeFileSync(gitignorePath, existing
|
|
84
|
+
writeFileSync(gitignorePath, `${existing}${block}`, "utf8");
|
|
84
85
|
ctx.logger.success(`Added ${GITIGNORE_ENTRY} to ${gitignorePath}`);
|
|
85
|
-
} else {
|
|
86
|
-
ctx.logger.info(`${GITIGNORE_ENTRY} already in ${gitignorePath}`);
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
|
|
@@ -100,7 +99,9 @@ export default command({
|
|
|
100
99
|
const existingPaths = (compilerOptions.paths ?? {}) as Record<string, string[]>;
|
|
101
100
|
|
|
102
101
|
// oxlint-disable-next-line security/detect-object-injection -- safe: PROMPTS_ALIAS is a known constant string
|
|
103
|
-
if (
|
|
102
|
+
if (existingPaths[PROMPTS_ALIAS]) {
|
|
103
|
+
ctx.logger.info(`${PROMPTS_ALIAS} alias already in ${tsconfigPath}`);
|
|
104
|
+
} else {
|
|
104
105
|
const updatedTsconfig = {
|
|
105
106
|
...tsconfig,
|
|
106
107
|
compilerOptions: {
|
|
@@ -113,10 +114,8 @@ export default command({
|
|
|
113
114
|
},
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
writeFileSync(tsconfigPath, JSON.stringify(updatedTsconfig, null, 2)
|
|
117
|
+
writeFileSync(tsconfigPath, `${JSON.stringify(updatedTsconfig, null, 2)}\n`, "utf8");
|
|
117
118
|
ctx.logger.success(`Added ${PROMPTS_ALIAS} alias to ${tsconfigPath}`);
|
|
118
|
-
} else {
|
|
119
|
-
ctx.logger.info(`${PROMPTS_ALIAS} alias already in ${tsconfigPath}`);
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -124,9 +123,49 @@ export default command({
|
|
|
124
123
|
},
|
|
125
124
|
});
|
|
126
125
|
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Private
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
/** @private */
|
|
131
|
+
function errorMessage(error: unknown): string {
|
|
132
|
+
if (error instanceof Error) {
|
|
133
|
+
return error.message;
|
|
134
|
+
}
|
|
135
|
+
return String(error);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @private */
|
|
139
|
+
function ensureRecommendation(current: readonly string[], id: string): string[] {
|
|
140
|
+
if (current.includes(id)) {
|
|
141
|
+
return [...current];
|
|
142
|
+
}
|
|
143
|
+
return [...current, id];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @private */
|
|
147
|
+
function readFileOrEmpty(filePath: string): string {
|
|
148
|
+
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading file from CLI discovery
|
|
149
|
+
if (existsSync(filePath)) {
|
|
150
|
+
return readFileSync(filePath, "utf8");
|
|
151
|
+
}
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** @private */
|
|
156
|
+
function trailingSeparator(content: string): string {
|
|
157
|
+
if (content.length > 0 && !content.endsWith("\n")) {
|
|
158
|
+
return "\n";
|
|
159
|
+
}
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
|
|
127
163
|
/**
|
|
128
|
-
* Read a JSON file, returning an empty object if it doesn't exist
|
|
129
|
-
*
|
|
164
|
+
* Read a JSON file, returning an empty object if it doesn't exist.
|
|
165
|
+
* Throws if the file exists but contains invalid JSON, preventing
|
|
166
|
+
* silent data loss from overwriting malformed config files.
|
|
167
|
+
*
|
|
168
|
+
* @private
|
|
130
169
|
*/
|
|
131
170
|
function readJsonFile(filePath: string): Record<string, unknown> {
|
|
132
171
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: tsconfig path from CLI discovery
|
|
@@ -134,11 +173,15 @@ function readJsonFile(filePath: string): Record<string, unknown> {
|
|
|
134
173
|
return {};
|
|
135
174
|
}
|
|
136
175
|
|
|
176
|
+
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading tsconfig file
|
|
177
|
+
const content = readFileSync(filePath, "utf8");
|
|
137
178
|
try {
|
|
138
|
-
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading tsconfig file
|
|
139
|
-
const content = readFileSync(filePath, "utf-8");
|
|
140
179
|
return JSON.parse(content) as Record<string, unknown>;
|
|
141
|
-
} catch {
|
|
142
|
-
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Failed to parse ${filePath}: ${errorMessage(error)}. ` +
|
|
183
|
+
"Fix the JSON syntax or remove the file before running setup.",
|
|
184
|
+
{ cause: error },
|
|
185
|
+
);
|
|
143
186
|
}
|
|
144
187
|
}
|
package/src/commands/validate.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import { extractVariables } from "@/lib/prompts/extract-variables.js";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe(extractVariables, () => {
|
|
6
6
|
it("extracts simple variables", () => {
|
|
7
7
|
expect(extractVariables("{{ name }}")).toEqual(["name"]);
|
|
8
8
|
});
|