docvars 0.3.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/README.md +209 -0
- package/dist/application/use-cases/dry-run.d.ts +12 -0
- package/dist/application/use-cases/dry-run.js +45 -0
- package/dist/application/use-cases/list-variables.d.ts +16 -0
- package/dist/application/use-cases/list-variables.js +48 -0
- package/dist/application/use-cases/process-templates.d.ts +2 -0
- package/dist/application/use-cases/process-templates.js +32 -0
- package/dist/application/use-cases/rename-variable.d.ts +13 -0
- package/dist/application/use-cases/rename-variable.js +80 -0
- package/dist/domain/entities/template.d.ts +5 -0
- package/dist/domain/entities/template.js +8 -0
- package/dist/domain/services/template-renderer.d.ts +3 -0
- package/dist/domain/services/template-renderer.js +15 -0
- package/dist/domain/value-objects/variables.d.ts +9 -0
- package/dist/domain/value-objects/variables.js +17 -0
- package/dist/infrastructure/repositories/template-repository.d.ts +3 -0
- package/dist/infrastructure/repositories/template-repository.js +11 -0
- package/dist/infrastructure/repositories/variables-repository.d.ts +2 -0
- package/dist/infrastructure/repositories/variables-repository.js +16 -0
- package/dist/infrastructure/services/file-scanner.d.ts +5 -0
- package/dist/infrastructure/services/file-scanner.js +8 -0
- package/dist/presentation/cli/commands/main.d.ts +48 -0
- package/dist/presentation/cli/commands/main.js +311 -0
- package/dist/presentation/cli/index.d.ts +2 -0
- package/dist/presentation/cli/index.js +4 -0
- package/dist/shared/errors.d.ts +9 -0
- package/dist/shared/errors.js +18 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.js +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# docvars
|
|
2
|
+
|
|
3
|
+
A CLI tool to replace `{{variables}}` in document templates with values from a YAML file.
|
|
4
|
+
|
|
5
|
+
Supports any text-based files: Markdown, HTML, TXT, and more.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g docvars
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx docvars ./templates ./output
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docvars <input> <output> [options]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Arguments
|
|
26
|
+
|
|
27
|
+
| Argument | Description |
|
|
28
|
+
| -------- | ----------------------------------------- |
|
|
29
|
+
| `input` | Input directory containing template files |
|
|
30
|
+
| `output` | Output directory for processed files |
|
|
31
|
+
|
|
32
|
+
### Options
|
|
33
|
+
|
|
34
|
+
| Option | Default | Description |
|
|
35
|
+
| --------------- | ---------------- | --------------------------------------------------- |
|
|
36
|
+
| `--vars` | `variables.yaml` | Path to the variables YAML file |
|
|
37
|
+
| `--include` | - | Glob pattern to include specific files |
|
|
38
|
+
| `--exclude` | - | Glob pattern to exclude specific files |
|
|
39
|
+
| `--watch` | `false` | Watch for file changes and rebuild automatically |
|
|
40
|
+
| `--rename-from` | - | Variable name to rename from (use with --rename-to) |
|
|
41
|
+
| `--rename-to` | - | Variable name to rename to (use with --rename-from) |
|
|
42
|
+
| `--list-vars` | `false` | List all variables used in templates |
|
|
43
|
+
| `--dry-run` | `false` | Preview changes without writing files |
|
|
44
|
+
|
|
45
|
+
## Examples
|
|
46
|
+
|
|
47
|
+
### Basic usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
docvars ./templates ./output
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Custom variables file
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
docvars ./templates ./output --vars production.yaml
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Filter files
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Include only files matching pattern
|
|
63
|
+
docvars ./templates ./output --include "api-*.md"
|
|
64
|
+
|
|
65
|
+
# Exclude files matching pattern
|
|
66
|
+
docvars ./templates ./output --exclude "draft-*.md"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Watch mode
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
docvars ./templates ./output --watch
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This will watch for changes in:
|
|
76
|
+
- Template files in the input directory
|
|
77
|
+
- The variables YAML file
|
|
78
|
+
|
|
79
|
+
When changes are detected, templates are automatically rebuilt.
|
|
80
|
+
|
|
81
|
+
### Rename variables
|
|
82
|
+
|
|
83
|
+
Rename a variable across all template files and the variables YAML file:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Simple rename
|
|
87
|
+
docvars ./templates ./output --rename-from "name" --rename-to "title"
|
|
88
|
+
|
|
89
|
+
# Rename nested variable
|
|
90
|
+
docvars ./templates ./output --rename-from "database.host" --rename-to "db.host"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This updates:
|
|
94
|
+
- All `{{oldName}}` occurrences in template files → `{{newName}}`
|
|
95
|
+
- The key in the variables YAML file
|
|
96
|
+
|
|
97
|
+
### List variables
|
|
98
|
+
|
|
99
|
+
Show all variables used in templates and their status:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
docvars ./templates ./output --list-vars
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Output:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Variables used in templates:
|
|
109
|
+
|
|
110
|
+
app.name (✓)
|
|
111
|
+
→ README.md
|
|
112
|
+
→ config.md
|
|
113
|
+
api.key (✗ undefined)
|
|
114
|
+
→ config.md
|
|
115
|
+
|
|
116
|
+
Unused variables (defined but not used):
|
|
117
|
+
|
|
118
|
+
deprecated.setting
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Dry run
|
|
122
|
+
|
|
123
|
+
Preview what files would be created or updated without actually writing them:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
docvars ./templates ./output --dry-run
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Output:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Dry run - no files written
|
|
133
|
+
|
|
134
|
+
Files to create (1):
|
|
135
|
+
+ config.md
|
|
136
|
+
|
|
137
|
+
Files to update (2):
|
|
138
|
+
~ README.md
|
|
139
|
+
~ api.md
|
|
140
|
+
|
|
141
|
+
Files unchanged (1):
|
|
142
|
+
= changelog.md
|
|
143
|
+
|
|
144
|
+
Summary: 1 create, 2 update, 1 unchanged
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Template Syntax
|
|
148
|
+
|
|
149
|
+
Use `{{variableName}}` syntax in your template files:
|
|
150
|
+
|
|
151
|
+
**Template (templates/hello.md):**
|
|
152
|
+
```markdown
|
|
153
|
+
# Hello {{name}}
|
|
154
|
+
|
|
155
|
+
Welcome to {{project}}!
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Variables (variables.yaml):**
|
|
159
|
+
```yaml
|
|
160
|
+
name: World
|
|
161
|
+
project: My Project
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Output (output/hello.md):**
|
|
165
|
+
```markdown
|
|
166
|
+
# Hello World
|
|
167
|
+
|
|
168
|
+
Welcome to My Project!
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Nested Variables
|
|
172
|
+
|
|
173
|
+
You can use nested objects in your variables file and access them with dot notation:
|
|
174
|
+
|
|
175
|
+
**Template:**
|
|
176
|
+
```markdown
|
|
177
|
+
# {{app.name}}
|
|
178
|
+
|
|
179
|
+
Database: {{database.host}}:{{database.port}}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Variables (variables.yaml):**
|
|
183
|
+
```yaml
|
|
184
|
+
app:
|
|
185
|
+
name: My App
|
|
186
|
+
|
|
187
|
+
database:
|
|
188
|
+
host: localhost
|
|
189
|
+
port: 5432
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Output:**
|
|
193
|
+
```markdown
|
|
194
|
+
# My App
|
|
195
|
+
|
|
196
|
+
Database: localhost:5432
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Error Handling
|
|
200
|
+
|
|
201
|
+
| Case | Behavior |
|
|
202
|
+
| --------------------------- | --------------------------------------------------- |
|
|
203
|
+
| Undefined variable | Warning is displayed, variable syntax is kept as-is |
|
|
204
|
+
| Same input/output directory | Error and exit |
|
|
205
|
+
| Variables file not found | Error and exit |
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CliOptions } from "../../shared/types.js";
|
|
2
|
+
export interface FileChange {
|
|
3
|
+
relativePath: string;
|
|
4
|
+
outputPath: string;
|
|
5
|
+
status: "create" | "update" | "unchanged";
|
|
6
|
+
undefinedVariables: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface DryRunResult {
|
|
9
|
+
changes: FileChange[];
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function dryRun(options: CliOptions): Promise<DryRunResult>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolve, relative, join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { SameInputOutputError } from "../../shared/errors.js";
|
|
4
|
+
import { loadVariables } from "../../infrastructure/repositories/variables-repository.js";
|
|
5
|
+
import { readTemplate } from "../../infrastructure/repositories/template-repository.js";
|
|
6
|
+
import { scanTemplates } from "../../infrastructure/services/file-scanner.js";
|
|
7
|
+
import { renderTemplate } from "../../domain/services/template-renderer.js";
|
|
8
|
+
export async function dryRun(options) {
|
|
9
|
+
const inputDir = resolve(options.input);
|
|
10
|
+
const outputDir = resolve(options.output);
|
|
11
|
+
if (inputDir === outputDir) {
|
|
12
|
+
throw new SameInputOutputError();
|
|
13
|
+
}
|
|
14
|
+
const variables = loadVariables(options.vars);
|
|
15
|
+
const files = await scanTemplates(inputDir, {
|
|
16
|
+
include: options.include,
|
|
17
|
+
exclude: options.exclude,
|
|
18
|
+
});
|
|
19
|
+
const changes = [];
|
|
20
|
+
const warnings = [];
|
|
21
|
+
for (const filePath of files) {
|
|
22
|
+
const template = readTemplate(filePath);
|
|
23
|
+
const result = renderTemplate(template.content, variables);
|
|
24
|
+
const relativePath = relative(inputDir, filePath);
|
|
25
|
+
const outputPath = join(outputDir, relativePath);
|
|
26
|
+
let status;
|
|
27
|
+
if (!existsSync(outputPath)) {
|
|
28
|
+
status = "create";
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const existingContent = readFileSync(outputPath, "utf-8");
|
|
32
|
+
status = existingContent === result.content ? "unchanged" : "update";
|
|
33
|
+
}
|
|
34
|
+
changes.push({
|
|
35
|
+
relativePath,
|
|
36
|
+
outputPath,
|
|
37
|
+
status,
|
|
38
|
+
undefinedVariables: result.undefinedVariables,
|
|
39
|
+
});
|
|
40
|
+
for (const varName of result.undefinedVariables) {
|
|
41
|
+
warnings.push(`Warning: undefined variable "{{${varName}}}" in ${relativePath}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { changes, warnings };
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ListVariablesOptions {
|
|
2
|
+
input: string;
|
|
3
|
+
vars: string;
|
|
4
|
+
include?: string;
|
|
5
|
+
exclude?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface VariableUsage {
|
|
8
|
+
name: string;
|
|
9
|
+
files: string[];
|
|
10
|
+
isDefined: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ListVariablesResult {
|
|
13
|
+
variables: VariableUsage[];
|
|
14
|
+
unusedVariables: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function listVariables(options: ListVariablesOptions): Promise<ListVariablesResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { scanTemplates } from "../../infrastructure/services/file-scanner.js";
|
|
4
|
+
import { loadVariables } from "../../infrastructure/repositories/variables-repository.js";
|
|
5
|
+
import { VariablesFileNotFoundError } from "../../shared/errors.js";
|
|
6
|
+
const VARIABLE_PATTERN = /\{\{([\w.]+)\}\}/g;
|
|
7
|
+
export async function listVariables(options) {
|
|
8
|
+
const { input, vars, include, exclude } = options;
|
|
9
|
+
if (!existsSync(vars)) {
|
|
10
|
+
throw new VariablesFileNotFoundError(vars);
|
|
11
|
+
}
|
|
12
|
+
// Load defined variables
|
|
13
|
+
const definedVars = loadVariables(vars);
|
|
14
|
+
const definedVarNames = new Set(Object.keys(definedVars));
|
|
15
|
+
// Scan templates and extract variable usage
|
|
16
|
+
const templateFiles = await scanTemplates(input, { include, exclude });
|
|
17
|
+
const variableUsageMap = new Map();
|
|
18
|
+
for (const file of templateFiles) {
|
|
19
|
+
const content = readFileSync(file, "utf-8");
|
|
20
|
+
const relativePath = relative(input, file);
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = VARIABLE_PATTERN.exec(content)) !== null) {
|
|
23
|
+
const varName = match[1];
|
|
24
|
+
if (!variableUsageMap.has(varName)) {
|
|
25
|
+
variableUsageMap.set(varName, new Set());
|
|
26
|
+
}
|
|
27
|
+
variableUsageMap.get(varName).add(relativePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Build result
|
|
31
|
+
const usedVarNames = new Set(variableUsageMap.keys());
|
|
32
|
+
const variables = [];
|
|
33
|
+
// Variables used in templates
|
|
34
|
+
for (const [name, files] of variableUsageMap) {
|
|
35
|
+
variables.push({
|
|
36
|
+
name,
|
|
37
|
+
files: Array.from(files).sort(),
|
|
38
|
+
isDefined: definedVarNames.has(name),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Sort by name
|
|
42
|
+
variables.sort((a, b) => a.name.localeCompare(b.name));
|
|
43
|
+
// Find unused variables (defined but not used)
|
|
44
|
+
const unusedVariables = Array.from(definedVarNames)
|
|
45
|
+
.filter((name) => !usedVarNames.has(name))
|
|
46
|
+
.sort();
|
|
47
|
+
return { variables, unusedVariables };
|
|
48
|
+
}
|
|
@@ -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,13 @@
|
|
|
1
|
+
export interface RenameOptions {
|
|
2
|
+
input: string;
|
|
3
|
+
vars: string;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
include?: string;
|
|
7
|
+
exclude?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RenameResult {
|
|
10
|
+
renamedInFiles: string[];
|
|
11
|
+
renamedInVars: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function renameVariable(options: RenameOptions): Promise<RenameResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { parse, stringify } from "yaml";
|
|
3
|
+
import { scanTemplates } from "../../infrastructure/services/file-scanner.js";
|
|
4
|
+
import { VariablesFileNotFoundError } from "../../shared/errors.js";
|
|
5
|
+
function escapeRegex(str) {
|
|
6
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
+
}
|
|
8
|
+
export async function renameVariable(options) {
|
|
9
|
+
const { input, vars, from, to, include, exclude } = options;
|
|
10
|
+
if (!existsSync(vars)) {
|
|
11
|
+
throw new VariablesFileNotFoundError(vars);
|
|
12
|
+
}
|
|
13
|
+
const result = {
|
|
14
|
+
renamedInFiles: [],
|
|
15
|
+
renamedInVars: false,
|
|
16
|
+
};
|
|
17
|
+
// Rename in template files
|
|
18
|
+
const templateFiles = await scanTemplates(input, { include, exclude });
|
|
19
|
+
const pattern = new RegExp(`\\{\\{${escapeRegex(from)}(\\}\\}|\\|)`, "g");
|
|
20
|
+
for (const file of templateFiles) {
|
|
21
|
+
const content = readFileSync(file, "utf-8");
|
|
22
|
+
const newContent = content.replace(pattern, `{{${to}$1`);
|
|
23
|
+
if (content !== newContent) {
|
|
24
|
+
writeFileSync(file, newContent, "utf-8");
|
|
25
|
+
result.renamedInFiles.push(file);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Rename in variables file
|
|
29
|
+
const varsContent = readFileSync(vars, "utf-8");
|
|
30
|
+
const varsData = parse(varsContent);
|
|
31
|
+
if (renameKeyInObject(varsData, from, to)) {
|
|
32
|
+
writeFileSync(vars, stringify(varsData), "utf-8");
|
|
33
|
+
result.renamedInVars = true;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function renameKeyInObject(obj, from, to) {
|
|
38
|
+
const fromParts = from.split(".");
|
|
39
|
+
const toParts = to.split(".");
|
|
40
|
+
// Simple key rename (no dots)
|
|
41
|
+
if (fromParts.length === 1 && toParts.length === 1) {
|
|
42
|
+
if (from in obj) {
|
|
43
|
+
obj[to] = obj[from];
|
|
44
|
+
delete obj[from];
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// Nested key rename
|
|
50
|
+
const fromParent = getNestedParent(obj, fromParts);
|
|
51
|
+
const fromKey = fromParts[fromParts.length - 1];
|
|
52
|
+
if (!fromParent || !(fromKey in fromParent)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const value = fromParent[fromKey];
|
|
56
|
+
delete fromParent[fromKey];
|
|
57
|
+
// Create target path if needed
|
|
58
|
+
const toParentParts = toParts.slice(0, -1);
|
|
59
|
+
const toKey = toParts[toParts.length - 1];
|
|
60
|
+
let toParent = obj;
|
|
61
|
+
for (const part of toParentParts) {
|
|
62
|
+
if (!(part in toParent)) {
|
|
63
|
+
toParent[part] = {};
|
|
64
|
+
}
|
|
65
|
+
toParent = toParent[part];
|
|
66
|
+
}
|
|
67
|
+
toParent[toKey] = value;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
function getNestedParent(obj, parts) {
|
|
71
|
+
let current = obj;
|
|
72
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
73
|
+
const part = parts[i];
|
|
74
|
+
if (!(part in current) || typeof current[part] !== "object") {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
current = current[part];
|
|
78
|
+
}
|
|
79
|
+
return current;
|
|
80
|
+
}
|
|
@@ -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,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const PrimitiveValueSchema: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
3
|
+
type NestedValue = z.infer<typeof PrimitiveValueSchema> | {
|
|
4
|
+
[key: string]: NestedValue;
|
|
5
|
+
};
|
|
6
|
+
export declare const VariablesSchema: z.ZodRecord<z.ZodString, z.ZodType<NestedValue, z.ZodTypeDef, NestedValue>>;
|
|
7
|
+
export type Variables = Record<string, string>;
|
|
8
|
+
export declare function flattenVariables(obj: Record<string, NestedValue>, prefix?: string): Variables;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const PrimitiveValueSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
3
|
+
const NestedValueSchema = z.lazy(() => z.union([PrimitiveValueSchema, z.record(z.string(), NestedValueSchema)]));
|
|
4
|
+
export const VariablesSchema = z.record(z.string(), NestedValueSchema);
|
|
5
|
+
export function flattenVariables(obj, prefix = "") {
|
|
6
|
+
const result = {};
|
|
7
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
8
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
9
|
+
if (typeof value === "object" && value !== null) {
|
|
10
|
+
Object.assign(result, flattenVariables(value, fullKey));
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
result[fullKey] = String(value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
@@ -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,16 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { parse } from "yaml";
|
|
3
|
+
import { VariablesSchema, flattenVariables } 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 flattenVariables(result.data);
|
|
16
|
+
}
|
|
@@ -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,48 @@
|
|
|
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
|
+
watch: {
|
|
26
|
+
type: "boolean";
|
|
27
|
+
description: string;
|
|
28
|
+
default: false;
|
|
29
|
+
};
|
|
30
|
+
"rename-from": {
|
|
31
|
+
type: "string";
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
"rename-to": {
|
|
35
|
+
type: "string";
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
"list-vars": {
|
|
39
|
+
type: "boolean";
|
|
40
|
+
description: string;
|
|
41
|
+
default: false;
|
|
42
|
+
};
|
|
43
|
+
"dry-run": {
|
|
44
|
+
type: "boolean";
|
|
45
|
+
description: string;
|
|
46
|
+
default: false;
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
import { watch } from "chokidar";
|
|
6
|
+
import { processTemplates } from "../../../application/use-cases/process-templates.js";
|
|
7
|
+
import { renameVariable } from "../../../application/use-cases/rename-variable.js";
|
|
8
|
+
import { listVariables } from "../../../application/use-cases/list-variables.js";
|
|
9
|
+
import { dryRun } from "../../../application/use-cases/dry-run.js";
|
|
10
|
+
import { VariablesFileNotFoundError, SameInputOutputError, InvalidVariablesError } from "../../../shared/errors.js";
|
|
11
|
+
async function runProcess(options) {
|
|
12
|
+
try {
|
|
13
|
+
const result = await processTemplates(options);
|
|
14
|
+
if (result.warnings.length > 0) {
|
|
15
|
+
for (const warning of result.warnings) {
|
|
16
|
+
console.warn(pc.yellow(`⚠ ${warning}`));
|
|
17
|
+
}
|
|
18
|
+
console.log();
|
|
19
|
+
}
|
|
20
|
+
if (result.processedFiles.length === 0) {
|
|
21
|
+
console.log(pc.gray("No files to process"));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const table = new Table({
|
|
25
|
+
head: [pc.bold("File"), pc.bold("Status")],
|
|
26
|
+
style: { head: [], border: [] },
|
|
27
|
+
});
|
|
28
|
+
for (const file of result.processedFiles) {
|
|
29
|
+
table.push([file, pc.green("✓ done")]);
|
|
30
|
+
}
|
|
31
|
+
console.log(pc.bold(pc.cyan("\n✨ Build complete\n")));
|
|
32
|
+
console.log(table.toString());
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(pc.bold("Processed: ") + pc.green(`${result.processedFiles.length} file(s)`));
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof VariablesFileNotFoundError ||
|
|
39
|
+
error instanceof SameInputOutputError ||
|
|
40
|
+
error instanceof InvalidVariablesError) {
|
|
41
|
+
console.error(pc.red(`✗ Error: ${error.message}`));
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function startWatch(options) {
|
|
48
|
+
const inputPath = resolve(options.input);
|
|
49
|
+
const varsPath = resolve(options.vars);
|
|
50
|
+
let debounceTimer = null;
|
|
51
|
+
const handleChange = (eventType, filePath) => {
|
|
52
|
+
if (debounceTimer) {
|
|
53
|
+
clearTimeout(debounceTimer);
|
|
54
|
+
}
|
|
55
|
+
debounceTimer = setTimeout(async () => {
|
|
56
|
+
console.log(pc.cyan(`\n👀 Change detected: ${pc.bold(filePath)} (${eventType})\n`));
|
|
57
|
+
await runProcess(options);
|
|
58
|
+
}, 100);
|
|
59
|
+
};
|
|
60
|
+
console.log(pc.bold(pc.magenta("\n👁 Watch mode enabled\n")));
|
|
61
|
+
const watchTable = new Table({
|
|
62
|
+
style: { head: [], border: [] },
|
|
63
|
+
});
|
|
64
|
+
watchTable.push([pc.gray("Templates"), inputPath], [pc.gray("Variables"), varsPath]);
|
|
65
|
+
console.log(watchTable.toString());
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(pc.gray("Waiting for changes... (Ctrl+C to stop)\n"));
|
|
68
|
+
const watcher = watch([inputPath, varsPath], {
|
|
69
|
+
ignoreInitial: true,
|
|
70
|
+
ignored: /(^|[\/\\])\../,
|
|
71
|
+
});
|
|
72
|
+
watcher.on("all", handleChange);
|
|
73
|
+
}
|
|
74
|
+
export const mainCommand = defineCommand({
|
|
75
|
+
meta: {
|
|
76
|
+
name: "docvars",
|
|
77
|
+
description: "Replace {{variables}} in document templates with YAML values",
|
|
78
|
+
},
|
|
79
|
+
args: {
|
|
80
|
+
input: {
|
|
81
|
+
type: "positional",
|
|
82
|
+
description: "Input directory containing templates",
|
|
83
|
+
required: true,
|
|
84
|
+
},
|
|
85
|
+
output: {
|
|
86
|
+
type: "positional",
|
|
87
|
+
description: "Output directory for processed files",
|
|
88
|
+
required: true,
|
|
89
|
+
},
|
|
90
|
+
vars: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Path to variables YAML file",
|
|
93
|
+
default: "variables.yaml",
|
|
94
|
+
},
|
|
95
|
+
include: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Glob pattern to include files",
|
|
98
|
+
},
|
|
99
|
+
exclude: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Glob pattern to exclude files",
|
|
102
|
+
},
|
|
103
|
+
watch: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
description: "Watch for file changes and rebuild automatically",
|
|
106
|
+
default: false,
|
|
107
|
+
},
|
|
108
|
+
"rename-from": {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Variable name to rename from (use with --rename-to)",
|
|
111
|
+
},
|
|
112
|
+
"rename-to": {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Variable name to rename to (use with --rename-from)",
|
|
115
|
+
},
|
|
116
|
+
"list-vars": {
|
|
117
|
+
type: "boolean",
|
|
118
|
+
description: "List all variables used in templates",
|
|
119
|
+
default: false,
|
|
120
|
+
},
|
|
121
|
+
"dry-run": {
|
|
122
|
+
type: "boolean",
|
|
123
|
+
description: "Preview changes without writing files",
|
|
124
|
+
default: false,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
async run({ args }) {
|
|
128
|
+
// Handle dry-run mode
|
|
129
|
+
if (args["dry-run"]) {
|
|
130
|
+
try {
|
|
131
|
+
const result = await dryRun({
|
|
132
|
+
input: args.input,
|
|
133
|
+
output: args.output,
|
|
134
|
+
vars: args.vars,
|
|
135
|
+
include: args.include,
|
|
136
|
+
exclude: args.exclude,
|
|
137
|
+
});
|
|
138
|
+
console.log(pc.bold(pc.cyan("\n🔍 Dry run - no files written\n")));
|
|
139
|
+
if (result.warnings.length > 0) {
|
|
140
|
+
for (const warning of result.warnings) {
|
|
141
|
+
console.warn(pc.yellow(`⚠ ${warning}`));
|
|
142
|
+
}
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
if (result.changes.length === 0) {
|
|
146
|
+
console.log(pc.gray("No files to process"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const table = new Table({
|
|
150
|
+
head: [pc.bold("File"), pc.bold("Status")],
|
|
151
|
+
style: { head: [], border: [] },
|
|
152
|
+
});
|
|
153
|
+
for (const change of result.changes) {
|
|
154
|
+
let status;
|
|
155
|
+
switch (change.status) {
|
|
156
|
+
case "create":
|
|
157
|
+
status = pc.green("+ create");
|
|
158
|
+
break;
|
|
159
|
+
case "update":
|
|
160
|
+
status = pc.yellow("~ update");
|
|
161
|
+
break;
|
|
162
|
+
case "unchanged":
|
|
163
|
+
status = pc.gray("= unchanged");
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
table.push([change.relativePath, status]);
|
|
167
|
+
}
|
|
168
|
+
console.log(table.toString());
|
|
169
|
+
console.log();
|
|
170
|
+
const creates = result.changes.filter((c) => c.status === "create").length;
|
|
171
|
+
const updates = result.changes.filter((c) => c.status === "update").length;
|
|
172
|
+
const unchanged = result.changes.filter((c) => c.status === "unchanged").length;
|
|
173
|
+
console.log(pc.bold("Summary: ") +
|
|
174
|
+
pc.green(`${creates} create`) +
|
|
175
|
+
pc.gray(" · ") +
|
|
176
|
+
pc.yellow(`${updates} update`) +
|
|
177
|
+
pc.gray(" · ") +
|
|
178
|
+
pc.gray(`${unchanged} unchanged`));
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (error instanceof VariablesFileNotFoundError ||
|
|
182
|
+
error instanceof SameInputOutputError ||
|
|
183
|
+
error instanceof InvalidVariablesError) {
|
|
184
|
+
console.error(`Error: ${error.message}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Handle list-vars mode
|
|
192
|
+
if (args["list-vars"]) {
|
|
193
|
+
try {
|
|
194
|
+
const result = await listVariables({
|
|
195
|
+
input: args.input,
|
|
196
|
+
vars: args.vars,
|
|
197
|
+
include: args.include,
|
|
198
|
+
exclude: args.exclude,
|
|
199
|
+
});
|
|
200
|
+
console.log(pc.bold(pc.cyan("\n📋 Variables\n")));
|
|
201
|
+
if (result.variables.length === 0 && result.unusedVariables.length === 0) {
|
|
202
|
+
console.log(pc.gray("No variables found"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (result.variables.length > 0) {
|
|
206
|
+
const table = new Table({
|
|
207
|
+
head: [pc.bold("Variable"), pc.bold("Status"), pc.bold("Used in")],
|
|
208
|
+
style: { head: [], border: [] },
|
|
209
|
+
wordWrap: true,
|
|
210
|
+
});
|
|
211
|
+
for (const v of result.variables) {
|
|
212
|
+
const status = v.isDefined ? pc.green("✓ defined") : pc.red("✗ undefined");
|
|
213
|
+
const files = v.files.map((f) => pc.gray(f)).join("\n");
|
|
214
|
+
table.push([v.name, status, files]);
|
|
215
|
+
}
|
|
216
|
+
console.log(table.toString());
|
|
217
|
+
}
|
|
218
|
+
if (result.unusedVariables.length > 0) {
|
|
219
|
+
console.log(pc.bold(pc.yellow("\n⚠ Unused variables (defined but not used):\n")));
|
|
220
|
+
for (const name of result.unusedVariables) {
|
|
221
|
+
console.log(pc.gray(` ${name}`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
console.log();
|
|
225
|
+
const defined = result.variables.filter((v) => v.isDefined).length;
|
|
226
|
+
const undefined_ = result.variables.filter((v) => !v.isDefined).length;
|
|
227
|
+
console.log(pc.bold("Summary: ") +
|
|
228
|
+
pc.green(`${defined} defined`) +
|
|
229
|
+
pc.gray(" · ") +
|
|
230
|
+
(undefined_ > 0 ? pc.red(`${undefined_} undefined`) : pc.gray("0 undefined")) +
|
|
231
|
+
pc.gray(" · ") +
|
|
232
|
+
pc.yellow(`${result.unusedVariables.length} unused`));
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error instanceof VariablesFileNotFoundError) {
|
|
236
|
+
console.error(pc.red(`✗ Error: ${error.message}`));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Handle rename mode
|
|
244
|
+
if (args["rename-from"] || args["rename-to"]) {
|
|
245
|
+
if (!args["rename-from"] || !args["rename-to"]) {
|
|
246
|
+
console.error(pc.red("✗ Error: Both --rename-from and --rename-to are required"));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const result = await renameVariable({
|
|
251
|
+
input: args.input,
|
|
252
|
+
vars: args.vars,
|
|
253
|
+
from: args["rename-from"],
|
|
254
|
+
to: args["rename-to"],
|
|
255
|
+
include: args.include,
|
|
256
|
+
exclude: args.exclude,
|
|
257
|
+
});
|
|
258
|
+
if (result.renamedInFiles.length === 0 && !result.renamedInVars) {
|
|
259
|
+
console.log(pc.yellow(`⚠ No occurrences of "${args["rename-from"]}" found`));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
console.log(pc.bold(pc.cyan("\n✏️ Rename complete\n")) +
|
|
263
|
+
pc.gray(` ${args["rename-from"]}`) +
|
|
264
|
+
pc.cyan(" → ") +
|
|
265
|
+
pc.green(args["rename-to"]) +
|
|
266
|
+
"\n");
|
|
267
|
+
const table = new Table({
|
|
268
|
+
head: [pc.bold("File"), pc.bold("Status")],
|
|
269
|
+
style: { head: [], border: [] },
|
|
270
|
+
});
|
|
271
|
+
if (result.renamedInVars) {
|
|
272
|
+
table.push([pc.italic("variables.yaml"), pc.green("✓ updated")]);
|
|
273
|
+
}
|
|
274
|
+
for (const file of result.renamedInFiles) {
|
|
275
|
+
table.push([file, pc.green("✓ updated")]);
|
|
276
|
+
}
|
|
277
|
+
console.log(table.toString());
|
|
278
|
+
console.log();
|
|
279
|
+
const total = result.renamedInFiles.length + (result.renamedInVars ? 1 : 0);
|
|
280
|
+
console.log(pc.bold("Updated: ") + pc.green(`${total} file(s)`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (error instanceof VariablesFileNotFoundError) {
|
|
285
|
+
console.error(pc.red(`✗ Error: ${error.message}`));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Normal processing mode
|
|
293
|
+
const options = {
|
|
294
|
+
input: args.input,
|
|
295
|
+
output: args.output,
|
|
296
|
+
vars: args.vars,
|
|
297
|
+
include: args.include,
|
|
298
|
+
exclude: args.exclude,
|
|
299
|
+
};
|
|
300
|
+
const success = await runProcess(options);
|
|
301
|
+
if (args.watch) {
|
|
302
|
+
if (!success) {
|
|
303
|
+
console.log("\nFix errors and save to retry...\n");
|
|
304
|
+
}
|
|
305
|
+
startWatch(options);
|
|
306
|
+
}
|
|
307
|
+
else if (!success) {
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
@@ -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,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docvars",
|
|
3
|
+
"author": "Shunta Toda",
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"description": "Replace {{variables}} in document 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
|
+
"docvars": "./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
|
+
"template",
|
|
24
|
+
"variables",
|
|
25
|
+
"yaml",
|
|
26
|
+
"cli",
|
|
27
|
+
"documents",
|
|
28
|
+
"markdown",
|
|
29
|
+
"html",
|
|
30
|
+
"text"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/ShuntaToda/docvars.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/ShuntaToda/docvars#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/ShuntaToda/docvars/issues"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chokidar": "^5.0.0",
|
|
46
|
+
"citty": "^0.1.6",
|
|
47
|
+
"cli-table3": "^0.6.5",
|
|
48
|
+
"fast-glob": "^3.3.2",
|
|
49
|
+
"picocolors": "^1.1.1",
|
|
50
|
+
"yaml": "^2.3.4",
|
|
51
|
+
"zod": "^3.22.4"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.11.0",
|
|
55
|
+
"typescript": "^5.3.3",
|
|
56
|
+
"vitest": "^1.2.0"
|
|
57
|
+
}
|
|
58
|
+
}
|