@ozyman42/ozy-cli 0.4.9-windows-x64.0 → 0.4.9

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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/bin/ozy +209 -0
  4. package/bin/ozy-signing-agent +209 -0
  5. package/bin/ozy-ssh-keygen +209 -0
  6. package/bin/ozy-virtual-security-key +209 -0
  7. package/dist/commands/git/hosts.js +18 -0
  8. package/dist/commands/git/hosts.js.map +1 -0
  9. package/dist/commands/git/index.js +8 -0
  10. package/dist/commands/git/index.js.map +1 -0
  11. package/dist/commands/git/setup.js +145 -0
  12. package/dist/commands/git/setup.js.map +1 -0
  13. package/dist/commands/index.js +13 -0
  14. package/dist/commands/index.js.map +1 -0
  15. package/dist/commands/npm/index.js +7 -0
  16. package/dist/commands/npm/index.js.map +1 -0
  17. package/dist/commands/npm/setup/git-remote.js +66 -0
  18. package/dist/commands/npm/setup/git-remote.js.map +1 -0
  19. package/dist/commands/npm/setup/index.js +168 -0
  20. package/dist/commands/npm/setup/index.js.map +1 -0
  21. package/dist/commands/npm/setup/npm-auth.js +62 -0
  22. package/dist/commands/npm/setup/npm-auth.js.map +1 -0
  23. package/dist/commands/npm/setup/package-json-validate.js +47 -0
  24. package/dist/commands/npm/setup/package-json-validate.js.map +1 -0
  25. package/dist/commands/npm/setup/package-registry.js +20 -0
  26. package/dist/commands/npm/setup/package-registry.js.map +1 -0
  27. package/dist/commands/npm/setup/setup.test.js +95 -0
  28. package/dist/commands/npm/setup/setup.test.js.map +1 -0
  29. package/dist/commands/npm/setup/trusted-publishing.js +84 -0
  30. package/dist/commands/npm/setup/trusted-publishing.js.map +1 -0
  31. package/dist/commands/npm/setup/workflow.js +19 -0
  32. package/dist/commands/npm/setup/workflow.js.map +1 -0
  33. package/dist/commands/ssh/get-sk-credential.js +104 -0
  34. package/dist/commands/ssh/get-sk-credential.js.map +1 -0
  35. package/dist/commands/ssh/index.js +7 -0
  36. package/dist/commands/ssh/index.js.map +1 -0
  37. package/dist/commands/upgrade.js +236 -0
  38. package/dist/commands/upgrade.js.map +1 -0
  39. package/dist/common/command.js +15 -0
  40. package/dist/common/command.js.map +1 -0
  41. package/dist/common/constants.js +25 -0
  42. package/dist/common/constants.js.map +1 -0
  43. package/dist/common/effective-modules-extensions.js +13 -0
  44. package/dist/common/effective-modules-extensions.js.map +1 -0
  45. package/dist/common/log.js +4 -0
  46. package/dist/common/log.js.map +1 -0
  47. package/dist/common/render-caller-tree.js +20 -0
  48. package/dist/common/render-caller-tree.js.map +1 -0
  49. package/dist/entrypoints/ozy-signing-agent.js +48 -0
  50. package/dist/entrypoints/ozy-signing-agent.js.map +1 -0
  51. package/dist/entrypoints/ozy-ssh-keygen.js +9 -0
  52. package/dist/entrypoints/ozy-ssh-keygen.js.map +1 -0
  53. package/dist/entrypoints/ozy-virtual-security-key.js +18 -0
  54. package/dist/entrypoints/ozy-virtual-security-key.js.map +1 -0
  55. package/dist/entrypoints/ozy.js +3 -0
  56. package/dist/entrypoints/ozy.js.map +1 -0
  57. package/dist/index.js +2 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/thing.js +4 -0
  60. package/dist/internal/thing.js.map +1 -0
  61. package/dist/modules/cli/agent-client/impl.js +109 -0
  62. package/dist/modules/cli/agent-client/impl.js.map +1 -0
  63. package/dist/modules/cli/agent-client/interface.js +11 -0
  64. package/dist/modules/cli/agent-client/interface.js.map +1 -0
  65. package/dist/modules/cli/git/impl.js +124 -0
  66. package/dist/modules/cli/git/impl.js.map +1 -0
  67. package/dist/modules/cli/git/interface.js +3 -0
  68. package/dist/modules/cli/git/interface.js.map +1 -0
  69. package/dist/modules/cli/git/submodule/fix.js +257 -0
  70. package/dist/modules/cli/git/submodule/fix.js.map +1 -0
  71. package/dist/modules/cli/git/submodule/git-state.js +154 -0
  72. package/dist/modules/cli/git/submodule/git-state.js.map +1 -0
  73. package/dist/modules/cli/git/submodule/gitmodules.js +113 -0
  74. package/dist/modules/cli/git/submodule/gitmodules.js.map +1 -0
  75. package/dist/modules/cli/git/submodule/printer.js +232 -0
  76. package/dist/modules/cli/git/submodule/printer.js.map +1 -0
  77. package/dist/modules/cli/git/submodule/resolve-git-dir.js +16 -0
  78. package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +1 -0
  79. package/dist/modules/cli/github/impl.js +137 -0
  80. package/dist/modules/cli/github/impl.js.map +1 -0
  81. package/dist/modules/cli/github/interface.js +2 -0
  82. package/dist/modules/cli/github/interface.js.map +1 -0
  83. package/dist/modules/cli/index.js +9 -0
  84. package/dist/modules/cli/index.js.map +1 -0
  85. package/dist/modules/common/crypto/impl.js +133 -0
  86. package/dist/modules/common/crypto/impl.js.map +1 -0
  87. package/dist/modules/common/crypto/interface.js +2 -0
  88. package/dist/modules/common/crypto/interface.js.map +1 -0
  89. package/dist/modules/common/index.js +10 -0
  90. package/dist/modules/common/index.js.map +1 -0
  91. package/dist/modules/common/kep-map-store/impl.js +42 -0
  92. package/dist/modules/common/kep-map-store/impl.js.map +1 -0
  93. package/dist/modules/common/kep-map-store/interface.js +2 -0
  94. package/dist/modules/common/kep-map-store/interface.js.map +1 -0
  95. package/dist/modules/common/os-platform/caller-info.js +132 -0
  96. package/dist/modules/common/os-platform/caller-info.js.map +1 -0
  97. package/dist/modules/common/os-platform/impl.js +95 -0
  98. package/dist/modules/common/os-platform/impl.js.map +1 -0
  99. package/dist/modules/common/os-platform/interface.js +2 -0
  100. package/dist/modules/common/os-platform/interface.js.map +1 -0
  101. package/dist/modules/common/os-platform/virtual-hid/index.js +12 -0
  102. package/dist/modules/common/os-platform/virtual-hid/index.js.map +1 -0
  103. package/dist/modules/common/os-platform/virtual-hid/linux.js +5 -0
  104. package/dist/modules/common/os-platform/virtual-hid/linux.js.map +1 -0
  105. package/dist/modules/common/os-platform/virtual-hid/mac.js +119 -0
  106. package/dist/modules/common/os-platform/virtual-hid/mac.js.map +1 -0
  107. package/dist/modules/common/os-platform/virtual-hid/windows.js +5 -0
  108. package/dist/modules/common/os-platform/virtual-hid/windows.js.map +1 -0
  109. package/dist/modules/common/ssh-config/impl.js +116 -0
  110. package/dist/modules/common/ssh-config/impl.js.map +1 -0
  111. package/dist/modules/common/ssh-config/interface.js +10 -0
  112. package/dist/modules/common/ssh-config/interface.js.map +1 -0
  113. package/dist/modules/ssh-agent/index.js +8 -0
  114. package/dist/modules/ssh-agent/index.js.map +1 -0
  115. package/dist/modules/ssh-agent/session/impl.js +265 -0
  116. package/dist/modules/ssh-agent/session/impl.js.map +1 -0
  117. package/dist/modules/ssh-agent/session/interface-rpc.js +20 -0
  118. package/dist/modules/ssh-agent/session/interface-rpc.js.map +1 -0
  119. package/dist/modules/ssh-agent/session/interface.js +32 -0
  120. package/dist/modules/ssh-agent/session/interface.js.map +1 -0
  121. package/dist/modules/ssh-agent/session/passkey-prf-page.js +204 -0
  122. package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +1 -0
  123. package/dist/modules/ssh-agent/session/prf-flow.js +103 -0
  124. package/dist/modules/ssh-agent/session/prf-flow.js.map +1 -0
  125. package/dist/modules/ssh-agent/ssh-agent/impl.js +117 -0
  126. package/dist/modules/ssh-agent/ssh-agent/impl.js.map +1 -0
  127. package/dist/modules/ssh-agent/ssh-agent/interface.js +2 -0
  128. package/dist/modules/ssh-agent/ssh-agent/interface.js.map +1 -0
  129. package/dist/scripts/build.js +444 -0
  130. package/dist/scripts/build.js.map +1 -0
  131. package/dist/scripts/check-npm-version.js +8 -0
  132. package/dist/scripts/check-npm-version.js.map +1 -0
  133. package/dist/scripts/constants.js +12 -0
  134. package/dist/scripts/constants.js.map +1 -0
  135. package/dist/scripts/kill-existing-agent.js +21 -0
  136. package/dist/scripts/kill-existing-agent.js.map +1 -0
  137. package/dist/scripts/publish.js +83 -0
  138. package/dist/scripts/publish.js.map +1 -0
  139. package/install.js +209 -0
  140. package/package.json +58 -11
  141. package/src/commands/git/hosts.ts +20 -0
  142. package/src/commands/git/index.ts +9 -0
  143. package/src/commands/git/setup.ts +182 -0
  144. package/src/commands/index.ts +15 -0
  145. package/src/commands/npm/index.ts +8 -0
  146. package/src/commands/npm/setup/git-remote.ts +78 -0
  147. package/src/commands/npm/setup/index.ts +189 -0
  148. package/src/commands/npm/setup/npm-auth.ts +58 -0
  149. package/src/commands/npm/setup/package-json-validate.ts +68 -0
  150. package/src/commands/npm/setup/package-registry.ts +24 -0
  151. package/src/commands/npm/setup/publish-workflow.yml +35 -0
  152. package/src/commands/npm/setup/setup.test.ts +117 -0
  153. package/src/commands/npm/setup/trusted-publishing.ts +106 -0
  154. package/src/commands/npm/setup/workflow.ts +20 -0
  155. package/src/commands/ssh/get-sk-credential.ts +110 -0
  156. package/src/commands/ssh/index.ts +8 -0
  157. package/src/commands/upgrade.ts +261 -0
  158. package/src/common/command.ts +16 -0
  159. package/src/common/constants.ts +26 -0
  160. package/src/common/effective-modules-extensions.ts +24 -0
  161. package/src/common/log.ts +3 -0
  162. package/src/common/render-caller-tree.ts +22 -0
  163. package/src/entrypoints/ozy-signing-agent.ts +48 -0
  164. package/src/entrypoints/ozy-ssh-keygen.ts +10 -0
  165. package/src/entrypoints/ozy-virtual-security-key.ts +21 -0
  166. package/src/entrypoints/ozy.ts +2 -0
  167. package/src/index.ts +1 -0
  168. package/src/internal/thing.ts +3 -0
  169. package/src/modules/cli/agent-client/impl.ts +152 -0
  170. package/src/modules/cli/agent-client/interface.ts +17 -0
  171. package/src/modules/cli/git/impl.ts +133 -0
  172. package/src/modules/cli/git/interface.ts +23 -0
  173. package/src/modules/cli/git/submodule/fix.ts +298 -0
  174. package/src/modules/cli/git/submodule/git-state.ts +217 -0
  175. package/src/modules/cli/git/submodule/gitmodules.ts +126 -0
  176. package/src/modules/cli/git/submodule/printer.ts +256 -0
  177. package/src/modules/cli/git/submodule/resolve-git-dir.ts +14 -0
  178. package/src/modules/cli/github/impl.ts +169 -0
  179. package/src/modules/cli/github/interface.ts +29 -0
  180. package/src/modules/cli/index.ts +16 -0
  181. package/src/modules/common/crypto/impl.ts +173 -0
  182. package/src/modules/common/crypto/interface.ts +20 -0
  183. package/src/modules/common/index.ts +19 -0
  184. package/src/modules/common/kep-map-store/impl.ts +47 -0
  185. package/src/modules/common/kep-map-store/interface.ts +9 -0
  186. package/src/modules/common/os-platform/caller-info.ts +152 -0
  187. package/src/modules/common/os-platform/impl.ts +91 -0
  188. package/src/modules/common/os-platform/interface.ts +16 -0
  189. package/src/modules/common/os-platform/virtual-hid/index.ts +12 -0
  190. package/src/modules/common/os-platform/virtual-hid/linux.ts +7 -0
  191. package/src/modules/common/os-platform/virtual-hid/mac.ts +150 -0
  192. package/src/modules/common/os-platform/virtual-hid/windows.ts +7 -0
  193. package/src/modules/common/ssh-config/impl.ts +130 -0
  194. package/src/modules/common/ssh-config/interface.ts +33 -0
  195. package/src/modules/ssh-agent/index.ts +13 -0
  196. package/src/modules/ssh-agent/session/agent-notes.md +210 -0
  197. package/src/modules/ssh-agent/session/design.md +2 -0
  198. package/src/modules/ssh-agent/session/html-refactor.md +23 -0
  199. package/src/modules/ssh-agent/session/impl.ts +328 -0
  200. package/src/modules/ssh-agent/session/interface-rpc.ts +24 -0
  201. package/src/modules/ssh-agent/session/interface.ts +62 -0
  202. package/src/modules/ssh-agent/session/passkey-prf-page.ts +224 -0
  203. package/src/modules/ssh-agent/session/prf-flow.ts +152 -0
  204. package/src/modules/ssh-agent/ssh-agent/impl.ts +136 -0
  205. package/src/modules/ssh-agent/ssh-agent/interface.ts +9 -0
  206. package/src/scripts/build.ts +559 -0
  207. package/src/scripts/check-npm-version.ts +7 -0
  208. package/src/scripts/command-launcher.js.ejs +186 -0
  209. package/src/scripts/constants.ts +11 -0
  210. package/src/scripts/kill-existing-agent.ts +22 -0
  211. package/src/scripts/publish.ts +89 -0
  212. package/src/scripts/windows-bin-shims.md +1104 -0
  213. package/multi-call-binary.exe +0 -0
