@decohere/create 0.0.1-dev.45 → 0.0.1-dev.47

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.
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pack `@decohere/create` for the builder image with production deps bundled
4
+ * from dist/release. Direct `@decohere/*` dependencies are rewritten to `file:`
5
+ * (not `overrides` — npm EOVERRIDE rejects overriding a direct dep). Nested
6
+ * workspace packages get the same rewrite so `npm install` never hits the
7
+ * private registry. The image then `npm install -g --offline`.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
11
+ import { dirname, join, relative, resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ /**
15
+ * @typedef {{ name: string, dir: string }} WorkspacePackage
16
+ */
17
+
18
+ /**
19
+ * @param {string} rootDir
20
+ * @returns {WorkspacePackage[]}
21
+ */
22
+ export function collectWorkspacePackages(rootDir) {
23
+ /** @type {WorkspacePackage[]} */
24
+ const found = [];
25
+ const walk = (dir) => {
26
+ let entries;
27
+ try {
28
+ entries = readdirSync(dir, { withFileTypes: true });
29
+ } catch {
30
+ return;
31
+ }
32
+ for (const entry of entries) {
33
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
34
+ const path = join(dir, entry.name);
35
+ if (entry.isDirectory()) {
36
+ walk(path);
37
+ continue;
38
+ }
39
+ if (entry.name !== "package.json") continue;
40
+ const pkg = JSON.parse(readFileSync(path, "utf8"));
41
+ if (typeof pkg.name === "string" && pkg.name.startsWith("@decohere/")) {
42
+ found.push({ name: pkg.name, dir });
43
+ }
44
+ }
45
+ };
46
+ walk(rootDir);
47
+ return found;
48
+ }
49
+
50
+ /**
51
+ * @param {string} fromDir
52
+ * @param {string} pkgDir
53
+ */
54
+ export function fileSpec(fromDir, pkgDir) {
55
+ let rel = relative(fromDir, pkgDir) || ".";
56
+ rel = rel.replaceAll("\\", "/");
57
+ if (!rel.startsWith(".")) rel = `./${rel}`;
58
+ return `file:${rel}`;
59
+ }
60
+
61
+ /**
62
+ * Rewrite `@decohere/*` dependency specs to local `file:` paths.
63
+ * Must mutate `dependencies` (not `overrides`) — npm throws EOVERRIDE when an
64
+ * override conflicts with a direct dependency.
65
+ *
66
+ * @param {Record<string, unknown>} pkg
67
+ * @param {string} fromDir
68
+ * @param {WorkspacePackage[]} packages
69
+ */
70
+ export function rewriteDecohereDepsToFile(pkg, fromDir, packages) {
71
+ const byName = new Map(packages.map((item) => [item.name, item.dir]));
72
+ const from = resolve(fromDir);
73
+ const rewrite = (deps) => {
74
+ if (deps == null || typeof deps !== "object" || Array.isArray(deps)) return deps;
75
+ const next = { .../** @type {Record<string, unknown>} */ (deps) };
76
+ for (const name of Object.keys(next)) {
77
+ const dir = byName.get(name);
78
+ if (dir == null || resolve(dir) === from) continue;
79
+ next[name] = fileSpec(fromDir, dir);
80
+ }
81
+ return next;
82
+ };
83
+ return {
84
+ ...pkg,
85
+ dependencies: rewrite(pkg.dependencies),
86
+ optionalDependencies: rewrite(pkg.optionalDependencies),
87
+ };
88
+ }
89
+
90
+ /**
91
+ * @param {Record<string, unknown>} pkg
92
+ * @returns {string[]}
93
+ */
94
+ export function productionDepNames(pkg) {
95
+ const deps = pkg.dependencies;
96
+ if (deps == null || typeof deps !== "object") return [];
97
+ return Object.keys(deps);
98
+ }
99
+
100
+ /**
101
+ * @param {Record<string, unknown>} pkg
102
+ */
103
+ export function withBundleDependencies(pkg) {
104
+ return { ...pkg, bundleDependencies: productionDepNames(pkg) };
105
+ }
106
+
107
+ function writeJson(path, value) {
108
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
109
+ }
110
+
111
+ function runNpm(cwd, args) {
112
+ const result = spawnSync("npm", args, { cwd, encoding: "utf8" });
113
+ if (result.status !== 0) {
114
+ throw new Error(
115
+ `npm ${args.join(" ")} failed (exit ${result.status}):\n${result.stderr}${result.stdout}`,
116
+ );
117
+ }
118
+ return result.stdout.trim();
119
+ }
120
+
121
+ export function packBuilderTgz(options) {
122
+ const outDir = resolve(options.outDir);
123
+ const workspaceOut = resolve(options.workspaceOut);
124
+ const dest = resolve(options.dest);
125
+ const packageJsonPath = join(outDir, "package.json");
126
+ const packages = collectWorkspacePackages(workspaceOut);
127
+ const originals = packages.map((pkg) => {
128
+ const path = join(pkg.dir, "package.json");
129
+ return { path, original: JSON.parse(readFileSync(path, "utf8")) };
130
+ });
131
+ const createOriginal = originals.find((entry) => entry.path === packageJsonPath)?.original;
132
+ if (createOriginal == null) {
133
+ throw new Error(`No @decohere package.json at ${packageJsonPath}`);
134
+ }
135
+
136
+ try {
137
+ for (const { path, original } of originals) {
138
+ writeJson(path, rewriteDecohereDepsToFile(original, dirname(path), packages));
139
+ }
140
+ runNpm(outDir, ["install", "--omit=dev", "--ignore-scripts"]);
141
+ } finally {
142
+ for (const { path, original } of originals) {
143
+ writeJson(path, original);
144
+ }
145
+ }
146
+
147
+ writeJson(packageJsonPath, withBundleDependencies(createOriginal));
148
+ try {
149
+ const packed = runNpm(outDir, ["pack", "--pack-destination", dirname(dest)]);
150
+ const tgzName = packed.split(/\r?\n/).at(-1)?.replace(/^.*\//, "");
151
+ if (tgzName == null || tgzName === "") {
152
+ throw new Error(`npm pack did not print a tarball name:\n${packed}`);
153
+ }
154
+ renameSync(join(dirname(dest), tgzName), dest);
155
+ } finally {
156
+ writeJson(packageJsonPath, createOriginal);
157
+ }
158
+ return dest;
159
+ }
160
+
161
+ function parseArgs(argv) {
162
+ /** @type {Record<string, string>} */
163
+ const parsed = {};
164
+ for (let i = 0; i < argv.length; i++) {
165
+ const arg = argv[i];
166
+ if (arg == null || !arg.startsWith("--")) continue;
167
+ const key = arg.slice(2);
168
+ const value = argv[i + 1];
169
+ if (value == null || value.startsWith("--")) {
170
+ throw new Error(`Missing value for --${key}`);
171
+ }
172
+ parsed[key] = value;
173
+ i++;
174
+ }
175
+ return parsed;
176
+ }
177
+
178
+ function main() {
179
+ const args = parseArgs(process.argv.slice(2));
180
+ const outDir = args["out-dir"];
181
+ const workspaceOut = args["workspace-out"];
182
+ const dest = args.dest;
183
+ if (outDir == null || workspaceOut == null || dest == null) {
184
+ throw new Error(
185
+ "Usage: pack-builder-tgz.mjs --out-dir <dir> --workspace-out <dir> --dest <create.tgz>",
186
+ );
187
+ }
188
+ packBuilderTgz({ outDir, workspaceOut, dest });
189
+ }
190
+
191
+ if (process.argv[1] != null && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
192
+ main();
193
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/create",
3
- "version": "0.0.1-dev.45",
3
+ "version": "0.0.1-dev.47",
4
4
  "description": "Scaffold a new Decohere project — build with Forge, serve with Flux",
5
5
  "keywords": [
6
6
  "decohere",
@@ -21,7 +21,7 @@
21
21
  "test": "echo \"Error: no test specified\" && exit 1"
22
22
  },
23
23
  "dependencies": {
24
- "@decohere/nexus": "0.0.1-dev.45",
24
+ "@decohere/nexus": "0.0.1-dev.47",
25
25
  "commander": "^13.1.0",
26
26
  "openapi-to-postmanv2": "^5.0.0"
27
27
  },