md-template-vars 0.1.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.
@@ -0,0 +1,2 @@
1
+ import type { CliOptions, ProcessResult } from "../../shared/types.js";
2
+ export declare function processTemplates(options: CliOptions): Promise<ProcessResult>;
@@ -0,0 +1,32 @@
1
+ import { resolve, relative, join } from "node:path";
2
+ import { SameInputOutputError } from "../../shared/errors.js";
3
+ import { loadVariables } from "../../infrastructure/repositories/variables-repository.js";
4
+ import { readTemplate, writeTemplate } from "../../infrastructure/repositories/template-repository.js";
5
+ import { scanTemplates } from "../../infrastructure/services/file-scanner.js";
6
+ import { renderTemplate } from "../../domain/services/template-renderer.js";
7
+ export async function processTemplates(options) {
8
+ const inputDir = resolve(options.input);
9
+ const outputDir = resolve(options.output);
10
+ if (inputDir === outputDir) {
11
+ throw new SameInputOutputError();
12
+ }
13
+ const variables = loadVariables(options.vars);
14
+ const files = await scanTemplates(inputDir, {
15
+ include: options.include,
16
+ exclude: options.exclude,
17
+ });
18
+ const processedFiles = [];
19
+ const warnings = [];
20
+ for (const filePath of files) {
21
+ const template = readTemplate(filePath);
22
+ const result = renderTemplate(template.content, variables);
23
+ const relativePath = relative(inputDir, filePath);
24
+ const outputPath = join(outputDir, relativePath);
25
+ writeTemplate(outputPath, result.content);
26
+ processedFiles.push(relativePath);
27
+ for (const varName of result.undefinedVariables) {
28
+ warnings.push(`Warning: undefined variable "{{${varName}}}" in ${relativePath}`);
29
+ }
30
+ }
31
+ return { processedFiles, warnings };
32
+ }
@@ -0,0 +1,5 @@
1
+ export declare class Template {
2
+ readonly path: string;
3
+ readonly content: string;
4
+ constructor(path: string, content: string);
5
+ }
@@ -0,0 +1,8 @@
1
+ export class Template {
2
+ path;
3
+ content;
4
+ constructor(path, content) {
5
+ this.path = path;
6
+ this.content = content;
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ import type { Variables } from "../value-objects/variables.js";
2
+ import type { RenderResult } from "../../shared/types.js";
3
+ export declare function renderTemplate(content: string, variables: Variables): RenderResult;
@@ -0,0 +1,15 @@
1
+ const VARIABLE_PATTERN = /\{\{(\w+)\}\}/g;
2
+ export function renderTemplate(content, variables) {
3
+ const undefinedVariables = [];
4
+ const renderedContent = content.replace(VARIABLE_PATTERN, (match, varName) => {
5
+ if (varName in variables) {
6
+ return variables[varName];
7
+ }
8
+ undefinedVariables.push(varName);
9
+ return match;
10
+ });
11
+ return {
12
+ content: renderedContent,
13
+ undefinedVariables,
14
+ };
15
+ }
@@ -0,0 +1,3 @@
1
+ import { z } from "zod";
2
+ export declare const VariablesSchema: z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, string, string | number | boolean>>;
3
+ export type Variables = Record<string, string>;
@@ -0,0 +1,3 @@
1
+ import { z } from "zod";
2
+ const VariableValueSchema = z.union([z.string(), z.number(), z.boolean()]).transform(String);
3
+ export const VariablesSchema = z.record(z.string(), VariableValueSchema);
@@ -0,0 +1,3 @@
1
+ import { Template } from "../../domain/entities/template.js";
2
+ export declare function readTemplate(filePath: string): Template;
3
+ export declare function writeTemplate(filePath: string, content: string): void;
@@ -0,0 +1,11 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { Template } from "../../domain/entities/template.js";
4
+ export function readTemplate(filePath) {
5
+ const content = readFileSync(filePath, "utf-8");
6
+ return new Template(filePath, content);
7
+ }
8
+ export function writeTemplate(filePath, content) {
9
+ mkdirSync(dirname(filePath), { recursive: true });
10
+ writeFileSync(filePath, content, "utf-8");
11
+ }
@@ -0,0 +1,2 @@
1
+ import { type Variables } from "../../domain/value-objects/variables.js";
2
+ export declare function loadVariables(filePath: string): Variables;
@@ -0,0 +1,16 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { parse } from "yaml";
3
+ import { VariablesSchema } from "../../domain/value-objects/variables.js";
4
+ import { VariablesFileNotFoundError, InvalidVariablesError } from "../../shared/errors.js";
5
+ export function loadVariables(filePath) {
6
+ if (!existsSync(filePath)) {
7
+ throw new VariablesFileNotFoundError(filePath);
8
+ }
9
+ const content = readFileSync(filePath, "utf-8");
10
+ const parsed = parse(content);
11
+ const result = VariablesSchema.safeParse(parsed);
12
+ if (!result.success) {
13
+ throw new InvalidVariablesError(result.error.message);
14
+ }
15
+ return result.data;
16
+ }
@@ -0,0 +1,5 @@
1
+ export interface ScanOptions {
2
+ include?: string;
3
+ exclude?: string;
4
+ }
5
+ export declare function scanTemplates(inputDir: string, options?: ScanOptions): Promise<string[]>;
@@ -0,0 +1,8 @@
1
+ import fg from "fast-glob";
2
+ export async function scanTemplates(inputDir, options = {}) {
3
+ const pattern = options.include
4
+ ? `${inputDir}/${options.include}`
5
+ : `${inputDir}/**/*.md`;
6
+ const ignore = options.exclude ? [`${inputDir}/${options.exclude}`] : [];
7
+ return fg(pattern, { ignore });
8
+ }
@@ -0,0 +1,25 @@
1
+ export declare const mainCommand: import("citty").CommandDef<{
2
+ input: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ output: {
8
+ type: "positional";
9
+ description: string;
10
+ required: true;
11
+ };
12
+ vars: {
13
+ type: "string";
14
+ description: string;
15
+ default: string;
16
+ };
17
+ include: {
18
+ type: "string";
19
+ description: string;
20
+ };
21
+ exclude: {
22
+ type: "string";
23
+ description: string;
24
+ };
25
+ }>;
@@ -0,0 +1,61 @@
1
+ import { defineCommand } from "citty";
2
+ import { processTemplates } from "../../../application/use-cases/process-templates.js";
3
+ import { VariablesFileNotFoundError, SameInputOutputError, InvalidVariablesError, } from "../../../shared/errors.js";
4
+ export const mainCommand = defineCommand({
5
+ meta: {
6
+ name: "md-template-vars",
7
+ description: "Replace {{variables}} in markdown templates with YAML values",
8
+ },
9
+ args: {
10
+ input: {
11
+ type: "positional",
12
+ description: "Input directory containing templates",
13
+ required: true,
14
+ },
15
+ output: {
16
+ type: "positional",
17
+ description: "Output directory for processed files",
18
+ required: true,
19
+ },
20
+ vars: {
21
+ type: "string",
22
+ description: "Path to variables YAML file",
23
+ default: "variables.yaml",
24
+ },
25
+ include: {
26
+ type: "string",
27
+ description: "Glob pattern to include files",
28
+ },
29
+ exclude: {
30
+ type: "string",
31
+ description: "Glob pattern to exclude files",
32
+ },
33
+ },
34
+ async run({ args }) {
35
+ try {
36
+ const result = await processTemplates({
37
+ input: args.input,
38
+ output: args.output,
39
+ vars: args.vars,
40
+ include: args.include,
41
+ exclude: args.exclude,
42
+ });
43
+ for (const warning of result.warnings) {
44
+ console.warn(warning);
45
+ }
46
+ console.log(`Processed ${result.processedFiles.length} file(s)`);
47
+ for (const file of result.processedFiles) {
48
+ console.log(` - ${file}`);
49
+ }
50
+ }
51
+ catch (error) {
52
+ if (error instanceof VariablesFileNotFoundError ||
53
+ error instanceof SameInputOutputError ||
54
+ error instanceof InvalidVariablesError) {
55
+ console.error(`Error: ${error.message}`);
56
+ process.exit(1);
57
+ }
58
+ throw error;
59
+ }
60
+ },
61
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runMain } from "citty";
3
+ import { mainCommand } from "./commands/main.js";
4
+ runMain(mainCommand);
@@ -0,0 +1,9 @@
1
+ export declare class VariablesFileNotFoundError extends Error {
2
+ constructor(filePath: string);
3
+ }
4
+ export declare class SameInputOutputError extends Error {
5
+ constructor();
6
+ }
7
+ export declare class InvalidVariablesError extends Error {
8
+ constructor(message: string);
9
+ }
@@ -0,0 +1,18 @@
1
+ export class VariablesFileNotFoundError extends Error {
2
+ constructor(filePath) {
3
+ super(`Variables file not found: ${filePath}`);
4
+ this.name = "VariablesFileNotFoundError";
5
+ }
6
+ }
7
+ export class SameInputOutputError extends Error {
8
+ constructor() {
9
+ super("Input and output directories cannot be the same");
10
+ this.name = "SameInputOutputError";
11
+ }
12
+ }
13
+ export class InvalidVariablesError extends Error {
14
+ constructor(message) {
15
+ super(`Invalid variables file: ${message}`);
16
+ this.name = "InvalidVariablesError";
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ export interface CliOptions {
2
+ input: string;
3
+ output: string;
4
+ vars: string;
5
+ include?: string;
6
+ exclude?: string;
7
+ }
8
+ export interface ProcessResult {
9
+ processedFiles: string[];
10
+ warnings: string[];
11
+ }
12
+ export interface RenderResult {
13
+ content: string;
14
+ undefinedVariables: string[];
15
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "md-template-vars",
3
+ "author": "Shunta Toda",
4
+ "version": "0.1.0",
5
+ "description": "Replace {{variables}} in markdown templates with YAML values",
6
+ "type": "module",
7
+ "main": "./dist/application/use-cases/process-templates.js",
8
+ "types": "./dist/application/use-cases/process-templates.d.ts",
9
+ "bin": {
10
+ "md-template-vars": "./dist/presentation/cli/index.js"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "start": "node ./dist/presentation/cli/index.js",
18
+ "dev": "tsc --watch",
19
+ "test": "vitest",
20
+ "prepublishOnly": "pnpm test run && pnpm build"
21
+ },
22
+ "keywords": [
23
+ "markdown",
24
+ "template",
25
+ "variables",
26
+ "yaml",
27
+ "cli"
28
+ ],
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": ""
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "dependencies": {
38
+ "citty": "^0.1.6",
39
+ "fast-glob": "^3.3.2",
40
+ "yaml": "^2.3.4",
41
+ "zod": "^3.22.4"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.11.0",
45
+ "typescript": "^5.3.3",
46
+ "vitest": "^1.2.0"
47
+ }
48
+ }