@menapo/vi18n 0.0.0-stage → 0.1.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.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-D34P-S--.js +1 -0
  6. package/dist/check-DyrAbv73.js +201 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-B5Mz6WJm.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-B7p40M__.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-DGsMbcTZ.js +103 -0
  25. package/dist/extract-Dujlsgnk.js +298 -0
  26. package/dist/formats-KPIg-08V.js +781 -0
  27. package/dist/i18next-CrUnKFfh.js +1596 -0
  28. package/dist/i18next-Rgo-v9l0.js +1 -0
  29. package/dist/import-Cwdhcywt.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-BecCTvKi.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui--PcOOYry.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-Cq1Nuu-Q.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-Cf0T7qPW.js +28 -0
  43. package/dist/merge-driver-CpiiSVIc.js +422 -0
  44. package/dist/messages-Cxa-5tR0.js +166 -0
  45. package/dist/migrate-CntqvrW4.js +1 -0
  46. package/dist/migrate-dPgq9uzy.js +1484 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DsaHKHEt.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C7g11THL.js +2031 -0
  52. package/dist/pipeline-wxS0SApG.js +1 -0
  53. package/dist/project-Dwx7iPm6.js +381 -0
  54. package/dist/project-pXipT8iG.js +1046 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-CaBVwJ9u.js +519 -0
  58. package/dist/restore-Btq66Tid.js +175 -0
  59. package/dist/rewrite-CiTRAICl.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-DYD_VXKy.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-BDkykMjb.js +11823 -0
  68. package/dist/server-qv3mz70d.js +10485 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DV3o8QD7.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-DZArW5aE.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-CcVzger_.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-DZR3MkSA.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-BlyLGlOa.js +28 -0
  93. package/dist/vite-scan-B_YW4X_a.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-CEMnpv7k.js +651 -0
  97. package/dist/which-C6PFI1Dm.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. package/schemas/catalog.json +57 -0
