1#!/usr/bin/env node
2import { PlatformServices, runMain } from "alchemy/Util/PlatformServices";
3import * as Config from "effect/Config";
4import * as Effect from "effect/Effect";
5import * as Option from "effect/Option";
6import * as Layer from "effect/Layer";
7import { CliConfig, CliError, Command, Flag, GlobalFlag } from "effect/cli";
8import * as FetchHttpClient from "effect/http/FetchHttpClient";
9import pkg from "../../package.json" with { type: "json" };
10import { Group, pack } from "./pack.ts";
11import { publish } from "./publish.ts";
12
13const groupFlag = Flag.String("group").pipe(
14 Flag.withDescription(
15 "Display group and directory glob, as NAME=GLOB or NAME[Collapsed]=GLOB (repeatable), e.g. --group Alchemy=./packages/*",
16 ),
17 Flag.withSchema(Group),
18 Flag.atLeast(1),
19);
20
21const registryFlag = Flag.String("registry").pipe(
22 Flag.withDescription(
23 "Registry origin used for install URLs. Falls back to PKG_REGISTRY, then https://pkg.alchemy.run",
24 ),
25 Flag.withFallbackConfig(Config.String("PKG_REGISTRY")),
26 Flag.withDefault("https://pkg.alchemy.run"),
27);
28
29const outFlag = Flag.String("out").pipe(
30 Flag.withDescription("Directory to write tarballs and the manifest into"),
31 Flag.withDefault(".pkg"),
32);
33
34export const packCommand = Command.make(
35 "pack",
36 {
37 group: groupFlag,
38 registry: registryFlag,
39 out: outFlag,
40 since: Flag.String("since").pipe(
41 Flag.withDescription(
42 "Compare HEAD with this Git ref; defaults to the PR base in Actions",
43 ),
44 Flag.optional,
45 ),
46 all: Flag.Boolean("all").pipe(
47 Flag.withDefault(false),
48 Flag.withDescription(
49 "Pack every configured package regardless of changes",
50 ),
51 ),
52 rebuildAllPaths: Flag.String("rebuild-all-path").pipe(
53 Flag.withDescription(
54 "Additional exact path or directory/** forcing all packages (repeatable)",
55 ),
56 Flag.atLeast(0),
57 ),
58 },
59 ({ group, registry, out, since, all, rebuildAllPaths }) =>
60 Effect.gen(function* () {
61 const cwd = yield* Effect.sync(() => process.cwd());
62 yield* pack({
63 cwd,
64 groups: group,
65 registry,
66 out,
67 since: Option.getOrUndefined(since),
68 all,
69 rebuildAllPaths,
70 });
71 }),
72).pipe(
73 Command.withDescription(
74 "Pack workspace packages into reproducible tarballs with dependencies rewritten to registry URLs",
75 ),
76 Command.withExamples([
77 {
78 command:
79 "pkg pack --group 'alchemy=./packages/alchemy' --group '@alchemy.run[Collapsed]=./packages/{better-auth,pkg}' --group '@distilled.cloud[Collapsed]=./submodules/distilled/packages/*'",
80 },
81 ]),
82);
83
84const dirFlag = Flag.String("dir").pipe(
85 Flag.withDescription("Directory written by pkg pack"),
86 Flag.withDefault(".pkg"),
87);
88
89export const publishCommand = Command.make(
90 "publish",
91 { registry: registryFlag, dir: dirFlag },
92 ({ registry, dir }) =>
93 Effect.gen(function* () {
94 const cwd = yield* Effect.sync(() => process.cwd());
95 yield* publish({ cwd, dir, registry });
96 }),
97).pipe(
98 Command.withDescription(
99 "Publish a pkg pack directory from the current GitHub Actions job, after its manifest artifact has been uploaded",
100 ),
101 Command.withExamples([
102 { command: "pkg publish --registry https://pkg.alchemy.run" },
103 ]),
104);
105
106export const root = Command.make("pkg", {}, () =>
107 Effect.fail(new CliError.ShowHelp({ commandPath: ["pkg"], errors: [] })),
108).pipe(
109 Command.withDescription(
110 "Pack and publish preview packages for pull requests.",
111 ),
112 Command.withSubcommands([packCommand, publishCommand]),
113);
114
115Command.run(root, { version: pkg.version }).pipe(
116 Effect.provide(
117 Layer.mergeAll(
118 PlatformServices,
119 FetchHttpClient.layer,
120 CliConfig.layer({
121 builtIns: [GlobalFlag.Help, GlobalFlag.Version],
122 }),
123 ),
124 ),
125 runMain,
126);