@@ -0,0 +1,559 @@
1
+ import {
2
+ access,
3
+ chmod,
4
+ cp,
5
+ mkdir,
6
+ readdir,
7
+ readFile,
8
+ rename,
9
+ rm,
10
+ writeFile,
11
+ } from "node:fs/promises";
12
+ import { basename, dirname, relative, resolve } from "node:path";
13
+ import { Schema } from "effect";
14
+ import {
15
+ applyEdits,
16
+ modify,
17
+ parse as parseJsonc,
18
+ printParseErrorCode,
19
+ type FormattingOptions,
20
+ type ParseError,
21
+ } from "jsonc-parser";
22
+ import ejs from "ejs";
23
+ import {
24
+ BIN_DIR,
25
+ INSTALL_SCRIPT,
26
+ LICENSE_FILE,
27
+ MULTI_CALL_BINARY,
28
+ NPM_PACKAGE_SPEC,
29
+ OUTDIR,
30
+ PUBLISH_ORDER_FILE,
31
+ README_FILE,
32
+ TARBALL_EXTENSION,
33
+ WINDOWS_EXECUTABLE_SUFFIX,
34
+ WORKDIR,
35
+ } from "./constants";
36
+
37
+ /**
38
+ * Jive library builder.
39
+ *
40
+ * Source package:
41
+ * src/index.ts Optional package TypeScript ESM entrypoint.
42
+ * src/entrypoints/* Command entrypoints included in the generated multi-call binary.
43
+ * src/agents/* Agent entrypoints, if present, included in the same multi-call binary.
44
+ *
45
+ * Generated base package:
46
+ * package.json Stripped package manifest plus generated command launchers.
47
+ * src/ Source tree for package consumers.
48
+ * dist/ TypeScript-emitted library files, when a source entrypoint exists.
49
+ * bin/<command> Generated JS launcher that self-repairs to the platform multi-call executable.
50
+ *
51
+ * Generated platform packages:
52
+ * package.json Same package name with platform-specific prerelease version and os/cpu.
53
+ * multi-call-binary Native Bun-compiled multi-call executable (`multi-call-binary.exe` on Windows).
54
+ *
55
+ * The dist folder contains packed .tgz files plus publish-order.json. The .tgz file name is the
56
+ * npm dist-tag to publish: latest.tgz for the base package and one <platform>.tgz per platform package.
57
+ *
58
+ * Files in the following directories will be non importable from dependents
59
+ * src/entrypoints/*
60
+ * src/agents/*
61
+ * src/internal
62
+ * So for example `import * as a from "my-package/internal"` will fail to compile
63
+ */
64
+
65
+ const COMMAND_SOURCE_DIRS = ["src/entrypoints", "src/agents"] as const;
66
+ const PRIVATE_SOURCE_DIRS = [...COMMAND_SOURCE_DIRS, "src/internal"] as const;
67
+ const SOURCE_ENTRYPOINT = "src/index.ts";
68
+ const SOURCE_SUFFIX = ".ts";
69
+ const BASE_PACKAGE_ROOT_FILES = [README_FILE, LICENSE_FILE] as const;
70
+ const COMMAND_LAUNCHER_TEMPLATE = "src/scripts/command-launcher.js.ejs";
71
+ const TSCONFIG_INCLUDE = ["src/**/*.ts"];
72
+ const TSCONFIG_EXCLUDE = [OUTDIR, "node_modules"];
73
+ const JSONC_FORMATTING_OPTIONS = {
74
+ insertSpaces: true,
75
+ tabSize: 2,
76
+ eol: "\n",
77
+ insertFinalNewline: true,
78
+ } as const satisfies FormattingOptions;
79
+
80
+ type Platform = {
81
+ readonly id: string;
82
+ readonly target: Bun.Build.CompileTarget;
83
+ readonly os: string;
84
+ readonly cpu: string;
85
+ };
86
+
87
+ const PLATFORMS = [
88
+ { id: "darwin-arm64", target: "bun-darwin-arm64", os: "darwin", cpu: "arm64" },
89
+ { id: "darwin-x64", target: "bun-darwin-x64", os: "darwin", cpu: "x64" },
90
+ { id: "linux-arm64", target: "bun-linux-arm64", os: "linux", cpu: "arm64" },
91
+ { id: "linux-x64", target: "bun-linux-x64", os: "linux", cpu: "x64" },
92
+ { id: "windows-x64", target: "bun-windows-x64", os: "win32", cpu: "x64" },
93
+ ] as const satisfies readonly Platform[];
94
+
95
+ const PackageJson = Schema.Struct({
96
+ name: Schema.String,
97
+ version: Schema.String,
98
+ description: Schema.optional(Schema.String),
99
+ type: Schema.optional(Schema.Literal("module")),
100
+ license: Schema.optional(Schema.String),
101
+ author: Schema.optional(Schema.Unknown),
102
+ contributors: Schema.optional(Schema.Unknown),
103
+ keywords: Schema.optional(Schema.Unknown),
104
+ homepage: Schema.optional(Schema.String),
105
+ bugs: Schema.optional(Schema.Unknown),
106
+ funding: Schema.optional(Schema.Unknown),
107
+ repository: Schema.optional(Schema.Unknown),
108
+ publishConfig: Schema.optional(Schema.Unknown),
109
+ dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
110
+ });
111
+ type PackageJson = Schema.Schema.Type<typeof PackageJson>;
112
+
113
+ type CommandSource = {
114
+ readonly command: string;
115
+ readonly sourcePath: string;
116
+ };
117
+
118
+ type BasePackageRootFile = {
119
+ readonly fileName: string;
120
+ readonly sourcePath: string;
121
+ };
122
+
123
+ class TscFailed extends Error {
124
+ constructor(readonly exitCode: number) {
125
+ super(`tsc failed with exit code ${exitCode}`);
126
+ }
127
+ }
128
+
129
+ const rootDir = process.cwd();
130
+ const outDir = resolve(rootDir, OUTDIR);
131
+ const workDir = resolve(outDir, WORKDIR);
132
+ const multiCallEntrypointPath = resolve(workDir, "multi-call.ts");
133
+ const packageJson = await readPackageJson();
134
+ const commandSources = await collectCommandSources();
135
+ const basePackageRootFiles = await collectBasePackageRootFiles();
136
+ const sourceEntrypoint = resolve(rootDir, SOURCE_ENTRYPOINT);
137
+ const hasSourceEntrypoint = await pathExists(sourceEntrypoint);
138
+ const hasCommandSources = commandSources.length > 0;
139
+
140
+ await rm(outDir, { recursive: true, force: true });
141
+ await mkdir(outDir, { recursive: true });
142
+ await mkdir(workDir, { recursive: true });
143
+
144
+ try {
145
+ if (hasCommandSources) {
146
+ await writeFile(multiCallEntrypointPath, generateMultiCallEntrypoint(multiCallEntrypointPath));
147
+ }
148
+
149
+ await buildBasePackage();
150
+
151
+ if (hasCommandSources) {
152
+ const platformPackages = await Promise.all(PLATFORMS.map(buildPlatformPackage));
153
+ for (const { packageDir, tag } of platformPackages) {
154
+ await packPackage(packageDir, tag);
155
+ }
156
+ }
157
+
158
+ await writePublishOrder();
159
+ } catch (error) {
160
+ if (error instanceof TscFailed) {
161
+ process.exitCode = error.exitCode;
162
+ } else {
163
+ throw error;
164
+ }
165
+ } finally {
166
+ await rm(workDir, { recursive: true, force: true });
167
+ }
168
+
169
+ async function readPackageJson(): Promise<PackageJson> {
170
+ const parsed = JSON.parse(await readFile(resolve(rootDir, "package.json"), "utf-8"));
171
+ return Schema.decodeUnknownSync(PackageJson)(parsed);
172
+ }
173
+
174
+ async function collectCommandSources(): Promise<readonly CommandSource[]> {
175
+ const commands = new Map<string, CommandSource>();
176
+
177
+ for (const sourceDir of COMMAND_SOURCE_DIRS) {
178
+ const absoluteSourceDir = resolve(rootDir, sourceDir);
179
+ if (!(await pathExists(absoluteSourceDir))) continue;
180
+
181
+ for (const child of (await readdir(absoluteSourceDir)).sort()) {
182
+ if (!child.endsWith(SOURCE_SUFFIX)) continue;
183
+
184
+ const command = basename(child, SOURCE_SUFFIX);
185
+ const sourcePath = resolve(absoluteSourceDir, child);
186
+ const existing = commands.get(command);
187
+ if (existing) {
188
+ throw new Error(`Duplicate command "${command}" in ${existing.sourcePath} and ${sourcePath}`);
189
+ }
190
+ commands.set(command, { command, sourcePath });
191
+ }
192
+ }
193
+
194
+ return [...commands.values()].sort((a, b) => a.command.localeCompare(b.command));
195
+ }
196
+
197
+ async function buildBasePackage(): Promise<void> {
198
+ const packageDir = resolve(workDir, "base-package");
199
+ await mkdir(packageDir, { recursive: true });
200
+
201
+ if (hasSourceEntrypoint) {
202
+ await transpileLibrarySource(packageDir);
203
+ }
204
+
205
+ await cp(resolve(rootDir, "src"), resolve(packageDir, "src"), { recursive: true });
206
+ if (hasCommandSources) {
207
+ await writeCommandLaunchers(packageDir);
208
+ }
209
+
210
+ await copyBasePackageRootFiles(packageDir);
211
+ await writePackageJson(packageDir, generateBasePackageJson());
212
+ await packPackage(packageDir, "latest");
213
+ }
214
+
215
+ async function buildPlatformPackage(platform: Platform): Promise<{ packageDir: string; tag: string }> {
216
+ const packageDir = resolve(workDir, platform.id);
217
+ await mkdir(packageDir, { recursive: true });
218
+
219
+ const binaryPath = resolve(packageDir, platformBinaryFile(platform));
220
+ const buildResult = await Bun.build({
221
+ entrypoints: [multiCallEntrypointPath],
222
+ compile: {
223
+ outfile: binaryPath,
224
+ target: platform.target,
225
+ },
226
+ sourcemap: "inline",
227
+ });
228
+ assertBuildSucceeded(buildResult, `compile ${platform.id} multi-call binary`);
229
+
230
+ await chmod(binaryPath, 0o755);
231
+
232
+ await writePackageJson(packageDir, generatePlatformPackageJson(platform));
233
+ return { packageDir, tag: platform.id };
234
+ }
235
+
236
+ async function writeCommandLaunchers(packageDir: string): Promise<void> {
237
+ const binDir = resolve(packageDir, BIN_DIR);
238
+ await mkdir(binDir, { recursive: true });
239
+
240
+ const commandLauncher = await renderCommandLauncher(false);
241
+ const installLauncher = await renderCommandLauncher(true);
242
+ await Promise.all([
243
+ ...commandSources.map(({ command }) => writeFile(resolve(binDir, command), commandLauncher, { mode: 0o755 })),
244
+ writeFile(resolve(packageDir, INSTALL_SCRIPT), installLauncher, { mode: 0o755 }),
245
+ ]);
246
+ }
247
+
248
+ function assertBuildSucceeded(result: Bun.BuildOutput, label: string): void {
249
+ if (result.success) return;
250
+
251
+ for (const log of result.logs) {
252
+ console.error(log);
253
+ }
254
+ throw new Error(`Failed to ${label}`);
255
+ }
256
+
257
+ async function writePackageJson(packageDir: string, value: Record<string, unknown>): Promise<void> {
258
+ await writeFile(resolve(packageDir, "package.json"), `${JSON.stringify(value, null, 2)}\n`);
259
+ }
260
+
261
+ async function collectBasePackageRootFiles(): Promise<readonly BasePackageRootFile[]> {
262
+ const files: BasePackageRootFile[] = [];
263
+ for (const fileName of BASE_PACKAGE_ROOT_FILES) {
264
+ const sourcePath = resolve(rootDir, fileName);
265
+ if (await pathExists(sourcePath)) files.push({ fileName, sourcePath });
266
+ }
267
+ return files;
268
+ }
269
+
270
+ async function copyBasePackageRootFiles(packageDir: string): Promise<void> {
271
+ await Promise.all(
272
+ basePackageRootFiles.map(({ fileName, sourcePath }) => cp(sourcePath, resolve(packageDir, fileName)))
273
+ );
274
+ }
275
+
276
+ async function writePublishOrder(): Promise<void> {
277
+ const orderedTarballs = [
278
+ ...(hasCommandSources ? PLATFORMS.map((platform) => tarballFileName(platform.id)) : []),
279
+ tarballFileName("latest"),
280
+ ];
281
+ await writeFile(resolve(outDir, PUBLISH_ORDER_FILE), `${JSON.stringify(orderedTarballs, null, 2)}\n`);
282
+ }
283
+
284
+ async function transpileLibrarySource(packageDir: string): Promise<void> {
285
+ const rootTsconfigPath = resolve(rootDir, "tsconfig.json");
286
+ await ensureTsconfigSourceBoundary(rootTsconfigPath);
287
+
288
+ const tscPath = resolve(rootDir, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
289
+
290
+ // TODO: tool should just come bundled with typescript.
291
+ if (!(await pathExists(tscPath))) {
292
+ throw new Error("TypeScript is required to emit library source. Install typescript as a devDependency.");
293
+ }
294
+
295
+ const exitCode = await Bun.spawn(
296
+ [
297
+ tscPath,
298
+ "--project",
299
+ rootTsconfigPath,
300
+ "--rootDir",
301
+ resolve(rootDir, "src"),
302
+ "--outDir",
303
+ resolve(packageDir, "dist"),
304
+ "--pretty",
305
+ "true",
306
+ "--noEmit",
307
+ "false",
308
+ "--declaration",
309
+ "false",
310
+ "--declarationMap",
311
+ "false",
312
+ "--sourceMap",
313
+ "--rewriteRelativeImportExtensions",
314
+ ],
315
+ {
316
+ stdin: "inherit",
317
+ stdout: "inherit",
318
+ stderr: "inherit",
319
+ }
320
+ ).exited;
321
+ if (exitCode !== 0) {
322
+ throw new TscFailed(exitCode);
323
+ }
324
+ }
325
+
326
+ async function ensureTsconfigSourceBoundary(tsconfigPath: string): Promise<void> {
327
+ const originalText = await readFile(tsconfigPath, "utf-8");
328
+ assertValidJsonc(tsconfigPath, originalText);
329
+
330
+ const withInclude = applyEdits(
331
+ originalText,
332
+ modify(originalText, ["include"], TSCONFIG_INCLUDE, {
333
+ formattingOptions: JSONC_FORMATTING_OPTIONS,
334
+ getInsertionIndex: (properties) => insertAfter(properties, "compilerOptions"),
335
+ })
336
+ );
337
+ const withExclude = applyEdits(
338
+ withInclude,
339
+ modify(withInclude, ["exclude"], TSCONFIG_EXCLUDE, {
340
+ formattingOptions: JSONC_FORMATTING_OPTIONS,
341
+ getInsertionIndex: (properties) => insertAfter(properties, "include"),
342
+ })
343
+ );
344
+
345
+ assertValidJsonc(tsconfigPath, withExclude);
346
+ if (withExclude !== originalText) {
347
+ await writeFile(tsconfigPath, withExclude);
348
+ }
349
+ }
350
+
351
+ function assertValidJsonc(filePath: string, text: string): void {
352
+ const errors: ParseError[] = [];
353
+ parseJsonc(text, errors, { allowTrailingComma: true });
354
+ if (errors.length === 0) return;
355
+
356
+ const details = errors
357
+ .map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`)
358
+ .join(", ");
359
+ throw new Error(`${filePath} is invalid JSONC: ${details}`);
360
+ }
361
+
362
+ function insertAfter(properties: readonly string[], property: string): number {
363
+ const index = properties.indexOf(property);
364
+ return index === -1 ? properties.length : index + 1;
365
+ }
366
+
367
+ function generateBasePackageJson(): Record<string, unknown> {
368
+ const generated: Record<string, unknown> = {};
369
+ for (const key of [
370
+ "name",
371
+ "version",
372
+ "description",
373
+ "type",
374
+ "license",
375
+ "author",
376
+ "contributors",
377
+ "keywords",
378
+ "homepage",
379
+ "bugs",
380
+ "funding",
381
+ "repository",
382
+ "publishConfig",
383
+ ] as const) {
384
+ const value = packageJson[key];
385
+ if (value !== undefined) generated[key] = value;
386
+ }
387
+
388
+ generated["type"] = packageJson["type"] ?? "module";
389
+ if (hasSourceEntrypoint) {
390
+ generated["main"] = "./dist/index.js";
391
+ generated["module"] = "./dist/index.js";
392
+ generated["types"] = "./src/index.ts";
393
+ generated["exports"] = {
394
+ ".": {
395
+ types: "./src/index.ts",
396
+ import: "./dist/index.js",
397
+ },
398
+ ...Object.fromEntries(
399
+ PRIVATE_SOURCE_DIRS.flatMap((sourceDir) => {
400
+ const subpath = sourceDir.replace(/^src\//, "");
401
+ return [
402
+ [`./${subpath}`, null],
403
+ [`./${subpath}/*`, null],
404
+ ];
405
+ })
406
+ ),
407
+ "./*": {
408
+ types: "./src/*.ts",
409
+ import: "./dist/*.js",
410
+ },
411
+ };
412
+ }
413
+ const files = [...basePackageRootFiles.map(({ fileName }) => fileName), ...(hasSourceEntrypoint ? ["dist", "src"] : [])];
414
+ if (hasCommandSources) files.push(BIN_DIR, INSTALL_SCRIPT);
415
+ if (files.length > 0) generated["files"] = files;
416
+ if (hasCommandSources) {
417
+ generated["scripts"] = { postinstall: `node ./${INSTALL_SCRIPT}` };
418
+ generated["bin"] = generateBaseBinEntries();
419
+ }
420
+ if (packageJson["dependencies"]) generated["dependencies"] = packageJson["dependencies"];
421
+ if (hasCommandSources) {
422
+ generated["optionalDependencies"] = Object.fromEntries(
423
+ PLATFORMS.map((platform) => [
424
+ platformAliasName(platform),
425
+ `npm:${packageJson.name}@${platformVersion(platform)}`,
426
+ ])
427
+ );
428
+ }
429
+
430
+ return generated;
431
+ }
432
+
433
+ function generatePlatformPackageJson(platform: Platform): Record<string, unknown> {
434
+ const generated: Record<string, unknown> = {
435
+ name: packageJson.name,
436
+ version: platformVersion(platform),
437
+ type: packageJson.type ?? "module",
438
+ os: [platform.os],
439
+ cpu: [platform.cpu],
440
+ files: [platformBinaryFile(platform)],
441
+ };
442
+
443
+ for (const key of ["description", "license", "repository", "publishConfig"] as const) {
444
+ const value = packageJson[key];
445
+ if (value !== undefined) generated[key] = value;
446
+ }
447
+
448
+ return generated;
449
+ }
450
+
451
+ function platformVersion(platform: Platform): string {
452
+ return `${packageJson.version}-${platform.id}.0`;
453
+ }
454
+
455
+ function platformAliasName(platform: Platform): string {
456
+ return `${packageJson.name}-${platform.id}`;
457
+ }
458
+
459
+ function platformBinaryFile(platform: Platform): string {
460
+ return platform.os === "win32" ? `${MULTI_CALL_BINARY}${WINDOWS_EXECUTABLE_SUFFIX}` : MULTI_CALL_BINARY;
461
+ }
462
+
463
+ function generateBaseBinEntries(): Record<string, string> {
464
+ return Object.fromEntries(commandSources.map(({ command }) => [command, `./${BIN_DIR}/${command}`]));
465
+ }
466
+
467
+ async function packPackage(packageDir: string, tag: string): Promise<void> {
468
+ const npmCacheDir = resolve(workDir, "npm-cache");
469
+ const output = await Bun.$`bunx ${NPM_PACKAGE_SPEC} pack ${packageDir} --pack-destination ${outDir} --ignore-scripts --cache ${npmCacheDir} --loglevel error`.text();
470
+ const generatedFile = output.trim().split("\n").at(-1);
471
+ if (!generatedFile) throw new Error(`npm pack did not report an output file for ${packageDir}`);
472
+
473
+ const sourcePath = resolve(outDir, generatedFile);
474
+ const taggedPath = resolve(outDir, tarballFileName(tag));
475
+ await rm(taggedPath, { force: true });
476
+ await rename(sourcePath, taggedPath);
477
+ console.log(`${packageDir} -> ${relative(rootDir, taggedPath)}`);
478
+ }
479
+
480
+ function tarballFileName(tag: string): string {
481
+ return `${tag}${TARBALL_EXTENSION}`;
482
+ }
483
+
484
+ function generateMultiCallEntrypoint(dispatcherPath: string): string {
485
+ const dispatcherDir = dirname(dispatcherPath);
486
+ const loaders = commandSources
487
+ .map(({ command, sourcePath }) => {
488
+ const importPath = toImportPath(relative(dispatcherDir, sourcePath));
489
+ return ` ${JSON.stringify(command)}: () => import(${JSON.stringify(importPath)}),`;
490
+ })
491
+ .join("\n");
492
+
493
+ return `import { basename } from "node:path";
494
+ // console.log("multi-call argv", JSON.stringify({ argv0: process.argv0, argv: process.argv }));
495
+ const commandLoaders: Record<string, () => Promise<unknown>> = {
496
+ ${loaders}
497
+ };
498
+
499
+ const invokedPath = process.argv0 || process.argv[1] || "";
500
+ const invokedName = basename(invokedPath);
501
+ const command = process.platform === "win32" ? invokedName.replace(/\\.exe$/i, "") : invokedName;
502
+ const loadCommand = commandLoaders[command];
503
+
504
+ if (!loadCommand) {
505
+ console.error(\`Unknown ${packageJson.name} command "\${command}". Expected one of: \${Object.keys(commandLoaders).sort().join(", ")}\`);
506
+ process.exit(1);
507
+ }
508
+
509
+ await loadCommand();
510
+ `;
511
+ }
512
+
513
+ async function renderCommandLauncher(isInstallScript: boolean): Promise<string> {
514
+ const platformMap = Object.fromEntries(
515
+ PLATFORMS.map((platform) => [`${platform.os}-${platform.cpu}`, platform.id])
516
+ );
517
+ const platformPackages = Object.fromEntries(
518
+ PLATFORMS.map((platform) => [platform.id, platformAliasName(platform)])
519
+ );
520
+ const platformBinaries = Object.fromEntries(
521
+ PLATFORMS.map((platform) => [platform.id, platformBinaryFile(platform)])
522
+ );
523
+ const commandNames = commandSources.map(({ command }) => command);
524
+ const template = await readFile(resolve(rootDir, COMMAND_LAUNCHER_TEMPLATE), "utf-8");
525
+ return ejs.render(
526
+ template,
527
+ {
528
+ commandNames,
529
+ binDir: BIN_DIR,
530
+ installScript: INSTALL_SCRIPT,
531
+ isInstallScript,
532
+ json: (value: unknown) => JSON.stringify(value, null, 2),
533
+ platformBinaries,
534
+ platformMap,
535
+ platformPackages,
536
+ windowsExecutableSuffix: WINDOWS_EXECUTABLE_SUFFIX,
537
+ },
538
+ { async: false }
539
+ );
540
+ }
541
+
542
+ function toImportPath(path: string): string {
543
+ const normalized = toPosixPath(path);
544
+ if (normalized.startsWith(".") || normalized.startsWith("/")) return normalized;
545
+ return `./${normalized}`;
546
+ }
547
+
548
+ function toPosixPath(path: string): string {
549
+ return path.split("\\").join("/");
550
+ }
551
+
552
+ async function pathExists(path: string): Promise<boolean> {
553
+ try {
554
+ await access(path);
555
+ return true;
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
@@ -0,0 +1,7 @@
1
+ const { name, version } = await Bun.file("package.json").json();
2
+
3
+ const res = await fetch(`https://registry.npmjs.org/${name}/${version}`);
4
+ if (res.status === 200) {
5
+ console.error(`✗ ${name}@${version} is already published on npm`);
6
+ process.exit(1);
7
+ }