@@ -0,0 +1,26 @@
1
+ import { o as isValidManualId } from "./validate-BxpupdAf.js";
2
+ //#region src/core/config/exclude.ts
3
+ /**
4
+ * `ai.exclude` entries (PLAN §4.6, §5.20; INTERNALS §2.15): a configured
5
+ * locale, a message id, or a glob over the config-relative files that use a
6
+ * message. One list keeps the config short, so the kind is read from the
7
+ * entry: the loader rejects what fits none, and `vi18n translate` matches
8
+ * with the same reading.
9
+ */
10
+ /** Everything a message id may hold: base64url, plus a manual id's `.` and `:`. */
11
+ const ID_CHARACTERS = /^[A-Za-z0-9_:.-]+$/;
12
+ const BASE64URL = /^[A-Za-z0-9_-]+$/;
13
+ /**
14
+ * The kind of an entry, or null when it is none. A glob is anything with a
15
+ * character no id or locale has, and must contain `/`, `*` or `?`: a bare
16
+ * `legal.tsx` reads as the manual id it also is, so a file at the root is
17
+ * written `./legal.tsx` or `**\/legal.tsx`.
18
+ */
19
+ function excludeKind(entry, locales, idLength) {
20
+ if (!ID_CHARACTERS.test(entry)) return /[/*?]/.test(entry) ? "glob" : null;
21
+ if (locales.includes(entry)) return "locale";
22
+ if (isValidManualId(entry)) return "id";
23
+ return entry.length === idLength && BASE64URL.test(entry) ? "id" : null;
24
+ }
25
+ //#endregion
26
+ export { excludeKind as t };
@@ -0,0 +1,103 @@
1
+ import { c as sourceIndexOf, n as EXTENSIONS, o as exchangeUnits } from "./units-CQ5Q99RC.js";
2
+ import { r as plural } from "./output-DsaHKHEt.js";
3
+ import { t as EXIT } from "./types-wSh9fCBY.js";
4
+ import { a as loadCatalogs, o as loadProject, t as CliExit, u as readErrors } from "./project-Dwx7iPm6.js";
5
+ import { n as serializeExport } from "./formats-KPIg-08V.js";
6
+ import { join, resolve } from "node:path";
7
+ import { mkdir, writeFile } from "node:fs/promises";
8
+ //#region src/cli/commands/export.ts
9
+ /**
10
+ * `vi18n export --format po|json|flat|xliff2|arb|csv [--locale l]… [--out dir]
11
+ * [--missing] [--ai]` (PLAN §4.4, §5.19, INTERNALS §2.6.9): each locale's
12
+ * catalog in an exchange format, for a TMS or a translation agency. It reads
13
+ * the catalogs as they are, so run `vi18n extract` first to send what the
14
+ * code uses now.
15
+ */
16
+ async function exportCatalogs(ctx, flags) {
17
+ const project = await loadProject(ctx);
18
+ const { config, codec, layout } = project;
19
+ const filtered = flags.missing || flags.ai;
20
+ const locales = exportLocales(config.locales, config.sourceLocale, flags.locales, filtered);
21
+ if (flags.out === null && locales.length !== 1) throw new CliExit(EXIT.usage, [{
22
+ code: "config-invalid",
23
+ severity: "error",
24
+ message: `${plural(locales.length, "locale")} to export: pass --out <dir> for one file per locale, or one --locale for stdout`
25
+ }]);
26
+ const loaded = await loadCatalogs(project);
27
+ const unreadable = readErrors(loaded.diagnostics);
28
+ if (unreadable.length > 0) throw new CliExit(EXIT.failure, unreadable);
29
+ const context = {
30
+ layout,
31
+ codec,
32
+ sourceIndex: sourceIndexOf(loaded.set.get(config.sourceLocale)?.catalog ?? null, layout, codec)
33
+ };
34
+ const data = {
35
+ format: flags.format,
36
+ files: []
37
+ };
38
+ const out = flags.out === null ? null : resolve(ctx.global.cwd, flags.out);
39
+ if (out !== null) await mkdir(out, { recursive: true });
40
+ for (const locale of locales) {
41
+ const isSourceLocale = locale === config.sourceLocale;
42
+ const catalog = loaded.set.get(locale)?.catalog ?? null;
43
+ const { units, unsourced } = exchangeUnits({
44
+ catalog,
45
+ context,
46
+ locale,
47
+ isSourceLocale
48
+ });
49
+ const selected = filtered && !isSourceLocale ? units.filter((unit) => flags.missing && !unit.usable || flags.ai && unit.state === "ai" && unit.translation !== "") : units;
50
+ if (unsourced > 0) ctx.out.note(`vi18n export: ${locale}: left out ${plural(unsourced, "entry", "entries")} whose source text the catalogs do not hold; run vi18n extract`);
51
+ const text = serializeExport(flags.format, selected, {
52
+ locale,
53
+ sourceLocale: config.sourceLocale,
54
+ isSourceLocale,
55
+ idScheme: codec.idScheme,
56
+ msgid: config.catalogs.msgid,
57
+ header: catalog?.header ?? null,
58
+ layout
59
+ });
60
+ if (out === null) {
61
+ data.files.push({
62
+ locale,
63
+ file: null,
64
+ entries: selected.length
65
+ });
66
+ if (ctx.global.json) data.content = text;
67
+ else ctx.io.stdout(ctx.out.redact(text));
68
+ continue;
69
+ }
70
+ const file = join(out, `${locale}${EXTENSIONS[flags.format]}`);
71
+ await writeFile(file, text);
72
+ const shown = ctx.out.display(file);
73
+ data.files.push({
74
+ locale,
75
+ file: shown,
76
+ entries: selected.length
77
+ });
78
+ ctx.out.result(`${locale} → ${shown} (${plural(selected.length, "entry", "entries")})`);
79
+ }
80
+ return {
81
+ exitCode: EXIT.ok,
82
+ data
83
+ };
84
+ }
85
+ /**
86
+ * `--locale` as given (each must be configured), else every locale; with a
87
+ * filter, every target locale, since the source locale has nothing missing.
88
+ */
89
+ function exportLocales(configured, sourceLocale, requested, filtered) {
90
+ if (requested.length === 0) return filtered ? configured.filter((locale) => locale !== sourceLocale) : [...configured];
91
+ const out = [];
92
+ for (const locale of requested) {
93
+ if (!configured.includes(locale)) throw new CliExit(EXIT.usage, [{
94
+ code: "config-invalid",
95
+ severity: "error",
96
+ message: `--locale ${locale} is not in locales (${configured.join(", ")})`
97
+ }]);
98
+ if (!out.includes(locale)) out.push(locale);
99
+ }
100
+ return out;
101
+ }
102
+ //#endregion
103
+ export { exportCatalogs };
@@ -0,0 +1,298 @@
1
+ import { m as updateCatalogFile, n as libraryCoverage, s as catalogPath } from "./library-D5CWvTQs.js";
2
+ import { n as scanProject, r as isIncluded } from "./project-pXipT8iG.js";
3
+ import { t as VERSION } from "./version-BlyLGlOa.js";
4
+ import { a as writeGenFile, n as syncCatalogs, t as syncCatalog } from "./sync-CcVzger_.js";
5
+ import { t as findCollisions } from "./collisions-CfpZU8e4.js";
6
+ import { a as table, i as rootRelative, r as plural } from "./output-DsaHKHEt.js";
7
+ import { t as EXIT } from "./types-wSh9fCBY.js";
8
+ import { a as loadCatalogs, f as scan, i as hasErrors, o as loadProject, r as computeDrift, s as occurrencesOf, u as readErrors } from "./project-Dwx7iPm6.js";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { watch } from "node:fs";
11
+ import { stat } from "node:fs/promises";
12
+ async function extract(ctx, flags) {
13
+ const project = await loadProject(ctx);
14
+ if (flags.watch) return watchMode(ctx, project);
15
+ const scanned = await scan(project, { vite: flags.vite });
16
+ ctx.out.report(scanned.diagnostics);
17
+ const loaded = await loadCatalogs(project);
18
+ ctx.out.report(loaded.diagnostics);
19
+ const data = {
20
+ check: flags.check,
21
+ wrote: [],
22
+ stats: {},
23
+ blocked: []
24
+ };
25
+ if (readErrors(loaded.diagnostics).length > 0) return {
26
+ exitCode: EXIT.failure,
27
+ data
28
+ };
29
+ if (hasErrors(scanned.diagnostics)) {
30
+ ctx.out.result(flags.check ? "vi18n extract --check: the code has errors; fix them before the catalogs are compared" : "nothing written: the code has errors; fix them and rerun vi18n extract");
31
+ return {
32
+ exitCode: EXIT.failure,
33
+ data
34
+ };
35
+ }
36
+ if (flags.check) {
37
+ const drift = await computeDrift(project, scanned.messages, loaded.set);
38
+ data.stats = drift.sync.stats;
39
+ ctx.out.report(collisions(project, scanned, loaded.set, drift.sync));
40
+ ctx.out.report(drift.diagnostics);
41
+ const failed = hasErrors(ctx.out.diagnostics);
42
+ ctx.out.result(failed ? `vi18n extract --check: ${drift.locales.length + (drift.gen ? 1 : 0)} out-of-date file(s)` : "vi18n extract --check: catalogs are up to date");
43
+ return {
44
+ exitCode: failed ? EXIT.failure : EXIT.ok,
45
+ data
46
+ };
47
+ }
48
+ const options = {
49
+ mode: "full",
50
+ yes: flags.yes
51
+ };
52
+ const { config, codec } = project;
53
+ const sync = syncCatalogs({
54
+ config,
55
+ codec,
56
+ messages: scanned.messages,
57
+ catalogs: loaded.set,
58
+ options
59
+ });
60
+ data.stats = sync.stats;
61
+ ctx.out.report(sync.diagnostics);
62
+ const collided = collisions(project, scanned, loaded.set, sync);
63
+ ctx.out.report(collided);
64
+ if (hasErrors(collided)) return {
65
+ exitCode: EXIT.failure,
66
+ data
67
+ };
68
+ if (sync.blocked.length > 0) {
69
+ data.blocked = sync.blocked;
70
+ ctx.out.result(table([
71
+ "locale",
72
+ "removals",
73
+ "limit"
74
+ ], sync.blocked.map((row) => [
75
+ row.locale,
76
+ row.removals,
77
+ row.limit
78
+ ])));
79
+ ctx.out.result("nothing written: rerun with --yes if the removals are intended");
80
+ return {
81
+ exitCode: EXIT.failure,
82
+ data
83
+ };
84
+ }
85
+ data.wrote = await writeCatalogs(project, loaded.set, sync, scanned, options);
86
+ if (await writeGenFile(config)) data.wrote.push(rootRelative(config.root, join(dirname(config.configFile), "vi18n.gen.ts")));
87
+ printStats(ctx, data);
88
+ return {
89
+ exitCode: hasErrors(ctx.out.diagnostics) ? EXIT.failure : EXIT.ok,
90
+ data
91
+ };
92
+ }
93
+ /**
94
+ * Collisions among the scan's texts and against the catalogs, ignoring the
95
+ * entries this run deletes (an entry whose text left the code cannot
96
+ * collide with the new text that took its id).
97
+ */
98
+ function collisions(project, scanned, set, sync) {
99
+ return findCollisions(occurrencesOf(scanned), set, project.codec, {
100
+ referenced: new Set(project.config.extraction.keep),
101
+ ignoreKeys: new Set(sync.deletedKeys)
102
+ });
103
+ }
104
+ /**
105
+ * Writes each changed locale with compare-and-swap: if the file changed
106
+ * since it was read (an editor, the dev server), the locale's sync re-runs
107
+ * on what is there now instead of overwriting it.
108
+ */
109
+ async function writeCatalogs(project, set, sync, scanned, options) {
110
+ const { config, codec, layout } = project;
111
+ const sourceCatalog = set.get(config.sourceLocale)?.catalog ?? null;
112
+ const coverage = libraryCoverage(set, scanned.messages, config, codec);
113
+ return (await Promise.all(sync.changed.map(async (locale) => {
114
+ const loaded = set.get(locale);
115
+ const path = loaded?.path ?? catalogPath(config, locale);
116
+ const precomputed = sync.catalogs.get(locale) ?? null;
117
+ let first = true;
118
+ return (await updateCatalogFile(path, loaded?.hash ?? null, (current) => {
119
+ if (first && current === null && loaded?.catalog == null) {
120
+ first = false;
121
+ return precomputed;
122
+ }
123
+ first = false;
124
+ return syncCatalog({
125
+ config,
126
+ codec,
127
+ messages: scanned.messages,
128
+ locale,
129
+ catalog: current,
130
+ sourceCatalog,
131
+ options,
132
+ coverage
133
+ }).catalog;
134
+ }, {
135
+ locale,
136
+ layout,
137
+ isSourceLocale: locale === config.sourceLocale,
138
+ version: VERSION
139
+ })).written ? rootRelative(config.root, path) : null;
140
+ }))).filter((file) => file !== null);
141
+ }
142
+ function printStats(ctx, data) {
143
+ const rows = Object.entries(data.stats).map(([locale, s]) => [
144
+ locale,
145
+ s.added,
146
+ s.deleted,
147
+ s.cleared,
148
+ s.moved,
149
+ s.rekeyed,
150
+ s.updated
151
+ ]);
152
+ if (rows.length > 0) ctx.out.result(table([
153
+ "locale",
154
+ "added",
155
+ "deleted",
156
+ "cleared",
157
+ "moved",
158
+ "rekeyed",
159
+ "updated"
160
+ ], rows));
161
+ ctx.out.result(data.wrote.length === 0 ? "catalogs are up to date" : `wrote ${plural(data.wrote.length, "file")}: ${data.wrote.join(", ")}`);
162
+ }
163
+ /**
164
+ * Add-only sync now and after every quiet period following a change under
165
+ * an include root, until the signal aborts (SIGINT in the binary). Each pass
166
+ * re-runs the cached whole-include scan: only changed files are parsed, and
167
+ * collisions are checked against every module, which a scan of the changed
168
+ * files alone could not do.
169
+ */
170
+ async function watchMode(ctx, project) {
171
+ const { config } = project;
172
+ const data = {
173
+ check: false,
174
+ wrote: [],
175
+ stats: {},
176
+ blocked: []
177
+ };
178
+ const signal = ctx.io.signal;
179
+ const wrote = /* @__PURE__ */ new Set();
180
+ const pass = async () => {
181
+ const scanned = await scanProject(config, project.codec, { cache: true });
182
+ ctx.out.report(scanned.diagnostics);
183
+ if (scanned.aborted) return;
184
+ const loaded = await loadCatalogs(project);
185
+ ctx.out.report(loaded.diagnostics);
186
+ if (readErrors(loaded.diagnostics).length > 0) return;
187
+ const options = {
188
+ mode: "add-only",
189
+ yes: false
190
+ };
191
+ const sync = syncCatalogs({
192
+ config,
193
+ codec: project.codec,
194
+ messages: scanned.messages,
195
+ catalogs: loaded.set,
196
+ options
197
+ });
198
+ ctx.out.report(sync.diagnostics);
199
+ const collided = collisions(project, scanned, loaded.set, sync);
200
+ ctx.out.report(collided);
201
+ if (hasErrors(collided)) return;
202
+ const files = await writeCatalogs(project, loaded.set, sync, scanned, options);
203
+ for (const [locale, stats] of Object.entries(sync.stats)) {
204
+ const total = data.stats[locale] ??= {
205
+ added: 0,
206
+ deleted: 0,
207
+ cleared: 0,
208
+ moved: 0,
209
+ rekeyed: 0,
210
+ updated: 0
211
+ };
212
+ total.added += stats.added;
213
+ total.updated += stats.updated;
214
+ }
215
+ for (const file of files) wrote.add(file);
216
+ if (files.length > 0) ctx.out.note(`vi18n: updated ${files.join(", ")}`);
217
+ };
218
+ await writeGenFile(config);
219
+ const roots = await watchRoots(config.project.include);
220
+ const watchers = [];
221
+ let timer = null;
222
+ let running = Promise.resolve();
223
+ let pending = false;
224
+ let stopped = false;
225
+ let first = true;
226
+ const schedule = () => {
227
+ if (timer !== null) clearTimeout(timer);
228
+ timer = setTimeout(() => {
229
+ timer = null;
230
+ if (!pending) return;
231
+ pending = false;
232
+ running = running.then(pass).catch((error) => {
233
+ ctx.out.note(`vi18n: ${error instanceof Error ? error.message : String(error)}`);
234
+ });
235
+ }, 200);
236
+ };
237
+ for (const root of roots) {
238
+ const onEvent = (_event, name) => {
239
+ if (stopped) return;
240
+ const file = name === null ? null : resolve(root.dir, name.toString());
241
+ if (file !== null && root.file !== null && file !== root.file) return;
242
+ if (file !== null && !isIncluded(file, config)) return;
243
+ pending = true;
244
+ if (!first) schedule();
245
+ };
246
+ try {
247
+ watchers.push(root.file === null ? watch(root.dir, { recursive: true }, onEvent) : watch(root.dir, {}, onEvent));
248
+ } catch (error) {
249
+ ctx.out.note(`vi18n: cannot watch ${root.dir}: ${error instanceof Error ? error.message : String(error)}`);
250
+ }
251
+ }
252
+ try {
253
+ await pass();
254
+ } catch (error) {
255
+ stopped = true;
256
+ for (const watcher of watchers) watcher.close();
257
+ throw error;
258
+ }
259
+ first = false;
260
+ if (pending) schedule();
261
+ await new Promise((done) => {
262
+ const stop = () => {
263
+ if (stopped) return;
264
+ stopped = true;
265
+ if (timer !== null) clearTimeout(timer);
266
+ for (const watcher of watchers) watcher.close();
267
+ running.then(done, done);
268
+ };
269
+ if (signal === void 0) return;
270
+ if (signal.aborted) stop();
271
+ else signal.addEventListener("abort", stop, { once: true });
272
+ });
273
+ return finishWatch(data, wrote);
274
+ }
275
+ function finishWatch(data, wrote) {
276
+ data.wrote = [...wrote].sort();
277
+ return {
278
+ exitCode: EXIT.ok,
279
+ data
280
+ };
281
+ }
282
+ /** Directories to watch: each include directory recursively, a file's parent for a file entry. */
283
+ async function watchRoots(include) {
284
+ const roots = [];
285
+ for (const entry of include) try {
286
+ const info = await stat(entry);
287
+ roots.push(info.isDirectory() ? {
288
+ dir: entry,
289
+ file: null
290
+ } : {
291
+ dir: dirname(entry),
292
+ file: entry
293
+ });
294
+ } catch {}
295
+ return roots;
296
+ }
297
+ //#endregion
298
+ export { extract };