@intentius/chant 0.34.0 → 0.34.1
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/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishing a package runs its prepack, and a prepack builds against and
|
|
3
|
+
* imports its workspace dependencies' *generated* output — output that only
|
|
4
|
+
* exists once that dependency has been published. So publish order has to be a
|
|
5
|
+
* topological order, and for a long time directory order stood in for one.
|
|
6
|
+
*
|
|
7
|
+
* It held until it did not. chant-v0.34.0 published twelve of fourteen packages
|
|
8
|
+
* and stranded lexicon-forgejo and lexicon-helm a version behind: forgejo needs
|
|
9
|
+
* github's `src/generated/index` and helm needs k8s's `dist/generated`, but
|
|
10
|
+
* `forgejo` < `github` and `helm` < `k8s`, so both ran before the thing they
|
|
11
|
+
* import existed. Half a release shipped and the failure only surfaced from
|
|
12
|
+
* npm, after the tag.
|
|
13
|
+
*
|
|
14
|
+
* publish-packages.sh now derives the order instead of assuming one. This
|
|
15
|
+
* asserts the derivation is actually topological, so the next lexicon that
|
|
16
|
+
* depends on an alphabetically earlier one fails here rather than mid-release.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, expect, it } from "vitest";
|
|
20
|
+
import { execFileSync } from "node:child_process";
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { join, dirname } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const REPO = join(dirname(fileURLToPath(import.meta.url)), "../../../..");
|
|
26
|
+
|
|
27
|
+
/** Run one of publish-packages.sh's own functions and read back what it prints. */
|
|
28
|
+
function ask(fn: string): string[] {
|
|
29
|
+
const script = readFileSync(join(REPO, "scripts/publish-packages.sh"), "utf8");
|
|
30
|
+
const body = script.match(new RegExp(`^${fn}\\(\\) \\{$.*?^\\}$`, "ms"));
|
|
31
|
+
if (!body) throw new Error(`${fn}() not found in scripts/publish-packages.sh`);
|
|
32
|
+
return execFileSync("bash", ["-c", `${body[0]}\n${fn}`], { cwd: REPO, encoding: "utf8" })
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const publishOrder = () => ask("publishable_dirs");
|
|
38
|
+
|
|
39
|
+
function manifest(dir: string) {
|
|
40
|
+
return JSON.parse(readFileSync(join(REPO, dir, "package.json"), "utf8"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("publish order", () => {
|
|
44
|
+
const order = publishOrder();
|
|
45
|
+
|
|
46
|
+
it("covers every publishable workspace package", () => {
|
|
47
|
+
// A package missing from the order never publishes at all, which is the
|
|
48
|
+
// same stranding by a different route.
|
|
49
|
+
const all = execFileSync(
|
|
50
|
+
"bash",
|
|
51
|
+
["-c", 'for d in packages/*/ lexicons/*/; do [ -f "$d/package.json" ] && echo "${d%/}"; done'],
|
|
52
|
+
{ cwd: REPO, encoding: "utf8" },
|
|
53
|
+
)
|
|
54
|
+
.split("\n")
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.filter((d) => !manifest(d).private);
|
|
57
|
+
|
|
58
|
+
expect([...order].sort()).toEqual([...all].sort());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("places every package after the workspace packages it depends on", () => {
|
|
62
|
+
const position = new Map(order.map((d, i) => [d, i]));
|
|
63
|
+
const owner = new Map(order.map((d) => [manifest(d).name as string, d]));
|
|
64
|
+
|
|
65
|
+
for (const dir of order) {
|
|
66
|
+
const pkg = manifest(dir);
|
|
67
|
+
const deps = Object.keys({
|
|
68
|
+
...pkg.dependencies,
|
|
69
|
+
...pkg.peerDependencies,
|
|
70
|
+
...pkg.optionalDependencies,
|
|
71
|
+
});
|
|
72
|
+
for (const name of deps) {
|
|
73
|
+
const depDir = owner.get(name);
|
|
74
|
+
if (!depDir || depDir === dir) continue;
|
|
75
|
+
expect(
|
|
76
|
+
position.get(depDir)!,
|
|
77
|
+
`${pkg.name} (${dir}) publishes before its dependency ${name} (${depDir}), ` +
|
|
78
|
+
`so ${name}'s generated output will not exist when ${pkg.name} prepacks`,
|
|
79
|
+
).toBeLessThan(position.get(dir)!);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("puts the two packages that stranded chant-v0.34.0 after what they import", () => {
|
|
85
|
+
// The specific regression, named, so the failure says what broke rather
|
|
86
|
+
// than only that some invariant did.
|
|
87
|
+
expect(order.indexOf("lexicons/github")).toBeLessThan(order.indexOf("lexicons/forgejo"));
|
|
88
|
+
expect(order.indexOf("lexicons/k8s")).toBeLessThan(order.indexOf("lexicons/helm"));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Ordering alone did not fix the release. The rerun published nothing, found
|
|
94
|
+
* k8s already at 0.34.0, skipped it — and skipping the publish skipped the
|
|
95
|
+
* prepack that builds `dist/generated`, so helm failed on the same missing
|
|
96
|
+
* module. Anything another package compiles against has to be built whether or
|
|
97
|
+
* not it needs publishing.
|
|
98
|
+
*/
|
|
99
|
+
describe("packages built even when their publish is skipped", () => {
|
|
100
|
+
const built = new Set(ask("depended_on_dirs"));
|
|
101
|
+
const order = publishOrder();
|
|
102
|
+
|
|
103
|
+
it("covers every workspace dependency, transitively", () => {
|
|
104
|
+
for (const dir of order) {
|
|
105
|
+
const pkg = manifest(dir);
|
|
106
|
+
const owner = new Map(order.map((d) => [manifest(d).name as string, d]));
|
|
107
|
+
for (const name of Object.keys({
|
|
108
|
+
...pkg.dependencies,
|
|
109
|
+
...pkg.peerDependencies,
|
|
110
|
+
...pkg.optionalDependencies,
|
|
111
|
+
})) {
|
|
112
|
+
const depDir = owner.get(name);
|
|
113
|
+
if (!depDir || depDir === dir) continue;
|
|
114
|
+
expect(
|
|
115
|
+
built.has(depDir),
|
|
116
|
+
`${name} (${depDir}) is a dependency of ${pkg.name} but would not be built when ` +
|
|
117
|
+
`its own publish is skipped, so ${pkg.name} compiles against nothing`,
|
|
118
|
+
).toBe(true);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("includes the two whose skipped build failed the chant-v0.34.0 rerun", () => {
|
|
124
|
+
expect(built.has("lexicons/k8s")).toBe(true);
|
|
125
|
+
expect(built.has("lexicons/github")).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("does not build a leaf nothing depends on", () => {
|
|
129
|
+
// The set is the reason a rerun is not a full 14-package rebuild.
|
|
130
|
+
expect(built.has("lexicons/helm")).toBe(false);
|
|
131
|
+
expect(built.has("lexicons/forgejo")).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
});
|