@decohere/create 0.0.1-dev.46 → 0.0.1-dev.48

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.
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Pack `@decohere/create` for the builder image with production deps bundled
4
- * from dist/release (local `file:` for `@decohere/*`) so `npm install -g --offline`
5
- * does not hit the registry.
3
+ * Vendor `@decohere/create` for the builder image: resolve `@decohere/*` from
4
+ * dist/release via `file:` (not `overrides`), `npm install --install-links`,
5
+ * then tar the installed tree including `node_modules`. `--install-links`
6
+ * materializes `file:` packages (and their deps) instead of leaving symlinks
7
+ * whose nested installs live outside the create directory.
6
8
  */
7
9
  import { spawnSync } from "node:child_process";
8
- import { readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
9
11
  import { dirname, join, relative, resolve } from "node:path";
10
12
  import { fileURLToPath } from "node:url";
11
13
 
@@ -47,52 +49,42 @@ export function collectWorkspacePackages(rootDir) {
47
49
 
48
50
  /**
49
51
  * @param {string} fromDir
50
- * @param {WorkspacePackage[]} packages
51
- * @returns {Record<string, string>}
52
+ * @param {string} pkgDir
52
53
  */
53
- export function decohereFileOverrides(fromDir, packages) {
54
- /** @type {Record<string, string>} */
55
- const overrides = {};
56
- const from = resolve(fromDir);
57
- for (const pkg of packages) {
58
- if (resolve(pkg.dir) === from) continue;
59
- let rel = relative(fromDir, pkg.dir) || ".";
60
- rel = rel.replaceAll("\\", "/");
61
- if (!rel.startsWith(".")) rel = `./${rel}`;
62
- overrides[pkg.name] = `file:${rel}`;
63
- }
64
- return overrides;
65
- }
66
-
67
- /**
68
- * @param {Record<string, unknown>} pkg
69
- * @returns {string[]}
70
- */
71
- export function productionDepNames(pkg) {
72
- const deps = pkg.dependencies;
73
- if (deps == null || typeof deps !== "object") return [];
74
- return Object.keys(deps);
75
- }
76
-
77
- /**
78
- * @param {Record<string, unknown>} pkg
79
- * @param {Record<string, string>} overrides
80
- */
81
- export function withInstallOverrides(pkg, overrides) {
82
- const existing = pkg.overrides;
83
- const previous =
84
- existing != null && typeof existing === "object" && !Array.isArray(existing)
85
- ? /** @type {Record<string, unknown>} */ (existing)
86
- : {};
87
- return { ...pkg, overrides: { ...previous, ...overrides } };
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}`;
88
59
  }
89
60
 
90
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
+ *
91
66
  * @param {Record<string, unknown>} pkg
67
+ * @param {string} fromDir
68
+ * @param {WorkspacePackage[]} packages
92
69
  */
93
- export function withBundleDependencies(pkg) {
94
- const { overrides: _overrides, ...rest } = pkg;
95
- return { ...rest, bundleDependencies: productionDepNames(pkg) };
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
+ };
96
88
  }
97
89
 
98
90
  function writeJson(path, value) {
@@ -109,22 +101,44 @@ function runNpm(cwd, args) {
109
101
  return result.stdout.trim();
110
102
  }
111
103
 
104
+ function packInstalledTree(outDir, dest) {
105
+ const result = spawnSync(
106
+ "tar",
107
+ ["--create", "--gzip", "--dereference", "--file", dest, "-C", outDir, "."],
108
+ { encoding: "utf8" },
109
+ );
110
+ if (result.status !== 0) {
111
+ throw new Error(`tar failed (exit ${result.status}):\n${result.stderr}${result.stdout}`);
112
+ }
113
+ }
114
+
112
115
  export function packBuilderTgz(options) {
113
116
  const outDir = resolve(options.outDir);
114
117
  const workspaceOut = resolve(options.workspaceOut);
115
118
  const dest = resolve(options.dest);
116
119
  const packageJsonPath = join(outDir, "package.json");
117
- const original = JSON.parse(readFileSync(packageJsonPath, "utf8"));
118
- const overrides = decohereFileOverrides(outDir, collectWorkspacePackages(workspaceOut));
119
- writeJson(packageJsonPath, withInstallOverrides(original, overrides));
120
- runNpm(outDir, ["install", "--omit=dev", "--ignore-scripts"]);
121
- writeJson(packageJsonPath, withBundleDependencies(original));
122
- const packed = runNpm(outDir, ["pack", "--pack-destination", dirname(dest)]);
123
- const tgzName = packed.split(/\r?\n/).at(-1)?.replace(/^.*\//, "");
124
- if (tgzName == null || tgzName === "") {
125
- throw new Error(`npm pack did not print a tarball name:\n${packed}`);
120
+ const packages = collectWorkspacePackages(workspaceOut);
121
+ const originals = packages.map((pkg) => {
122
+ const path = join(pkg.dir, "package.json");
123
+ return { path, original: JSON.parse(readFileSync(path, "utf8")) };
124
+ });
125
+ const createOriginal = originals.find((entry) => entry.path === packageJsonPath)?.original;
126
+ if (createOriginal == null) {
127
+ throw new Error(`No @decohere package.json at ${packageJsonPath}`);
126
128
  }
127
- renameSync(join(dirname(dest), tgzName), dest);
129
+
130
+ try {
131
+ for (const { path, original } of originals) {
132
+ writeJson(path, rewriteDecohereDepsToFile(original, dirname(path), packages));
133
+ }
134
+ runNpm(outDir, ["install", "--omit=dev", "--ignore-scripts", "--install-links"]);
135
+ } finally {
136
+ for (const { path, original } of originals) {
137
+ writeJson(path, original);
138
+ }
139
+ }
140
+
141
+ packInstalledTree(outDir, dest);
128
142
  return dest;
129
143
  }
130
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/create",
3
- "version": "0.0.1-dev.46",
3
+ "version": "0.0.1-dev.48",
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.46",
24
+ "@decohere/nexus": "0.0.1-dev.48",
25
25
  "commander": "^13.1.0",
26
26
  "openapi-to-postmanv2": "^5.0.0"
27
27
  },