@let-value/translate-extract 1.0.30 → 1.0.31

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.
@@ -0,0 +1,1439 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import glob from "fast-glob";
3
+ import fs, { readFile } from "node:fs/promises";
4
+ import path, { dirname, extname, join, relative, resolve } from "node:path";
5
+ import * as gettextParser from "gettext-parser";
6
+ import fs$1 from "node:fs";
7
+ import Parser from "tree-sitter";
8
+ import JavaScript from "tree-sitter-javascript";
9
+ import TS from "tree-sitter-typescript";
10
+ import { ResolverFactory } from "oxc-resolver";
11
+ import { getFormula, getNPlurals } from "plural-forms";
12
+
13
+ //#region src/defer.ts
14
+ var Defer = class {
15
+ pending = 0;
16
+ promise = Promise.resolve();
17
+ resolve;
18
+ enqueue() {
19
+ if (this.pending++ === 0) this.promise = new Promise((res) => {
20
+ this.resolve = res;
21
+ });
22
+ }
23
+ dequeue() {
24
+ if (this.pending > 0 && --this.pending === 0) this.resolve?.();
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ //#region src/plugins/cleanup/cleanup.ts
30
+ const namespace$2 = "cleanup";
31
+ function cleanup() {
32
+ return {
33
+ name: "cleanup",
34
+ setup(build) {
35
+ build.context.logger?.debug("cleanup plugin initialized");
36
+ const processed = /* @__PURE__ */ new Set();
37
+ const generated = /* @__PURE__ */ new Set();
38
+ const dirs = /* @__PURE__ */ new Set();
39
+ let dispatched = false;
40
+ build.onResolve({
41
+ namespace: namespace$2,
42
+ filter: /.*/
43
+ }, ({ path: path$1 }) => {
44
+ generated.add(path$1);
45
+ dirs.add(dirname(path$1));
46
+ Promise.all([
47
+ build.defer("source"),
48
+ build.defer("translate"),
49
+ build.defer(namespace$2)
50
+ ]).then(() => {
51
+ if (dispatched) return;
52
+ dispatched = true;
53
+ for (const path$2 of dirs.values()) build.process({
54
+ entrypoint: path$2,
55
+ path: path$2,
56
+ namespace: namespace$2,
57
+ data: void 0
58
+ });
59
+ });
60
+ });
61
+ build.onProcess({
62
+ namespace: namespace$2,
63
+ filter: /.*/
64
+ }, async ({ path: path$1 }) => {
65
+ if (processed.has(path$1)) return;
66
+ processed.add(path$1);
67
+ const files = await fs.readdir(path$1).catch(() => []);
68
+ for (const f of files.filter((p) => p.endsWith(".po"))) {
69
+ const full = join(path$1, f);
70
+ const contents = await fs.readFile(full).catch(() => void 0);
71
+ if (!contents) continue;
72
+ const parsed = gettextParser.po.parse(contents);
73
+ const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
74
+ if (!hasTranslations && generated.has(full)) await fs.unlink(full);
75
+ if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
76
+ }
77
+ });
78
+ }
79
+ };
80
+ }
81
+
82
+ //#endregion
83
+ //#region src/plugins/core/queries/comment.ts
84
+ function getReference(node, { path: path$1 }) {
85
+ const line = node.startPosition.row + 1;
86
+ return `${relative(process.cwd(), path$1).replace(/\\+/g, "/")}:${line}`;
87
+ }
88
+ function getComment(node) {
89
+ const text = node.text;
90
+ if (text.startsWith("/*")) return text.slice(2, -2).replace(/^\s*\*?\s*/gm, "").trim();
91
+ return text.replace(/^\/\/\s?/, "").trim();
92
+ }
93
+ const withComment = (query) => ({
94
+ pattern: `(
95
+ ((comment) @comment)?
96
+ .
97
+ (_ ${query.pattern})
98
+ )`,
99
+ extract(match) {
100
+ const result = query.extract(match);
101
+ if (!result?.translation) return result;
102
+ const comment = match.captures.find((c) => c.name === "comment")?.node;
103
+ if (!comment) return result;
104
+ if (comment) result.translation.comments = {
105
+ ...result.translation.comments,
106
+ extracted: getComment(comment)
107
+ };
108
+ return result;
109
+ }
110
+ });
111
+
112
+ //#endregion
113
+ //#region src/plugins/core/queries/import.ts
114
+ const importQuery = {
115
+ pattern: `
116
+ [
117
+ (import_statement
118
+ source: (string (string_fragment) @import))
119
+ (export_statement
120
+ source: (string (string_fragment) @import))
121
+ (call_expression
122
+ function: (identifier) @func
123
+ arguments: (arguments (string (string_fragment) @import))
124
+ (#eq? @func "require"))
125
+ (call_expression
126
+ function: (member_expression
127
+ object: (identifier) @obj
128
+ property: (property_identifier) @method)
129
+ arguments: (arguments (string (string_fragment) @import))
130
+ (#eq? @obj "require")
131
+ (#eq? @method "resolve"))
132
+ (call_expression
133
+ function: (import)
134
+ arguments: (arguments (string (string_fragment) @import)))
135
+ ]
136
+ `,
137
+ extract(match) {
138
+ return (match.captures.find((c) => c.name === "import")?.node)?.text;
139
+ }
140
+ };
141
+
142
+ //#endregion
143
+ //#region src/plugins/core/queries/utils.ts
144
+ const callPattern = (fnName, args, allowMember = true) => `(
145
+ (call_expression
146
+ function: ${allowMember ? `[
147
+ (identifier) @func
148
+ (member_expression property: (property_identifier) @func)
149
+ ]` : `(identifier) @func`}
150
+ arguments: ${args}
151
+ ) @call
152
+ (#eq? @func "${fnName}")
153
+ )`;
154
+ function isDescendant(node, ancestor) {
155
+ let current = node;
156
+ while (current) {
157
+ if (current.id === ancestor.id) return true;
158
+ current = current.parent;
159
+ }
160
+ return false;
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/plugins/core/queries/message.ts
165
+ const notInPlural = (query) => ({
166
+ pattern: query.pattern,
167
+ extract(match) {
168
+ const result = query.extract(match);
169
+ if (!result?.node) return result;
170
+ let parent = result.node.parent;
171
+ if (parent && parent.type === "arguments") parent = parent.parent;
172
+ if (parent && parent.type === "call_expression") {
173
+ const fn = parent.childForFieldName("function");
174
+ if (fn) {
175
+ if (fn.type === "identifier" && (fn.text === "plural" || fn.text === "ngettext" || fn.text === "pgettext" || fn.text === "npgettext") || fn.type === "member_expression" && [
176
+ "plural",
177
+ "ngettext",
178
+ "pgettext",
179
+ "npgettext"
180
+ ].includes(fn.childForFieldName("property")?.text ?? "")) return;
181
+ }
182
+ }
183
+ return result;
184
+ }
185
+ });
186
+ const messageArg = `[
187
+ (string (string_fragment) @msgid)
188
+ (object
189
+ (_)*
190
+ (pair
191
+ key: (property_identifier) @id_key
192
+ value: (string (string_fragment) @id)
193
+ (#eq? @id_key "id")
194
+ )?
195
+ (_)*
196
+ (pair
197
+ key: (property_identifier) @msg_key
198
+ value: (string (string_fragment) @message)
199
+ (#eq? @msg_key "message")
200
+ )?
201
+ (_)*
202
+ )
203
+ (template_string) @tpl
204
+ ]`;
205
+ const messageArgs = `[ (arguments ${messageArg}) (template_string) @tpl ]`;
206
+ const extractMessage = (name) => (match) => {
207
+ const node = match.captures.find((c) => c.name === "call")?.node;
208
+ if (!node) return;
209
+ const msgid = match.captures.find((c) => c.name === "msgid")?.node.text;
210
+ if (msgid) return {
211
+ node,
212
+ translation: {
213
+ id: msgid,
214
+ message: [msgid]
215
+ }
216
+ };
217
+ const tpl = match.captures.find((c) => c.name === "tpl")?.node;
218
+ if (tpl) {
219
+ for (const child of tpl.children) {
220
+ if (child.type !== "template_substitution") continue;
221
+ const expr = child.namedChildren[0];
222
+ if (!expr || expr.type !== "identifier") return {
223
+ node,
224
+ error: `${name}() template expressions must be simple identifiers`
225
+ };
226
+ }
227
+ const text = tpl.text.slice(1, -1);
228
+ return {
229
+ node,
230
+ translation: {
231
+ id: text,
232
+ message: [text]
233
+ }
234
+ };
235
+ }
236
+ const id = match.captures.find((c) => c.name === "id")?.node.text;
237
+ const message = match.captures.find((c) => c.name === "message")?.node.text;
238
+ const msgId = id ?? message;
239
+ if (!msgId) return;
240
+ return {
241
+ node,
242
+ translation: {
243
+ id: msgId,
244
+ message: [message ?? id ?? ""]
245
+ }
246
+ };
247
+ };
248
+ const messageQuery$1 = notInPlural(withComment({
249
+ pattern: callPattern("message", messageArgs),
250
+ extract: extractMessage("message")
251
+ }));
252
+ const allowed$1 = new Set([
253
+ "string",
254
+ "object",
255
+ "template_string"
256
+ ]);
257
+ const messageInvalidQuery = notInPlural({
258
+ pattern: callPattern("message", "(arguments (_) @arg)"),
259
+ extract(match) {
260
+ const call = match.captures.find((c) => c.name === "call")?.node;
261
+ const node = match.captures.find((c) => c.name === "arg")?.node;
262
+ if (!call || !node) return;
263
+ if (allowed$1.has(node.type)) return;
264
+ return {
265
+ node,
266
+ error: "message() argument must be a string literal, object literal, or template literal"
267
+ };
268
+ }
269
+ });
270
+
271
+ //#endregion
272
+ //#region src/plugins/core/queries/plural-utils.ts
273
+ const extractPluralForms = (name) => (match) => {
274
+ const call = match.captures.find((c) => c.name === "call")?.node;
275
+ const n = match.captures.find((c) => c.name === "n")?.node;
276
+ if (!call || !n || n.nextNamedSibling) return;
277
+ const msgctxt = match.captures.find((c) => c.name === "msgctxt")?.node?.text;
278
+ const msgNodes = match.captures.filter((c) => c.name === "msg").map((c) => c.node);
279
+ const ids = [];
280
+ const strs = [];
281
+ for (const node of msgNodes) {
282
+ const relevant = match.captures.filter((c) => [
283
+ "msgid",
284
+ "id",
285
+ "message",
286
+ "tpl"
287
+ ].includes(c.name) && isDescendant(c.node, node));
288
+ const subMatch = {
289
+ pattern: 0,
290
+ captures: [{
291
+ name: "call",
292
+ node
293
+ }, ...relevant]
294
+ };
295
+ const result = extractMessage(name)(subMatch);
296
+ if (!result) continue;
297
+ if (result.error) return {
298
+ node: call,
299
+ error: result.error
300
+ };
301
+ if (result.translation) {
302
+ ids.push(result.translation.id);
303
+ strs.push(result.translation.message[0] ?? "");
304
+ }
305
+ }
306
+ if (ids.length === 0) return;
307
+ const translation = {
308
+ id: ids[0],
309
+ plural: ids[1],
310
+ message: strs
311
+ };
312
+ if (msgctxt) translation.context = msgctxt;
313
+ return {
314
+ node: call,
315
+ translation
316
+ };
317
+ };
318
+
319
+ //#endregion
320
+ //#region src/plugins/core/queries/context.ts
321
+ const ctxCall = callPattern("context", `[
322
+ (arguments (string (string_fragment) @msgctxt))
323
+ (template_string) @msgctxt
324
+ ]`).replace(/@call/g, "@ctx").replace(/@func/g, "@ctxfn");
325
+ const contextMsgQuery = withComment({
326
+ pattern: `(
327
+ (call_expression
328
+ function: (member_expression
329
+ object: ${ctxCall}
330
+ property: (property_identifier) @func
331
+ )
332
+ arguments: ${messageArgs}
333
+ ) @call
334
+ (#eq? @func "message")
335
+ )`,
336
+ extract(match) {
337
+ const result = extractMessage("context.message")(match);
338
+ const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
339
+ if (!result || !result.translation || !contextNode) return result;
340
+ if (contextNode.type === "template_string") for (const child of contextNode.children) {
341
+ if (child.type !== "template_substitution") continue;
342
+ const expr = child.namedChildren[0];
343
+ if (!expr || expr.type !== "identifier") return {
344
+ node: contextNode,
345
+ error: "context() template expressions must be simple identifiers"
346
+ };
347
+ }
348
+ const contextText = contextNode.type === "template_string" ? contextNode.text.slice(1, -1) : contextNode.text;
349
+ return {
350
+ node: result.node,
351
+ translation: {
352
+ ...result.translation,
353
+ context: contextText
354
+ }
355
+ };
356
+ }
357
+ });
358
+ const msgCall$3 = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
359
+ const contextPluralQuery = withComment({
360
+ pattern: `(
361
+ (call_expression
362
+ function: (member_expression
363
+ object: ${ctxCall}
364
+ property: (property_identifier) @func
365
+ )
366
+ arguments: (arguments (
367
+ (${msgCall$3} ("," )?)+
368
+ (number) @n
369
+ ))
370
+ ) @call
371
+ (#eq? @func "plural")
372
+ )`,
373
+ extract: extractPluralForms("context.plural")
374
+ });
375
+ const contextInvalidQuery = withComment({
376
+ pattern: ctxCall,
377
+ extract(match) {
378
+ const call = match.captures.find((c) => c.name === "ctx")?.node;
379
+ if (!call) return;
380
+ const parent = call.parent;
381
+ if (parent && parent.type === "member_expression" && parent.childForFieldName("object")?.id === call.id) {
382
+ const property = parent.childForFieldName("property")?.text;
383
+ const grandparent = parent.parent;
384
+ if (grandparent && grandparent.type === "call_expression" && grandparent.childForFieldName("function")?.id === parent.id && (property === "message" || property === "plural")) return;
385
+ }
386
+ return {
387
+ node: call,
388
+ error: "context() must be used with message() or plural() in the same expression"
389
+ };
390
+ }
391
+ });
392
+
393
+ //#endregion
394
+ //#region src/plugins/core/queries/gettext.ts
395
+ const gettextQuery = withComment({
396
+ pattern: callPattern("gettext", messageArgs),
397
+ extract: extractMessage("gettext")
398
+ });
399
+ const allowed = new Set([
400
+ "string",
401
+ "object",
402
+ "template_string",
403
+ "identifier",
404
+ "call_expression"
405
+ ]);
406
+ const gettextInvalidQuery = {
407
+ pattern: callPattern("gettext", "(arguments (_) @arg)"),
408
+ extract(match) {
409
+ const call = match.captures.find((c) => c.name === "call")?.node;
410
+ const node = match.captures.find((c) => c.name === "arg")?.node;
411
+ if (!call || !node) return;
412
+ if (allowed.has(node.type)) return;
413
+ return {
414
+ node,
415
+ error: "gettext() argument must be a string literal, object literal, or template literal"
416
+ };
417
+ }
418
+ };
419
+
420
+ //#endregion
421
+ //#region src/plugins/core/queries/ngettext.ts
422
+ const msgCall$2 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
423
+ const plainMsg$1 = `(${messageArg}) @msg`;
424
+ const msgArg$1 = `[${msgCall$2} ${plainMsg$1}]`;
425
+ const ngettextQuery = withComment({
426
+ pattern: callPattern("ngettext", `(arguments ${msgArg$1} "," ${msgArg$1} ("," ${msgArg$1})* "," (_) @n)`),
427
+ extract: extractPluralForms("ngettext")
428
+ });
429
+
430
+ //#endregion
431
+ //#region src/plugins/core/queries/npgettext.ts
432
+ const msgCall$1 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
433
+ const plainMsg = `(${messageArg}) @msg`;
434
+ const msgArg = `[${msgCall$1} ${plainMsg}]`;
435
+ const npgettextQuery = withComment({
436
+ pattern: callPattern("npgettext", `(arguments (string (string_fragment) @msgctxt) "," ${msgArg} "," ${msgArg} ("," ${msgArg})* "," (_) @n)`),
437
+ extract: extractPluralForms("npgettext")
438
+ });
439
+
440
+ //#endregion
441
+ //#region src/plugins/core/queries/pgettext.ts
442
+ const pgettextQuery = withComment({
443
+ pattern: callPattern("pgettext", `(arguments (string (string_fragment) @msgctxt) "," ${messageArg})`),
444
+ extract(match) {
445
+ const result = extractMessage("pgettext")(match);
446
+ const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
447
+ if (!result || !contextNode || !result.translation) return result;
448
+ return {
449
+ node: result.node,
450
+ translation: {
451
+ ...result.translation,
452
+ context: contextNode.text
453
+ }
454
+ };
455
+ }
456
+ });
457
+
458
+ //#endregion
459
+ //#region src/plugins/core/queries/plural.ts
460
+ const msgCall = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
461
+ const pluralQuery$1 = withComment({
462
+ pattern: callPattern("plural", `(arguments (
463
+ (${msgCall} ("," )?)+
464
+ (number) @n
465
+ ))`, false),
466
+ extract: extractPluralForms("plural")
467
+ });
468
+
469
+ //#endregion
470
+ //#region src/plugins/core/queries/index.ts
471
+ const queries$1 = [
472
+ contextMsgQuery,
473
+ contextPluralQuery,
474
+ contextInvalidQuery,
475
+ messageQuery$1,
476
+ messageInvalidQuery,
477
+ gettextQuery,
478
+ gettextInvalidQuery,
479
+ pluralQuery$1,
480
+ ngettextQuery,
481
+ pgettextQuery,
482
+ npgettextQuery
483
+ ];
484
+
485
+ //#endregion
486
+ //#region src/plugins/core/parse.ts
487
+ function getLanguage(ext) {
488
+ switch (ext) {
489
+ case ".ts": return TS.typescript;
490
+ case ".tsx": return TS.tsx;
491
+ default: return JavaScript;
492
+ }
493
+ }
494
+ const parserCache = /* @__PURE__ */ new Map();
495
+ const queryCache = /* @__PURE__ */ new WeakMap();
496
+ function getCachedParser(ext) {
497
+ let cached = parserCache.get(ext);
498
+ if (!cached) {
499
+ const parser = new Parser();
500
+ const language = getLanguage(ext);
501
+ parser.setLanguage(language);
502
+ cached = {
503
+ parser,
504
+ language
505
+ };
506
+ parserCache.set(ext, cached);
507
+ }
508
+ return cached;
509
+ }
510
+ function getCachedQuery(language, pattern) {
511
+ let cache = queryCache.get(language);
512
+ if (!cache) {
513
+ cache = /* @__PURE__ */ new Map();
514
+ queryCache.set(language, cache);
515
+ }
516
+ let query = cache.get(pattern);
517
+ if (!query) {
518
+ query = new Parser.Query(language, pattern);
519
+ cache.set(pattern, query);
520
+ }
521
+ return query;
522
+ }
523
+ function getParser(path$1) {
524
+ const ext = extname(path$1);
525
+ return getCachedParser(ext);
526
+ }
527
+ function getQuery(language, pattern) {
528
+ return getCachedQuery(language, pattern);
529
+ }
530
+ function parseSource$1(source, path$1) {
531
+ const context = { path: path$1 };
532
+ const { parser, language } = getParser(path$1);
533
+ const tree = parser.parse(source);
534
+ const translations = [];
535
+ const warnings = [];
536
+ const imports = [];
537
+ const seen = /* @__PURE__ */ new Set();
538
+ for (const spec of queries$1) {
539
+ const query = getCachedQuery(language, spec.pattern);
540
+ for (const match of query.matches(tree.rootNode)) {
541
+ const message = spec.extract(match);
542
+ if (!message) continue;
543
+ const { node, translation, error } = message;
544
+ if (seen.has(node.id)) continue;
545
+ seen.add(node.id);
546
+ const reference = getReference(node, context);
547
+ if (translation) translations.push({
548
+ ...translation,
549
+ comments: {
550
+ ...translation.comments,
551
+ reference
552
+ }
553
+ });
554
+ if (error) warnings.push({
555
+ error,
556
+ reference
557
+ });
558
+ }
559
+ }
560
+ const importTreeQuery = getCachedQuery(language, importQuery.pattern);
561
+ for (const match of importTreeQuery.matches(tree.rootNode)) {
562
+ const imp = importQuery.extract(match);
563
+ if (imp) imports.push(imp);
564
+ }
565
+ return {
566
+ translations,
567
+ imports,
568
+ warnings
569
+ };
570
+ }
571
+
572
+ //#endregion
573
+ //#region src/plugins/core/resolve.ts
574
+ function findTsconfig(dir) {
575
+ let current = dir;
576
+ while (true) {
577
+ const config = path.join(current, "tsconfig.json");
578
+ if (fs$1.existsSync(config)) return config;
579
+ const parent = path.dirname(current);
580
+ if (parent === current) return;
581
+ current = parent;
582
+ }
583
+ }
584
+ const resolverCache = /* @__PURE__ */ new Map();
585
+ function getResolver(dir) {
586
+ const tsconfig = findTsconfig(dir);
587
+ const key = tsconfig ?? "__default__";
588
+ let resolver = resolverCache.get(key);
589
+ if (!resolver) {
590
+ resolver = new ResolverFactory({
591
+ extensions: [
592
+ ".ts",
593
+ ".tsx",
594
+ ".js",
595
+ ".jsx",
596
+ ".mjs",
597
+ ".cjs",
598
+ ".json"
599
+ ],
600
+ conditionNames: [
601
+ "import",
602
+ "require",
603
+ "node"
604
+ ],
605
+ ...tsconfig ? { tsconfig: { configFile: tsconfig } } : {}
606
+ });
607
+ resolverCache.set(key, resolver);
608
+ }
609
+ return resolver;
610
+ }
611
+ function resolveImports(file, imports) {
612
+ const dir = path.dirname(path.resolve(file));
613
+ const resolver = getResolver(dir);
614
+ const resolved = [];
615
+ for (const spec of imports) {
616
+ const res = resolver.sync(dir, spec);
617
+ if (res.path) resolved.push(res.path);
618
+ }
619
+ return resolved;
620
+ }
621
+
622
+ //#endregion
623
+ //#region src/plugins/core/core.ts
624
+ const filter$1 = /\.([cm]?tsx?|jsx?)$/;
625
+ const namespace$1 = "source";
626
+ function core() {
627
+ return {
628
+ name: "core",
629
+ setup(build) {
630
+ build.context.logger?.debug("core plugin initialized");
631
+ build.onResolve({
632
+ filter: filter$1,
633
+ namespace: namespace$1
634
+ }, ({ entrypoint, path: path$1 }) => {
635
+ return {
636
+ entrypoint,
637
+ namespace: namespace$1,
638
+ path: resolve(path$1)
639
+ };
640
+ });
641
+ build.onLoad({
642
+ filter: filter$1,
643
+ namespace: namespace$1
644
+ }, async ({ entrypoint, path: path$1 }) => {
645
+ const data = await readFile(path$1, "utf8");
646
+ return {
647
+ entrypoint,
648
+ path: path$1,
649
+ namespace: namespace$1,
650
+ data
651
+ };
652
+ });
653
+ build.onProcess({
654
+ filter: filter$1,
655
+ namespace: namespace$1
656
+ }, ({ entrypoint, path: path$1, data }) => {
657
+ const { translations, imports, warnings } = parseSource$1(data, path$1);
658
+ if (build.context.config.walk) {
659
+ const paths = resolveImports(path$1, imports);
660
+ for (const path$2 of paths) build.resolve({
661
+ entrypoint,
662
+ path: path$2,
663
+ namespace: namespace$1
664
+ });
665
+ }
666
+ for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
667
+ build.resolve({
668
+ entrypoint,
669
+ path: path$1,
670
+ namespace: "translate",
671
+ data: translations
672
+ });
673
+ });
674
+ }
675
+ };
676
+ }
677
+
678
+ //#endregion
679
+ //#region src/plugins/po/collect.ts
680
+ function collect(source, locale) {
681
+ const translations = { "": {} };
682
+ const nplurals = locale ? getNPlurals(locale) : void 0;
683
+ for (const { context, id, message, comments, obsolete, plural } of source) {
684
+ const ctx = context || "";
685
+ if (!translations[ctx]) translations[ctx] = {};
686
+ const length = plural ? nplurals ?? message.length : 1;
687
+ const existing = translations[ctx][id];
688
+ const refs = /* @__PURE__ */ new Set();
689
+ if (existing?.comments?.reference) existing.comments.reference.split(/\r?\n|\r/).forEach((r) => {
690
+ refs.add(r);
691
+ });
692
+ if (comments?.reference) comments.reference.split(/\r?\n|\r/).forEach((r) => {
693
+ refs.add(r);
694
+ });
695
+ const msgstr = existing?.msgstr ? existing.msgstr.slice(0, length) : Array.from({ length }, () => "");
696
+ while (msgstr.length < length) msgstr.push("");
697
+ translations[ctx][id] = {
698
+ msgctxt: context || void 0,
699
+ msgid: id,
700
+ msgid_plural: plural,
701
+ msgstr,
702
+ comments: {
703
+ ...existing?.comments,
704
+ ...comments,
705
+ reference: refs.size ? Array.from(refs).join("\n") : void 0
706
+ },
707
+ obsolete: existing?.obsolete ?? obsolete
708
+ };
709
+ }
710
+ return translations;
711
+ }
712
+
713
+ //#endregion
714
+ //#region src/plugins/po/hasChanges.ts
715
+ const IGNORED_HEADER_KEYS = new Set(["pot-creation-date", "po-revision-date"]);
716
+ const IGNORED_HEADER_LINE_PREFIXES = ["pot-creation-date:", "po-revision-date:"];
717
+ function normalizeHeaderString(value) {
718
+ const lines = value.split("\n");
719
+ const hadTrailingNewline = value.endsWith("\n");
720
+ const filtered = lines.filter((line) => {
721
+ const trimmed = line.trimStart().toLowerCase();
722
+ return !IGNORED_HEADER_LINE_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
723
+ }).map((line) => {
724
+ const separatorIndex = line.indexOf(":");
725
+ if (separatorIndex === -1) return line;
726
+ const key = line.slice(0, separatorIndex).trim().toLowerCase();
727
+ const value$1 = line.slice(separatorIndex + 1);
728
+ return `${key}:${value$1}`;
729
+ });
730
+ if (hadTrailingNewline && filtered[filtered.length - 1] !== "") filtered.push("");
731
+ return filtered.join("\n");
732
+ }
733
+ function normalize(translations) {
734
+ const compiled = gettextParser.po.compile(translations);
735
+ const parsed = gettextParser.po.parse(compiled);
736
+ if (parsed.headers) {
737
+ const normalizedHeaders = {};
738
+ for (const [key, value] of Object.entries(parsed.headers)) if (!IGNORED_HEADER_KEYS.has(key.toLowerCase())) normalizedHeaders[key.toLowerCase()] = value;
739
+ parsed.headers = normalizedHeaders;
740
+ }
741
+ const headerMessage = parsed.translations?.[""]?.[""];
742
+ if (headerMessage?.msgstr) headerMessage.msgstr = headerMessage.msgstr.map((item) => normalizeHeaderString(item));
743
+ return parsed;
744
+ }
745
+ function hasChanges(left, right) {
746
+ if (!right) return true;
747
+ const normalizedLeft = normalize(left);
748
+ const normalizedRight = normalize(right);
749
+ return !isDeepStrictEqual(normalizedLeft, normalizedRight);
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/plugins/po/merge.ts
754
+ function formatDate(date) {
755
+ const pad = (n) => n.toString().padStart(2, "0");
756
+ const year = date.getFullYear();
757
+ const month = pad(date.getMonth() + 1);
758
+ const day = pad(date.getDate());
759
+ const hours = pad(date.getHours());
760
+ const minutes = pad(date.getMinutes());
761
+ const tzo = -date.getTimezoneOffset();
762
+ const sign = tzo >= 0 ? "+" : "-";
763
+ const offsetHours = pad(Math.floor(Math.abs(tzo) / 60));
764
+ const offsetMinutes = pad(Math.abs(tzo) % 60);
765
+ return `${year}-${month}-${day} ${hours}:${minutes}${sign}${offsetHours}${offsetMinutes}`;
766
+ }
767
+ function merge(sources, existing, obsolete, locale, generatedAt) {
768
+ let headers = {};
769
+ let translations = { "": {} };
770
+ let obsoleteTranslations = {};
771
+ const nplurals = getNPlurals(locale);
772
+ if (existing) {
773
+ headers = existing.headers ? structuredClone(existing.headers) : {};
774
+ translations = existing.translations ? structuredClone(existing.translations) : { "": {} };
775
+ obsoleteTranslations = existing.obsolete ? structuredClone(existing.obsolete) : {};
776
+ for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) {
777
+ if (ctx === "" && id === "") continue;
778
+ translations[ctx][id].obsolete = true;
779
+ }
780
+ }
781
+ const collected = { "": {} };
782
+ for (const { translations: translations$1 } of sources) for (const [ctx, msgs] of Object.entries(translations$1)) {
783
+ if (!collected[ctx]) collected[ctx] = {};
784
+ for (const [id, entry] of Object.entries(msgs)) {
785
+ const existing$1 = collected[ctx][id];
786
+ const refs = /* @__PURE__ */ new Set();
787
+ if (existing$1?.comments?.reference) existing$1.comments.reference.split(/\r?\n|\r/).forEach((r) => {
788
+ refs.add(r);
789
+ });
790
+ if (entry.comments?.reference) entry.comments.reference.split(/\r?\n|\r/).forEach((r) => {
791
+ refs.add(r);
792
+ });
793
+ collected[ctx][id] = {
794
+ ...existing$1,
795
+ ...entry,
796
+ comments: {
797
+ ...existing$1?.comments,
798
+ ...entry.comments,
799
+ reference: refs.size ? Array.from(refs).join("\n") : void 0
800
+ }
801
+ };
802
+ }
803
+ }
804
+ for (const [ctx, msgs] of Object.entries(collected)) {
805
+ if (!translations[ctx]) translations[ctx] = {};
806
+ for (const [id, entry] of Object.entries(msgs)) {
807
+ const existingEntry = translations[ctx][id] ?? obsoleteTranslations[ctx]?.[id];
808
+ if (existingEntry) {
809
+ entry.msgstr = existingEntry.msgstr;
810
+ entry.comments = {
811
+ ...entry.comments,
812
+ translator: existingEntry.comments?.translator
813
+ };
814
+ }
815
+ delete entry.obsolete;
816
+ entry.msgstr = entry.msgstr.slice(0, nplurals);
817
+ while (entry.msgstr.length < nplurals) entry.msgstr.push("");
818
+ translations[ctx][id] = entry;
819
+ if (obsoleteTranslations[ctx]) delete obsoleteTranslations[ctx][id];
820
+ }
821
+ }
822
+ for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) {
823
+ if (ctx === "" && id === "") continue;
824
+ const entry = translations[ctx][id];
825
+ if (entry.obsolete) {
826
+ if (!obsoleteTranslations[ctx]) obsoleteTranslations[ctx] = {};
827
+ obsoleteTranslations[ctx][id] = entry;
828
+ delete translations[ctx][id];
829
+ }
830
+ }
831
+ headers = {
832
+ ...headers,
833
+ "Content-Type": headers["Content-Type"] || "text/plain; charset=UTF-8",
834
+ "Plural-Forms": `nplurals=${nplurals}; plural=${getFormula(locale)};`,
835
+ language: locale,
836
+ "MIME-Version": "1.0",
837
+ "Content-Transfer-Encoding": "8bit",
838
+ "POT-Creation-Date": formatDate(generatedAt),
839
+ "x-generator": "@let-value/translate-extract"
840
+ };
841
+ return {
842
+ charset: "utf-8",
843
+ headers,
844
+ translations,
845
+ ...obsolete === "mark" && Object.keys(obsoleteTranslations).length ? { obsolete: obsoleteTranslations } : {}
846
+ };
847
+ }
848
+
849
+ //#endregion
850
+ //#region src/plugins/po/po.ts
851
+ const namespace = "translate";
852
+ function po() {
853
+ return {
854
+ name: "po",
855
+ setup(build) {
856
+ build.context.logger?.debug("po plugin initialized");
857
+ const collections = /* @__PURE__ */ new Map();
858
+ let dispatched = false;
859
+ build.onResolve({
860
+ filter: /.*/,
861
+ namespace
862
+ }, async ({ entrypoint, path: path$1, data }) => {
863
+ if (!data || !Array.isArray(data)) return;
864
+ for (const locale of build.context.config.locales) {
865
+ const destination = build.context.config.destination({
866
+ entrypoint,
867
+ locale,
868
+ path: path$1
869
+ });
870
+ if (!collections.has(destination)) collections.set(destination, {
871
+ locale,
872
+ translations: []
873
+ });
874
+ collections.get(destination)?.translations.push(...data);
875
+ }
876
+ Promise.all([build.defer("source"), build.defer(namespace)]).then(() => {
877
+ if (dispatched) return;
878
+ dispatched = true;
879
+ for (const path$2 of collections.keys()) build.load({
880
+ entrypoint,
881
+ path: path$2,
882
+ namespace
883
+ });
884
+ });
885
+ });
886
+ build.onLoad({
887
+ filter: /.*\.po$/,
888
+ namespace
889
+ }, async ({ entrypoint, path: path$1 }) => {
890
+ const contents = await fs.readFile(path$1).catch(() => void 0);
891
+ const data = contents ? gettextParser.po.parse(contents) : void 0;
892
+ return {
893
+ entrypoint,
894
+ path: path$1,
895
+ namespace,
896
+ data
897
+ };
898
+ });
899
+ build.onProcess({
900
+ filter: /.*\.po$/,
901
+ namespace
902
+ }, async ({ entrypoint, path: path$1, data }) => {
903
+ const collected = collections.get(path$1);
904
+ if (!collected) {
905
+ build.context.logger?.warn({ path: path$1 }, "no translations collected for this path");
906
+ return;
907
+ }
908
+ const { locale, translations } = collected;
909
+ const record = collect(translations, locale);
910
+ const out = merge([{ translations: record }], data, build.context.config.obsolete, locale, build.context.generatedAt);
911
+ if (hasChanges(out, data)) {
912
+ await fs.mkdir(dirname(path$1), { recursive: true });
913
+ await fs.writeFile(path$1, gettextParser.po.compile(out));
914
+ }
915
+ build.resolve({
916
+ entrypoint,
917
+ path: path$1,
918
+ namespace: "cleanup",
919
+ data: translations
920
+ });
921
+ });
922
+ }
923
+ };
924
+ }
925
+
926
+ //#endregion
927
+ //#region src/plugins/react/queries/utils.ts
928
+ function buildTemplate(node) {
929
+ const source = node.tree.rootNode.text;
930
+ const open = node.childForFieldName("open_tag");
931
+ const close = node.childForFieldName("close_tag");
932
+ const contentStart = open?.endIndex ?? node.startIndex;
933
+ const contentEnd = close?.startIndex ?? node.endIndex;
934
+ const parts = [];
935
+ let segmentStart = contentStart;
936
+ const pushRawText = (endIndex) => {
937
+ if (endIndex <= segmentStart) {
938
+ segmentStart = Math.max(segmentStart, endIndex);
939
+ return;
940
+ }
941
+ const text$1 = source.slice(segmentStart, endIndex);
942
+ if (text$1) parts.push({
943
+ kind: "text",
944
+ text: text$1,
945
+ raw: true
946
+ });
947
+ segmentStart = endIndex;
948
+ };
949
+ const children = node.namedChildren.slice(1, -1);
950
+ for (const child of children) if (child.type === "jsx_expression") {
951
+ pushRawText(child.startIndex);
952
+ const expr = child.namedChildren[0];
953
+ if (!expr) return {
954
+ text: "",
955
+ error: "Empty JSX expression"
956
+ };
957
+ if (expr.type === "identifier") parts.push({
958
+ kind: "expr",
959
+ value: expr.text
960
+ });
961
+ else if (expr.type === "string") parts.push({
962
+ kind: "text",
963
+ text: expr.text.slice(1, -1),
964
+ raw: false
965
+ });
966
+ else if (expr.type === "template_string") {
967
+ if (expr.children.some((c) => c.type === "template_substitution")) return {
968
+ text: "",
969
+ error: "JSX expressions with template substitutions are not supported"
970
+ };
971
+ parts.push({
972
+ kind: "text",
973
+ text: expr.text.slice(1, -1),
974
+ raw: false
975
+ });
976
+ } else return {
977
+ text: "",
978
+ error: "JSX expressions must be simple identifiers, strings, or template literals"
979
+ };
980
+ segmentStart = child.endIndex;
981
+ } else if (child.type === "string") {
982
+ pushRawText(child.startIndex);
983
+ parts.push({
984
+ kind: "text",
985
+ text: child.text.slice(1, -1),
986
+ raw: false
987
+ });
988
+ segmentStart = child.endIndex;
989
+ } else if (child.type === "jsx_text" || child.type === "html_character_reference" || child.isError) {} else return {
990
+ text: "",
991
+ error: "Unsupported JSX child"
992
+ };
993
+ pushRawText(contentEnd);
994
+ const firstRawIndex = parts.findIndex((part) => part.kind === "text" && part.raw);
995
+ if (firstRawIndex === 0) {
996
+ const part = parts[firstRawIndex];
997
+ part.text = part.text.replace(/^\s+/, "");
998
+ }
999
+ let lastRawIndex = -1;
1000
+ for (let i = parts.length - 1; i >= 0; i--) {
1001
+ const part = parts[i];
1002
+ if (part.kind === "text" && part.raw) {
1003
+ lastRawIndex = i;
1004
+ break;
1005
+ }
1006
+ }
1007
+ if (lastRawIndex !== -1 && lastRawIndex === parts.length - 1) {
1008
+ const part = parts[lastRawIndex];
1009
+ part.text = part.text.replace(/\s+$/, "");
1010
+ }
1011
+ const strings = [""];
1012
+ const values = [];
1013
+ for (const part of parts) if (part.kind === "text") {
1014
+ if (part.text) strings[strings.length - 1] += part.text;
1015
+ } else {
1016
+ values.push(part.value);
1017
+ strings.push("");
1018
+ }
1019
+ let text = "";
1020
+ for (let i = 0; i < strings.length; i++) {
1021
+ text += strings[i];
1022
+ if (values[i]) text += `\${${values[i]}}`;
1023
+ }
1024
+ return { text };
1025
+ }
1026
+ function buildAttrValue(node) {
1027
+ if (node.type === "string") return { text: node.text.slice(1, -1) };
1028
+ if (node.type === "jsx_expression") {
1029
+ const expr = node.namedChildren[0];
1030
+ if (!expr) return {
1031
+ text: "",
1032
+ error: "Empty JSX expression"
1033
+ };
1034
+ if (expr.type === "identifier") return { text: `\${${expr.text}}` };
1035
+ else if (expr.type === "string") return { text: expr.text.slice(1, -1) };
1036
+ else if (expr.type === "template_string") {
1037
+ if (expr.children.some((c) => c.type === "template_substitution")) return {
1038
+ text: "",
1039
+ error: "JSX expressions with template substitutions are not supported"
1040
+ };
1041
+ return { text: expr.text.slice(1, -1) };
1042
+ } else return {
1043
+ text: "",
1044
+ error: "JSX expressions must be simple identifiers, strings, or template literals"
1045
+ };
1046
+ }
1047
+ return {
1048
+ text: "",
1049
+ error: "Unsupported JSX child"
1050
+ };
1051
+ }
1052
+
1053
+ //#endregion
1054
+ //#region src/plugins/react/queries/message.ts
1055
+ const messageQuery = withComment({
1056
+ pattern: `(
1057
+ [
1058
+ (jsx_element (jsx_opening_element name: (identifier) @name)) @call
1059
+ (jsx_self_closing_element name: (identifier) @name) @call
1060
+ (lexical_declaration
1061
+ (variable_declarator
1062
+ value: [
1063
+ (jsx_element (jsx_opening_element name: (identifier) @name)) @call
1064
+ (jsx_self_closing_element name: (identifier) @name) @call
1065
+ ]
1066
+ )
1067
+ )
1068
+ ]
1069
+ (#eq? @name "Message")
1070
+ )`,
1071
+ extract(match) {
1072
+ const node = match.captures.find((c) => c.name === "call")?.node;
1073
+ if (!node) return void 0;
1074
+ let attrs = [];
1075
+ if (node.type === "jsx_element") {
1076
+ const open = node.childForFieldName("open_tag");
1077
+ if (open) attrs = open.namedChildren;
1078
+ } else if (node.type === "jsx_self_closing_element") attrs = node.namedChildren.slice(1);
1079
+ let msgctxt;
1080
+ let childValue;
1081
+ for (const child of attrs) {
1082
+ if (child.type !== "jsx_attribute") continue;
1083
+ const name = child.child(0);
1084
+ const value = child.child(child.childCount - 1);
1085
+ if (name?.text === "context" && value?.type === "string") msgctxt = value.text.slice(1, -1);
1086
+ else if (name?.text === "children" && value) childValue = value;
1087
+ }
1088
+ let text = "";
1089
+ let error;
1090
+ if (node.type === "jsx_element") ({text, error} = buildTemplate(node));
1091
+ else if (childValue) ({text, error} = buildAttrValue(childValue));
1092
+ if (error) return {
1093
+ node,
1094
+ error
1095
+ };
1096
+ if (!text) return void 0;
1097
+ const translation = {
1098
+ id: text,
1099
+ message: [text]
1100
+ };
1101
+ if (msgctxt) translation.context = msgctxt;
1102
+ return {
1103
+ node,
1104
+ translation
1105
+ };
1106
+ }
1107
+ });
1108
+
1109
+ //#endregion
1110
+ //#region src/plugins/react/queries/plural.ts
1111
+ function parseForms(node) {
1112
+ const forms = [];
1113
+ if (node.type === "jsx_expression") {
1114
+ const arr = node.namedChildren[0];
1115
+ if (!arr || arr.type !== "array") return {
1116
+ forms: [],
1117
+ error: "Plural forms must be an array"
1118
+ };
1119
+ for (const el of arr.namedChildren) if (el.type === "jsx_element" || el.type === "jsx_fragment") {
1120
+ const { text, error } = buildTemplate(el);
1121
+ if (error) return {
1122
+ forms: [],
1123
+ error
1124
+ };
1125
+ forms.push(text);
1126
+ } else if (el.type === "string") forms.push(el.text.slice(1, -1));
1127
+ else return {
1128
+ forms: [],
1129
+ error: "Unsupported plural form"
1130
+ };
1131
+ }
1132
+ return { forms };
1133
+ }
1134
+ const pluralQuery = withComment({
1135
+ pattern: `(
1136
+ [
1137
+ (jsx_element (jsx_opening_element name: (identifier) @name))
1138
+ (jsx_self_closing_element name: (identifier) @name)
1139
+ ] @call
1140
+ (#eq? @name "Plural")
1141
+ )`,
1142
+ extract(match) {
1143
+ const node = match.captures.find((c) => c.name === "call")?.node;
1144
+ if (!node) return void 0;
1145
+ let attrs = [];
1146
+ if (node.type === "jsx_element") {
1147
+ const open = node.childForFieldName("open_tag");
1148
+ if (open) attrs = open.namedChildren;
1149
+ } else if (node.type === "jsx_self_closing_element") attrs = node.namedChildren.slice(1);
1150
+ let msgctxt;
1151
+ let formsNode;
1152
+ for (const child of attrs) {
1153
+ if (child.type !== "jsx_attribute") continue;
1154
+ const name = child.child(0);
1155
+ const value = child.child(child.childCount - 1);
1156
+ if (name?.text === "context" && value?.type === "string") msgctxt = value.text.slice(1, -1);
1157
+ else if (name?.text === "forms" && value) formsNode = value;
1158
+ }
1159
+ if (!formsNode) return void 0;
1160
+ const { forms, error } = parseForms(formsNode);
1161
+ if (error) return {
1162
+ node,
1163
+ error
1164
+ };
1165
+ if (forms.length === 0) return void 0;
1166
+ const translation = {
1167
+ id: forms[0],
1168
+ plural: forms[1],
1169
+ message: forms
1170
+ };
1171
+ if (msgctxt) translation.context = msgctxt;
1172
+ return {
1173
+ node,
1174
+ translation
1175
+ };
1176
+ }
1177
+ });
1178
+
1179
+ //#endregion
1180
+ //#region src/plugins/react/queries/index.ts
1181
+ const queries = [messageQuery, pluralQuery];
1182
+
1183
+ //#endregion
1184
+ //#region src/plugins/react/parse.ts
1185
+ function parseSource(source, path$1) {
1186
+ const context = { path: path$1 };
1187
+ const { parser, language } = getParser(path$1);
1188
+ const tree = parser.parse(source);
1189
+ const translations = [];
1190
+ const warnings = [];
1191
+ const seen = /* @__PURE__ */ new Set();
1192
+ for (const spec of queries) {
1193
+ const query = getQuery(language, spec.pattern);
1194
+ for (const match of query.matches(tree.rootNode)) {
1195
+ const message = spec.extract(match);
1196
+ if (!message) continue;
1197
+ const { node, translation, error } = message;
1198
+ if (seen.has(node.id)) continue;
1199
+ seen.add(node.id);
1200
+ const reference = getReference(node, context);
1201
+ if (translation) translations.push({
1202
+ ...translation,
1203
+ comments: {
1204
+ ...translation.comments,
1205
+ reference
1206
+ }
1207
+ });
1208
+ if (error) warnings.push({
1209
+ error,
1210
+ reference
1211
+ });
1212
+ }
1213
+ }
1214
+ return {
1215
+ translations,
1216
+ warnings
1217
+ };
1218
+ }
1219
+
1220
+ //#endregion
1221
+ //#region src/plugins/react/react.ts
1222
+ const filter = /\.[cm]?[jt]sx$/;
1223
+ function react() {
1224
+ return {
1225
+ name: "react",
1226
+ setup(build) {
1227
+ build.context.logger?.debug("react plugin initialized");
1228
+ build.onResolve({
1229
+ filter: /.*/,
1230
+ namespace: "source"
1231
+ }, ({ entrypoint, path: path$1, namespace: namespace$3 }) => {
1232
+ return {
1233
+ entrypoint,
1234
+ namespace: namespace$3,
1235
+ path: resolve(path$1)
1236
+ };
1237
+ });
1238
+ build.onLoad({
1239
+ filter,
1240
+ namespace: "source"
1241
+ }, async ({ entrypoint, path: path$1, namespace: namespace$3 }) => {
1242
+ const data = await readFile(path$1, "utf8");
1243
+ return {
1244
+ entrypoint,
1245
+ path: path$1,
1246
+ namespace: namespace$3,
1247
+ data
1248
+ };
1249
+ });
1250
+ build.onProcess({
1251
+ filter,
1252
+ namespace: "source"
1253
+ }, ({ entrypoint, path: path$1, data }) => {
1254
+ const { translations, warnings } = parseSource(data, path$1);
1255
+ for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
1256
+ build.resolve({
1257
+ entrypoint,
1258
+ path: path$1,
1259
+ namespace: "translate",
1260
+ data: translations
1261
+ });
1262
+ });
1263
+ }
1264
+ };
1265
+ }
1266
+
1267
+ //#endregion
1268
+ //#region src/plugins/static.ts
1269
+ const original = {
1270
+ cleanup,
1271
+ core,
1272
+ po,
1273
+ react
1274
+ };
1275
+ function resolveStaticPlugin(item) {
1276
+ if ("static" in item) return original[item.static.name](...item.static.props);
1277
+ return item;
1278
+ }
1279
+
1280
+ //#endregion
1281
+ //#region src/run.ts
1282
+ async function run(entrypoint, { config, logger }) {
1283
+ const destination = entrypoint.destination ?? config.destination;
1284
+ const obsolete = entrypoint.obsolete ?? config.obsolete;
1285
+ const exclude = entrypoint.exclude ?? config.exclude;
1286
+ const walk = entrypoint.walk ?? config.walk;
1287
+ const context = {
1288
+ config: {
1289
+ ...config,
1290
+ destination,
1291
+ obsolete,
1292
+ exclude,
1293
+ walk
1294
+ },
1295
+ generatedAt: /* @__PURE__ */ new Date(),
1296
+ logger
1297
+ };
1298
+ logger?.info(entrypoint, "starting extraction");
1299
+ const resolvers = [];
1300
+ const loaders = [];
1301
+ const processors = [];
1302
+ const hooks = {
1303
+ resolve: resolvers,
1304
+ load: loaders,
1305
+ process: processors
1306
+ };
1307
+ const pending = /* @__PURE__ */ new Map();
1308
+ const queue = [];
1309
+ function getDeferred(namespace$3) {
1310
+ let defer$1 = pending.get(namespace$3);
1311
+ if (defer$1 === void 0) {
1312
+ defer$1 = new Defer();
1313
+ pending.set(namespace$3, defer$1);
1314
+ }
1315
+ return defer$1;
1316
+ }
1317
+ function defer(namespace$3) {
1318
+ return getDeferred(namespace$3).promise;
1319
+ }
1320
+ function resolve$1(args) {
1321
+ const { entrypoint: entrypoint$1, path: path$1, namespace: namespace$3 } = args;
1322
+ const skipped = context.config.exclude.some((ex) => typeof ex === "function" ? ex(args) : ex.test(args.path));
1323
+ logger?.debug({
1324
+ entrypoint: entrypoint$1,
1325
+ path: path$1,
1326
+ namespace: namespace$3,
1327
+ skipped
1328
+ }, "resolve");
1329
+ if (skipped) return;
1330
+ queue.push({
1331
+ type: "resolve",
1332
+ args
1333
+ });
1334
+ getDeferred(namespace$3).enqueue();
1335
+ }
1336
+ function load(args) {
1337
+ const { entrypoint: entrypoint$1, path: path$1, namespace: namespace$3 } = args;
1338
+ logger?.debug({
1339
+ entrypoint: entrypoint$1,
1340
+ path: path$1,
1341
+ namespace: namespace$3
1342
+ }, "load");
1343
+ queue.push({
1344
+ type: "load",
1345
+ args
1346
+ });
1347
+ getDeferred(namespace$3).enqueue();
1348
+ }
1349
+ function process$1(args) {
1350
+ const { entrypoint: entrypoint$1, path: path$1, namespace: namespace$3 } = args;
1351
+ logger?.debug({
1352
+ entrypoint: entrypoint$1,
1353
+ path: path$1,
1354
+ namespace: namespace$3
1355
+ }, "process");
1356
+ queue.push({
1357
+ type: "process",
1358
+ args
1359
+ });
1360
+ getDeferred(namespace$3).enqueue();
1361
+ }
1362
+ const build = {
1363
+ context,
1364
+ resolve: resolve$1,
1365
+ load,
1366
+ process: process$1,
1367
+ defer,
1368
+ onResolve(filter$2, hook) {
1369
+ resolvers.push({
1370
+ filter: filter$2,
1371
+ hook
1372
+ });
1373
+ },
1374
+ onLoad(filter$2, hook) {
1375
+ loaders.push({
1376
+ filter: filter$2,
1377
+ hook
1378
+ });
1379
+ },
1380
+ onProcess(filter$2, hook) {
1381
+ processors.push({
1382
+ filter: filter$2,
1383
+ hook
1384
+ });
1385
+ }
1386
+ };
1387
+ for (const item of config.plugins) {
1388
+ const plugin = resolveStaticPlugin(item);
1389
+ logger?.debug({ plugin: plugin.name }, "setting up plugin");
1390
+ plugin.setup(build);
1391
+ }
1392
+ const pattern = entrypoint.entrypoint.replace(/\\/g, "/");
1393
+ const paths = glob.isDynamicPattern(pattern) ? await glob(pattern, { onlyFiles: true }) : [entrypoint.entrypoint];
1394
+ logger?.debug({
1395
+ entrypoint: entrypoint.entrypoint,
1396
+ paths
1397
+ }, "resolved paths");
1398
+ for (const path$1 of paths) resolve$1({
1399
+ entrypoint: path$1,
1400
+ path: path$1,
1401
+ namespace: "source"
1402
+ });
1403
+ async function processTask(task) {
1404
+ const { type } = task;
1405
+ let args = task.args;
1406
+ const { entrypoint: entrypoint$1, path: path$1, namespace: namespace$3 } = args;
1407
+ logger?.trace({
1408
+ type,
1409
+ entrypoint: entrypoint$1,
1410
+ path: path$1,
1411
+ namespace: namespace$3
1412
+ }, "processing task");
1413
+ for (const { filter: filter$2, hook } of hooks[type]) {
1414
+ if (filter$2.namespace !== namespace$3) continue;
1415
+ if (filter$2.filter && !filter$2.filter.test(path$1)) continue;
1416
+ const result = await hook(args);
1417
+ if (result !== void 0) args = result;
1418
+ }
1419
+ if (args !== void 0) {
1420
+ if (type === "resolve") load(args);
1421
+ else if (type === "load") process$1(args);
1422
+ }
1423
+ getDeferred(namespace$3).dequeue();
1424
+ }
1425
+ while (queue.length || Array.from(pending.values()).some((d) => d.pending > 0)) {
1426
+ while (queue.length) {
1427
+ const task = queue.shift();
1428
+ if (!task) break;
1429
+ await processTask(task);
1430
+ }
1431
+ await Promise.all(Array.from(pending.values()).map((d) => d.promise));
1432
+ await Promise.resolve();
1433
+ }
1434
+ logger?.info(entrypoint, "extraction completed");
1435
+ }
1436
+
1437
+ //#endregion
1438
+ export { cleanup, core, po, react, run };
1439
+ //# sourceMappingURL=run-DcO6U79B.js.map