@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 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, current = root) {
265
+ function walkFiles(root, excludedRoots = []) {
256
266
  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)));
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, 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);
284
+ function copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest) {
275
285
  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;
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
- copyFileSync(renderedPath, templatePath);
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, mapPath, manifest, options.include);
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, current = root) {
764
+ function walkFiles(root, excludedRoots = []) {
742
765
  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)));
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, 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);
783
+ function copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest) {
761
784
  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;
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
- copyFileSync2(renderedPath, templatePath);
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, mapPath, manifest, options.include);
850
+ filesWritten += copyIncludedRenderedFiles(renderedRoot, templateRoot, includedOutputPaths, directoryMap, manifest);
817
851
  return { filesWritten, warnings };
818
852
  }
819
853
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregorlohaus/tdir",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",