@let-value/translate-extract 1.1.6-beta.3 → 1.2.3
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/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.d.cts +1 -1
- package/dist/bin/cli.d.mts +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/{configuration-oHJyypq5.d.mts → configuration-Bwtek7Fh.d.mts} +57 -55
- package/dist/configuration-Bwtek7Fh.d.mts.map +1 -0
- package/dist/{configuration-BVNh7M_I.d.cts → configuration-DLSMbmU5.d.cts} +58 -54
- package/dist/configuration-DLSMbmU5.d.cts.map +1 -0
- package/dist/{run-oGs6tkeL.cjs → run-Bcz92fEY.cjs} +785 -290
- package/dist/{run-Bv3fv1N8.mjs → run-CzeTVOug.mjs} +785 -290
- package/dist/run-CzeTVOug.mjs.map +1 -0
- package/dist/src/core.d.cts +2 -2
- package/dist/src/core.d.mts +2 -2
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.d.cts +4 -16
- package/dist/src/index.d.cts.map +1 -1
- package/dist/src/index.d.mts +4 -16
- package/dist/src/index.d.mts.map +1 -1
- package/dist/src/index.mjs +1 -1
- package/dist/src/static.d.cts +1 -1
- package/dist/src/static.d.mts +1 -1
- package/package.json +4 -3
- package/dist/configuration-BVNh7M_I.d.cts.map +0 -1
- package/dist/configuration-oHJyypq5.d.mts.map +0 -1
- package/dist/run-Bv3fv1N8.mjs.map +0 -1
|
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
19
|
value: mod,
|
|
20
20
|
enumerable: true
|
|
21
21
|
}) : target, mod));
|
|
@@ -42,52 +42,25 @@ let node_util = require("node:util");
|
|
|
42
42
|
let fast_glob = require("fast-glob");
|
|
43
43
|
fast_glob = __toESM(fast_glob, 1);
|
|
44
44
|
//#region src/plugins/cleanup/cleanup.ts
|
|
45
|
-
const namespace$2 = "cleanup";
|
|
46
45
|
function cleanup() {
|
|
47
46
|
return {
|
|
48
47
|
name: "cleanup",
|
|
49
48
|
setup(build) {
|
|
50
49
|
build.context.logger?.debug("cleanup plugin initialized");
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
]).then(() => {
|
|
66
|
-
if (dispatched) return;
|
|
67
|
-
dispatched = true;
|
|
68
|
-
for (const path of dirs.values()) build.process({
|
|
69
|
-
entrypoint: path,
|
|
70
|
-
path,
|
|
71
|
-
namespace: namespace$2,
|
|
72
|
-
data: void 0
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
build.onProcess({
|
|
77
|
-
namespace: namespace$2,
|
|
78
|
-
filter: /.*/
|
|
79
|
-
}, async ({ path }) => {
|
|
80
|
-
if (processed.has(path)) return;
|
|
81
|
-
processed.add(path);
|
|
82
|
-
const files = await node_fs_promises.default.readdir(path).catch(() => []);
|
|
83
|
-
for (const f of files.filter((p) => p.endsWith(".po"))) {
|
|
84
|
-
const full = (0, node_path.join)(path, f);
|
|
85
|
-
const contents = await node_fs_promises.default.readFile(full).catch(() => void 0);
|
|
86
|
-
if (!contents) continue;
|
|
87
|
-
const parsed = gettext_parser.po.parse(contents);
|
|
88
|
-
const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
|
|
89
|
-
if (!hasTranslations && generated.has(full)) await node_fs_promises.default.unlink(full);
|
|
90
|
-
if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
|
|
50
|
+
build.onOutputs(async ({ outputs }) => {
|
|
51
|
+
const generated = new Set(outputs);
|
|
52
|
+
const dirs = new Set(outputs.map((path) => (0, node_path.dirname)(path)));
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
const files = await node_fs_promises.default.readdir(dir).catch(() => []);
|
|
55
|
+
for (const file of files.filter((name) => name.endsWith(".po"))) {
|
|
56
|
+
const full = (0, node_path.join)(dir, file);
|
|
57
|
+
const contents = await node_fs_promises.default.readFile(full).catch(() => void 0);
|
|
58
|
+
if (!contents) continue;
|
|
59
|
+
const parsed = gettext_parser.po.parse(contents);
|
|
60
|
+
const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
|
|
61
|
+
if (!hasTranslations && generated.has(full)) await node_fs_promises.default.unlink(full);
|
|
62
|
+
if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
|
|
63
|
+
}
|
|
91
64
|
}
|
|
92
65
|
});
|
|
93
66
|
}
|
|
@@ -288,7 +261,7 @@ const messageQuery$1 = notInPlural(withComment({
|
|
|
288
261
|
pattern: callPattern("message", messageArgs),
|
|
289
262
|
extract: extractMessage("message")
|
|
290
263
|
}));
|
|
291
|
-
const allowed$1 = new Set([
|
|
264
|
+
const allowed$1 = /* @__PURE__ */ new Set([
|
|
292
265
|
"string",
|
|
293
266
|
"object",
|
|
294
267
|
"template_string"
|
|
@@ -431,7 +404,7 @@ const gettextQuery = withComment({
|
|
|
431
404
|
pattern: callPattern("gettext", messageArgs),
|
|
432
405
|
extract: extractMessage("gettext")
|
|
433
406
|
});
|
|
434
|
-
const allowed = new Set([
|
|
407
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
435
408
|
"string",
|
|
436
409
|
"object",
|
|
437
410
|
"template_string",
|
|
@@ -603,7 +576,7 @@ function findTsconfig(dir) {
|
|
|
603
576
|
}
|
|
604
577
|
}
|
|
605
578
|
const resolverCache = /* @__PURE__ */ new Map();
|
|
606
|
-
const builtins = new Set([...node_module.builtinModules, ...node_module.builtinModules.map((name) => `node:${name}`)]);
|
|
579
|
+
const builtins = /* @__PURE__ */ new Set([...node_module.builtinModules, ...node_module.builtinModules.map((name) => `node:${name}`)]);
|
|
607
580
|
function isBuiltin(spec) {
|
|
608
581
|
if (builtins.has(spec)) return true;
|
|
609
582
|
const [base, subpath] = (spec.startsWith("node:") ? spec.slice(5) : spec).split("/", 2);
|
|
@@ -680,58 +653,29 @@ function resolveImportResults(file, imports) {
|
|
|
680
653
|
//#endregion
|
|
681
654
|
//#region src/plugins/core/core.ts
|
|
682
655
|
const filter$1 = /\.([cm]?tsx?|jsx?)$/;
|
|
683
|
-
const namespace$1 = "source";
|
|
684
656
|
function core() {
|
|
685
657
|
return {
|
|
686
658
|
name: "core",
|
|
687
659
|
setup(build) {
|
|
688
660
|
build.context.logger?.debug("core plugin initialized");
|
|
689
|
-
build.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}, ({ entrypoint, path, import: imp, data }) => {
|
|
693
|
-
return {
|
|
694
|
-
entrypoint,
|
|
695
|
-
namespace: namespace$1,
|
|
696
|
-
path: (0, node_path.resolve)(path),
|
|
697
|
-
import: imp,
|
|
698
|
-
data
|
|
699
|
-
};
|
|
700
|
-
});
|
|
701
|
-
build.onLoad({
|
|
702
|
-
filter: filter$1,
|
|
703
|
-
namespace: namespace$1
|
|
704
|
-
}, async ({ entrypoint, path }) => {
|
|
705
|
-
return {
|
|
706
|
-
entrypoint,
|
|
707
|
-
path,
|
|
708
|
-
namespace: namespace$1,
|
|
709
|
-
data: await (0, node_fs_promises.readFile)(path, "utf8")
|
|
710
|
-
};
|
|
711
|
-
});
|
|
712
|
-
build.onProcess({
|
|
713
|
-
filter: filter$1,
|
|
714
|
-
namespace: namespace$1
|
|
715
|
-
}, ({ entrypoint, path, data }) => {
|
|
716
|
-
const result = parseSource$1(data, path);
|
|
661
|
+
build.onLoad(filter$1, ({ path }) => (0, node_fs_promises.readFile)(path, "utf8"));
|
|
662
|
+
build.onProcess(filter$1, ({ entrypoint, path, contents, emit }) => {
|
|
663
|
+
const result = parseSource$1(contents, path);
|
|
717
664
|
if (result.entrypoint && entrypoint !== path) {
|
|
718
|
-
build.source(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
data: []
|
|
724
|
-
};
|
|
665
|
+
build.source({
|
|
666
|
+
entrypoint: path,
|
|
667
|
+
path
|
|
668
|
+
});
|
|
669
|
+
return true;
|
|
725
670
|
}
|
|
726
671
|
const { translations, imports, warnings } = result;
|
|
727
672
|
if (build.context.config.walk) {
|
|
728
673
|
const { resolved, unresolved } = resolveImportResults(path, imports);
|
|
729
674
|
for (const result of resolved) {
|
|
730
675
|
if (build.context.paths.has(result.path)) continue;
|
|
731
|
-
build.
|
|
676
|
+
build.source({
|
|
732
677
|
entrypoint,
|
|
733
678
|
path: result.path,
|
|
734
|
-
namespace: namespace$1,
|
|
735
679
|
import: result.import
|
|
736
680
|
});
|
|
737
681
|
}
|
|
@@ -740,19 +684,14 @@ function core() {
|
|
|
740
684
|
if (imp && require_exclude.isExcluded({
|
|
741
685
|
entrypoint,
|
|
742
686
|
path: spec,
|
|
743
|
-
namespace:
|
|
687
|
+
namespace: "source",
|
|
744
688
|
import: imp
|
|
745
689
|
}, build.context.config.exclude)) continue;
|
|
746
690
|
build.context.logger?.warn(`Unable to resolve import "${spec}" from ${path}${error ? `: ${error}` : ""}`);
|
|
747
691
|
}
|
|
748
692
|
}
|
|
749
693
|
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
750
|
-
|
|
751
|
-
entrypoint,
|
|
752
|
-
path,
|
|
753
|
-
namespace: "translate",
|
|
754
|
-
data: translations
|
|
755
|
-
});
|
|
694
|
+
emit(translations);
|
|
756
695
|
});
|
|
757
696
|
}
|
|
758
697
|
};
|
|
@@ -793,7 +732,7 @@ function collect(source, locale) {
|
|
|
793
732
|
}
|
|
794
733
|
//#endregion
|
|
795
734
|
//#region src/plugins/po/hasChanges.ts
|
|
796
|
-
const IGNORED_HEADER_KEYS = new Set(["pot-creation-date", "po-revision-date"]);
|
|
735
|
+
const IGNORED_HEADER_KEYS = /* @__PURE__ */ new Set(["pot-creation-date", "po-revision-date"]);
|
|
797
736
|
const IGNORED_HEADER_LINE_PREFIXES = ["pot-creation-date:", "po-revision-date:"];
|
|
798
737
|
function normalizeHeaderString(value) {
|
|
799
738
|
const lines = value.split("\n");
|
|
@@ -809,7 +748,7 @@ function normalizeHeaderString(value) {
|
|
|
809
748
|
if (hadTrailingNewline && filtered[filtered.length - 1] !== "") filtered.push("");
|
|
810
749
|
return filtered.join("\n");
|
|
811
750
|
}
|
|
812
|
-
function normalize(translations) {
|
|
751
|
+
function normalize$1(translations) {
|
|
813
752
|
const compiled = gettext_parser.po.compile(translations);
|
|
814
753
|
const parsed = gettext_parser.po.parse(compiled);
|
|
815
754
|
if (parsed.headers) {
|
|
@@ -823,7 +762,9 @@ function normalize(translations) {
|
|
|
823
762
|
}
|
|
824
763
|
function hasChanges(left, right) {
|
|
825
764
|
if (!right) return true;
|
|
826
|
-
|
|
765
|
+
const normalizedLeft = normalize$1(left);
|
|
766
|
+
const normalizedRight = normalize$1(right);
|
|
767
|
+
return !(0, node_util.isDeepStrictEqual)(normalizedLeft, normalizedRight);
|
|
827
768
|
}
|
|
828
769
|
//#endregion
|
|
829
770
|
//#region src/plugins/po/merge.ts
|
|
@@ -920,74 +861,39 @@ function merge(sources, existing, obsolete, locale, generatedAt) {
|
|
|
920
861
|
}
|
|
921
862
|
//#endregion
|
|
922
863
|
//#region src/plugins/po/po.ts
|
|
923
|
-
const namespace = "translate";
|
|
924
864
|
function po() {
|
|
925
865
|
return {
|
|
926
866
|
name: "po",
|
|
927
867
|
setup(build) {
|
|
928
868
|
build.context.logger?.debug("po plugin initialized");
|
|
929
869
|
const collections = /* @__PURE__ */ new Map();
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
filter: /.*/,
|
|
933
|
-
namespace
|
|
934
|
-
}, async ({ entrypoint, path, data }) => {
|
|
935
|
-
if (!data || !Array.isArray(data)) return;
|
|
936
|
-
for (const locale of build.context.config.locales) {
|
|
870
|
+
build.onCollected(({ entrypoint, files, output }) => {
|
|
871
|
+
for (const { path, translations } of files) for (const locale of build.context.config.locales) {
|
|
937
872
|
const destination = build.context.config.destination({
|
|
938
873
|
entrypoint,
|
|
939
874
|
locale,
|
|
940
875
|
path
|
|
941
876
|
});
|
|
942
|
-
|
|
877
|
+
const collection = collections.get(destination);
|
|
878
|
+
if (collection) {
|
|
879
|
+
collection.translations.push(...translations);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const created = {
|
|
943
883
|
locale,
|
|
944
|
-
translations: []
|
|
945
|
-
}
|
|
946
|
-
collections.
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
884
|
+
translations: [...translations]
|
|
885
|
+
};
|
|
886
|
+
collections.set(destination, created);
|
|
887
|
+
output(destination, async () => {
|
|
888
|
+
const contents = await node_fs_promises.default.readFile(destination).catch(() => void 0);
|
|
889
|
+
const existing = contents ? gettext_parser.po.parse(contents) : void 0;
|
|
890
|
+
const out = merge([{ translations: collect(created.translations, created.locale) }], existing, build.context.config.obsolete, created.locale, build.context.generatedAt);
|
|
891
|
+
if (hasChanges(out, existing)) {
|
|
892
|
+
await node_fs_promises.default.mkdir((0, node_path.dirname)(destination), { recursive: true });
|
|
893
|
+
await node_fs_promises.default.writeFile(destination, gettext_parser.po.compile(out));
|
|
894
|
+
}
|
|
955
895
|
});
|
|
956
|
-
});
|
|
957
|
-
});
|
|
958
|
-
build.onLoad({
|
|
959
|
-
filter: /.*\.po$/,
|
|
960
|
-
namespace
|
|
961
|
-
}, async ({ entrypoint, path }) => {
|
|
962
|
-
const contents = await node_fs_promises.default.readFile(path).catch(() => void 0);
|
|
963
|
-
return {
|
|
964
|
-
entrypoint,
|
|
965
|
-
path,
|
|
966
|
-
namespace,
|
|
967
|
-
data: contents ? gettext_parser.po.parse(contents) : void 0
|
|
968
|
-
};
|
|
969
|
-
});
|
|
970
|
-
build.onProcess({
|
|
971
|
-
filter: /.*\.po$/,
|
|
972
|
-
namespace
|
|
973
|
-
}, async ({ entrypoint, path, data }) => {
|
|
974
|
-
const collected = collections.get(path);
|
|
975
|
-
if (!collected) {
|
|
976
|
-
build.context.logger?.warn({ path }, "no translations collected for this path");
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
const { locale, translations } = collected;
|
|
980
|
-
const out = merge([{ translations: collect(translations, locale) }], data, build.context.config.obsolete, locale, build.context.generatedAt);
|
|
981
|
-
if (hasChanges(out, data)) {
|
|
982
|
-
await node_fs_promises.default.mkdir((0, node_path.dirname)(path), { recursive: true });
|
|
983
|
-
await node_fs_promises.default.writeFile(path, gettext_parser.po.compile(out));
|
|
984
896
|
}
|
|
985
|
-
build.resolve({
|
|
986
|
-
entrypoint,
|
|
987
|
-
path,
|
|
988
|
-
namespace: "cleanup",
|
|
989
|
-
data: translations
|
|
990
|
-
});
|
|
991
897
|
});
|
|
992
898
|
}
|
|
993
899
|
};
|
|
@@ -1288,35 +1194,576 @@ function react() {
|
|
|
1288
1194
|
name: "react",
|
|
1289
1195
|
setup(build) {
|
|
1290
1196
|
build.context.logger?.debug("react plugin initialized");
|
|
1291
|
-
build.onProcess({
|
|
1292
|
-
|
|
1293
|
-
namespace: "source"
|
|
1294
|
-
}, ({ entrypoint, path, data }) => {
|
|
1295
|
-
const { translations, warnings } = parseSource(data, path);
|
|
1197
|
+
build.onProcess(filter, ({ path, contents, emit }) => {
|
|
1198
|
+
const { translations, warnings } = parseSource(contents, path);
|
|
1296
1199
|
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
1297
|
-
|
|
1298
|
-
entrypoint,
|
|
1299
|
-
path,
|
|
1300
|
-
namespace: "translate",
|
|
1301
|
-
data: translations
|
|
1302
|
-
});
|
|
1200
|
+
emit(translations);
|
|
1303
1201
|
});
|
|
1304
1202
|
}
|
|
1305
1203
|
};
|
|
1306
1204
|
}
|
|
1307
1205
|
//#endregion
|
|
1308
|
-
//#region
|
|
1309
|
-
var
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1206
|
+
//#region ../graph/dist/index.mjs
|
|
1207
|
+
var CycleError = class extends Error {
|
|
1208
|
+
constructor(dependency, dependent) {
|
|
1209
|
+
super(`Adding edge "${dependency}" -> "${dependent}" would create a cycle`);
|
|
1210
|
+
this.name = "CycleError";
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
var SkippedError = class extends Error {
|
|
1214
|
+
constructor(id) {
|
|
1215
|
+
super(`Node "${id}" was skipped because a dependency failed or the run was aborted`);
|
|
1216
|
+
this.name = "SkippedError";
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
var NodeState = class {
|
|
1220
|
+
id;
|
|
1221
|
+
status = "pending";
|
|
1222
|
+
result;
|
|
1223
|
+
reason;
|
|
1224
|
+
lazy = false;
|
|
1225
|
+
/**
|
|
1226
|
+
* Dependencies that have not fulfilled yet. Readiness is maintained as
|
|
1227
|
+
* edges are added and dependencies settle, so scheduling never has to
|
|
1228
|
+
* rescan the pending set.
|
|
1229
|
+
*/
|
|
1230
|
+
unresolved = 0;
|
|
1231
|
+
/** Whether the node is already sitting in the ready queue. */
|
|
1232
|
+
queued = false;
|
|
1233
|
+
/** Resumers of demanders suspended on this node, flushed on settle. */
|
|
1234
|
+
waiters;
|
|
1235
|
+
/** Ordered data dependencies, passed as run arguments. */
|
|
1236
|
+
arguments_;
|
|
1237
|
+
/** All dependencies, including ordering-only edges added via connect(). */
|
|
1238
|
+
dependencies = /* @__PURE__ */ new Set();
|
|
1239
|
+
dependents = /* @__PURE__ */ new Set();
|
|
1240
|
+
execute;
|
|
1241
|
+
constructor(id, arguments_, execute) {
|
|
1242
|
+
this.id = id;
|
|
1243
|
+
this.arguments_ = arguments_;
|
|
1244
|
+
this.execute = execute;
|
|
1245
|
+
for (const dependency of arguments_) this.dependencies.add(dependency);
|
|
1246
|
+
}
|
|
1247
|
+
get value() {
|
|
1248
|
+
if (this.status !== "fulfilled") throw new Error(`Node "${this.id}" has no value (status: ${this.status})`);
|
|
1249
|
+
return this.result;
|
|
1250
|
+
}
|
|
1251
|
+
get error() {
|
|
1252
|
+
return this.reason;
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
/**
|
|
1256
|
+
* A dynamic, asynchronous DAG of computations.
|
|
1257
|
+
*
|
|
1258
|
+
* Nodes can be added before or during a run, so a running node can discover
|
|
1259
|
+
* and schedule new work. Edges added with {@link Graph.connect} impose
|
|
1260
|
+
* ordering without contributing run arguments, which lets consumers build
|
|
1261
|
+
* barriers: a node that only starts once a dynamically growing set of
|
|
1262
|
+
* predecessors has finished.
|
|
1263
|
+
*/
|
|
1264
|
+
var Graph = class {
|
|
1265
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
1266
|
+
#root;
|
|
1267
|
+
#pending = /* @__PURE__ */ new Set();
|
|
1268
|
+
#dormant = /* @__PURE__ */ new Set();
|
|
1269
|
+
/** Pending nodes whose dependencies have all fulfilled. */
|
|
1270
|
+
#ready = [];
|
|
1271
|
+
/** Demanders that finished waiting and need a slot before resuming. */
|
|
1272
|
+
#slotWaiters = [];
|
|
1273
|
+
#runningCount = 0;
|
|
1274
|
+
#suspendedCount = 0;
|
|
1275
|
+
#concurrency = Infinity;
|
|
1276
|
+
#controller;
|
|
1277
|
+
#finish;
|
|
1278
|
+
#started = false;
|
|
1279
|
+
#finished = false;
|
|
1280
|
+
#errors = [];
|
|
1281
|
+
constructor() {
|
|
1282
|
+
INTERNALS.set(this, {
|
|
1283
|
+
kinds: /* @__PURE__ */ new Map(),
|
|
1284
|
+
workers: /* @__PURE__ */ new Map(),
|
|
1285
|
+
wrappers: /* @__PURE__ */ new Map(),
|
|
1286
|
+
frozen: false
|
|
1287
|
+
});
|
|
1288
|
+
this.#root = new Scope(this, void 0, "");
|
|
1289
|
+
}
|
|
1290
|
+
/** The root scope. Nodes added here belong to no named scope. */
|
|
1291
|
+
get root() {
|
|
1292
|
+
return this.#root;
|
|
1293
|
+
}
|
|
1294
|
+
/** Creates or returns a named child scope of the root scope. */
|
|
1295
|
+
scope(name) {
|
|
1296
|
+
return this.#root.scope(name);
|
|
1297
|
+
}
|
|
1298
|
+
/** Declares a typed node kind. Kind names are unique per graph. */
|
|
1299
|
+
kind(name) {
|
|
1300
|
+
const internals = INTERNALS.get(this);
|
|
1301
|
+
if (internals.kinds.has(name)) throw new Error(`Kind "${name}" is already registered`);
|
|
1302
|
+
const kind = { name };
|
|
1303
|
+
internals.kinds.set(name, kind);
|
|
1304
|
+
return kind;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Registers a worker that runs for every node of `source`, in the same
|
|
1308
|
+
* scope as that node. Worker results form their own kind, so they can be
|
|
1309
|
+
* awaited and collected with scope.completion(). Part of the graph's
|
|
1310
|
+
* definition: allowed until run() starts, and applies to nodes added
|
|
1311
|
+
* before the registration as well.
|
|
1312
|
+
*/
|
|
1313
|
+
each(source, name, worker) {
|
|
1314
|
+
const internals = INTERNALS.get(this);
|
|
1315
|
+
if (internals.frozen) throw new Error(`Cannot register worker "${name}" after run() started`);
|
|
1316
|
+
const kind = this.kind(name);
|
|
1317
|
+
const registrations = internals.workers.get(source.name) ?? [];
|
|
1318
|
+
registrations.push({
|
|
1319
|
+
kind,
|
|
1320
|
+
worker
|
|
1321
|
+
});
|
|
1322
|
+
internals.workers.set(source.name, registrations);
|
|
1323
|
+
return kind;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Interposes on every node of a kind: consumers of the node receive the
|
|
1327
|
+
* outermost wrapper's value instead of the raw computation. The chain is
|
|
1328
|
+
* materialized as real intermediate nodes ("<id>@raw", "<id>@<name>"),
|
|
1329
|
+
* evaluated on demand: a wrapper that never calls next() (e.g. a cache
|
|
1330
|
+
* hit) keeps the inner nodes from running at all. Wrappers compose; the
|
|
1331
|
+
* last registered one is outermost. Part of the graph's definition:
|
|
1332
|
+
* allowed until run() starts.
|
|
1333
|
+
*/
|
|
1334
|
+
wrap(kind, name, wrapper) {
|
|
1335
|
+
const internals = INTERNALS.get(this);
|
|
1336
|
+
if (internals.frozen) throw new Error(`Cannot register wrapper "${name}" after run() started`);
|
|
1337
|
+
const registrations = internals.wrappers.get(kind.name) ?? [];
|
|
1338
|
+
registrations.push({
|
|
1339
|
+
name,
|
|
1340
|
+
wrap: wrapper
|
|
1341
|
+
});
|
|
1342
|
+
internals.wrappers.set(kind.name, registrations);
|
|
1343
|
+
}
|
|
1344
|
+
/** Whether `node` transitively depends on `dependency`. */
|
|
1345
|
+
dependsOn(node, dependency) {
|
|
1346
|
+
const target = this.#resolve(dependency);
|
|
1347
|
+
const stack = [...this.#resolve(node).dependencies];
|
|
1348
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1349
|
+
while (stack.length > 0) {
|
|
1350
|
+
const current = stack.pop();
|
|
1351
|
+
if (current === target) return true;
|
|
1352
|
+
if (seen.has(current)) continue;
|
|
1353
|
+
seen.add(current);
|
|
1354
|
+
stack.push(...current.dependencies);
|
|
1355
|
+
}
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
add(id, options) {
|
|
1359
|
+
if (this.#finished) throw new Error(`Cannot add node "${id}": the graph has already finished`);
|
|
1360
|
+
if (this.#nodes.has(id)) throw new Error(`Node "${id}" already exists`);
|
|
1361
|
+
const dependencies = (options.dependencies ?? []).map((dependency) => this.#resolve(dependency));
|
|
1362
|
+
const node = new NodeState(id, dependencies, options.run);
|
|
1363
|
+
node.lazy = options.lazy ?? false;
|
|
1364
|
+
let doomed;
|
|
1365
|
+
for (const dependency of dependencies) {
|
|
1366
|
+
dependency.dependents.add(node);
|
|
1367
|
+
if (dependency.status === "rejected" || dependency.status === "skipped") doomed ??= dependency;
|
|
1368
|
+
else if (dependency.status !== "fulfilled") node.unresolved += 1;
|
|
1369
|
+
}
|
|
1370
|
+
this.#nodes.set(id, node);
|
|
1371
|
+
if (node.lazy) this.#dormant.add(node);
|
|
1372
|
+
else {
|
|
1373
|
+
this.#pending.add(node);
|
|
1374
|
+
for (const dependency of dependencies) this.#wake(dependency);
|
|
1375
|
+
}
|
|
1376
|
+
if (doomed) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1377
|
+
else if (!node.lazy) this.#enqueue(node);
|
|
1378
|
+
if (this.#started) queueMicrotask(() => this.#pump());
|
|
1379
|
+
return node;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Adds an ordering-only edge: `dependent` will not start until
|
|
1383
|
+
* `dependency` has fulfilled. The dependency's value is not passed to the
|
|
1384
|
+
* dependent's run function; read it from the node handle if needed.
|
|
1385
|
+
*/
|
|
1386
|
+
connect(dependency, dependent) {
|
|
1387
|
+
const from = this.#resolve(dependency);
|
|
1388
|
+
const to = this.#resolve(dependent);
|
|
1389
|
+
if (to.status !== "pending") throw new Error(`Cannot add dependency to node "${to.id}": it has already started`);
|
|
1390
|
+
if (from === to || this.#reaches(to, from)) throw new CycleError(from.id, to.id);
|
|
1391
|
+
if (to.dependencies.has(from)) return;
|
|
1392
|
+
to.dependencies.add(from);
|
|
1393
|
+
from.dependents.add(to);
|
|
1394
|
+
if (from.status === "rejected" || from.status === "skipped") {
|
|
1395
|
+
this.#settle(to, "skipped", void 0, new SkippedError(to.id));
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (from.status !== "fulfilled") to.unresolved += 1;
|
|
1399
|
+
if (!this.#dormant.has(to)) this.#wake(from);
|
|
1400
|
+
if (this.#started) queueMicrotask(() => this.#pump());
|
|
1401
|
+
}
|
|
1402
|
+
get(id) {
|
|
1403
|
+
return this.#nodes.get(id);
|
|
1404
|
+
}
|
|
1405
|
+
get nodes() {
|
|
1406
|
+
return this.#nodes;
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Runs the graph until every node has settled, including nodes added
|
|
1410
|
+
* while running. Rejects with an AggregateError if any node rejected, or
|
|
1411
|
+
* with the abort reason if the signal aborted. Can only be called once.
|
|
1412
|
+
*/
|
|
1413
|
+
async run(options = {}) {
|
|
1414
|
+
if (this.#started) throw new Error("Graph is already running or has finished");
|
|
1415
|
+
Scope.materialize(this.#root);
|
|
1416
|
+
INTERNALS.get(this).frozen = true;
|
|
1417
|
+
this.#started = true;
|
|
1418
|
+
this.#concurrency = options.concurrency ?? Infinity;
|
|
1419
|
+
if (this.#concurrency < 1) throw new Error("concurrency must be at least 1");
|
|
1420
|
+
this.#controller = new AbortController();
|
|
1421
|
+
const signal = options.signal;
|
|
1422
|
+
if (signal) if (signal.aborted) this.#controller.abort(signal.reason);
|
|
1423
|
+
else signal.addEventListener("abort", () => {
|
|
1424
|
+
this.#controller?.abort(signal.reason);
|
|
1425
|
+
this.#pump();
|
|
1426
|
+
});
|
|
1427
|
+
return new Promise((resolve, reject) => {
|
|
1428
|
+
this.#finish = {
|
|
1429
|
+
resolve,
|
|
1430
|
+
reject
|
|
1431
|
+
};
|
|
1432
|
+
this.#pump();
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
#resolve(handle) {
|
|
1436
|
+
const node = this.#nodes.get(handle.id);
|
|
1437
|
+
if (!node || node !== handle) throw new Error(`Node "${handle.id}" does not belong to this graph`);
|
|
1438
|
+
return node;
|
|
1439
|
+
}
|
|
1440
|
+
#reaches(from, target) {
|
|
1441
|
+
const stack = [...from.dependents];
|
|
1442
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1443
|
+
while (stack.length > 0) {
|
|
1444
|
+
const node = stack.pop();
|
|
1445
|
+
if (node === target) return true;
|
|
1446
|
+
if (seen.has(node)) continue;
|
|
1447
|
+
seen.add(node);
|
|
1448
|
+
stack.push(...node.dependents);
|
|
1449
|
+
}
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
/** Offers a pending node to the scheduler once its dependencies are in. */
|
|
1453
|
+
#enqueue(node) {
|
|
1454
|
+
if (node.queued || node.unresolved > 0 || node.status !== "pending" || this.#dormant.has(node)) return;
|
|
1455
|
+
node.queued = true;
|
|
1456
|
+
this.#ready.push(node);
|
|
1457
|
+
}
|
|
1458
|
+
#pump() {
|
|
1459
|
+
if (!this.#started || this.#finished || !this.#finish) return;
|
|
1460
|
+
if (this.#controller?.signal.aborted) {
|
|
1461
|
+
while (this.#slotWaiters.length > 0) {
|
|
1462
|
+
this.#runningCount += 1;
|
|
1463
|
+
this.#slotWaiters.shift()();
|
|
1464
|
+
}
|
|
1465
|
+
for (const node of [...this.#pending]) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1466
|
+
} else {
|
|
1467
|
+
while (this.#slotWaiters.length > 0 && this.#runningCount < this.#concurrency) {
|
|
1468
|
+
this.#runningCount += 1;
|
|
1469
|
+
this.#slotWaiters.shift()();
|
|
1470
|
+
}
|
|
1471
|
+
while (this.#ready.length > 0 && this.#runningCount < this.#concurrency) {
|
|
1472
|
+
const node = this.#ready.shift();
|
|
1473
|
+
node.queued = false;
|
|
1474
|
+
if (node.unresolved === 0 && node.status === "pending") this.#start(node);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (this.#pending.size === 0 && this.#runningCount === 0 && this.#suspendedCount === 0) this.#complete();
|
|
1478
|
+
}
|
|
1479
|
+
/** Moves a dormant lazy node (and its dormant dependencies) into scheduling. */
|
|
1480
|
+
#wake(node) {
|
|
1481
|
+
const stack = [node];
|
|
1482
|
+
while (stack.length > 0) {
|
|
1483
|
+
const current = stack.pop();
|
|
1484
|
+
if (!this.#dormant.delete(current)) continue;
|
|
1485
|
+
this.#pending.add(current);
|
|
1486
|
+
this.#enqueue(current);
|
|
1487
|
+
stack.push(...current.dependencies);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Takes a concurrency slot, waiting for one when the graph is at its
|
|
1492
|
+
* limit. The slot is reserved by #pump at hand-off, so a granted slot
|
|
1493
|
+
* cannot be taken by anything else in the meantime.
|
|
1494
|
+
*/
|
|
1495
|
+
#acquire() {
|
|
1496
|
+
if (this.#runningCount < this.#concurrency) {
|
|
1497
|
+
this.#runningCount += 1;
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
return new Promise((resume) => {
|
|
1501
|
+
this.#slotWaiters.push(resume);
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
async #demand(demander, handle) {
|
|
1505
|
+
if (demander.status !== "running") throw new Error(`Node "${demander.id}" called demand() while it is "${demander.status}": a node context is only usable for as long as its node runs`);
|
|
1506
|
+
const target = this.#resolve(handle);
|
|
1507
|
+
if (target === demander || this.#reaches(demander, target)) throw new CycleError(target.id, demander.id);
|
|
1508
|
+
if (!demander.dependencies.has(target)) {
|
|
1509
|
+
demander.dependencies.add(target);
|
|
1510
|
+
target.dependents.add(demander);
|
|
1511
|
+
}
|
|
1512
|
+
this.#wake(target);
|
|
1513
|
+
if (target.status === "pending" || target.status === "running") this.#pump();
|
|
1514
|
+
if (target.status === "pending" || target.status === "running") {
|
|
1515
|
+
this.#runningCount -= 1;
|
|
1516
|
+
this.#suspendedCount += 1;
|
|
1517
|
+
this.#pump();
|
|
1518
|
+
await new Promise((resume) => {
|
|
1519
|
+
(target.waiters ??= []).push(resume);
|
|
1520
|
+
});
|
|
1521
|
+
await this.#acquire();
|
|
1522
|
+
this.#suspendedCount -= 1;
|
|
1523
|
+
}
|
|
1524
|
+
if (target.status === "fulfilled") return target.result;
|
|
1525
|
+
if (target.status === "rejected") throw target.reason;
|
|
1526
|
+
throw new SkippedError(target.id);
|
|
1527
|
+
}
|
|
1528
|
+
#start(node) {
|
|
1529
|
+
this.#pending.delete(node);
|
|
1530
|
+
node.status = "running";
|
|
1531
|
+
this.#runningCount += 1;
|
|
1532
|
+
const context = {
|
|
1533
|
+
id: node.id,
|
|
1534
|
+
signal: this.#controller?.signal,
|
|
1535
|
+
demand: (handle) => this.#demand(node, handle)
|
|
1536
|
+
};
|
|
1537
|
+
const values = node.arguments_.map((dependency) => dependency.value);
|
|
1538
|
+
Promise.resolve().then(() => node.execute(context, ...values)).then((value) => {
|
|
1539
|
+
this.#runningCount -= 1;
|
|
1540
|
+
this.#settle(node, "fulfilled", value, void 0);
|
|
1541
|
+
this.#pump();
|
|
1542
|
+
}, (reason) => {
|
|
1543
|
+
this.#runningCount -= 1;
|
|
1544
|
+
this.#errors.push(reason);
|
|
1545
|
+
this.#settle(node, "rejected", void 0, reason);
|
|
1546
|
+
this.#pump();
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Settles a node and propagates the consequences to its dependents: a
|
|
1551
|
+
* fulfilled node makes them one dependency readier, a failed or skipped
|
|
1552
|
+
* one skips them in turn. Iterative, so a long chain of skips cannot
|
|
1553
|
+
* overflow the stack.
|
|
1554
|
+
*/
|
|
1555
|
+
#settle(node, status, value, reason) {
|
|
1556
|
+
const stack = [{
|
|
1557
|
+
node,
|
|
1558
|
+
status,
|
|
1559
|
+
value,
|
|
1560
|
+
reason
|
|
1561
|
+
}];
|
|
1562
|
+
while (stack.length > 0) {
|
|
1563
|
+
const current = stack.pop();
|
|
1564
|
+
const settled = current.node;
|
|
1565
|
+
if (settled.status !== "pending" && settled.status !== "running") continue;
|
|
1566
|
+
this.#pending.delete(settled);
|
|
1567
|
+
this.#dormant.delete(settled);
|
|
1568
|
+
settled.status = current.status;
|
|
1569
|
+
settled.result = current.value;
|
|
1570
|
+
settled.reason = current.reason;
|
|
1571
|
+
const waiters = settled.waiters;
|
|
1572
|
+
settled.waiters = void 0;
|
|
1573
|
+
waiters?.forEach((resume) => resume());
|
|
1574
|
+
const doomed = current.status === "rejected" || current.status === "skipped";
|
|
1575
|
+
for (const dependent of settled.dependents) {
|
|
1576
|
+
if (doomed) {
|
|
1577
|
+
if (dependent.status === "pending") stack.push({
|
|
1578
|
+
node: dependent,
|
|
1579
|
+
status: "skipped",
|
|
1580
|
+
value: void 0,
|
|
1581
|
+
reason: new SkippedError(dependent.id)
|
|
1582
|
+
});
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (dependent.unresolved > 0) {
|
|
1586
|
+
dependent.unresolved -= 1;
|
|
1587
|
+
this.#enqueue(dependent);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
#complete() {
|
|
1593
|
+
const finish = this.#finish;
|
|
1594
|
+
if (!finish) return;
|
|
1595
|
+
for (const node of [...this.#dormant]) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1596
|
+
this.#finished = true;
|
|
1597
|
+
this.#finish = void 0;
|
|
1598
|
+
if (this.#controller?.signal.aborted) finish.reject(this.#controller.signal.reason);
|
|
1599
|
+
else if (this.#errors.length > 0) finish.reject(new AggregateError(this.#errors, "One or more graph nodes failed"));
|
|
1600
|
+
else finish.resolve();
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
const INTERNALS = /* @__PURE__ */ new WeakMap();
|
|
1604
|
+
function normalize(spec) {
|
|
1605
|
+
return typeof spec === "function" ? { run: spec } : spec;
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* A hierarchical namespace of kinded nodes. Scopes make phase completion
|
|
1609
|
+
* automatic: scope.completion(kind) fulfills once every node in the scope
|
|
1610
|
+
* (and its descendants) has settled — including nodes that are created
|
|
1611
|
+
* dynamically while the graph runs — without the producers having to wire
|
|
1612
|
+
* any edges themselves.
|
|
1613
|
+
*/
|
|
1614
|
+
var Scope = class Scope {
|
|
1615
|
+
name;
|
|
1616
|
+
path;
|
|
1617
|
+
#graph;
|
|
1618
|
+
#parent;
|
|
1619
|
+
#children = /* @__PURE__ */ new Map();
|
|
1620
|
+
#entries = /* @__PURE__ */ new Map();
|
|
1621
|
+
/** Nodes this scope's completions must wait for (kinded nodes + workers). */
|
|
1622
|
+
#members = [];
|
|
1623
|
+
#completions = /* @__PURE__ */ new Map();
|
|
1624
|
+
/** @internal Use graph.scope() or scope.scope() instead. */
|
|
1625
|
+
constructor(graph, parent, name) {
|
|
1626
|
+
this.#graph = graph;
|
|
1627
|
+
this.#parent = parent;
|
|
1628
|
+
this.name = name;
|
|
1629
|
+
this.path = parent && parent.path ? `${parent.path}/${name}` : name;
|
|
1630
|
+
}
|
|
1631
|
+
/** Creates or returns a named child scope. */
|
|
1632
|
+
scope(name) {
|
|
1633
|
+
let child = this.#children.get(name);
|
|
1634
|
+
if (!child) {
|
|
1635
|
+
child = new Scope(this.#graph, this, name);
|
|
1636
|
+
this.#children.set(name, child);
|
|
1637
|
+
}
|
|
1638
|
+
return child;
|
|
1639
|
+
}
|
|
1640
|
+
get(kind, key) {
|
|
1641
|
+
return this.#entries.get(kind.name)?.get(key);
|
|
1642
|
+
}
|
|
1643
|
+
add(kind, key, spec) {
|
|
1644
|
+
if (this.get(kind, key)) throw new Error(`Node "${kind.name}:${key}" already exists in scope "${this.path || "(root)"}"`);
|
|
1645
|
+
return this.#create(kind, key, normalize(spec));
|
|
1646
|
+
}
|
|
1647
|
+
/** Like add(), but returns the existing node if the key is already present. */
|
|
1648
|
+
ensure(kind, key, spec) {
|
|
1649
|
+
return this.get(kind, key) ?? this.#create(kind, key, normalize(spec));
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* A barrier node that fulfills once this scope is quiescent: every node
|
|
1653
|
+
* in the scope and its descendants has settled, except nodes that
|
|
1654
|
+
* transitively depend on the barrier itself (its consumers). Its value is
|
|
1655
|
+
* the collected values of all `kind` nodes in the scope subtree. After it
|
|
1656
|
+
* fires the scope is sealed: adding non-consumer nodes throws.
|
|
1657
|
+
*/
|
|
1658
|
+
completion(kind) {
|
|
1659
|
+
const existing = this.#completions.get(kind.name);
|
|
1660
|
+
if (existing) return existing;
|
|
1661
|
+
const completion = this.#graph.add(`${this.#prefix()}${kind.name}:$completion`, { run: () => this.#collect(kind) });
|
|
1662
|
+
this.#completions.set(kind.name, completion);
|
|
1663
|
+
for (const member of this.#allMembers()) this.#attach(member, completion);
|
|
1664
|
+
return completion;
|
|
1665
|
+
}
|
|
1666
|
+
#prefix() {
|
|
1667
|
+
return this.path ? `${this.path}/` : "";
|
|
1668
|
+
}
|
|
1669
|
+
#create(kind, key, options) {
|
|
1670
|
+
const internals = INTERNALS.get(this.#graph);
|
|
1671
|
+
const dependencies = options.dependencies ?? [];
|
|
1672
|
+
this.#assertOpen(`${kind.name}:${key}`, dependencies);
|
|
1673
|
+
const scope = this;
|
|
1674
|
+
const graph = this.#graph;
|
|
1675
|
+
const id = `${this.#prefix()}${kind.name}:${key}`;
|
|
1676
|
+
const node = graph.add(id, {
|
|
1677
|
+
dependencies,
|
|
1678
|
+
run: (context, ...values) => {
|
|
1679
|
+
const scoped = {
|
|
1680
|
+
id: context.id,
|
|
1681
|
+
key,
|
|
1682
|
+
signal: context.signal,
|
|
1683
|
+
scope,
|
|
1684
|
+
demand: context.demand
|
|
1685
|
+
};
|
|
1686
|
+
const compute = () => options.run(scoped, ...values);
|
|
1687
|
+
const wrappers = internals.wrappers.get(kind.name) ?? [];
|
|
1688
|
+
if (wrappers.length === 0) return compute();
|
|
1689
|
+
let inner = graph.add(`${id}@raw`, {
|
|
1690
|
+
lazy: true,
|
|
1691
|
+
run: () => compute()
|
|
1692
|
+
});
|
|
1693
|
+
for (const registration of wrappers.slice(0, -1)) {
|
|
1694
|
+
const previous = inner;
|
|
1695
|
+
inner = graph.add(`${id}@${registration.name}`, {
|
|
1696
|
+
lazy: true,
|
|
1697
|
+
run: (innerContext) => registration.wrap({
|
|
1698
|
+
...scoped,
|
|
1699
|
+
id: innerContext.id,
|
|
1700
|
+
demand: innerContext.demand
|
|
1701
|
+
}, () => {
|
|
1702
|
+
return innerContext.demand(previous);
|
|
1703
|
+
})
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
return wrappers[wrappers.length - 1].wrap(scoped, () => context.demand(inner));
|
|
1707
|
+
}
|
|
1316
1708
|
});
|
|
1709
|
+
let byKey = this.#entries.get(kind.name);
|
|
1710
|
+
if (!byKey) {
|
|
1711
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1712
|
+
this.#entries.set(kind.name, byKey);
|
|
1713
|
+
}
|
|
1714
|
+
byKey.set(key, node);
|
|
1715
|
+
this.#members.push(node);
|
|
1716
|
+
for (let ancestor = this; ancestor; ancestor = ancestor.#parent) for (const completion of ancestor.#completions.values()) this.#attach(node, completion);
|
|
1717
|
+
for (const registration of internals.workers.get(kind.name) ?? []) this.#create(registration.kind, key, {
|
|
1718
|
+
dependencies: [node],
|
|
1719
|
+
run: (context, value) => registration.worker(context, value, node)
|
|
1720
|
+
});
|
|
1721
|
+
return node;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Attaches workers registered after some nodes of their kind were
|
|
1725
|
+
* already added. Runs once when the definition phase ends (run()).
|
|
1726
|
+
*/
|
|
1727
|
+
static materialize(scope) {
|
|
1728
|
+
const internals = INTERNALS.get(scope.#graph);
|
|
1729
|
+
for (const [kindName, byKey] of scope.#entries) for (const registration of internals.workers.get(kindName) ?? []) for (const [key, node] of [...byKey]) {
|
|
1730
|
+
if (scope.#entries.get(registration.kind.name)?.has(key)) continue;
|
|
1731
|
+
scope.#create(registration.kind, key, {
|
|
1732
|
+
dependencies: [node],
|
|
1733
|
+
run: (context, value) => registration.worker(context, value, node)
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
for (const child of scope.#children.values()) Scope.materialize(child);
|
|
1317
1737
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1738
|
+
/**
|
|
1739
|
+
* A scope seals once a completion fires: new nodes are only allowed if
|
|
1740
|
+
* they are consumers of that completion (depend on it), since the
|
|
1741
|
+
* completion has already reported the scope as done.
|
|
1742
|
+
*/
|
|
1743
|
+
#assertOpen(label, dependencies) {
|
|
1744
|
+
for (let ancestor = this; ancestor; ancestor = ancestor.#parent) for (const completion of ancestor.#completions.values()) {
|
|
1745
|
+
if (completion.status === "pending") continue;
|
|
1746
|
+
if (!dependencies.some((dependency) => dependency === completion || this.#graph.dependsOn(dependency, completion))) throw new Error(`Cannot add "${label}" to scope "${ancestor.path || "(root)"}": its completion "${completion.id}" has already fired`);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
#attach(member, completion) {
|
|
1750
|
+
if (completion.status !== "pending") return;
|
|
1751
|
+
if (member === completion || this.#graph.dependsOn(member, completion)) return;
|
|
1752
|
+
this.#graph.connect(member, completion);
|
|
1753
|
+
}
|
|
1754
|
+
#allMembers() {
|
|
1755
|
+
const members = [...this.#members];
|
|
1756
|
+
for (const child of this.#children.values()) members.push(...child.#allMembers());
|
|
1757
|
+
return members;
|
|
1758
|
+
}
|
|
1759
|
+
#collect(kind) {
|
|
1760
|
+
const values = [];
|
|
1761
|
+
const byKey = this.#entries.get(kind.name);
|
|
1762
|
+
if (byKey) {
|
|
1763
|
+
for (const node of byKey.values()) if (node.status === "fulfilled") values.push(node.value);
|
|
1764
|
+
}
|
|
1765
|
+
for (const child of this.#children.values()) values.push(...child.#collect(kind));
|
|
1766
|
+
return values;
|
|
1320
1767
|
}
|
|
1321
1768
|
};
|
|
1322
1769
|
//#endregion
|
|
@@ -1338,13 +1785,19 @@ async function getPaths(entrypoint) {
|
|
|
1338
1785
|
const paths = fast_glob.default.isDynamicPattern(pattern) ? await (0, fast_glob.default)(pattern, { onlyFiles: true }) : [entrypoint.entrypoint];
|
|
1339
1786
|
return new Set(paths.map((path) => (0, node_path.resolve)(path)));
|
|
1340
1787
|
}
|
|
1788
|
+
function toRealPath(path) {
|
|
1789
|
+
const abs = (0, node_path.resolve)(path);
|
|
1790
|
+
try {
|
|
1791
|
+
return (0, node_fs.realpathSync)(abs);
|
|
1792
|
+
} catch {
|
|
1793
|
+
return abs;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1341
1796
|
async function run(entrypoint, { config, logger }) {
|
|
1342
1797
|
const destination = entrypoint.destination ?? config.destination;
|
|
1343
1798
|
const obsolete = entrypoint.obsolete ?? config.obsolete;
|
|
1344
1799
|
const exclude = entrypoint.exclude ?? config.exclude;
|
|
1345
1800
|
const walk = entrypoint.walk ?? config.walk;
|
|
1346
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1347
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
1348
1801
|
const context = {
|
|
1349
1802
|
config: {
|
|
1350
1803
|
...config,
|
|
@@ -1354,110 +1807,139 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1354
1807
|
walk
|
|
1355
1808
|
},
|
|
1356
1809
|
generatedAt: /* @__PURE__ */ new Date(),
|
|
1357
|
-
paths,
|
|
1810
|
+
paths: /* @__PURE__ */ new Set(),
|
|
1358
1811
|
logger
|
|
1359
1812
|
};
|
|
1360
1813
|
logger?.info(entrypoint, "starting extraction");
|
|
1361
|
-
const resolvers = [];
|
|
1362
1814
|
const loaders = [];
|
|
1363
1815
|
const processors = [];
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
pending.set(namespace, defer);
|
|
1376
|
-
}
|
|
1377
|
-
return defer;
|
|
1816
|
+
const collectors = [];
|
|
1817
|
+
const finalizers = [];
|
|
1818
|
+
const failures = [];
|
|
1819
|
+
const failed = /* @__PURE__ */ new Set();
|
|
1820
|
+
/** Records a failure that leaves `scope`'s translations incomplete. */
|
|
1821
|
+
function fail(scope, path, error) {
|
|
1822
|
+
failed.add(scope);
|
|
1823
|
+
record({
|
|
1824
|
+
entrypoint: scope,
|
|
1825
|
+
path
|
|
1826
|
+
}, error);
|
|
1378
1827
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1828
|
+
/** Records a failure that no longer has an entrypoint to invalidate. */
|
|
1829
|
+
function record(where, error) {
|
|
1830
|
+
failures.push(error);
|
|
1831
|
+
logger?.error({
|
|
1832
|
+
...where,
|
|
1833
|
+
error
|
|
1834
|
+
}, "extraction failed");
|
|
1381
1835
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1836
|
+
const graph = new Graph();
|
|
1837
|
+
const source = graph.kind("source");
|
|
1838
|
+
const collect = graph.kind("collect");
|
|
1839
|
+
const plan = graph.kind("plan");
|
|
1840
|
+
const output = graph.kind("output");
|
|
1841
|
+
const finalize = graph.kind("finalize");
|
|
1842
|
+
const processed = graph.each(source, "process", async (node, contents) => {
|
|
1843
|
+
if (contents === void 0) return;
|
|
1844
|
+
const path = node.key;
|
|
1845
|
+
let emitted;
|
|
1846
|
+
const args = {
|
|
1847
|
+
entrypoint: node.scope.name,
|
|
1848
|
+
path,
|
|
1849
|
+
contents,
|
|
1850
|
+
emit(translations) {
|
|
1851
|
+
emitted ??= {
|
|
1852
|
+
path,
|
|
1853
|
+
translations: []
|
|
1854
|
+
};
|
|
1855
|
+
emitted.translations.push(...translations);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1385
1858
|
try {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1859
|
+
for (const { filter, hook } of processors) {
|
|
1860
|
+
if (!filter.test(path)) continue;
|
|
1861
|
+
if (await hook(args) !== void 0) break;
|
|
1862
|
+
}
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
fail(node.scope.name, path, error);
|
|
1865
|
+
return;
|
|
1389
1866
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
resolve({
|
|
1397
|
-
entrypoint: resolvedPath,
|
|
1398
|
-
path: resolvedPath,
|
|
1399
|
-
namespace: "source"
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
|
-
function resolve(args) {
|
|
1403
|
-
const { entrypoint, path, namespace } = args;
|
|
1404
|
-
const key = `${entrypoint}:${namespace}:${path}`;
|
|
1405
|
-
const visited = resolved.has(key);
|
|
1406
|
-
const skipped = require_exclude.isExcluded(args, context.config.exclude);
|
|
1407
|
-
logger?.debug({
|
|
1408
|
-
entrypoint,
|
|
1867
|
+
return emitted;
|
|
1868
|
+
});
|
|
1869
|
+
function addSource(scope, path, importReference) {
|
|
1870
|
+
if (scope.get(source, path)) return;
|
|
1871
|
+
const args = {
|
|
1872
|
+
entrypoint: scope.name,
|
|
1409
1873
|
path,
|
|
1410
|
-
namespace,
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
queue.push({
|
|
1418
|
-
type: "resolve",
|
|
1419
|
-
args
|
|
1420
|
-
});
|
|
1421
|
-
getDeferred(namespace).enqueue();
|
|
1422
|
-
}
|
|
1423
|
-
function load(args) {
|
|
1424
|
-
const { entrypoint, path, namespace } = args;
|
|
1874
|
+
namespace: "source",
|
|
1875
|
+
import: importReference
|
|
1876
|
+
};
|
|
1877
|
+
if (require_exclude.isExcluded(args, context.config.exclude)) {
|
|
1878
|
+
logger?.debug(args, "excluded");
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1425
1881
|
logger?.debug({
|
|
1426
|
-
entrypoint,
|
|
1427
|
-
path
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1882
|
+
entrypoint: scope.name,
|
|
1883
|
+
path
|
|
1884
|
+
}, "source");
|
|
1885
|
+
scope.add(source, path, async () => {
|
|
1886
|
+
try {
|
|
1887
|
+
for (const { filter, hook } of loaders) {
|
|
1888
|
+
if (!filter.test(path)) continue;
|
|
1889
|
+
const contents = await hook({
|
|
1890
|
+
entrypoint: scope.name,
|
|
1891
|
+
path
|
|
1892
|
+
});
|
|
1893
|
+
if (contents !== void 0) return contents;
|
|
1894
|
+
}
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
fail(scope.name, path, error);
|
|
1897
|
+
}
|
|
1433
1898
|
});
|
|
1434
|
-
getDeferred(namespace).enqueue();
|
|
1435
1899
|
}
|
|
1436
|
-
function
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1900
|
+
function pipeline(path) {
|
|
1901
|
+
if (context.paths.has(path)) return;
|
|
1902
|
+
context.paths.add(path);
|
|
1903
|
+
const scope = graph.scope(path);
|
|
1904
|
+
addSource(scope, path);
|
|
1905
|
+
const collected = scope.completion(processed);
|
|
1906
|
+
collectors.forEach((hook, index) => {
|
|
1907
|
+
scope.add(collect, String(index), {
|
|
1908
|
+
dependencies: [collected],
|
|
1909
|
+
run: async (_node, files) => {
|
|
1910
|
+
if (failed.has(path)) {
|
|
1911
|
+
logger?.warn({ entrypoint: path }, "skipping outputs: entrypoint failed");
|
|
1912
|
+
return [];
|
|
1913
|
+
}
|
|
1914
|
+
const contributions = [];
|
|
1915
|
+
try {
|
|
1916
|
+
await hook({
|
|
1917
|
+
entrypoint: path,
|
|
1918
|
+
files: files.filter((file) => file !== void 0),
|
|
1919
|
+
output: (outputPath, produce) => {
|
|
1920
|
+
contributions.push({
|
|
1921
|
+
path: outputPath,
|
|
1922
|
+
produce
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
fail(path, path, error);
|
|
1928
|
+
return [];
|
|
1929
|
+
}
|
|
1930
|
+
return contributions;
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1446
1933
|
});
|
|
1447
|
-
getDeferred(namespace).enqueue();
|
|
1448
1934
|
}
|
|
1449
1935
|
const build = {
|
|
1450
1936
|
context,
|
|
1451
|
-
source,
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
resolvers.push({
|
|
1458
|
-
filter,
|
|
1459
|
-
hook
|
|
1460
|
-
});
|
|
1937
|
+
source({ entrypoint: sourceEntrypoint, path, import: importReference }) {
|
|
1938
|
+
if (sourceEntrypoint === path) {
|
|
1939
|
+
pipeline(toRealPath(path));
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
addSource(graph.scope(sourceEntrypoint), path, importReference);
|
|
1461
1943
|
},
|
|
1462
1944
|
onLoad(filter, hook) {
|
|
1463
1945
|
loaders.push({
|
|
@@ -1470,6 +1952,12 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1470
1952
|
filter,
|
|
1471
1953
|
hook
|
|
1472
1954
|
});
|
|
1955
|
+
},
|
|
1956
|
+
onCollected(hook) {
|
|
1957
|
+
collectors.push(hook);
|
|
1958
|
+
},
|
|
1959
|
+
onOutputs(hook) {
|
|
1960
|
+
finalizers.push(hook);
|
|
1473
1961
|
}
|
|
1474
1962
|
};
|
|
1475
1963
|
for (const item of config.plugins) {
|
|
@@ -1477,41 +1965,48 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1477
1965
|
logger?.debug({ plugin: plugin.name }, "setting up plugin");
|
|
1478
1966
|
plugin.setup(build);
|
|
1479
1967
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
namespace
|
|
1490
|
-
}, "processing task");
|
|
1491
|
-
for (const { filter, hook } of hooks[type]) {
|
|
1492
|
-
if (filter.namespace !== namespace) continue;
|
|
1493
|
-
if (filter.filter && !filter.filter.test(path)) continue;
|
|
1494
|
-
const result = await hook(args);
|
|
1495
|
-
if (result !== void 0) {
|
|
1496
|
-
args = result;
|
|
1497
|
-
break;
|
|
1968
|
+
const contributed = graph.root.completion(collect);
|
|
1969
|
+
graph.root.add(plan, "outputs", {
|
|
1970
|
+
dependencies: [contributed],
|
|
1971
|
+
run: (_node, collections) => {
|
|
1972
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1973
|
+
for (const contribution of collections.flat()) {
|
|
1974
|
+
const group = byPath.get(contribution.path) ?? [];
|
|
1975
|
+
group.push(contribution);
|
|
1976
|
+
byPath.set(contribution.path, group);
|
|
1498
1977
|
}
|
|
1978
|
+
for (const [outputPath, contributions] of byPath) graph.root.add(output, outputPath, {
|
|
1979
|
+
dependencies: [contributed],
|
|
1980
|
+
run: async () => {
|
|
1981
|
+
try {
|
|
1982
|
+
for (const { produce } of contributions) await produce();
|
|
1983
|
+
} catch (error) {
|
|
1984
|
+
record({ output: outputPath }, error);
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
return outputPath;
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1499
1990
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1991
|
+
});
|
|
1992
|
+
const outputs = graph.root.completion(output);
|
|
1993
|
+
finalizers.forEach((hook, index) => {
|
|
1994
|
+
graph.root.add(finalize, String(index), {
|
|
1995
|
+
dependencies: [outputs],
|
|
1996
|
+
run: async (_node, produced) => {
|
|
1997
|
+
try {
|
|
1998
|
+
await hook({ outputs: produced.filter((path) => path !== void 0) });
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
record({ finalizer: String(index) }, error);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
});
|
|
2005
|
+
for (const path of await getPaths(entrypoint)) pipeline(toRealPath(path));
|
|
2006
|
+
await graph.run().catch((error) => {
|
|
2007
|
+
record({ graph: "run" }, error);
|
|
2008
|
+
});
|
|
2009
|
+
if (failures.length > 0) throw new AggregateError(failures, `Extraction of "${entrypoint.entrypoint}" failed`);
|
|
1515
2010
|
logger?.info(entrypoint, "extraction completed");
|
|
1516
2011
|
}
|
|
1517
2012
|
//#endregion
|