@gregorlohaus/tdir 0.1.3 → 0.1.4

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
@@ -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 plus per-file occurrences with path/range context:
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,7 @@ 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
- The command writes files at their original template paths and restores recorded `<@var(...)>` tokens in file contents and file paths. It does not infer conditional blocks that were removed during rendering; keep the original template structure when those blocks need to be preserved.
211
+ 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
212
 
196
213
  ## Unmatched directives
197
214
 
package/dist/cli.js CHANGED
@@ -55,10 +55,66 @@ function replaceFirst(content, token) {
55
55
  return null;
56
56
  return `${content.slice(0, index)}${token.token}${content.slice(index + token.result.length)}`;
57
57
  }
58
+ function applyActiveBranch(token, branchContent) {
59
+ if (!token.activeRange)
60
+ return token.token;
61
+ return [
62
+ token.token.slice(0, token.activeRange.start),
63
+ branchContent,
64
+ token.token.slice(token.activeRange.end)
65
+ ].join("");
66
+ }
67
+ function findPrefixEnd(content, before) {
68
+ if (before === "")
69
+ return 0;
70
+ let candidate = before;
71
+ while (candidate.length >= 8) {
72
+ const index = content.indexOf(candidate);
73
+ if (index !== -1)
74
+ return index + candidate.length;
75
+ candidate = candidate.slice(-Math.max(1, Math.floor(candidate.length / 2)));
76
+ }
77
+ return -1;
78
+ }
79
+ function findSuffixStart(content, after, from) {
80
+ if (after === "")
81
+ return content.length;
82
+ let candidate = after;
83
+ while (candidate.length >= 8) {
84
+ const index = content.indexOf(candidate, from);
85
+ if (index !== -1)
86
+ return index;
87
+ candidate = candidate.slice(0, Math.floor(candidate.length / 2));
88
+ }
89
+ return -1;
90
+ }
91
+ function replaceConditional(content, token) {
92
+ const exactIndex = content.indexOf(token.result);
93
+ if (exactIndex !== -1) {
94
+ return [
95
+ content.slice(0, exactIndex),
96
+ token.token,
97
+ content.slice(exactIndex + token.result.length)
98
+ ].join("");
99
+ }
100
+ if (token.before === undefined || token.after === undefined)
101
+ return null;
102
+ const branchStart = findPrefixEnd(content, token.before);
103
+ if (branchStart === -1)
104
+ return null;
105
+ const afterIndex = findSuffixStart(content, token.after, branchStart);
106
+ if (afterIndex === -1)
107
+ return null;
108
+ return [
109
+ content.slice(0, branchStart),
110
+ applyActiveBranch(token, content.slice(branchStart, afterIndex)),
111
+ content.slice(afterIndex)
112
+ ].join("");
113
+ }
58
114
  function reverseContent(content, file, warnings) {
59
- const tokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
115
+ const contentTokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
60
116
  let reversed = content;
61
- for (const token of tokens) {
117
+ for (const token of contentTokens) {
62
118
  const rangeResult = replaceAtRange(reversed, token);
63
119
  if (rangeResult !== null) {
64
120
  reversed = rangeResult;
@@ -76,8 +132,33 @@ function reverseContent(content, file, warnings) {
76
132
  message: "Rendered value was not found; token was not restored"
77
133
  });
78
134
  }
135
+ const conditionalTokens = file.tokens.filter((token) => token.kind === "conditional").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
136
+ for (const token of conditionalTokens) {
137
+ const result = replaceConditional(reversed, token);
138
+ if (result !== null) {
139
+ reversed = result;
140
+ continue;
141
+ }
142
+ warnings.push({
143
+ outputPath: file.outputPath,
144
+ token: token.token,
145
+ result: token.result,
146
+ message: "Rendered conditional block was not found; block was not restored"
147
+ });
148
+ }
79
149
  return reversed;
80
150
  }
151
+ function writeSkippedTemplate(templateRoot, skipped) {
152
+ const templatePath = resolveInside(templateRoot, skipped.templatePath);
153
+ if (skipped.kind === "directory") {
154
+ mkdirSync(templatePath, { recursive: true });
155
+ return 0;
156
+ }
157
+ mkdirSync(dirname(templatePath), { recursive: true });
158
+ const content = skipped.encoding === "base64" ? Buffer.from(skipped.content ?? "", "base64") : skipped.content ?? "";
159
+ writeFileSync(templatePath, content);
160
+ return 1;
161
+ }
81
162
  function reverseDir(renderedDir, templateDir, options = {}) {
82
163
  const renderedRoot = resolvePath(renderedDir);
83
164
  const templateRoot = resolvePath(templateDir);
@@ -114,6 +195,9 @@ function reverseDir(renderedDir, templateDir, options = {}) {
114
195
  writeFileSync(templatePath, reverseContent(content.toString("utf-8"), file, warnings));
115
196
  filesWritten += 1;
116
197
  }
198
+ for (const skipped of manifest.skipped ?? []) {
199
+ filesWritten += writeSkippedTemplate(templateRoot, skipped);
200
+ }
117
201
  return { filesWritten, warnings };
118
202
  }
119
203
 
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 processIfBlocks(content, context) {
235
- let result = "";
236
- let pos = 0;
237
- const stack = [];
238
- const re = new RegExp(DIRECTIVE_RE2.source, "g");
239
- let match;
240
- function isEmitting() {
241
- return stack.every((f) => f.active);
242
- }
243
- while ((match = re.exec(content)) !== null) {
244
- const directive = match[1];
245
- const condPath = match[2];
246
- if (directive === "if") {
247
- if (isEmitting())
248
- result += content.slice(pos, match.index);
249
- const truthy = evalCondition(condPath, context);
250
- stack.push({ matched: truthy, active: truthy, sawElse: false });
251
- pos = re.lastIndex;
252
- } else if (directive === "elseif") {
253
- if (stack.length === 0)
254
- throw new Error("Unexpected <@elseif> without <@if>");
255
- const top = stack[stack.length - 1];
256
- if (top.sawElse)
257
- throw new Error("Unexpected <@elseif> after <@else>");
258
- if (isEmitting())
259
- result += content.slice(pos, match.index);
260
- if (top.matched) {
261
- top.active = false;
262
- } else {
263
- const truthy = evalCondition(condPath, context);
264
- top.matched = truthy;
265
- top.active = truthy;
266
- }
267
- pos = re.lastIndex;
268
- } else if (directive === "else") {
269
- if (stack.length === 0)
270
- throw new Error("Unexpected <@else> without <@if>");
271
- const top = stack[stack.length - 1];
272
- if (top.sawElse)
273
- throw new Error("Unexpected duplicate <@else>");
274
- if (isEmitting())
275
- result += content.slice(pos, match.index);
276
- top.active = !top.matched;
277
- top.matched = true;
278
- top.sawElse = true;
279
- pos = re.lastIndex;
280
- } else if (directive === "endif") {
281
- if (stack.length === 0)
282
- throw new Error("Unexpected <@endif> without <@if>");
283
- if (isEmitting())
284
- result += content.slice(pos, match.index);
285
- stack.pop();
286
- pos = re.lastIndex;
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
- if (stack.length > 0) {
290
- throw new Error("Unmatched <@if> without <@endif>");
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 processed = processIfBlocks(content, context);
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 (tempFile.tokens.length > 0)
389
- state.manifest?.files.push(tempFile);
483
+ if (state.manifest)
484
+ state.manifest.files.push(tempFile);
390
485
  }
391
486
  }
392
487
  }
@@ -446,10 +541,66 @@ function replaceFirst(content, token) {
446
541
  return null;
447
542
  return `${content.slice(0, index)}${token.token}${content.slice(index + token.result.length)}`;
448
543
  }
544
+ function applyActiveBranch(token, branchContent) {
545
+ if (!token.activeRange)
546
+ return token.token;
547
+ return [
548
+ token.token.slice(0, token.activeRange.start),
549
+ branchContent,
550
+ token.token.slice(token.activeRange.end)
551
+ ].join("");
552
+ }
553
+ function findPrefixEnd(content, before) {
554
+ if (before === "")
555
+ return 0;
556
+ let candidate = before;
557
+ while (candidate.length >= 8) {
558
+ const index = content.indexOf(candidate);
559
+ if (index !== -1)
560
+ return index + candidate.length;
561
+ candidate = candidate.slice(-Math.max(1, Math.floor(candidate.length / 2)));
562
+ }
563
+ return -1;
564
+ }
565
+ function findSuffixStart(content, after, from) {
566
+ if (after === "")
567
+ return content.length;
568
+ let candidate = after;
569
+ while (candidate.length >= 8) {
570
+ const index = content.indexOf(candidate, from);
571
+ if (index !== -1)
572
+ return index;
573
+ candidate = candidate.slice(0, Math.floor(candidate.length / 2));
574
+ }
575
+ return -1;
576
+ }
577
+ function replaceConditional(content, token) {
578
+ const exactIndex = content.indexOf(token.result);
579
+ if (exactIndex !== -1) {
580
+ return [
581
+ content.slice(0, exactIndex),
582
+ token.token,
583
+ content.slice(exactIndex + token.result.length)
584
+ ].join("");
585
+ }
586
+ if (token.before === undefined || token.after === undefined)
587
+ return null;
588
+ const branchStart = findPrefixEnd(content, token.before);
589
+ if (branchStart === -1)
590
+ return null;
591
+ const afterIndex = findSuffixStart(content, token.after, branchStart);
592
+ if (afterIndex === -1)
593
+ return null;
594
+ return [
595
+ content.slice(0, branchStart),
596
+ applyActiveBranch(token, content.slice(branchStart, afterIndex)),
597
+ content.slice(afterIndex)
598
+ ].join("");
599
+ }
449
600
  function reverseContent(content, file, warnings) {
450
- const tokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
601
+ const contentTokens = file.tokens.filter((token) => token.kind === "content").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
451
602
  let reversed = content;
452
- for (const token of tokens) {
603
+ for (const token of contentTokens) {
453
604
  const rangeResult = replaceAtRange(reversed, token);
454
605
  if (rangeResult !== null) {
455
606
  reversed = rangeResult;
@@ -467,8 +618,33 @@ function reverseContent(content, file, warnings) {
467
618
  message: "Rendered value was not found; token was not restored"
468
619
  });
469
620
  }
621
+ const conditionalTokens = file.tokens.filter((token) => token.kind === "conditional").sort((a, b) => (b.range?.start ?? -1) - (a.range?.start ?? -1));
622
+ for (const token of conditionalTokens) {
623
+ const result = replaceConditional(reversed, token);
624
+ if (result !== null) {
625
+ reversed = result;
626
+ continue;
627
+ }
628
+ warnings.push({
629
+ outputPath: file.outputPath,
630
+ token: token.token,
631
+ result: token.result,
632
+ message: "Rendered conditional block was not found; block was not restored"
633
+ });
634
+ }
470
635
  return reversed;
471
636
  }
637
+ function writeSkippedTemplate(templateRoot, skipped) {
638
+ const templatePath = resolveInside(templateRoot, skipped.templatePath);
639
+ if (skipped.kind === "directory") {
640
+ mkdirSync2(templatePath, { recursive: true });
641
+ return 0;
642
+ }
643
+ mkdirSync2(dirname2(templatePath), { recursive: true });
644
+ const content = skipped.encoding === "base64" ? Buffer.from(skipped.content ?? "", "base64") : skipped.content ?? "";
645
+ writeFileSync2(templatePath, content);
646
+ return 1;
647
+ }
472
648
  function reverseDir(renderedDir, templateDir, options = {}) {
473
649
  const renderedRoot = resolvePath2(renderedDir);
474
650
  const templateRoot = resolvePath2(templateDir);
@@ -505,6 +681,9 @@ function reverseDir(renderedDir, templateDir, options = {}) {
505
681
  writeFileSync2(templatePath, reverseContent(content.toString("utf-8"), file, warnings));
506
682
  filesWritten += 1;
507
683
  }
684
+ for (const skipped of manifest.skipped ?? []) {
685
+ filesWritten += writeSkippedTemplate(templateRoot, skipped);
686
+ }
508
687
  return { filesWritten, warnings };
509
688
  }
510
689
 
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregorlohaus/tdir",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",