@gregorlohaus/tdir 0.1.5 → 0.1.7
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 +8 -0
- package/dist/cli.js +45 -24
- package/dist/index.js +58 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,6 +225,14 @@ reverseDir("./output", "./templates", {
|
|
|
225
225
|
|
|
226
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.
|
|
227
227
|
|
|
228
|
+
The template output directory may be inside the rendered directory, for example:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
tdir reverse ./ ./reversed --include "components/**"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
When the template output directory is inside the rendered directory, reverse snapshots included files before writing and excludes the output directory from include glob matching.
|
|
235
|
+
|
|
228
236
|
## Unmatched directives
|
|
229
237
|
|
|
230
238
|
A `<@if>` without a matching `<@endif>` throws when the renderer is initialized:
|
package/dist/cli.js
CHANGED
|
@@ -199,6 +199,16 @@ function writeSkippedTemplate(templateRoot, skipped) {
|
|
|
199
199
|
writeFileSync(templatePath, content);
|
|
200
200
|
return 1;
|
|
201
201
|
}
|
|
202
|
+
function replaceFlatTokens(content, manifest) {
|
|
203
|
+
const entries = Object.entries(manifest.tokens).filter(([, tokens]) => tokens.length > 0).sort(([a], [b]) => b.length - a.length);
|
|
204
|
+
let result = content;
|
|
205
|
+
for (const [rendered, tokens] of entries) {
|
|
206
|
+
if (rendered === "")
|
|
207
|
+
continue;
|
|
208
|
+
result = result.split(rendered).join(tokens[0]);
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
202
212
|
function dirnamePath(path) {
|
|
203
213
|
const normalized = normalizePath(path);
|
|
204
214
|
const index = normalized.lastIndexOf("/");
|
|
@@ -252,47 +262,58 @@ function inferTemplatePath(outputPath, directoryMap) {
|
|
|
252
262
|
const suffix = bestOutputDir === "" ? outputDir : outputDir.slice(bestOutputDir.length).replace(/^\//, "");
|
|
253
263
|
return joinPath(bestTemplateDir, suffix, basenamePath(normalized));
|
|
254
264
|
}
|
|
255
|
-
function walkFiles(root,
|
|
265
|
+
function walkFiles(root, excludedRoots = []) {
|
|
256
266
|
const files = [];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
const pending = [root];
|
|
268
|
+
while (pending.length > 0) {
|
|
269
|
+
const current = pending.pop();
|
|
270
|
+
for (const entry of readdirSync(current).sort()) {
|
|
271
|
+
const path = resolvePath(current, entry);
|
|
272
|
+
if (excludedRoots.some((excluded) => isInsidePath(excluded, path)))
|
|
273
|
+
continue;
|
|
274
|
+
const stat = statSync(path);
|
|
275
|
+
if (stat.isDirectory()) {
|
|
276
|
+
pending.push(path);
|
|
277
|
+
} else if (stat.isFile()) {
|
|
278
|
+
files.push(normalizePath(relative(root, path)));
|
|
279
|
+
}
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
return files;
|
|
267
283
|
}
|
|
268
|
-
function copyIncludedRenderedFiles(renderedRoot, templateRoot,
|
|
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);
|
|
284
|
+
function copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest) {
|
|
275
285
|
let filesWritten = 0;
|
|
276
|
-
for (const outputPath of
|
|
277
|
-
if (outputPath === mapRelativePath)
|
|
278
|
-
continue;
|
|
279
|
-
if (mappedOutputPaths.has(outputPath))
|
|
280
|
-
continue;
|
|
281
|
-
if (!matchesAny(outputPath, matchers))
|
|
282
|
-
continue;
|
|
286
|
+
for (const outputPath of includedOutputPaths) {
|
|
283
287
|
const renderedPath = resolveInside(renderedRoot, outputPath);
|
|
284
288
|
const templatePath = resolveInside(templateRoot, inferTemplatePath(outputPath, directoryMap));
|
|
285
289
|
mkdirSync(dirname(templatePath), { recursive: true });
|
|
286
|
-
|
|
290
|
+
const content = readFileSync(renderedPath);
|
|
291
|
+
if (isUtf8Text(content)) {
|
|
292
|
+
writeFileSync(templatePath, replaceFlatTokens(content.toString("utf-8"), manifest));
|
|
293
|
+
} else {
|
|
294
|
+
copyFileSync(renderedPath, templatePath);
|
|
295
|
+
}
|
|
287
296
|
filesWritten += 1;
|
|
288
297
|
}
|
|
289
298
|
return filesWritten;
|
|
290
299
|
}
|
|
300
|
+
function getIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, include) {
|
|
301
|
+
const matchers = getIncludeMatchers(include);
|
|
302
|
+
if (matchers.length === 0)
|
|
303
|
+
return [];
|
|
304
|
+
const mappedOutputPaths = new Set(manifest.files.map((file) => normalizePath(file.outputPath)));
|
|
305
|
+
const mapRelativePath = normalizePath(relative(renderedRoot, mapPath));
|
|
306
|
+
return walkFiles(renderedRoot, [templateRoot]).filter((outputPath) => {
|
|
307
|
+
return outputPath !== mapRelativePath && !mappedOutputPaths.has(outputPath) && matchesAny(outputPath, matchers);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
291
310
|
function reverseDir(renderedDir, templateDir, options = {}) {
|
|
292
311
|
const renderedRoot = resolvePath(renderedDir);
|
|
293
312
|
const templateRoot = resolvePath(templateDir);
|
|
294
313
|
const mapPath = options.mapPath ? resolvePath(renderedRoot, options.mapPath) : resolvePath(renderedRoot, ".tdir-map.json");
|
|
295
314
|
const manifest = readManifest(mapPath);
|
|
315
|
+
const directoryMap = buildDirectoryMap(manifest);
|
|
316
|
+
const includedOutputPaths = getIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, options.include);
|
|
296
317
|
const warnings = [];
|
|
297
318
|
let filesWritten = 0;
|
|
298
319
|
for (const file of manifest.files) {
|
|
@@ -327,7 +348,7 @@ function reverseDir(renderedDir, templateDir, options = {}) {
|
|
|
327
348
|
for (const skipped of manifest.skipped ?? []) {
|
|
328
349
|
filesWritten += writeSkippedTemplate(templateRoot, skipped);
|
|
329
350
|
}
|
|
330
|
-
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot,
|
|
351
|
+
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest);
|
|
331
352
|
return { filesWritten, warnings };
|
|
332
353
|
}
|
|
333
354
|
|
package/dist/index.js
CHANGED
|
@@ -186,6 +186,14 @@ function addReverseMapToken(state, file, token) {
|
|
|
186
186
|
tokens.push(token.token);
|
|
187
187
|
state.manifest.tokens[token.result] = tokens;
|
|
188
188
|
}
|
|
189
|
+
function addFlatToken(state, result, token) {
|
|
190
|
+
if (!state?.manifest)
|
|
191
|
+
return;
|
|
192
|
+
const tokens = state.manifest.tokens[result] ?? [];
|
|
193
|
+
if (!tokens.includes(token))
|
|
194
|
+
tokens.push(token);
|
|
195
|
+
state.manifest.tokens[result] = tokens;
|
|
196
|
+
}
|
|
189
197
|
function getReverseMapPath(destRoot, reverseMap) {
|
|
190
198
|
if (reverseMap === true)
|
|
191
199
|
return resolveOutputPath(destRoot, ".tdir-map.json");
|
|
@@ -341,6 +349,11 @@ function processIfBlocksWithMap(content, context) {
|
|
|
341
349
|
};
|
|
342
350
|
}
|
|
343
351
|
function renderContentWithMap(content, context, state, file) {
|
|
352
|
+
for (const match of content.matchAll(VAR_RE2)) {
|
|
353
|
+
const token = match[0];
|
|
354
|
+
const path = match[1];
|
|
355
|
+
addFlatToken(state, String(resolveContext(context, path) ?? ""), token);
|
|
356
|
+
}
|
|
344
357
|
const processedResult = processIfBlocksWithMap(content, context);
|
|
345
358
|
const processed = processedResult.content;
|
|
346
359
|
for (const token of processedResult.conditionalTokens) {
|
|
@@ -685,6 +698,16 @@ function writeSkippedTemplate(templateRoot, skipped) {
|
|
|
685
698
|
writeFileSync2(templatePath, content);
|
|
686
699
|
return 1;
|
|
687
700
|
}
|
|
701
|
+
function replaceFlatTokens(content, manifest) {
|
|
702
|
+
const entries = Object.entries(manifest.tokens).filter(([, tokens]) => tokens.length > 0).sort(([a], [b]) => b.length - a.length);
|
|
703
|
+
let result = content;
|
|
704
|
+
for (const [rendered, tokens] of entries) {
|
|
705
|
+
if (rendered === "")
|
|
706
|
+
continue;
|
|
707
|
+
result = result.split(rendered).join(tokens[0]);
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
688
711
|
function dirnamePath(path) {
|
|
689
712
|
const normalized = normalizePath(path);
|
|
690
713
|
const index = normalized.lastIndexOf("/");
|
|
@@ -738,47 +761,58 @@ function inferTemplatePath(outputPath, directoryMap) {
|
|
|
738
761
|
const suffix = bestOutputDir === "" ? outputDir : outputDir.slice(bestOutputDir.length).replace(/^\//, "");
|
|
739
762
|
return joinPath(bestTemplateDir, suffix, basenamePath(normalized));
|
|
740
763
|
}
|
|
741
|
-
function walkFiles(root,
|
|
764
|
+
function walkFiles(root, excludedRoots = []) {
|
|
742
765
|
const files = [];
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
766
|
+
const pending = [root];
|
|
767
|
+
while (pending.length > 0) {
|
|
768
|
+
const current = pending.pop();
|
|
769
|
+
for (const entry of readdirSync3(current).sort()) {
|
|
770
|
+
const path = resolvePath2(current, entry);
|
|
771
|
+
if (excludedRoots.some((excluded) => isInsidePath2(excluded, path)))
|
|
772
|
+
continue;
|
|
773
|
+
const stat = statSync3(path);
|
|
774
|
+
if (stat.isDirectory()) {
|
|
775
|
+
pending.push(path);
|
|
776
|
+
} else if (stat.isFile()) {
|
|
777
|
+
files.push(normalizePath(relative2(root, path)));
|
|
778
|
+
}
|
|
750
779
|
}
|
|
751
780
|
}
|
|
752
781
|
return files;
|
|
753
782
|
}
|
|
754
|
-
function copyIncludedRenderedFiles(renderedRoot, templateRoot,
|
|
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);
|
|
783
|
+
function copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest) {
|
|
761
784
|
let filesWritten = 0;
|
|
762
|
-
for (const outputPath of
|
|
763
|
-
if (outputPath === mapRelativePath)
|
|
764
|
-
continue;
|
|
765
|
-
if (mappedOutputPaths.has(outputPath))
|
|
766
|
-
continue;
|
|
767
|
-
if (!matchesAny(outputPath, matchers))
|
|
768
|
-
continue;
|
|
785
|
+
for (const outputPath of includedOutputPaths) {
|
|
769
786
|
const renderedPath = resolveInside(renderedRoot, outputPath);
|
|
770
787
|
const templatePath = resolveInside(templateRoot, inferTemplatePath(outputPath, directoryMap));
|
|
771
788
|
mkdirSync2(dirname2(templatePath), { recursive: true });
|
|
772
|
-
|
|
789
|
+
const content = readFileSync3(renderedPath);
|
|
790
|
+
if (isUtf8Text3(content)) {
|
|
791
|
+
writeFileSync2(templatePath, replaceFlatTokens(content.toString("utf-8"), manifest));
|
|
792
|
+
} else {
|
|
793
|
+
copyFileSync2(renderedPath, templatePath);
|
|
794
|
+
}
|
|
773
795
|
filesWritten += 1;
|
|
774
796
|
}
|
|
775
797
|
return filesWritten;
|
|
776
798
|
}
|
|
799
|
+
function getIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, include) {
|
|
800
|
+
const matchers = getIncludeMatchers(include);
|
|
801
|
+
if (matchers.length === 0)
|
|
802
|
+
return [];
|
|
803
|
+
const mappedOutputPaths = new Set(manifest.files.map((file) => normalizePath(file.outputPath)));
|
|
804
|
+
const mapRelativePath = normalizePath(relative2(renderedRoot, mapPath));
|
|
805
|
+
return walkFiles(renderedRoot, [templateRoot]).filter((outputPath) => {
|
|
806
|
+
return outputPath !== mapRelativePath && !mappedOutputPaths.has(outputPath) && matchesAny(outputPath, matchers);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
777
809
|
function reverseDir(renderedDir, templateDir, options = {}) {
|
|
778
810
|
const renderedRoot = resolvePath2(renderedDir);
|
|
779
811
|
const templateRoot = resolvePath2(templateDir);
|
|
780
812
|
const mapPath = options.mapPath ? resolvePath2(renderedRoot, options.mapPath) : resolvePath2(renderedRoot, ".tdir-map.json");
|
|
781
813
|
const manifest = readManifest(mapPath);
|
|
814
|
+
const directoryMap = buildDirectoryMap(manifest);
|
|
815
|
+
const includedOutputPaths = getIncludedRenderedFiles(renderedRoot, templateRoot, mapPath, manifest, options.include);
|
|
782
816
|
const warnings = [];
|
|
783
817
|
let filesWritten = 0;
|
|
784
818
|
for (const file of manifest.files) {
|
|
@@ -813,7 +847,7 @@ function reverseDir(renderedDir, templateDir, options = {}) {
|
|
|
813
847
|
for (const skipped of manifest.skipped ?? []) {
|
|
814
848
|
filesWritten += writeSkippedTemplate(templateRoot, skipped);
|
|
815
849
|
}
|
|
816
|
-
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot,
|
|
850
|
+
filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest);
|
|
817
851
|
return { filesWritten, warnings };
|
|
818
852
|
}
|
|
819
853
|
|