@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
|
@@ -12,52 +12,25 @@ import { getFormula, getNPlurals } from "plural-forms";
|
|
|
12
12
|
import { isDeepStrictEqual } from "node:util";
|
|
13
13
|
import glob from "fast-glob";
|
|
14
14
|
//#region src/plugins/cleanup/cleanup.ts
|
|
15
|
-
const namespace$2 = "cleanup";
|
|
16
15
|
function cleanup() {
|
|
17
16
|
return {
|
|
18
17
|
name: "cleanup",
|
|
19
18
|
setup(build) {
|
|
20
19
|
build.context.logger?.debug("cleanup plugin initialized");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
]).then(() => {
|
|
36
|
-
if (dispatched) return;
|
|
37
|
-
dispatched = true;
|
|
38
|
-
for (const path of dirs.values()) build.process({
|
|
39
|
-
entrypoint: path,
|
|
40
|
-
path,
|
|
41
|
-
namespace: namespace$2,
|
|
42
|
-
data: void 0
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
build.onProcess({
|
|
47
|
-
namespace: namespace$2,
|
|
48
|
-
filter: /.*/
|
|
49
|
-
}, async ({ path }) => {
|
|
50
|
-
if (processed.has(path)) return;
|
|
51
|
-
processed.add(path);
|
|
52
|
-
const files = await fs.readdir(path).catch(() => []);
|
|
53
|
-
for (const f of files.filter((p) => p.endsWith(".po"))) {
|
|
54
|
-
const full = join(path, f);
|
|
55
|
-
const contents = await fs.readFile(full).catch(() => void 0);
|
|
56
|
-
if (!contents) continue;
|
|
57
|
-
const parsed = gettextParser.po.parse(contents);
|
|
58
|
-
const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
|
|
59
|
-
if (!hasTranslations && generated.has(full)) await fs.unlink(full);
|
|
60
|
-
if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
|
|
20
|
+
build.onOutputs(async ({ outputs }) => {
|
|
21
|
+
const generated = new Set(outputs);
|
|
22
|
+
const dirs = new Set(outputs.map((path) => dirname(path)));
|
|
23
|
+
for (const dir of dirs) {
|
|
24
|
+
const files = await fs.readdir(dir).catch(() => []);
|
|
25
|
+
for (const file of files.filter((name) => name.endsWith(".po"))) {
|
|
26
|
+
const full = join(dir, file);
|
|
27
|
+
const contents = await fs.readFile(full).catch(() => void 0);
|
|
28
|
+
if (!contents) continue;
|
|
29
|
+
const parsed = gettextParser.po.parse(contents);
|
|
30
|
+
const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
|
|
31
|
+
if (!hasTranslations && generated.has(full)) await fs.unlink(full);
|
|
32
|
+
if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
|
|
33
|
+
}
|
|
61
34
|
}
|
|
62
35
|
});
|
|
63
36
|
}
|
|
@@ -258,7 +231,7 @@ const messageQuery$1 = notInPlural(withComment({
|
|
|
258
231
|
pattern: callPattern("message", messageArgs),
|
|
259
232
|
extract: extractMessage("message")
|
|
260
233
|
}));
|
|
261
|
-
const allowed$1 = new Set([
|
|
234
|
+
const allowed$1 = /* @__PURE__ */ new Set([
|
|
262
235
|
"string",
|
|
263
236
|
"object",
|
|
264
237
|
"template_string"
|
|
@@ -401,7 +374,7 @@ const gettextQuery = withComment({
|
|
|
401
374
|
pattern: callPattern("gettext", messageArgs),
|
|
402
375
|
extract: extractMessage("gettext")
|
|
403
376
|
});
|
|
404
|
-
const allowed = new Set([
|
|
377
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
405
378
|
"string",
|
|
406
379
|
"object",
|
|
407
380
|
"template_string",
|
|
@@ -573,7 +546,7 @@ function findTsconfig(dir) {
|
|
|
573
546
|
}
|
|
574
547
|
}
|
|
575
548
|
const resolverCache = /* @__PURE__ */ new Map();
|
|
576
|
-
const builtins = new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)]);
|
|
549
|
+
const builtins = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)]);
|
|
577
550
|
function isBuiltin(spec) {
|
|
578
551
|
if (builtins.has(spec)) return true;
|
|
579
552
|
const [base, subpath] = (spec.startsWith("node:") ? spec.slice(5) : spec).split("/", 2);
|
|
@@ -650,58 +623,29 @@ function resolveImportResults(file, imports) {
|
|
|
650
623
|
//#endregion
|
|
651
624
|
//#region src/plugins/core/core.ts
|
|
652
625
|
const filter$1 = /\.([cm]?tsx?|jsx?)$/;
|
|
653
|
-
const namespace$1 = "source";
|
|
654
626
|
function core() {
|
|
655
627
|
return {
|
|
656
628
|
name: "core",
|
|
657
629
|
setup(build) {
|
|
658
630
|
build.context.logger?.debug("core plugin initialized");
|
|
659
|
-
build.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}, ({ entrypoint, path, import: imp, data }) => {
|
|
663
|
-
return {
|
|
664
|
-
entrypoint,
|
|
665
|
-
namespace: namespace$1,
|
|
666
|
-
path: resolve(path),
|
|
667
|
-
import: imp,
|
|
668
|
-
data
|
|
669
|
-
};
|
|
670
|
-
});
|
|
671
|
-
build.onLoad({
|
|
672
|
-
filter: filter$1,
|
|
673
|
-
namespace: namespace$1
|
|
674
|
-
}, async ({ entrypoint, path }) => {
|
|
675
|
-
return {
|
|
676
|
-
entrypoint,
|
|
677
|
-
path,
|
|
678
|
-
namespace: namespace$1,
|
|
679
|
-
data: await readFile(path, "utf8")
|
|
680
|
-
};
|
|
681
|
-
});
|
|
682
|
-
build.onProcess({
|
|
683
|
-
filter: filter$1,
|
|
684
|
-
namespace: namespace$1
|
|
685
|
-
}, ({ entrypoint, path, data }) => {
|
|
686
|
-
const result = parseSource$1(data, path);
|
|
631
|
+
build.onLoad(filter$1, ({ path }) => readFile(path, "utf8"));
|
|
632
|
+
build.onProcess(filter$1, ({ entrypoint, path, contents, emit }) => {
|
|
633
|
+
const result = parseSource$1(contents, path);
|
|
687
634
|
if (result.entrypoint && entrypoint !== path) {
|
|
688
|
-
build.source(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
data: []
|
|
694
|
-
};
|
|
635
|
+
build.source({
|
|
636
|
+
entrypoint: path,
|
|
637
|
+
path
|
|
638
|
+
});
|
|
639
|
+
return true;
|
|
695
640
|
}
|
|
696
641
|
const { translations, imports, warnings } = result;
|
|
697
642
|
if (build.context.config.walk) {
|
|
698
643
|
const { resolved, unresolved } = resolveImportResults(path, imports);
|
|
699
644
|
for (const result of resolved) {
|
|
700
645
|
if (build.context.paths.has(result.path)) continue;
|
|
701
|
-
build.
|
|
646
|
+
build.source({
|
|
702
647
|
entrypoint,
|
|
703
648
|
path: result.path,
|
|
704
|
-
namespace: namespace$1,
|
|
705
649
|
import: result.import
|
|
706
650
|
});
|
|
707
651
|
}
|
|
@@ -710,19 +654,14 @@ function core() {
|
|
|
710
654
|
if (imp && isExcluded({
|
|
711
655
|
entrypoint,
|
|
712
656
|
path: spec,
|
|
713
|
-
namespace:
|
|
657
|
+
namespace: "source",
|
|
714
658
|
import: imp
|
|
715
659
|
}, build.context.config.exclude)) continue;
|
|
716
660
|
build.context.logger?.warn(`Unable to resolve import "${spec}" from ${path}${error ? `: ${error}` : ""}`);
|
|
717
661
|
}
|
|
718
662
|
}
|
|
719
663
|
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
720
|
-
|
|
721
|
-
entrypoint,
|
|
722
|
-
path,
|
|
723
|
-
namespace: "translate",
|
|
724
|
-
data: translations
|
|
725
|
-
});
|
|
664
|
+
emit(translations);
|
|
726
665
|
});
|
|
727
666
|
}
|
|
728
667
|
};
|
|
@@ -763,7 +702,7 @@ function collect(source, locale) {
|
|
|
763
702
|
}
|
|
764
703
|
//#endregion
|
|
765
704
|
//#region src/plugins/po/hasChanges.ts
|
|
766
|
-
const IGNORED_HEADER_KEYS = new Set(["pot-creation-date", "po-revision-date"]);
|
|
705
|
+
const IGNORED_HEADER_KEYS = /* @__PURE__ */ new Set(["pot-creation-date", "po-revision-date"]);
|
|
767
706
|
const IGNORED_HEADER_LINE_PREFIXES = ["pot-creation-date:", "po-revision-date:"];
|
|
768
707
|
function normalizeHeaderString(value) {
|
|
769
708
|
const lines = value.split("\n");
|
|
@@ -779,7 +718,7 @@ function normalizeHeaderString(value) {
|
|
|
779
718
|
if (hadTrailingNewline && filtered[filtered.length - 1] !== "") filtered.push("");
|
|
780
719
|
return filtered.join("\n");
|
|
781
720
|
}
|
|
782
|
-
function normalize(translations) {
|
|
721
|
+
function normalize$1(translations) {
|
|
783
722
|
const compiled = gettextParser.po.compile(translations);
|
|
784
723
|
const parsed = gettextParser.po.parse(compiled);
|
|
785
724
|
if (parsed.headers) {
|
|
@@ -793,7 +732,9 @@ function normalize(translations) {
|
|
|
793
732
|
}
|
|
794
733
|
function hasChanges(left, right) {
|
|
795
734
|
if (!right) return true;
|
|
796
|
-
|
|
735
|
+
const normalizedLeft = normalize$1(left);
|
|
736
|
+
const normalizedRight = normalize$1(right);
|
|
737
|
+
return !isDeepStrictEqual(normalizedLeft, normalizedRight);
|
|
797
738
|
}
|
|
798
739
|
//#endregion
|
|
799
740
|
//#region src/plugins/po/merge.ts
|
|
@@ -890,74 +831,39 @@ function merge(sources, existing, obsolete, locale, generatedAt) {
|
|
|
890
831
|
}
|
|
891
832
|
//#endregion
|
|
892
833
|
//#region src/plugins/po/po.ts
|
|
893
|
-
const namespace = "translate";
|
|
894
834
|
function po() {
|
|
895
835
|
return {
|
|
896
836
|
name: "po",
|
|
897
837
|
setup(build) {
|
|
898
838
|
build.context.logger?.debug("po plugin initialized");
|
|
899
839
|
const collections = /* @__PURE__ */ new Map();
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
filter: /.*/,
|
|
903
|
-
namespace
|
|
904
|
-
}, async ({ entrypoint, path, data }) => {
|
|
905
|
-
if (!data || !Array.isArray(data)) return;
|
|
906
|
-
for (const locale of build.context.config.locales) {
|
|
840
|
+
build.onCollected(({ entrypoint, files, output }) => {
|
|
841
|
+
for (const { path, translations } of files) for (const locale of build.context.config.locales) {
|
|
907
842
|
const destination = build.context.config.destination({
|
|
908
843
|
entrypoint,
|
|
909
844
|
locale,
|
|
910
845
|
path
|
|
911
846
|
});
|
|
912
|
-
|
|
847
|
+
const collection = collections.get(destination);
|
|
848
|
+
if (collection) {
|
|
849
|
+
collection.translations.push(...translations);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const created = {
|
|
913
853
|
locale,
|
|
914
|
-
translations: []
|
|
915
|
-
}
|
|
916
|
-
collections.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
854
|
+
translations: [...translations]
|
|
855
|
+
};
|
|
856
|
+
collections.set(destination, created);
|
|
857
|
+
output(destination, async () => {
|
|
858
|
+
const contents = await fs.readFile(destination).catch(() => void 0);
|
|
859
|
+
const existing = contents ? gettextParser.po.parse(contents) : void 0;
|
|
860
|
+
const out = merge([{ translations: collect(created.translations, created.locale) }], existing, build.context.config.obsolete, created.locale, build.context.generatedAt);
|
|
861
|
+
if (hasChanges(out, existing)) {
|
|
862
|
+
await fs.mkdir(dirname(destination), { recursive: true });
|
|
863
|
+
await fs.writeFile(destination, gettextParser.po.compile(out));
|
|
864
|
+
}
|
|
925
865
|
});
|
|
926
|
-
});
|
|
927
|
-
});
|
|
928
|
-
build.onLoad({
|
|
929
|
-
filter: /.*\.po$/,
|
|
930
|
-
namespace
|
|
931
|
-
}, async ({ entrypoint, path }) => {
|
|
932
|
-
const contents = await fs.readFile(path).catch(() => void 0);
|
|
933
|
-
return {
|
|
934
|
-
entrypoint,
|
|
935
|
-
path,
|
|
936
|
-
namespace,
|
|
937
|
-
data: contents ? gettextParser.po.parse(contents) : void 0
|
|
938
|
-
};
|
|
939
|
-
});
|
|
940
|
-
build.onProcess({
|
|
941
|
-
filter: /.*\.po$/,
|
|
942
|
-
namespace
|
|
943
|
-
}, async ({ entrypoint, path, data }) => {
|
|
944
|
-
const collected = collections.get(path);
|
|
945
|
-
if (!collected) {
|
|
946
|
-
build.context.logger?.warn({ path }, "no translations collected for this path");
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
const { locale, translations } = collected;
|
|
950
|
-
const out = merge([{ translations: collect(translations, locale) }], data, build.context.config.obsolete, locale, build.context.generatedAt);
|
|
951
|
-
if (hasChanges(out, data)) {
|
|
952
|
-
await fs.mkdir(dirname(path), { recursive: true });
|
|
953
|
-
await fs.writeFile(path, gettextParser.po.compile(out));
|
|
954
866
|
}
|
|
955
|
-
build.resolve({
|
|
956
|
-
entrypoint,
|
|
957
|
-
path,
|
|
958
|
-
namespace: "cleanup",
|
|
959
|
-
data: translations
|
|
960
|
-
});
|
|
961
867
|
});
|
|
962
868
|
}
|
|
963
869
|
};
|
|
@@ -1258,35 +1164,576 @@ function react() {
|
|
|
1258
1164
|
name: "react",
|
|
1259
1165
|
setup(build) {
|
|
1260
1166
|
build.context.logger?.debug("react plugin initialized");
|
|
1261
|
-
build.onProcess({
|
|
1262
|
-
|
|
1263
|
-
namespace: "source"
|
|
1264
|
-
}, ({ entrypoint, path, data }) => {
|
|
1265
|
-
const { translations, warnings } = parseSource(data, path);
|
|
1167
|
+
build.onProcess(filter, ({ path, contents, emit }) => {
|
|
1168
|
+
const { translations, warnings } = parseSource(contents, path);
|
|
1266
1169
|
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
1267
|
-
|
|
1268
|
-
entrypoint,
|
|
1269
|
-
path,
|
|
1270
|
-
namespace: "translate",
|
|
1271
|
-
data: translations
|
|
1272
|
-
});
|
|
1170
|
+
emit(translations);
|
|
1273
1171
|
});
|
|
1274
1172
|
}
|
|
1275
1173
|
};
|
|
1276
1174
|
}
|
|
1277
1175
|
//#endregion
|
|
1278
|
-
//#region
|
|
1279
|
-
var
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1176
|
+
//#region ../graph/dist/index.mjs
|
|
1177
|
+
var CycleError = class extends Error {
|
|
1178
|
+
constructor(dependency, dependent) {
|
|
1179
|
+
super(`Adding edge "${dependency}" -> "${dependent}" would create a cycle`);
|
|
1180
|
+
this.name = "CycleError";
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
var SkippedError = class extends Error {
|
|
1184
|
+
constructor(id) {
|
|
1185
|
+
super(`Node "${id}" was skipped because a dependency failed or the run was aborted`);
|
|
1186
|
+
this.name = "SkippedError";
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
var NodeState = class {
|
|
1190
|
+
id;
|
|
1191
|
+
status = "pending";
|
|
1192
|
+
result;
|
|
1193
|
+
reason;
|
|
1194
|
+
lazy = false;
|
|
1195
|
+
/**
|
|
1196
|
+
* Dependencies that have not fulfilled yet. Readiness is maintained as
|
|
1197
|
+
* edges are added and dependencies settle, so scheduling never has to
|
|
1198
|
+
* rescan the pending set.
|
|
1199
|
+
*/
|
|
1200
|
+
unresolved = 0;
|
|
1201
|
+
/** Whether the node is already sitting in the ready queue. */
|
|
1202
|
+
queued = false;
|
|
1203
|
+
/** Resumers of demanders suspended on this node, flushed on settle. */
|
|
1204
|
+
waiters;
|
|
1205
|
+
/** Ordered data dependencies, passed as run arguments. */
|
|
1206
|
+
arguments_;
|
|
1207
|
+
/** All dependencies, including ordering-only edges added via connect(). */
|
|
1208
|
+
dependencies = /* @__PURE__ */ new Set();
|
|
1209
|
+
dependents = /* @__PURE__ */ new Set();
|
|
1210
|
+
execute;
|
|
1211
|
+
constructor(id, arguments_, execute) {
|
|
1212
|
+
this.id = id;
|
|
1213
|
+
this.arguments_ = arguments_;
|
|
1214
|
+
this.execute = execute;
|
|
1215
|
+
for (const dependency of arguments_) this.dependencies.add(dependency);
|
|
1216
|
+
}
|
|
1217
|
+
get value() {
|
|
1218
|
+
if (this.status !== "fulfilled") throw new Error(`Node "${this.id}" has no value (status: ${this.status})`);
|
|
1219
|
+
return this.result;
|
|
1220
|
+
}
|
|
1221
|
+
get error() {
|
|
1222
|
+
return this.reason;
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
/**
|
|
1226
|
+
* A dynamic, asynchronous DAG of computations.
|
|
1227
|
+
*
|
|
1228
|
+
* Nodes can be added before or during a run, so a running node can discover
|
|
1229
|
+
* and schedule new work. Edges added with {@link Graph.connect} impose
|
|
1230
|
+
* ordering without contributing run arguments, which lets consumers build
|
|
1231
|
+
* barriers: a node that only starts once a dynamically growing set of
|
|
1232
|
+
* predecessors has finished.
|
|
1233
|
+
*/
|
|
1234
|
+
var Graph = class {
|
|
1235
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
1236
|
+
#root;
|
|
1237
|
+
#pending = /* @__PURE__ */ new Set();
|
|
1238
|
+
#dormant = /* @__PURE__ */ new Set();
|
|
1239
|
+
/** Pending nodes whose dependencies have all fulfilled. */
|
|
1240
|
+
#ready = [];
|
|
1241
|
+
/** Demanders that finished waiting and need a slot before resuming. */
|
|
1242
|
+
#slotWaiters = [];
|
|
1243
|
+
#runningCount = 0;
|
|
1244
|
+
#suspendedCount = 0;
|
|
1245
|
+
#concurrency = Infinity;
|
|
1246
|
+
#controller;
|
|
1247
|
+
#finish;
|
|
1248
|
+
#started = false;
|
|
1249
|
+
#finished = false;
|
|
1250
|
+
#errors = [];
|
|
1251
|
+
constructor() {
|
|
1252
|
+
INTERNALS.set(this, {
|
|
1253
|
+
kinds: /* @__PURE__ */ new Map(),
|
|
1254
|
+
workers: /* @__PURE__ */ new Map(),
|
|
1255
|
+
wrappers: /* @__PURE__ */ new Map(),
|
|
1256
|
+
frozen: false
|
|
1257
|
+
});
|
|
1258
|
+
this.#root = new Scope(this, void 0, "");
|
|
1259
|
+
}
|
|
1260
|
+
/** The root scope. Nodes added here belong to no named scope. */
|
|
1261
|
+
get root() {
|
|
1262
|
+
return this.#root;
|
|
1263
|
+
}
|
|
1264
|
+
/** Creates or returns a named child scope of the root scope. */
|
|
1265
|
+
scope(name) {
|
|
1266
|
+
return this.#root.scope(name);
|
|
1267
|
+
}
|
|
1268
|
+
/** Declares a typed node kind. Kind names are unique per graph. */
|
|
1269
|
+
kind(name) {
|
|
1270
|
+
const internals = INTERNALS.get(this);
|
|
1271
|
+
if (internals.kinds.has(name)) throw new Error(`Kind "${name}" is already registered`);
|
|
1272
|
+
const kind = { name };
|
|
1273
|
+
internals.kinds.set(name, kind);
|
|
1274
|
+
return kind;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Registers a worker that runs for every node of `source`, in the same
|
|
1278
|
+
* scope as that node. Worker results form their own kind, so they can be
|
|
1279
|
+
* awaited and collected with scope.completion(). Part of the graph's
|
|
1280
|
+
* definition: allowed until run() starts, and applies to nodes added
|
|
1281
|
+
* before the registration as well.
|
|
1282
|
+
*/
|
|
1283
|
+
each(source, name, worker) {
|
|
1284
|
+
const internals = INTERNALS.get(this);
|
|
1285
|
+
if (internals.frozen) throw new Error(`Cannot register worker "${name}" after run() started`);
|
|
1286
|
+
const kind = this.kind(name);
|
|
1287
|
+
const registrations = internals.workers.get(source.name) ?? [];
|
|
1288
|
+
registrations.push({
|
|
1289
|
+
kind,
|
|
1290
|
+
worker
|
|
1291
|
+
});
|
|
1292
|
+
internals.workers.set(source.name, registrations);
|
|
1293
|
+
return kind;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Interposes on every node of a kind: consumers of the node receive the
|
|
1297
|
+
* outermost wrapper's value instead of the raw computation. The chain is
|
|
1298
|
+
* materialized as real intermediate nodes ("<id>@raw", "<id>@<name>"),
|
|
1299
|
+
* evaluated on demand: a wrapper that never calls next() (e.g. a cache
|
|
1300
|
+
* hit) keeps the inner nodes from running at all. Wrappers compose; the
|
|
1301
|
+
* last registered one is outermost. Part of the graph's definition:
|
|
1302
|
+
* allowed until run() starts.
|
|
1303
|
+
*/
|
|
1304
|
+
wrap(kind, name, wrapper) {
|
|
1305
|
+
const internals = INTERNALS.get(this);
|
|
1306
|
+
if (internals.frozen) throw new Error(`Cannot register wrapper "${name}" after run() started`);
|
|
1307
|
+
const registrations = internals.wrappers.get(kind.name) ?? [];
|
|
1308
|
+
registrations.push({
|
|
1309
|
+
name,
|
|
1310
|
+
wrap: wrapper
|
|
1311
|
+
});
|
|
1312
|
+
internals.wrappers.set(kind.name, registrations);
|
|
1313
|
+
}
|
|
1314
|
+
/** Whether `node` transitively depends on `dependency`. */
|
|
1315
|
+
dependsOn(node, dependency) {
|
|
1316
|
+
const target = this.#resolve(dependency);
|
|
1317
|
+
const stack = [...this.#resolve(node).dependencies];
|
|
1318
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1319
|
+
while (stack.length > 0) {
|
|
1320
|
+
const current = stack.pop();
|
|
1321
|
+
if (current === target) return true;
|
|
1322
|
+
if (seen.has(current)) continue;
|
|
1323
|
+
seen.add(current);
|
|
1324
|
+
stack.push(...current.dependencies);
|
|
1325
|
+
}
|
|
1326
|
+
return false;
|
|
1327
|
+
}
|
|
1328
|
+
add(id, options) {
|
|
1329
|
+
if (this.#finished) throw new Error(`Cannot add node "${id}": the graph has already finished`);
|
|
1330
|
+
if (this.#nodes.has(id)) throw new Error(`Node "${id}" already exists`);
|
|
1331
|
+
const dependencies = (options.dependencies ?? []).map((dependency) => this.#resolve(dependency));
|
|
1332
|
+
const node = new NodeState(id, dependencies, options.run);
|
|
1333
|
+
node.lazy = options.lazy ?? false;
|
|
1334
|
+
let doomed;
|
|
1335
|
+
for (const dependency of dependencies) {
|
|
1336
|
+
dependency.dependents.add(node);
|
|
1337
|
+
if (dependency.status === "rejected" || dependency.status === "skipped") doomed ??= dependency;
|
|
1338
|
+
else if (dependency.status !== "fulfilled") node.unresolved += 1;
|
|
1339
|
+
}
|
|
1340
|
+
this.#nodes.set(id, node);
|
|
1341
|
+
if (node.lazy) this.#dormant.add(node);
|
|
1342
|
+
else {
|
|
1343
|
+
this.#pending.add(node);
|
|
1344
|
+
for (const dependency of dependencies) this.#wake(dependency);
|
|
1345
|
+
}
|
|
1346
|
+
if (doomed) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1347
|
+
else if (!node.lazy) this.#enqueue(node);
|
|
1348
|
+
if (this.#started) queueMicrotask(() => this.#pump());
|
|
1349
|
+
return node;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Adds an ordering-only edge: `dependent` will not start until
|
|
1353
|
+
* `dependency` has fulfilled. The dependency's value is not passed to the
|
|
1354
|
+
* dependent's run function; read it from the node handle if needed.
|
|
1355
|
+
*/
|
|
1356
|
+
connect(dependency, dependent) {
|
|
1357
|
+
const from = this.#resolve(dependency);
|
|
1358
|
+
const to = this.#resolve(dependent);
|
|
1359
|
+
if (to.status !== "pending") throw new Error(`Cannot add dependency to node "${to.id}": it has already started`);
|
|
1360
|
+
if (from === to || this.#reaches(to, from)) throw new CycleError(from.id, to.id);
|
|
1361
|
+
if (to.dependencies.has(from)) return;
|
|
1362
|
+
to.dependencies.add(from);
|
|
1363
|
+
from.dependents.add(to);
|
|
1364
|
+
if (from.status === "rejected" || from.status === "skipped") {
|
|
1365
|
+
this.#settle(to, "skipped", void 0, new SkippedError(to.id));
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (from.status !== "fulfilled") to.unresolved += 1;
|
|
1369
|
+
if (!this.#dormant.has(to)) this.#wake(from);
|
|
1370
|
+
if (this.#started) queueMicrotask(() => this.#pump());
|
|
1371
|
+
}
|
|
1372
|
+
get(id) {
|
|
1373
|
+
return this.#nodes.get(id);
|
|
1374
|
+
}
|
|
1375
|
+
get nodes() {
|
|
1376
|
+
return this.#nodes;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Runs the graph until every node has settled, including nodes added
|
|
1380
|
+
* while running. Rejects with an AggregateError if any node rejected, or
|
|
1381
|
+
* with the abort reason if the signal aborted. Can only be called once.
|
|
1382
|
+
*/
|
|
1383
|
+
async run(options = {}) {
|
|
1384
|
+
if (this.#started) throw new Error("Graph is already running or has finished");
|
|
1385
|
+
Scope.materialize(this.#root);
|
|
1386
|
+
INTERNALS.get(this).frozen = true;
|
|
1387
|
+
this.#started = true;
|
|
1388
|
+
this.#concurrency = options.concurrency ?? Infinity;
|
|
1389
|
+
if (this.#concurrency < 1) throw new Error("concurrency must be at least 1");
|
|
1390
|
+
this.#controller = new AbortController();
|
|
1391
|
+
const signal = options.signal;
|
|
1392
|
+
if (signal) if (signal.aborted) this.#controller.abort(signal.reason);
|
|
1393
|
+
else signal.addEventListener("abort", () => {
|
|
1394
|
+
this.#controller?.abort(signal.reason);
|
|
1395
|
+
this.#pump();
|
|
1396
|
+
});
|
|
1397
|
+
return new Promise((resolve, reject) => {
|
|
1398
|
+
this.#finish = {
|
|
1399
|
+
resolve,
|
|
1400
|
+
reject
|
|
1401
|
+
};
|
|
1402
|
+
this.#pump();
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
#resolve(handle) {
|
|
1406
|
+
const node = this.#nodes.get(handle.id);
|
|
1407
|
+
if (!node || node !== handle) throw new Error(`Node "${handle.id}" does not belong to this graph`);
|
|
1408
|
+
return node;
|
|
1409
|
+
}
|
|
1410
|
+
#reaches(from, target) {
|
|
1411
|
+
const stack = [...from.dependents];
|
|
1412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1413
|
+
while (stack.length > 0) {
|
|
1414
|
+
const node = stack.pop();
|
|
1415
|
+
if (node === target) return true;
|
|
1416
|
+
if (seen.has(node)) continue;
|
|
1417
|
+
seen.add(node);
|
|
1418
|
+
stack.push(...node.dependents);
|
|
1419
|
+
}
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
/** Offers a pending node to the scheduler once its dependencies are in. */
|
|
1423
|
+
#enqueue(node) {
|
|
1424
|
+
if (node.queued || node.unresolved > 0 || node.status !== "pending" || this.#dormant.has(node)) return;
|
|
1425
|
+
node.queued = true;
|
|
1426
|
+
this.#ready.push(node);
|
|
1427
|
+
}
|
|
1428
|
+
#pump() {
|
|
1429
|
+
if (!this.#started || this.#finished || !this.#finish) return;
|
|
1430
|
+
if (this.#controller?.signal.aborted) {
|
|
1431
|
+
while (this.#slotWaiters.length > 0) {
|
|
1432
|
+
this.#runningCount += 1;
|
|
1433
|
+
this.#slotWaiters.shift()();
|
|
1434
|
+
}
|
|
1435
|
+
for (const node of [...this.#pending]) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1436
|
+
} else {
|
|
1437
|
+
while (this.#slotWaiters.length > 0 && this.#runningCount < this.#concurrency) {
|
|
1438
|
+
this.#runningCount += 1;
|
|
1439
|
+
this.#slotWaiters.shift()();
|
|
1440
|
+
}
|
|
1441
|
+
while (this.#ready.length > 0 && this.#runningCount < this.#concurrency) {
|
|
1442
|
+
const node = this.#ready.shift();
|
|
1443
|
+
node.queued = false;
|
|
1444
|
+
if (node.unresolved === 0 && node.status === "pending") this.#start(node);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
if (this.#pending.size === 0 && this.#runningCount === 0 && this.#suspendedCount === 0) this.#complete();
|
|
1448
|
+
}
|
|
1449
|
+
/** Moves a dormant lazy node (and its dormant dependencies) into scheduling. */
|
|
1450
|
+
#wake(node) {
|
|
1451
|
+
const stack = [node];
|
|
1452
|
+
while (stack.length > 0) {
|
|
1453
|
+
const current = stack.pop();
|
|
1454
|
+
if (!this.#dormant.delete(current)) continue;
|
|
1455
|
+
this.#pending.add(current);
|
|
1456
|
+
this.#enqueue(current);
|
|
1457
|
+
stack.push(...current.dependencies);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Takes a concurrency slot, waiting for one when the graph is at its
|
|
1462
|
+
* limit. The slot is reserved by #pump at hand-off, so a granted slot
|
|
1463
|
+
* cannot be taken by anything else in the meantime.
|
|
1464
|
+
*/
|
|
1465
|
+
#acquire() {
|
|
1466
|
+
if (this.#runningCount < this.#concurrency) {
|
|
1467
|
+
this.#runningCount += 1;
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
return new Promise((resume) => {
|
|
1471
|
+
this.#slotWaiters.push(resume);
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
async #demand(demander, handle) {
|
|
1475
|
+
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`);
|
|
1476
|
+
const target = this.#resolve(handle);
|
|
1477
|
+
if (target === demander || this.#reaches(demander, target)) throw new CycleError(target.id, demander.id);
|
|
1478
|
+
if (!demander.dependencies.has(target)) {
|
|
1479
|
+
demander.dependencies.add(target);
|
|
1480
|
+
target.dependents.add(demander);
|
|
1481
|
+
}
|
|
1482
|
+
this.#wake(target);
|
|
1483
|
+
if (target.status === "pending" || target.status === "running") this.#pump();
|
|
1484
|
+
if (target.status === "pending" || target.status === "running") {
|
|
1485
|
+
this.#runningCount -= 1;
|
|
1486
|
+
this.#suspendedCount += 1;
|
|
1487
|
+
this.#pump();
|
|
1488
|
+
await new Promise((resume) => {
|
|
1489
|
+
(target.waiters ??= []).push(resume);
|
|
1490
|
+
});
|
|
1491
|
+
await this.#acquire();
|
|
1492
|
+
this.#suspendedCount -= 1;
|
|
1493
|
+
}
|
|
1494
|
+
if (target.status === "fulfilled") return target.result;
|
|
1495
|
+
if (target.status === "rejected") throw target.reason;
|
|
1496
|
+
throw new SkippedError(target.id);
|
|
1497
|
+
}
|
|
1498
|
+
#start(node) {
|
|
1499
|
+
this.#pending.delete(node);
|
|
1500
|
+
node.status = "running";
|
|
1501
|
+
this.#runningCount += 1;
|
|
1502
|
+
const context = {
|
|
1503
|
+
id: node.id,
|
|
1504
|
+
signal: this.#controller?.signal,
|
|
1505
|
+
demand: (handle) => this.#demand(node, handle)
|
|
1506
|
+
};
|
|
1507
|
+
const values = node.arguments_.map((dependency) => dependency.value);
|
|
1508
|
+
Promise.resolve().then(() => node.execute(context, ...values)).then((value) => {
|
|
1509
|
+
this.#runningCount -= 1;
|
|
1510
|
+
this.#settle(node, "fulfilled", value, void 0);
|
|
1511
|
+
this.#pump();
|
|
1512
|
+
}, (reason) => {
|
|
1513
|
+
this.#runningCount -= 1;
|
|
1514
|
+
this.#errors.push(reason);
|
|
1515
|
+
this.#settle(node, "rejected", void 0, reason);
|
|
1516
|
+
this.#pump();
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Settles a node and propagates the consequences to its dependents: a
|
|
1521
|
+
* fulfilled node makes them one dependency readier, a failed or skipped
|
|
1522
|
+
* one skips them in turn. Iterative, so a long chain of skips cannot
|
|
1523
|
+
* overflow the stack.
|
|
1524
|
+
*/
|
|
1525
|
+
#settle(node, status, value, reason) {
|
|
1526
|
+
const stack = [{
|
|
1527
|
+
node,
|
|
1528
|
+
status,
|
|
1529
|
+
value,
|
|
1530
|
+
reason
|
|
1531
|
+
}];
|
|
1532
|
+
while (stack.length > 0) {
|
|
1533
|
+
const current = stack.pop();
|
|
1534
|
+
const settled = current.node;
|
|
1535
|
+
if (settled.status !== "pending" && settled.status !== "running") continue;
|
|
1536
|
+
this.#pending.delete(settled);
|
|
1537
|
+
this.#dormant.delete(settled);
|
|
1538
|
+
settled.status = current.status;
|
|
1539
|
+
settled.result = current.value;
|
|
1540
|
+
settled.reason = current.reason;
|
|
1541
|
+
const waiters = settled.waiters;
|
|
1542
|
+
settled.waiters = void 0;
|
|
1543
|
+
waiters?.forEach((resume) => resume());
|
|
1544
|
+
const doomed = current.status === "rejected" || current.status === "skipped";
|
|
1545
|
+
for (const dependent of settled.dependents) {
|
|
1546
|
+
if (doomed) {
|
|
1547
|
+
if (dependent.status === "pending") stack.push({
|
|
1548
|
+
node: dependent,
|
|
1549
|
+
status: "skipped",
|
|
1550
|
+
value: void 0,
|
|
1551
|
+
reason: new SkippedError(dependent.id)
|
|
1552
|
+
});
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
if (dependent.unresolved > 0) {
|
|
1556
|
+
dependent.unresolved -= 1;
|
|
1557
|
+
this.#enqueue(dependent);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
#complete() {
|
|
1563
|
+
const finish = this.#finish;
|
|
1564
|
+
if (!finish) return;
|
|
1565
|
+
for (const node of [...this.#dormant]) this.#settle(node, "skipped", void 0, new SkippedError(node.id));
|
|
1566
|
+
this.#finished = true;
|
|
1567
|
+
this.#finish = void 0;
|
|
1568
|
+
if (this.#controller?.signal.aborted) finish.reject(this.#controller.signal.reason);
|
|
1569
|
+
else if (this.#errors.length > 0) finish.reject(new AggregateError(this.#errors, "One or more graph nodes failed"));
|
|
1570
|
+
else finish.resolve();
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
const INTERNALS = /* @__PURE__ */ new WeakMap();
|
|
1574
|
+
function normalize(spec) {
|
|
1575
|
+
return typeof spec === "function" ? { run: spec } : spec;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* A hierarchical namespace of kinded nodes. Scopes make phase completion
|
|
1579
|
+
* automatic: scope.completion(kind) fulfills once every node in the scope
|
|
1580
|
+
* (and its descendants) has settled — including nodes that are created
|
|
1581
|
+
* dynamically while the graph runs — without the producers having to wire
|
|
1582
|
+
* any edges themselves.
|
|
1583
|
+
*/
|
|
1584
|
+
var Scope = class Scope {
|
|
1585
|
+
name;
|
|
1586
|
+
path;
|
|
1587
|
+
#graph;
|
|
1588
|
+
#parent;
|
|
1589
|
+
#children = /* @__PURE__ */ new Map();
|
|
1590
|
+
#entries = /* @__PURE__ */ new Map();
|
|
1591
|
+
/** Nodes this scope's completions must wait for (kinded nodes + workers). */
|
|
1592
|
+
#members = [];
|
|
1593
|
+
#completions = /* @__PURE__ */ new Map();
|
|
1594
|
+
/** @internal Use graph.scope() or scope.scope() instead. */
|
|
1595
|
+
constructor(graph, parent, name) {
|
|
1596
|
+
this.#graph = graph;
|
|
1597
|
+
this.#parent = parent;
|
|
1598
|
+
this.name = name;
|
|
1599
|
+
this.path = parent && parent.path ? `${parent.path}/${name}` : name;
|
|
1600
|
+
}
|
|
1601
|
+
/** Creates or returns a named child scope. */
|
|
1602
|
+
scope(name) {
|
|
1603
|
+
let child = this.#children.get(name);
|
|
1604
|
+
if (!child) {
|
|
1605
|
+
child = new Scope(this.#graph, this, name);
|
|
1606
|
+
this.#children.set(name, child);
|
|
1607
|
+
}
|
|
1608
|
+
return child;
|
|
1609
|
+
}
|
|
1610
|
+
get(kind, key) {
|
|
1611
|
+
return this.#entries.get(kind.name)?.get(key);
|
|
1612
|
+
}
|
|
1613
|
+
add(kind, key, spec) {
|
|
1614
|
+
if (this.get(kind, key)) throw new Error(`Node "${kind.name}:${key}" already exists in scope "${this.path || "(root)"}"`);
|
|
1615
|
+
return this.#create(kind, key, normalize(spec));
|
|
1616
|
+
}
|
|
1617
|
+
/** Like add(), but returns the existing node if the key is already present. */
|
|
1618
|
+
ensure(kind, key, spec) {
|
|
1619
|
+
return this.get(kind, key) ?? this.#create(kind, key, normalize(spec));
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* A barrier node that fulfills once this scope is quiescent: every node
|
|
1623
|
+
* in the scope and its descendants has settled, except nodes that
|
|
1624
|
+
* transitively depend on the barrier itself (its consumers). Its value is
|
|
1625
|
+
* the collected values of all `kind` nodes in the scope subtree. After it
|
|
1626
|
+
* fires the scope is sealed: adding non-consumer nodes throws.
|
|
1627
|
+
*/
|
|
1628
|
+
completion(kind) {
|
|
1629
|
+
const existing = this.#completions.get(kind.name);
|
|
1630
|
+
if (existing) return existing;
|
|
1631
|
+
const completion = this.#graph.add(`${this.#prefix()}${kind.name}:$completion`, { run: () => this.#collect(kind) });
|
|
1632
|
+
this.#completions.set(kind.name, completion);
|
|
1633
|
+
for (const member of this.#allMembers()) this.#attach(member, completion);
|
|
1634
|
+
return completion;
|
|
1635
|
+
}
|
|
1636
|
+
#prefix() {
|
|
1637
|
+
return this.path ? `${this.path}/` : "";
|
|
1638
|
+
}
|
|
1639
|
+
#create(kind, key, options) {
|
|
1640
|
+
const internals = INTERNALS.get(this.#graph);
|
|
1641
|
+
const dependencies = options.dependencies ?? [];
|
|
1642
|
+
this.#assertOpen(`${kind.name}:${key}`, dependencies);
|
|
1643
|
+
const scope = this;
|
|
1644
|
+
const graph = this.#graph;
|
|
1645
|
+
const id = `${this.#prefix()}${kind.name}:${key}`;
|
|
1646
|
+
const node = graph.add(id, {
|
|
1647
|
+
dependencies,
|
|
1648
|
+
run: (context, ...values) => {
|
|
1649
|
+
const scoped = {
|
|
1650
|
+
id: context.id,
|
|
1651
|
+
key,
|
|
1652
|
+
signal: context.signal,
|
|
1653
|
+
scope,
|
|
1654
|
+
demand: context.demand
|
|
1655
|
+
};
|
|
1656
|
+
const compute = () => options.run(scoped, ...values);
|
|
1657
|
+
const wrappers = internals.wrappers.get(kind.name) ?? [];
|
|
1658
|
+
if (wrappers.length === 0) return compute();
|
|
1659
|
+
let inner = graph.add(`${id}@raw`, {
|
|
1660
|
+
lazy: true,
|
|
1661
|
+
run: () => compute()
|
|
1662
|
+
});
|
|
1663
|
+
for (const registration of wrappers.slice(0, -1)) {
|
|
1664
|
+
const previous = inner;
|
|
1665
|
+
inner = graph.add(`${id}@${registration.name}`, {
|
|
1666
|
+
lazy: true,
|
|
1667
|
+
run: (innerContext) => registration.wrap({
|
|
1668
|
+
...scoped,
|
|
1669
|
+
id: innerContext.id,
|
|
1670
|
+
demand: innerContext.demand
|
|
1671
|
+
}, () => {
|
|
1672
|
+
return innerContext.demand(previous);
|
|
1673
|
+
})
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
return wrappers[wrappers.length - 1].wrap(scoped, () => context.demand(inner));
|
|
1677
|
+
}
|
|
1286
1678
|
});
|
|
1679
|
+
let byKey = this.#entries.get(kind.name);
|
|
1680
|
+
if (!byKey) {
|
|
1681
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1682
|
+
this.#entries.set(kind.name, byKey);
|
|
1683
|
+
}
|
|
1684
|
+
byKey.set(key, node);
|
|
1685
|
+
this.#members.push(node);
|
|
1686
|
+
for (let ancestor = this; ancestor; ancestor = ancestor.#parent) for (const completion of ancestor.#completions.values()) this.#attach(node, completion);
|
|
1687
|
+
for (const registration of internals.workers.get(kind.name) ?? []) this.#create(registration.kind, key, {
|
|
1688
|
+
dependencies: [node],
|
|
1689
|
+
run: (context, value) => registration.worker(context, value, node)
|
|
1690
|
+
});
|
|
1691
|
+
return node;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Attaches workers registered after some nodes of their kind were
|
|
1695
|
+
* already added. Runs once when the definition phase ends (run()).
|
|
1696
|
+
*/
|
|
1697
|
+
static materialize(scope) {
|
|
1698
|
+
const internals = INTERNALS.get(scope.#graph);
|
|
1699
|
+
for (const [kindName, byKey] of scope.#entries) for (const registration of internals.workers.get(kindName) ?? []) for (const [key, node] of [...byKey]) {
|
|
1700
|
+
if (scope.#entries.get(registration.kind.name)?.has(key)) continue;
|
|
1701
|
+
scope.#create(registration.kind, key, {
|
|
1702
|
+
dependencies: [node],
|
|
1703
|
+
run: (context, value) => registration.worker(context, value, node)
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
for (const child of scope.#children.values()) Scope.materialize(child);
|
|
1287
1707
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1708
|
+
/**
|
|
1709
|
+
* A scope seals once a completion fires: new nodes are only allowed if
|
|
1710
|
+
* they are consumers of that completion (depend on it), since the
|
|
1711
|
+
* completion has already reported the scope as done.
|
|
1712
|
+
*/
|
|
1713
|
+
#assertOpen(label, dependencies) {
|
|
1714
|
+
for (let ancestor = this; ancestor; ancestor = ancestor.#parent) for (const completion of ancestor.#completions.values()) {
|
|
1715
|
+
if (completion.status === "pending") continue;
|
|
1716
|
+
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`);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
#attach(member, completion) {
|
|
1720
|
+
if (completion.status !== "pending") return;
|
|
1721
|
+
if (member === completion || this.#graph.dependsOn(member, completion)) return;
|
|
1722
|
+
this.#graph.connect(member, completion);
|
|
1723
|
+
}
|
|
1724
|
+
#allMembers() {
|
|
1725
|
+
const members = [...this.#members];
|
|
1726
|
+
for (const child of this.#children.values()) members.push(...child.#allMembers());
|
|
1727
|
+
return members;
|
|
1728
|
+
}
|
|
1729
|
+
#collect(kind) {
|
|
1730
|
+
const values = [];
|
|
1731
|
+
const byKey = this.#entries.get(kind.name);
|
|
1732
|
+
if (byKey) {
|
|
1733
|
+
for (const node of byKey.values()) if (node.status === "fulfilled") values.push(node.value);
|
|
1734
|
+
}
|
|
1735
|
+
for (const child of this.#children.values()) values.push(...child.#collect(kind));
|
|
1736
|
+
return values;
|
|
1290
1737
|
}
|
|
1291
1738
|
};
|
|
1292
1739
|
//#endregion
|
|
@@ -1308,13 +1755,19 @@ async function getPaths(entrypoint) {
|
|
|
1308
1755
|
const paths = glob.isDynamicPattern(pattern) ? await glob(pattern, { onlyFiles: true }) : [entrypoint.entrypoint];
|
|
1309
1756
|
return new Set(paths.map((path) => resolve(path)));
|
|
1310
1757
|
}
|
|
1758
|
+
function toRealPath(path) {
|
|
1759
|
+
const abs = resolve(path);
|
|
1760
|
+
try {
|
|
1761
|
+
return realpathSync(abs);
|
|
1762
|
+
} catch {
|
|
1763
|
+
return abs;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1311
1766
|
async function run(entrypoint, { config, logger }) {
|
|
1312
1767
|
const destination = entrypoint.destination ?? config.destination;
|
|
1313
1768
|
const obsolete = entrypoint.obsolete ?? config.obsolete;
|
|
1314
1769
|
const exclude = entrypoint.exclude ?? config.exclude;
|
|
1315
1770
|
const walk = entrypoint.walk ?? config.walk;
|
|
1316
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1317
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
1318
1771
|
const context = {
|
|
1319
1772
|
config: {
|
|
1320
1773
|
...config,
|
|
@@ -1324,110 +1777,139 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1324
1777
|
walk
|
|
1325
1778
|
},
|
|
1326
1779
|
generatedAt: /* @__PURE__ */ new Date(),
|
|
1327
|
-
paths,
|
|
1780
|
+
paths: /* @__PURE__ */ new Set(),
|
|
1328
1781
|
logger
|
|
1329
1782
|
};
|
|
1330
1783
|
logger?.info(entrypoint, "starting extraction");
|
|
1331
|
-
const resolvers = [];
|
|
1332
1784
|
const loaders = [];
|
|
1333
1785
|
const processors = [];
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
pending.set(namespace, defer);
|
|
1346
|
-
}
|
|
1347
|
-
return defer;
|
|
1786
|
+
const collectors = [];
|
|
1787
|
+
const finalizers = [];
|
|
1788
|
+
const failures = [];
|
|
1789
|
+
const failed = /* @__PURE__ */ new Set();
|
|
1790
|
+
/** Records a failure that leaves `scope`'s translations incomplete. */
|
|
1791
|
+
function fail(scope, path, error) {
|
|
1792
|
+
failed.add(scope);
|
|
1793
|
+
record({
|
|
1794
|
+
entrypoint: scope,
|
|
1795
|
+
path
|
|
1796
|
+
}, error);
|
|
1348
1797
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1798
|
+
/** Records a failure that no longer has an entrypoint to invalidate. */
|
|
1799
|
+
function record(where, error) {
|
|
1800
|
+
failures.push(error);
|
|
1801
|
+
logger?.error({
|
|
1802
|
+
...where,
|
|
1803
|
+
error
|
|
1804
|
+
}, "extraction failed");
|
|
1351
1805
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1806
|
+
const graph = new Graph();
|
|
1807
|
+
const source = graph.kind("source");
|
|
1808
|
+
const collect = graph.kind("collect");
|
|
1809
|
+
const plan = graph.kind("plan");
|
|
1810
|
+
const output = graph.kind("output");
|
|
1811
|
+
const finalize = graph.kind("finalize");
|
|
1812
|
+
const processed = graph.each(source, "process", async (node, contents) => {
|
|
1813
|
+
if (contents === void 0) return;
|
|
1814
|
+
const path = node.key;
|
|
1815
|
+
let emitted;
|
|
1816
|
+
const args = {
|
|
1817
|
+
entrypoint: node.scope.name,
|
|
1818
|
+
path,
|
|
1819
|
+
contents,
|
|
1820
|
+
emit(translations) {
|
|
1821
|
+
emitted ??= {
|
|
1822
|
+
path,
|
|
1823
|
+
translations: []
|
|
1824
|
+
};
|
|
1825
|
+
emitted.translations.push(...translations);
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1355
1828
|
try {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1829
|
+
for (const { filter, hook } of processors) {
|
|
1830
|
+
if (!filter.test(path)) continue;
|
|
1831
|
+
if (await hook(args) !== void 0) break;
|
|
1832
|
+
}
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
fail(node.scope.name, path, error);
|
|
1835
|
+
return;
|
|
1359
1836
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
resolve$1({
|
|
1367
|
-
entrypoint: resolvedPath,
|
|
1368
|
-
path: resolvedPath,
|
|
1369
|
-
namespace: "source"
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
function resolve$1(args) {
|
|
1373
|
-
const { entrypoint, path, namespace } = args;
|
|
1374
|
-
const key = `${entrypoint}:${namespace}:${path}`;
|
|
1375
|
-
const visited = resolved.has(key);
|
|
1376
|
-
const skipped = isExcluded(args, context.config.exclude);
|
|
1377
|
-
logger?.debug({
|
|
1378
|
-
entrypoint,
|
|
1837
|
+
return emitted;
|
|
1838
|
+
});
|
|
1839
|
+
function addSource(scope, path, importReference) {
|
|
1840
|
+
if (scope.get(source, path)) return;
|
|
1841
|
+
const args = {
|
|
1842
|
+
entrypoint: scope.name,
|
|
1379
1843
|
path,
|
|
1380
|
-
namespace,
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
queue.push({
|
|
1388
|
-
type: "resolve",
|
|
1389
|
-
args
|
|
1390
|
-
});
|
|
1391
|
-
getDeferred(namespace).enqueue();
|
|
1392
|
-
}
|
|
1393
|
-
function load(args) {
|
|
1394
|
-
const { entrypoint, path, namespace } = args;
|
|
1844
|
+
namespace: "source",
|
|
1845
|
+
import: importReference
|
|
1846
|
+
};
|
|
1847
|
+
if (isExcluded(args, context.config.exclude)) {
|
|
1848
|
+
logger?.debug(args, "excluded");
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1395
1851
|
logger?.debug({
|
|
1396
|
-
entrypoint,
|
|
1397
|
-
path
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1852
|
+
entrypoint: scope.name,
|
|
1853
|
+
path
|
|
1854
|
+
}, "source");
|
|
1855
|
+
scope.add(source, path, async () => {
|
|
1856
|
+
try {
|
|
1857
|
+
for (const { filter, hook } of loaders) {
|
|
1858
|
+
if (!filter.test(path)) continue;
|
|
1859
|
+
const contents = await hook({
|
|
1860
|
+
entrypoint: scope.name,
|
|
1861
|
+
path
|
|
1862
|
+
});
|
|
1863
|
+
if (contents !== void 0) return contents;
|
|
1864
|
+
}
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
fail(scope.name, path, error);
|
|
1867
|
+
}
|
|
1403
1868
|
});
|
|
1404
|
-
getDeferred(namespace).enqueue();
|
|
1405
1869
|
}
|
|
1406
|
-
function
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1870
|
+
function pipeline(path) {
|
|
1871
|
+
if (context.paths.has(path)) return;
|
|
1872
|
+
context.paths.add(path);
|
|
1873
|
+
const scope = graph.scope(path);
|
|
1874
|
+
addSource(scope, path);
|
|
1875
|
+
const collected = scope.completion(processed);
|
|
1876
|
+
collectors.forEach((hook, index) => {
|
|
1877
|
+
scope.add(collect, String(index), {
|
|
1878
|
+
dependencies: [collected],
|
|
1879
|
+
run: async (_node, files) => {
|
|
1880
|
+
if (failed.has(path)) {
|
|
1881
|
+
logger?.warn({ entrypoint: path }, "skipping outputs: entrypoint failed");
|
|
1882
|
+
return [];
|
|
1883
|
+
}
|
|
1884
|
+
const contributions = [];
|
|
1885
|
+
try {
|
|
1886
|
+
await hook({
|
|
1887
|
+
entrypoint: path,
|
|
1888
|
+
files: files.filter((file) => file !== void 0),
|
|
1889
|
+
output: (outputPath, produce) => {
|
|
1890
|
+
contributions.push({
|
|
1891
|
+
path: outputPath,
|
|
1892
|
+
produce
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
fail(path, path, error);
|
|
1898
|
+
return [];
|
|
1899
|
+
}
|
|
1900
|
+
return contributions;
|
|
1901
|
+
}
|
|
1902
|
+
});
|
|
1416
1903
|
});
|
|
1417
|
-
getDeferred(namespace).enqueue();
|
|
1418
1904
|
}
|
|
1419
1905
|
const build = {
|
|
1420
1906
|
context,
|
|
1421
|
-
source,
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
resolvers.push({
|
|
1428
|
-
filter,
|
|
1429
|
-
hook
|
|
1430
|
-
});
|
|
1907
|
+
source({ entrypoint: sourceEntrypoint, path, import: importReference }) {
|
|
1908
|
+
if (sourceEntrypoint === path) {
|
|
1909
|
+
pipeline(toRealPath(path));
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
addSource(graph.scope(sourceEntrypoint), path, importReference);
|
|
1431
1913
|
},
|
|
1432
1914
|
onLoad(filter, hook) {
|
|
1433
1915
|
loaders.push({
|
|
@@ -1440,6 +1922,12 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1440
1922
|
filter,
|
|
1441
1923
|
hook
|
|
1442
1924
|
});
|
|
1925
|
+
},
|
|
1926
|
+
onCollected(hook) {
|
|
1927
|
+
collectors.push(hook);
|
|
1928
|
+
},
|
|
1929
|
+
onOutputs(hook) {
|
|
1930
|
+
finalizers.push(hook);
|
|
1443
1931
|
}
|
|
1444
1932
|
};
|
|
1445
1933
|
for (const item of config.plugins) {
|
|
@@ -1447,44 +1935,51 @@ async function run(entrypoint, { config, logger }) {
|
|
|
1447
1935
|
logger?.debug({ plugin: plugin.name }, "setting up plugin");
|
|
1448
1936
|
plugin.setup(build);
|
|
1449
1937
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
namespace
|
|
1460
|
-
}, "processing task");
|
|
1461
|
-
for (const { filter, hook } of hooks[type]) {
|
|
1462
|
-
if (filter.namespace !== namespace) continue;
|
|
1463
|
-
if (filter.filter && !filter.filter.test(path)) continue;
|
|
1464
|
-
const result = await hook(args);
|
|
1465
|
-
if (result !== void 0) {
|
|
1466
|
-
args = result;
|
|
1467
|
-
break;
|
|
1938
|
+
const contributed = graph.root.completion(collect);
|
|
1939
|
+
graph.root.add(plan, "outputs", {
|
|
1940
|
+
dependencies: [contributed],
|
|
1941
|
+
run: (_node, collections) => {
|
|
1942
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1943
|
+
for (const contribution of collections.flat()) {
|
|
1944
|
+
const group = byPath.get(contribution.path) ?? [];
|
|
1945
|
+
group.push(contribution);
|
|
1946
|
+
byPath.set(contribution.path, group);
|
|
1468
1947
|
}
|
|
1948
|
+
for (const [outputPath, contributions] of byPath) graph.root.add(output, outputPath, {
|
|
1949
|
+
dependencies: [contributed],
|
|
1950
|
+
run: async () => {
|
|
1951
|
+
try {
|
|
1952
|
+
for (const { produce } of contributions) await produce();
|
|
1953
|
+
} catch (error) {
|
|
1954
|
+
record({ output: outputPath }, error);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
return outputPath;
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1469
1960
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1961
|
+
});
|
|
1962
|
+
const outputs = graph.root.completion(output);
|
|
1963
|
+
finalizers.forEach((hook, index) => {
|
|
1964
|
+
graph.root.add(finalize, String(index), {
|
|
1965
|
+
dependencies: [outputs],
|
|
1966
|
+
run: async (_node, produced) => {
|
|
1967
|
+
try {
|
|
1968
|
+
await hook({ outputs: produced.filter((path) => path !== void 0) });
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
record({ finalizer: String(index) }, error);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
});
|
|
1975
|
+
for (const path of await getPaths(entrypoint)) pipeline(toRealPath(path));
|
|
1976
|
+
await graph.run().catch((error) => {
|
|
1977
|
+
record({ graph: "run" }, error);
|
|
1978
|
+
});
|
|
1979
|
+
if (failures.length > 0) throw new AggregateError(failures, `Extraction of "${entrypoint.entrypoint}" failed`);
|
|
1485
1980
|
logger?.info(entrypoint, "extraction completed");
|
|
1486
1981
|
}
|
|
1487
1982
|
//#endregion
|
|
1488
1983
|
export { cleanup as a, core as i, react as n, po as r, run as t };
|
|
1489
1984
|
|
|
1490
|
-
//# sourceMappingURL=run-
|
|
1985
|
+
//# sourceMappingURL=run-CzeTVOug.mjs.map
|