Open History to see the last commit
1// Checks the publishable workspace packages before a release, and in CI.
2//
3// - Every publishable alchemy package (`packages/*`) carries the npm metadata
4// a release needs.
5// - The alchemy packages share one version, and so do the distilled packages
6// (`submodules/distilled/packages/*`): each group is released in lockstep,
7// and `scripts/release/prepare.ts` bumps the alchemy packages from theirs.
8//
9// Usage: node scripts/validate-publish-packages.ts
10import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
11import * as NodeServices from "@effect/platform-node/NodeServices";
12import { Command } from "effect/cli";
13import * as Console from "effect/Console";
14import * as Data from "effect/Data";
15import * as Effect from "effect/Effect";
16import * as Path from "effect/Path";
17import { publishablePackages, sharedVersion, type WorkspacePackage } from "./package-manifest.ts";
18
19export class MissingPublishMetadata extends Data.TaggedError("MissingPublishMetadata")<{
20 readonly message: string;
21 readonly dir: string;
22 readonly fields: ReadonlyArray<string>;
23}> {}
24
25const REQUIRED = [
26 "name",
27 "version",
28 "description",
29 "homepage",
30 "license",
31 "author",
32 "keywords",
33 "repository",
34 "bugs",
35 "files",
36 "exports",
37] as const;
38
39const assertMetadata = Effect.fn(function* (packages: ReadonlyArray<WorkspacePackage>) {
40 for (const { dir, raw } of packages) {
41 const fields: Array<string> = REQUIRED.filter((field) => raw[field] == null);
42 const publishConfig = raw.publishConfig as { access?: unknown } | undefined;
43 if (publishConfig?.access !== "public") fields.push("publishConfig.access=public");
44 if (fields.length > 0) {
45 return yield* new MissingPublishMetadata({
46 message: `${dir}: missing publish metadata: ${fields.join(", ")}`,
47 dir,
48 fields,
49 });
50 }
51 }
52});
53
54const assertOneVersion = Effect.fn(function* (
55 group: string,
56 packages: ReadonlyArray<WorkspacePackage>,
57) {
58 const version = yield* sharedVersion(packages);
59 yield* Console.log(`Validated ${packages.length} ${group} packages at ${version}`);
60});
61
62const command = Command.make(
63 "validate-publish-packages",
64 {},
65 Effect.fn(function* () {
66 const path = yield* Path.Path;
67 const root = path.resolve(import.meta.dirname, "..");
68
69 const alchemy = yield* publishablePackages(root, "packages");
70 yield* assertMetadata(alchemy);
71 yield* assertOneVersion("alchemy", alchemy);
72 yield* assertOneVersion(
73 "distilled",
74 yield* publishablePackages(root, "submodules/distilled/packages"),
75 );
76 }),
77).pipe(Command.withDescription("Check publish metadata and lockstep versions"));
78
79Command.run(command, { version: "0.0.0" }).pipe(
80 Effect.provide(NodeServices.layer),
81 NodeRuntime.runMain,
82);