@gregorlohaus/tdir 0.1.3 → 0.1.5
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 +34 -2
- package/dist/cli.js +233 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +372 -63
- package/dist/render.d.ts +14 -1
- package/dist/reverse.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,7 +142,7 @@ Pass a string to choose a custom JSON path inside the output directory:
|
|
|
142
142
|
render("./output", context, { reverseMap: "meta/reverse-map.json" })
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
The map contains a flat lookup from rendered strings to template tokens
|
|
145
|
+
The map contains a flat lookup from rendered strings to template tokens, per-file occurrences with path/range context, inline conditional blocks, and template files skipped by path conditionals:
|
|
146
146
|
|
|
147
147
|
```json
|
|
148
148
|
{
|
|
@@ -160,10 +160,27 @@ The map contains a flat lookup from rendered strings to template tokens plus per
|
|
|
160
160
|
"outputPath": "web/index.html",
|
|
161
161
|
"templatePath": "<@if(context.web)>web/index.html",
|
|
162
162
|
"range": { "start": 16, "end": 21 }
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"kind": "conditional",
|
|
166
|
+
"result": "<head><@var(context.header.title)></head>",
|
|
167
|
+
"token": "<@if(context.header.show)><head><@var(context.header.title)></head><@endif>",
|
|
168
|
+
"outputPath": "web/index.html",
|
|
169
|
+
"templatePath": "<@if(context.web)>web/index.html",
|
|
170
|
+
"range": { "start": 9, "end": 53 },
|
|
171
|
+
"activeRange": { "start": 27, "end": 71 }
|
|
163
172
|
}
|
|
164
173
|
]
|
|
165
174
|
}
|
|
166
175
|
],
|
|
176
|
+
"skipped": [
|
|
177
|
+
{
|
|
178
|
+
"kind": "file",
|
|
179
|
+
"templatePath": "<@if(context.docs)>docs/readme.md",
|
|
180
|
+
"encoding": "utf8",
|
|
181
|
+
"content": "# Docs"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
167
184
|
"tokens": {
|
|
168
185
|
"Hello": ["<@var(context.header.title)>"]
|
|
169
186
|
}
|
|
@@ -191,7 +208,22 @@ tdir reverse ./output ./templates --map meta/reverse-map.json
|
|
|
191
208
|
bunx @gregorlohaus/tdir reverse ./output ./templates --map meta/reverse-map.json
|
|
192
209
|
```
|
|
193
210
|
|
|
194
|
-
|
|
211
|
+
New files created in the rendered directory are ignored by default. Include them explicitly with one or more glob patterns:
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
tdir reverse ./output ./templates --include "components/**"
|
|
215
|
+
tdir reverse ./output ./templates --include "components/**/*.ts" --include "pages/*.html"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Programmatically, pass the same globs to `reverseDir`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
reverseDir("./output", "./templates", {
|
|
222
|
+
include: ["components/**/*.ts", "pages/*.html"]
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The command writes files at their original template paths, restores recorded `<@var(...)>` tokens, wraps edited inline conditional output back in the original conditional block, and restores template files that were skipped by path conditionals.
|
|
195
227
|
|
|
196
228
|
## Unmatched directives
|
|
197
229
|
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
existsSync,
|
|
7
7
|
mkdirSync,
|
|
8
8
|
readFileSync,
|
|
9
|
+
readdirSync,
|
|
9
10
|
statSync,
|
|
10
11
|
writeFileSync
|
|
11
12
|
} from "node:fs";
|
|
@@ -39,6 +40,45 @@ function readManifest(mapPath) {
|
|
|
39
40
|
}
|
|
40
41
|
return manifest;
|
|
41
42
|
}
|
|
43
|
+
function normalizePath(path) {
|
|
44
|
+
return path.split("\\").join("/");
|
|
45
|
+
}
|
|
46
|
+
function escapeRegExp(text) {
|
|
47
|
+
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
48
|
+
}
|
|
49
|
+
function globToRegExp(glob) {
|
|
50
|
+
let source = "^";
|
|
51
|
+
const pattern = normalizePath(glob);
|
|
52
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
53
|
+
const char = pattern[i];
|
|
54
|
+
const next = pattern[i + 1];
|
|
55
|
+
if (char === "*" && next === "*") {
|
|
56
|
+
if (pattern[i + 2] === "/") {
|
|
57
|
+
source += "(?:.*/)?";
|
|
58
|
+
i += 2;
|
|
59
|
+
} else {
|
|
60
|
+
source += ".*";
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
} else if (char === "*") {
|
|
64
|
+
source += "[^/]*";
|
|
65
|
+
} else if (char === "?") {
|
|
66
|
+
source += "[^/]";
|
|
67
|
+
} else {
|
|
68
|
+
source += escapeRegExp(char);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return new RegExp(`${source}$`);
|
|
72
|
+
}
|
|
73
|
+
function getIncludeMatchers(include) {
|
|
74
|
+
if (!include)
|
|
75
|
+
return [];
|
|
76
|
+
return (Array.isArray(include) ? include : [include]).map(globToRegExp);
|
|
77
|
+
}
|
|
78
|
+
function matchesAny(path, matchers) {
|
|
79
|
+
const normalized = normalizePath(path);
|
|
80
|
+
return matchers.some((matcher) => matcher.test(normalized));
|
|
81
|
+
}
|
|
42
82
|
function replaceAtRange(content, token) {
|
|
43
83
|
if (!token.range)
|
|
44
84
|
return null;
|
|
@@ -55,10 +95,66 @@ function replaceFirst(content, token) {
|
|
|
55
95
|
return null;
|
|
56
96
|
return `${content.slice(0, index)}${token.token}${content.slice(index + token.result.length)}`;
|
|
57
97
|
}
|
|
98
|
+
function applyActiveBranch(token, branchContent) {
|
|
99
|
+
if (!token.activeRange)
|
|
100
|
+
return token.token;
|
|
101
|
+
return [
|
|
102
|
+
token.token.slice(0, token.activeRange.start),
|
|
103
|
+
branchContent,
|
|
104
|
+
token.token.slice(token.activeRange.end)
|
|
105
|
+
].join("");
|
|
106
|
+
}
|
|
107
|
+
function findPrefixEnd(content, before) {
|
|
108
|
+
if (before === "")
|
|
109
|
+
return 0;
|
|
110
|
+
let candidate = before;
|
|
111
|
+
while (candidate.length >= 8) {
|
|
112
|
+
const index = content.indexOf(candidate);
|
|
113
|
+
if (index !== -1)
|
|
114
|
+
return index + candidate.length;
|
|
115
|
+
candidate = candidate.slice(-Math.max(1, Math.floor(candidate.length / 2)));
|
|
116
|
+
}
|
|
117
|
+
return -1;
|
|
118
|
+
}
|
|
119
|
+
function findSuffixStart(content, after, from) {
|
|
120
|
+
if (after === "")
|
|
121
|
+
return content.length;
|
|
122
|
+
let candidate = after;
|
|
123
|
+
while (candidate.length >= 8) {
|
|
124
|
+
const index = content.indexOf(candidate, from);
|
|
125
|
+
if (index !== -1)
|
|
126
|
+
return index;
|
|
127
|
+
candidate = candidate.slice(0, Math.floor(candidate.length / 2));
|
|
128
|
+
}
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
function replaceConditional(content, token) {
|
|
132
|
+
const exactIndex = content.indexOf(token.result);
|
|
133
|
+
if (exactIndex !== -1) {
|
|
134
|
+
return [
|
|
135
|
+
content.slice(0, exactIndex),
|
|
136
|
+
token.token,
|
|
137
|
+
content.slice(exactIndex + token.result.length)
|
|
138
|
+
].join("");
|
|
139
|
+
}
|
|
140
|
+
if (token.before === undefined || token.after === undefined)
|
|
141
|
+
return null;
|
|
142
|
+
const branchStart = findPrefixEnd(content, token.before);
|
|
143
|
+
if (branchStart === -1)
|
|
144
|
+
return null;
|
|
145
|
+
const afterIndex = findSuffixStart(content, token.after, branchStart);
|
|
146
|
+
if (afterIndex === -1)
|
|
147
|
+
return null;
|
|
148
|
+
return [
|
|
149
|
+
content.slice(0, branchStart),
|
|
150
|
+
applyActiveBranch(token, content.slice(branchStart, afterIndex)),
|
|
151
|
+
content.slice(afterIndex)
|
|
152
|
+
].join("");
|
|
153
|
+
}
|
|
58
154
|
function reverseContent(content, file, warnings) {
|
|
59
|
-
const
|
|
155
|
+
const contentTokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
|
|
60
156
|
let reversed = content;
|
|
61
|
-
for (const token of
|
|
157
|
+
for (const token of contentTokens) {
|
|
62
158
|
const rangeResult = replaceAtRange(reversed, token);
|
|
63
159
|
if (rangeResult !== null) {
|
|
64
160
|
reversed = rangeResult;
|
|
@@ -76,8 +172,122 @@ function reverseContent(content, file, warnings) {
|
|
|
76
172
|
message: "Rendered value was not found; token was not restored"
|
|
77
173
|
});
|
|
78
174
|
}
|
|
175
|
+
const conditionalTokens = file.tokens.filter((token) => token.kind === "conditional").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
|
|
176
|
+
for (const token of conditionalTokens) {
|
|
177
|
+
const result = replaceConditional(reversed, token);
|
|
178
|
+
if (result !== null) {
|
|
179
|
+
reversed = result;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
warnings.push({
|
|
183
|
+
outputPath: file.outputPath,
|
|
184
|
+
token: token.token,
|
|
185
|
+
result: token.result,
|
|
186
|
+
message: "Rendered conditional block was not found; block was not restored"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
79
189
|
return reversed;
|
|
80
190
|
}
|
|
191
|
+
function writeSkippedTemplate(templateRoot, skipped) {
|
|
192
|
+
const templatePath = resolveInside(templateRoot, skipped.templatePath);
|
|
193
|
+
if (skipped.kind === "directory") {
|
|
194
|
+
mkdirSync(templatePath, { recursive: true });
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
mkdirSync(dirname(templatePath), { recursive: true });
|
|
198
|
+
const content = skipped.encoding === "base64" ? Buffer.from(skipped.content ?? "", "base64") : skipped.content ?? "";
|
|
199
|
+
writeFileSync(templatePath, content);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
function dirnamePath(path) {
|
|
203
|
+
const normalized = normalizePath(path);
|
|
204
|
+
const index = normalized.lastIndexOf("/");
|
|
205
|
+
return index === -1 ? "" : normalized.slice(0, index);
|
|
206
|
+
}
|
|
207
|
+
function basenamePath(path) {
|
|
208
|
+
const normalized = normalizePath(path);
|
|
209
|
+
const index = normalized.lastIndexOf("/");
|
|
210
|
+
return index === -1 ? normalized : normalized.slice(index + 1);
|
|
211
|
+
}
|
|
212
|
+
function joinPath(...parts) {
|
|
213
|
+
return parts.filter((part) => part !== "").join("/");
|
|
214
|
+
}
|
|
215
|
+
function buildDirectoryMap(manifest) {
|
|
216
|
+
const mappings = new Map([["", ""]]);
|
|
217
|
+
function addMapping(outputDir, templateDir) {
|
|
218
|
+
const outputParts = normalizePath(outputDir).split("/").filter(Boolean);
|
|
219
|
+
const templateParts = normalizePath(templateDir).split("/").filter(Boolean);
|
|
220
|
+
for (let i = 1;i <= outputParts.length; i++) {
|
|
221
|
+
if (i <= templateParts.length) {
|
|
222
|
+
mappings.set(outputParts.slice(0, i).join("/"), templateParts.slice(0, i).join("/"));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
mappings.set(normalizePath(outputDir), normalizePath(templateDir));
|
|
226
|
+
}
|
|
227
|
+
for (const file of manifest.files) {
|
|
228
|
+
const output = normalizePath(file.outputPath);
|
|
229
|
+
const template = normalizePath(file.templatePath);
|
|
230
|
+
addMapping(dirnamePath(output), dirnamePath(template));
|
|
231
|
+
if (file.tokens.some((token) => token.kind === "path"))
|
|
232
|
+
addMapping(output, template);
|
|
233
|
+
}
|
|
234
|
+
for (const skipped of manifest.skipped ?? []) {
|
|
235
|
+
if (skipped.kind === "directory")
|
|
236
|
+
continue;
|
|
237
|
+
addMapping(dirnamePath(skipped.templatePath), dirnamePath(skipped.templatePath));
|
|
238
|
+
}
|
|
239
|
+
return mappings;
|
|
240
|
+
}
|
|
241
|
+
function inferTemplatePath(outputPath, directoryMap) {
|
|
242
|
+
const normalized = normalizePath(outputPath);
|
|
243
|
+
const outputDir = dirnamePath(normalized);
|
|
244
|
+
let bestOutputDir = "";
|
|
245
|
+
let bestTemplateDir = "";
|
|
246
|
+
for (const [mappedOutputDir, mappedTemplateDir] of directoryMap) {
|
|
247
|
+
if (mappedOutputDir.length >= bestOutputDir.length && (outputDir === mappedOutputDir || outputDir.startsWith(`${mappedOutputDir}/`))) {
|
|
248
|
+
bestOutputDir = mappedOutputDir;
|
|
249
|
+
bestTemplateDir = mappedTemplateDir;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const suffix = bestOutputDir === "" ? outputDir : outputDir.slice(bestOutputDir.length).replace(/^\//, "");
|
|
253
|
+
return joinPath(bestTemplateDir, suffix, basenamePath(normalized));
|
|
254
|
+
}
|
|
255
|
+
function walkFiles(root, current = root) {
|
|
256
|
+
const files = [];
|
|
257
|
+
for (const entry of readdirSync(current).sort()) {
|
|
258
|
+
const path = resolvePath(current, entry);
|
|
259
|
+
const stat = statSync(path);
|
|
260
|
+
if (stat.isDirectory()) {
|
|
261
|
+
files.push(...walkFiles(root, path));
|
|
262
|
+
} else if (stat.isFile()) {
|
|
263
|
+
files.push(normalizePath(relative(root, path)));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return files;
|
|
267
|
+
}
|
|
268
|
+
function copyIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, include) {
|
|
269
|
+
const matchers = getIncludeMatchers(include);
|
|
270
|
+
if (matchers.length === 0)
|
|
271
|
+
return 0;
|
|
272
|
+
const mappedOutputPaths = new Set(manifest.files.map((file) => normalizePath(file.outputPath)));
|
|
273
|
+
const mapRelativePath = normalizePath(relative(renderedRoot, mapPath));
|
|
274
|
+
const directoryMap = buildDirectoryMap(manifest);
|
|
275
|
+
let filesWritten = 0;
|
|
276
|
+
for (const outputPath of walkFiles(renderedRoot)) {
|
|
277
|
+
if (outputPath === mapRelativePath)
|
|
278
|
+
continue;
|
|
279
|
+
if (mappedOutputPaths.has(outputPath))
|
|
280
|
+
continue;
|
|
281
|
+
if (!matchesAny(outputPath, matchers))
|
|
282
|
+
continue;
|
|
283
|
+
const renderedPath = resolveInside(renderedRoot, outputPath);
|
|
284
|
+
const templatePath = resolveInside(templateRoot, inferTemplatePath(outputPath, directoryMap));
|
|
285
|
+
mkdirSync(dirname(templatePath), { recursive: true });
|
|
286
|
+
copyFileSync(renderedPath, templatePath);
|
|
287
|
+
filesWritten += 1;
|
|
288
|
+
}
|
|
289
|
+
return filesWritten;
|
|
290
|
+
}
|
|
81
291
|
function reverseDir(renderedDir, templateDir, options = {}) {
|
|
82
292
|
const renderedRoot = resolvePath(renderedDir);
|
|
83
293
|
const templateRoot = resolvePath(templateDir);
|
|
@@ -114,6 +324,10 @@ function reverseDir(renderedDir, templateDir, options = {}) {
|
|
|
114
324
|
writeFileSync(templatePath, reverseContent(content.toString("utf-8"), file, warnings));
|
|
115
325
|
filesWritten += 1;
|
|
116
326
|
}
|
|
327
|
+
for (const skipped of manifest.skipped ?? []) {
|
|
328
|
+
filesWritten += writeSkippedTemplate(templateRoot, skipped);
|
|
329
|
+
}
|
|
330
|
+
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, options.include);
|
|
117
331
|
return { filesWritten, warnings };
|
|
118
332
|
}
|
|
119
333
|
|
|
@@ -122,7 +336,7 @@ function printHelp() {
|
|
|
122
336
|
console.log(`tdir
|
|
123
337
|
|
|
124
338
|
Usage:
|
|
125
|
-
tdir reverse <rendered-dir> <template-dir> [--map <path>]
|
|
339
|
+
tdir reverse <rendered-dir> <template-dir> [--map <path>] [--include <glob>...]
|
|
126
340
|
|
|
127
341
|
Commands:
|
|
128
342
|
reverse Rebuild template files from a rendered directory and reverse map
|
|
@@ -130,16 +344,18 @@ Commands:
|
|
|
130
344
|
Options:
|
|
131
345
|
--map Reverse map path. Defaults to <rendered-dir>/.tdir-map.json.
|
|
132
346
|
Relative paths are resolved from <rendered-dir>.
|
|
347
|
+
--include Include new rendered files matching a glob. Can be repeated.
|
|
133
348
|
--help Show this help message.
|
|
134
349
|
`);
|
|
135
350
|
}
|
|
136
351
|
function parseReverseArgs(args) {
|
|
137
352
|
const positional = [];
|
|
353
|
+
const include = [];
|
|
138
354
|
let mapPath;
|
|
139
355
|
for (let i = 0;i < args.length; i++) {
|
|
140
356
|
const arg = args[i];
|
|
141
357
|
if (arg === "--help" || arg === "-h") {
|
|
142
|
-
return { help: true, positional, mapPath };
|
|
358
|
+
return { help: true, positional, mapPath, include };
|
|
143
359
|
}
|
|
144
360
|
if (arg === "--map") {
|
|
145
361
|
const value = args[++i];
|
|
@@ -148,9 +364,16 @@ function parseReverseArgs(args) {
|
|
|
148
364
|
mapPath = value;
|
|
149
365
|
continue;
|
|
150
366
|
}
|
|
367
|
+
if (arg === "--include") {
|
|
368
|
+
const value = args[++i];
|
|
369
|
+
if (!value)
|
|
370
|
+
throw new Error("Missing value for --include");
|
|
371
|
+
include.push(value);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
151
374
|
positional.push(arg);
|
|
152
375
|
}
|
|
153
|
-
return { help: false, positional, mapPath };
|
|
376
|
+
return { help: false, positional, mapPath, include };
|
|
154
377
|
}
|
|
155
378
|
function main(argv) {
|
|
156
379
|
const [command, ...args] = argv;
|
|
@@ -168,9 +391,12 @@ function main(argv) {
|
|
|
168
391
|
}
|
|
169
392
|
const [renderedDir, templateDir] = parsed.positional;
|
|
170
393
|
if (!renderedDir || !templateDir || parsed.positional.length > 2) {
|
|
171
|
-
throw new Error("Usage: tdir reverse <rendered-dir> <template-dir> [--map <path>]");
|
|
394
|
+
throw new Error("Usage: tdir reverse <rendered-dir> <template-dir> [--map <path>] [--include <glob>...]");
|
|
172
395
|
}
|
|
173
|
-
const result = reverseDir(renderedDir, templateDir, {
|
|
396
|
+
const result = reverseDir(renderedDir, templateDir, {
|
|
397
|
+
mapPath: parsed.mapPath,
|
|
398
|
+
include: parsed.include
|
|
399
|
+
});
|
|
174
400
|
console.log(`Wrote ${result.filesWritten} file${result.filesWritten === 1 ? "" : "s"}`);
|
|
175
401
|
for (const warning of result.warnings) {
|
|
176
402
|
console.warn(`Warning: ${warning.outputPath}: ${warning.message}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type RenderOptions } from "./render";
|
|
3
|
-
export type { RenderOptions, ReverseMapFile, ReverseMapManifest, ReverseMapToken } from "./render";
|
|
3
|
+
export type { RenderOptions, ReverseMapFile, ReverseMapManifest, ReverseMapStoredTemplate, ReverseMapToken } from "./render";
|
|
4
4
|
export { reverseDir, type ReverseOptions, type ReverseResult, type ReverseWarning } from "./reverse";
|
|
5
5
|
interface Stringable {
|
|
6
6
|
toString: () => string;
|
package/dist/index.js
CHANGED
|
@@ -175,7 +175,7 @@ function resolveOutputPath(destRoot, outputName) {
|
|
|
175
175
|
return outputPath;
|
|
176
176
|
}
|
|
177
177
|
function createReverseMapManifest() {
|
|
178
|
-
return { version: 1, files: [], tokens: {} };
|
|
178
|
+
return { version: 1, files: [], skipped: [], tokens: {} };
|
|
179
179
|
}
|
|
180
180
|
function addReverseMapToken(state, file, token) {
|
|
181
181
|
if (!state?.manifest || !file)
|
|
@@ -231,69 +231,131 @@ function isUtf8Text2(buffer) {
|
|
|
231
231
|
return false;
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
if (
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
234
|
+
function getDirectiveTokens(content) {
|
|
235
|
+
return Array.from(content.matchAll(DIRECTIVE_RE2), (match) => ({
|
|
236
|
+
type: match[1],
|
|
237
|
+
condition: match[2],
|
|
238
|
+
index: match.index,
|
|
239
|
+
end: match.index + match[0].length
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
function parseNodes(content, tokens, tokenIndex, pos, stopTypes) {
|
|
243
|
+
const nodes = [];
|
|
244
|
+
while (tokenIndex < tokens.length) {
|
|
245
|
+
const token = tokens[tokenIndex];
|
|
246
|
+
if (stopTypes.includes(token.type)) {
|
|
247
|
+
if (token.index > pos)
|
|
248
|
+
nodes.push({ type: "text", text: content.slice(pos, token.index) });
|
|
249
|
+
return { nodes, pos: token.index, tokenIndex, stop: token };
|
|
250
|
+
}
|
|
251
|
+
if (token.type !== "if") {
|
|
252
|
+
throw new Error(`Unexpected <@${token.type}> without <@if>`);
|
|
253
|
+
}
|
|
254
|
+
if (token.index > pos)
|
|
255
|
+
nodes.push({ type: "text", text: content.slice(pos, token.index) });
|
|
256
|
+
const parsed = parseIfNode(content, tokens, tokenIndex);
|
|
257
|
+
nodes.push(parsed.node);
|
|
258
|
+
tokenIndex = parsed.tokenIndex;
|
|
259
|
+
pos = parsed.pos;
|
|
260
|
+
}
|
|
261
|
+
if (pos < content.length)
|
|
262
|
+
nodes.push({ type: "text", text: content.slice(pos) });
|
|
263
|
+
return { nodes, pos: content.length, tokenIndex };
|
|
264
|
+
}
|
|
265
|
+
function parseIfNode(content, tokens, tokenIndex) {
|
|
266
|
+
const firstToken = tokens[tokenIndex];
|
|
267
|
+
const sourceStart = firstToken.index;
|
|
268
|
+
const branches = [];
|
|
269
|
+
let branchType = "if";
|
|
270
|
+
let branchCondition = firstToken.condition;
|
|
271
|
+
let branchContentStart = firstToken.end;
|
|
272
|
+
tokenIndex += 1;
|
|
273
|
+
while (true) {
|
|
274
|
+
const parsed = parseNodes(content, tokens, tokenIndex, branchContentStart, ["elseif", "else", "endif"]);
|
|
275
|
+
if (!parsed.stop)
|
|
276
|
+
throw new Error("Unmatched <@if> without <@endif>");
|
|
277
|
+
branches.push({
|
|
278
|
+
type: branchType,
|
|
279
|
+
condition: branchCondition,
|
|
280
|
+
nodes: parsed.nodes,
|
|
281
|
+
contentStart: branchContentStart,
|
|
282
|
+
contentEnd: parsed.pos
|
|
283
|
+
});
|
|
284
|
+
if (parsed.stop.type === "endif") {
|
|
285
|
+
const sourceEnd = parsed.stop.end;
|
|
286
|
+
return {
|
|
287
|
+
node: {
|
|
288
|
+
type: "if",
|
|
289
|
+
sourceStart,
|
|
290
|
+
sourceEnd,
|
|
291
|
+
source: content.slice(sourceStart, sourceEnd),
|
|
292
|
+
branches
|
|
293
|
+
},
|
|
294
|
+
pos: sourceEnd,
|
|
295
|
+
tokenIndex: parsed.tokenIndex + 1
|
|
296
|
+
};
|
|
287
297
|
}
|
|
298
|
+
branchType = parsed.stop.type;
|
|
299
|
+
branchCondition = parsed.stop.condition;
|
|
300
|
+
branchContentStart = parsed.stop.end;
|
|
301
|
+
tokenIndex = parsed.tokenIndex + 1;
|
|
288
302
|
}
|
|
289
|
-
|
|
290
|
-
|
|
303
|
+
}
|
|
304
|
+
function getActiveBranch(node, context) {
|
|
305
|
+
for (const branch of node.branches) {
|
|
306
|
+
if (branch.type === "else" || evalCondition(branch.condition, context))
|
|
307
|
+
return branch;
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
function renderNodes(nodes, context, conditionalTokens, outputStart = 0) {
|
|
312
|
+
let result = "";
|
|
313
|
+
for (const node of nodes) {
|
|
314
|
+
if (node.type === "text") {
|
|
315
|
+
result += node.text;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const activeBranch = getActiveBranch(node, context);
|
|
319
|
+
const start = outputStart + result.length;
|
|
320
|
+
const renderedBranch = activeBranch ? renderNodes(activeBranch.nodes, context, conditionalTokens, start) : "";
|
|
321
|
+
result += renderedBranch;
|
|
322
|
+
conditionalTokens.push({
|
|
323
|
+
result: renderedBranch,
|
|
324
|
+
token: node.source,
|
|
325
|
+
range: { start, end: start + renderedBranch.length },
|
|
326
|
+
activeRange: activeBranch ? {
|
|
327
|
+
start: activeBranch.contentStart - node.sourceStart,
|
|
328
|
+
end: activeBranch.contentEnd - node.sourceStart
|
|
329
|
+
} : { start: node.source.length, end: node.source.length }
|
|
330
|
+
});
|
|
291
331
|
}
|
|
292
|
-
result += content.slice(pos);
|
|
293
332
|
return result;
|
|
294
333
|
}
|
|
334
|
+
function processIfBlocksWithMap(content, context) {
|
|
335
|
+
const tokens = getDirectiveTokens(content);
|
|
336
|
+
const parsed = parseNodes(content, tokens, 0, 0, []);
|
|
337
|
+
const conditionalTokens = [];
|
|
338
|
+
return {
|
|
339
|
+
content: renderNodes(parsed.nodes, context, conditionalTokens),
|
|
340
|
+
conditionalTokens
|
|
341
|
+
};
|
|
342
|
+
}
|
|
295
343
|
function renderContentWithMap(content, context, state, file) {
|
|
296
|
-
const
|
|
344
|
+
const processedResult = processIfBlocksWithMap(content, context);
|
|
345
|
+
const processed = processedResult.content;
|
|
346
|
+
for (const token of processedResult.conditionalTokens) {
|
|
347
|
+
addReverseMapToken(state, file, {
|
|
348
|
+
kind: "conditional",
|
|
349
|
+
result: token.result,
|
|
350
|
+
token: token.token,
|
|
351
|
+
outputPath: file?.outputPath ?? "",
|
|
352
|
+
templatePath: file?.templatePath ?? "",
|
|
353
|
+
range: token.range,
|
|
354
|
+
activeRange: token.activeRange,
|
|
355
|
+
before: processed.slice(0, token.range.start),
|
|
356
|
+
after: processed.slice(token.range.end)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
297
359
|
let result = "";
|
|
298
360
|
let pos = 0;
|
|
299
361
|
for (const match of processed.matchAll(VAR_RE2)) {
|
|
@@ -350,6 +412,37 @@ function validateOutputPaths(srcDir, destDir, context) {
|
|
|
350
412
|
}
|
|
351
413
|
}
|
|
352
414
|
}
|
|
415
|
+
function storeSkippedTemplate(srcPath, state) {
|
|
416
|
+
if (!state.manifest)
|
|
417
|
+
return;
|
|
418
|
+
const stat = statSync2(srcPath);
|
|
419
|
+
const templatePath = relative(state.sourceRoot, srcPath);
|
|
420
|
+
if (stat.isDirectory()) {
|
|
421
|
+
state.manifest.skipped.push({ kind: "directory", templatePath });
|
|
422
|
+
for (const entry of readdirSync2(srcPath).sort()) {
|
|
423
|
+
storeSkippedTemplate(resolvePath(srcPath, entry), state);
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!stat.isFile())
|
|
428
|
+
return;
|
|
429
|
+
const content = readFileSync2(srcPath);
|
|
430
|
+
if (isUtf8Text2(content)) {
|
|
431
|
+
state.manifest.skipped.push({
|
|
432
|
+
kind: "file",
|
|
433
|
+
templatePath,
|
|
434
|
+
encoding: "utf8",
|
|
435
|
+
content: content.toString("utf-8")
|
|
436
|
+
});
|
|
437
|
+
} else {
|
|
438
|
+
state.manifest.skipped.push({
|
|
439
|
+
kind: "file",
|
|
440
|
+
templatePath,
|
|
441
|
+
encoding: "base64",
|
|
442
|
+
content: content.toString("base64")
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
353
446
|
function renderDirInner(srcDir, destDir, context, state) {
|
|
354
447
|
mkdirSync(destDir, { recursive: true });
|
|
355
448
|
const entries = readdirSync2(srcDir).sort();
|
|
@@ -363,8 +456,10 @@ function renderDirInner(srcDir, destDir, context, state) {
|
|
|
363
456
|
tokens: []
|
|
364
457
|
};
|
|
365
458
|
const outputName = getOutputName(entry, context, state, tempFile);
|
|
366
|
-
if (outputName === null)
|
|
459
|
+
if (outputName === null) {
|
|
460
|
+
storeSkippedTemplate(srcPath, state);
|
|
367
461
|
continue;
|
|
462
|
+
}
|
|
368
463
|
const destPath = resolveOutputPath(destDir, outputName);
|
|
369
464
|
const outputPath = relative(state.destRoot, destPath);
|
|
370
465
|
tempFile.outputPath = outputPath;
|
|
@@ -385,8 +480,8 @@ function renderDirInner(srcDir, destDir, context, state) {
|
|
|
385
480
|
} else {
|
|
386
481
|
copyFileSync(srcPath, destPath);
|
|
387
482
|
}
|
|
388
|
-
if (
|
|
389
|
-
state.manifest
|
|
483
|
+
if (state.manifest)
|
|
484
|
+
state.manifest.files.push(tempFile);
|
|
390
485
|
}
|
|
391
486
|
}
|
|
392
487
|
}
|
|
@@ -397,6 +492,7 @@ import {
|
|
|
397
492
|
existsSync,
|
|
398
493
|
mkdirSync as mkdirSync2,
|
|
399
494
|
readFileSync as readFileSync3,
|
|
495
|
+
readdirSync as readdirSync3,
|
|
400
496
|
statSync as statSync3,
|
|
401
497
|
writeFileSync as writeFileSync2
|
|
402
498
|
} from "node:fs";
|
|
@@ -430,6 +526,45 @@ function readManifest(mapPath) {
|
|
|
430
526
|
}
|
|
431
527
|
return manifest;
|
|
432
528
|
}
|
|
529
|
+
function normalizePath(path) {
|
|
530
|
+
return path.split("\\").join("/");
|
|
531
|
+
}
|
|
532
|
+
function escapeRegExp(text) {
|
|
533
|
+
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
534
|
+
}
|
|
535
|
+
function globToRegExp(glob) {
|
|
536
|
+
let source = "^";
|
|
537
|
+
const pattern = normalizePath(glob);
|
|
538
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
539
|
+
const char = pattern[i];
|
|
540
|
+
const next = pattern[i + 1];
|
|
541
|
+
if (char === "*" && next === "*") {
|
|
542
|
+
if (pattern[i + 2] === "/") {
|
|
543
|
+
source += "(?:.*/)?";
|
|
544
|
+
i += 2;
|
|
545
|
+
} else {
|
|
546
|
+
source += ".*";
|
|
547
|
+
i += 1;
|
|
548
|
+
}
|
|
549
|
+
} else if (char === "*") {
|
|
550
|
+
source += "[^/]*";
|
|
551
|
+
} else if (char === "?") {
|
|
552
|
+
source += "[^/]";
|
|
553
|
+
} else {
|
|
554
|
+
source += escapeRegExp(char);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return new RegExp(`${source}$`);
|
|
558
|
+
}
|
|
559
|
+
function getIncludeMatchers(include) {
|
|
560
|
+
if (!include)
|
|
561
|
+
return [];
|
|
562
|
+
return (Array.isArray(include) ? include : [include]).map(globToRegExp);
|
|
563
|
+
}
|
|
564
|
+
function matchesAny(path, matchers) {
|
|
565
|
+
const normalized = normalizePath(path);
|
|
566
|
+
return matchers.some((matcher) => matcher.test(normalized));
|
|
567
|
+
}
|
|
433
568
|
function replaceAtRange(content, token) {
|
|
434
569
|
if (!token.range)
|
|
435
570
|
return null;
|
|
@@ -446,10 +581,66 @@ function replaceFirst(content, token) {
|
|
|
446
581
|
return null;
|
|
447
582
|
return `${content.slice(0, index)}${token.token}${content.slice(index + token.result.length)}`;
|
|
448
583
|
}
|
|
584
|
+
function applyActiveBranch(token, branchContent) {
|
|
585
|
+
if (!token.activeRange)
|
|
586
|
+
return token.token;
|
|
587
|
+
return [
|
|
588
|
+
token.token.slice(0, token.activeRange.start),
|
|
589
|
+
branchContent,
|
|
590
|
+
token.token.slice(token.activeRange.end)
|
|
591
|
+
].join("");
|
|
592
|
+
}
|
|
593
|
+
function findPrefixEnd(content, before) {
|
|
594
|
+
if (before === "")
|
|
595
|
+
return 0;
|
|
596
|
+
let candidate = before;
|
|
597
|
+
while (candidate.length >= 8) {
|
|
598
|
+
const index = content.indexOf(candidate);
|
|
599
|
+
if (index !== -1)
|
|
600
|
+
return index + candidate.length;
|
|
601
|
+
candidate = candidate.slice(-Math.max(1, Math.floor(candidate.length / 2)));
|
|
602
|
+
}
|
|
603
|
+
return -1;
|
|
604
|
+
}
|
|
605
|
+
function findSuffixStart(content, after, from) {
|
|
606
|
+
if (after === "")
|
|
607
|
+
return content.length;
|
|
608
|
+
let candidate = after;
|
|
609
|
+
while (candidate.length >= 8) {
|
|
610
|
+
const index = content.indexOf(candidate, from);
|
|
611
|
+
if (index !== -1)
|
|
612
|
+
return index;
|
|
613
|
+
candidate = candidate.slice(0, Math.floor(candidate.length / 2));
|
|
614
|
+
}
|
|
615
|
+
return -1;
|
|
616
|
+
}
|
|
617
|
+
function replaceConditional(content, token) {
|
|
618
|
+
const exactIndex = content.indexOf(token.result);
|
|
619
|
+
if (exactIndex !== -1) {
|
|
620
|
+
return [
|
|
621
|
+
content.slice(0, exactIndex),
|
|
622
|
+
token.token,
|
|
623
|
+
content.slice(exactIndex + token.result.length)
|
|
624
|
+
].join("");
|
|
625
|
+
}
|
|
626
|
+
if (token.before === undefined || token.after === undefined)
|
|
627
|
+
return null;
|
|
628
|
+
const branchStart = findPrefixEnd(content, token.before);
|
|
629
|
+
if (branchStart === -1)
|
|
630
|
+
return null;
|
|
631
|
+
const afterIndex = findSuffixStart(content, token.after, branchStart);
|
|
632
|
+
if (afterIndex === -1)
|
|
633
|
+
return null;
|
|
634
|
+
return [
|
|
635
|
+
content.slice(0, branchStart),
|
|
636
|
+
applyActiveBranch(token, content.slice(branchStart, afterIndex)),
|
|
637
|
+
content.slice(afterIndex)
|
|
638
|
+
].join("");
|
|
639
|
+
}
|
|
449
640
|
function reverseContent(content, file, warnings) {
|
|
450
|
-
const
|
|
641
|
+
const contentTokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
|
|
451
642
|
let reversed = content;
|
|
452
|
-
for (const token of
|
|
643
|
+
for (const token of contentTokens) {
|
|
453
644
|
const rangeResult = replaceAtRange(reversed, token);
|
|
454
645
|
if (rangeResult !== null) {
|
|
455
646
|
reversed = rangeResult;
|
|
@@ -467,8 +658,122 @@ function reverseContent(content, file, warnings) {
|
|
|
467
658
|
message: "Rendered value was not found; token was not restored"
|
|
468
659
|
});
|
|
469
660
|
}
|
|
661
|
+
const conditionalTokens = file.tokens.filter((token) => token.kind === "conditional").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
|
|
662
|
+
for (const token of conditionalTokens) {
|
|
663
|
+
const result = replaceConditional(reversed, token);
|
|
664
|
+
if (result !== null) {
|
|
665
|
+
reversed = result;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
warnings.push({
|
|
669
|
+
outputPath: file.outputPath,
|
|
670
|
+
token: token.token,
|
|
671
|
+
result: token.result,
|
|
672
|
+
message: "Rendered conditional block was not found; block was not restored"
|
|
673
|
+
});
|
|
674
|
+
}
|
|
470
675
|
return reversed;
|
|
471
676
|
}
|
|
677
|
+
function writeSkippedTemplate(templateRoot, skipped) {
|
|
678
|
+
const templatePath = resolveInside(templateRoot, skipped.templatePath);
|
|
679
|
+
if (skipped.kind === "directory") {
|
|
680
|
+
mkdirSync2(templatePath, { recursive: true });
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
mkdirSync2(dirname2(templatePath), { recursive: true });
|
|
684
|
+
const content = skipped.encoding === "base64" ? Buffer.from(skipped.content ?? "", "base64") : skipped.content ?? "";
|
|
685
|
+
writeFileSync2(templatePath, content);
|
|
686
|
+
return 1;
|
|
687
|
+
}
|
|
688
|
+
function dirnamePath(path) {
|
|
689
|
+
const normalized = normalizePath(path);
|
|
690
|
+
const index = normalized.lastIndexOf("/");
|
|
691
|
+
return index === -1 ? "" : normalized.slice(0, index);
|
|
692
|
+
}
|
|
693
|
+
function basenamePath(path) {
|
|
694
|
+
const normalized = normalizePath(path);
|
|
695
|
+
const index = normalized.lastIndexOf("/");
|
|
696
|
+
return index === -1 ? normalized : normalized.slice(index + 1);
|
|
697
|
+
}
|
|
698
|
+
function joinPath(...parts) {
|
|
699
|
+
return parts.filter((part) => part !== "").join("/");
|
|
700
|
+
}
|
|
701
|
+
function buildDirectoryMap(manifest) {
|
|
702
|
+
const mappings = new Map([["", ""]]);
|
|
703
|
+
function addMapping(outputDir, templateDir) {
|
|
704
|
+
const outputParts = normalizePath(outputDir).split("/").filter(Boolean);
|
|
705
|
+
const templateParts = normalizePath(templateDir).split("/").filter(Boolean);
|
|
706
|
+
for (let i = 1;i <= outputParts.length; i++) {
|
|
707
|
+
if (i <= templateParts.length) {
|
|
708
|
+
mappings.set(outputParts.slice(0, i).join("/"), templateParts.slice(0, i).join("/"));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
mappings.set(normalizePath(outputDir), normalizePath(templateDir));
|
|
712
|
+
}
|
|
713
|
+
for (const file of manifest.files) {
|
|
714
|
+
const output = normalizePath(file.outputPath);
|
|
715
|
+
const template = normalizePath(file.templatePath);
|
|
716
|
+
addMapping(dirnamePath(output), dirnamePath(template));
|
|
717
|
+
if (file.tokens.some((token) => token.kind === "path"))
|
|
718
|
+
addMapping(output, template);
|
|
719
|
+
}
|
|
720
|
+
for (const skipped of manifest.skipped ?? []) {
|
|
721
|
+
if (skipped.kind === "directory")
|
|
722
|
+
continue;
|
|
723
|
+
addMapping(dirnamePath(skipped.templatePath), dirnamePath(skipped.templatePath));
|
|
724
|
+
}
|
|
725
|
+
return mappings;
|
|
726
|
+
}
|
|
727
|
+
function inferTemplatePath(outputPath, directoryMap) {
|
|
728
|
+
const normalized = normalizePath(outputPath);
|
|
729
|
+
const outputDir = dirnamePath(normalized);
|
|
730
|
+
let bestOutputDir = "";
|
|
731
|
+
let bestTemplateDir = "";
|
|
732
|
+
for (const [mappedOutputDir, mappedTemplateDir] of directoryMap) {
|
|
733
|
+
if (mappedOutputDir.length >= bestOutputDir.length && (outputDir === mappedOutputDir || outputDir.startsWith(`${mappedOutputDir}/`))) {
|
|
734
|
+
bestOutputDir = mappedOutputDir;
|
|
735
|
+
bestTemplateDir = mappedTemplateDir;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const suffix = bestOutputDir === "" ? outputDir : outputDir.slice(bestOutputDir.length).replace(/^\//, "");
|
|
739
|
+
return joinPath(bestTemplateDir, suffix, basenamePath(normalized));
|
|
740
|
+
}
|
|
741
|
+
function walkFiles(root, current = root) {
|
|
742
|
+
const files = [];
|
|
743
|
+
for (const entry of readdirSync3(current).sort()) {
|
|
744
|
+
const path = resolvePath2(current, entry);
|
|
745
|
+
const stat = statSync3(path);
|
|
746
|
+
if (stat.isDirectory()) {
|
|
747
|
+
files.push(...walkFiles(root, path));
|
|
748
|
+
} else if (stat.isFile()) {
|
|
749
|
+
files.push(normalizePath(relative2(root, path)));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return files;
|
|
753
|
+
}
|
|
754
|
+
function copyIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, include) {
|
|
755
|
+
const matchers = getIncludeMatchers(include);
|
|
756
|
+
if (matchers.length === 0)
|
|
757
|
+
return 0;
|
|
758
|
+
const mappedOutputPaths = new Set(manifest.files.map((file) => normalizePath(file.outputPath)));
|
|
759
|
+
const mapRelativePath = normalizePath(relative2(renderedRoot, mapPath));
|
|
760
|
+
const directoryMap = buildDirectoryMap(manifest);
|
|
761
|
+
let filesWritten = 0;
|
|
762
|
+
for (const outputPath of walkFiles(renderedRoot)) {
|
|
763
|
+
if (outputPath === mapRelativePath)
|
|
764
|
+
continue;
|
|
765
|
+
if (mappedOutputPaths.has(outputPath))
|
|
766
|
+
continue;
|
|
767
|
+
if (!matchesAny(outputPath, matchers))
|
|
768
|
+
continue;
|
|
769
|
+
const renderedPath = resolveInside(renderedRoot, outputPath);
|
|
770
|
+
const templatePath = resolveInside(templateRoot, inferTemplatePath(outputPath, directoryMap));
|
|
771
|
+
mkdirSync2(dirname2(templatePath), { recursive: true });
|
|
772
|
+
copyFileSync2(renderedPath, templatePath);
|
|
773
|
+
filesWritten += 1;
|
|
774
|
+
}
|
|
775
|
+
return filesWritten;
|
|
776
|
+
}
|
|
472
777
|
function reverseDir(renderedDir, templateDir, options = {}) {
|
|
473
778
|
const renderedRoot = resolvePath2(renderedDir);
|
|
474
779
|
const templateRoot = resolvePath2(templateDir);
|
|
@@ -505,6 +810,10 @@ function reverseDir(renderedDir, templateDir, options = {}) {
|
|
|
505
810
|
writeFileSync2(templatePath, reverseContent(content.toString("utf-8"), file, warnings));
|
|
506
811
|
filesWritten += 1;
|
|
507
812
|
}
|
|
813
|
+
for (const skipped of manifest.skipped ?? []) {
|
|
814
|
+
filesWritten += writeSkippedTemplate(templateRoot, skipped);
|
|
815
|
+
}
|
|
816
|
+
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, options.include);
|
|
508
817
|
return { filesWritten, warnings };
|
|
509
818
|
}
|
|
510
819
|
|
package/dist/render.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ReverseMapToken = {
|
|
2
|
-
kind: "path" | "content";
|
|
2
|
+
kind: "path" | "content" | "conditional";
|
|
3
3
|
result: string;
|
|
4
4
|
token: string;
|
|
5
5
|
contextPath?: string;
|
|
@@ -9,15 +9,28 @@ export type ReverseMapToken = {
|
|
|
9
9
|
start: number;
|
|
10
10
|
end: number;
|
|
11
11
|
};
|
|
12
|
+
activeRange?: {
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
};
|
|
16
|
+
before?: string;
|
|
17
|
+
after?: string;
|
|
12
18
|
};
|
|
13
19
|
export type ReverseMapFile = {
|
|
14
20
|
outputPath: string;
|
|
15
21
|
templatePath: string;
|
|
16
22
|
tokens: ReverseMapToken[];
|
|
17
23
|
};
|
|
24
|
+
export type ReverseMapStoredTemplate = {
|
|
25
|
+
kind: "directory" | "file";
|
|
26
|
+
templatePath: string;
|
|
27
|
+
encoding?: "utf8" | "base64";
|
|
28
|
+
content?: string;
|
|
29
|
+
};
|
|
18
30
|
export type ReverseMapManifest = {
|
|
19
31
|
version: 1;
|
|
20
32
|
files: ReverseMapFile[];
|
|
33
|
+
skipped: ReverseMapStoredTemplate[];
|
|
21
34
|
tokens: Record<string, string[]>;
|
|
22
35
|
};
|
|
23
36
|
export type RenderOptions = {
|
package/dist/reverse.d.ts
CHANGED