@pizzapi/pizza 0.1.35-dev.0 → 0.1.35-dev.2

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 (2) hide show
  1. package/bin/install.mjs +110 -44
  2. package/package.json +6 -6
package/bin/install.mjs CHANGED
@@ -4,13 +4,16 @@
4
4
  * PizzaPi postinstall script.
5
5
  *
6
6
  * Runs after `npm install @pizzapi/pizza` to ensure the platform-specific
7
- * binary package is present. npm doesn't always install optionalDependencies
8
- * reliably — especially with nvm-windows, global installs, or when the
9
- * lockfile is stale (npm/cli#4828).
7
+ * binary package is present **and at the correct version**.
8
+ *
9
+ * npm doesn't always install optionalDependencies reliably — especially with
10
+ * nvm-windows, global installs, or when the lockfile is stale (npm/cli#4828).
11
+ * Even when the binary exists, a stale platform package from a previous
12
+ * version can linger after an upgrade, causing hard-to-debug issues.
10
13
  *
11
14
  * Strategy:
12
- * 1. Check if the platform binary already exists (optionalDeps worked)
13
- * 2. If not, invoke the user's package manager to install it
15
+ * 1. Locate the platform binary package
16
+ * 2. If missing OR its version doesn't match @pizzapi/pizza, (re)install it
14
17
  *
15
18
  * This mirrors the approach used by esbuild, rollup, swc, etc.
16
19
  */
@@ -39,6 +42,7 @@ if (!pkgName) {
39
42
  process.exit(0);
40
43
  }
41
44
 
45
+ /** Read our own version from @pizzapi/pizza's package.json. */
42
46
  function getOwnVersion() {
43
47
  try {
44
48
  return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
@@ -47,38 +51,71 @@ function getOwnVersion() {
47
51
  }
48
52
  }
49
53
 
50
- /** Check if the platform binary is already installed. */
51
- function binaryExists() {
52
- const binName = process.platform === "win32" ? "pizza.exe" : "pizza";
54
+ /**
55
+ * Find the platform package's directory.
56
+ * Returns the path to its package.json directory, or null if not found.
57
+ */
58
+ function findPlatformPkgDir() {
53
59
  const parts = pkgName.split("/");
54
60
 
55
- // Check via require.resolve
61
+ // Strategy 1: require.resolve
56
62
  try {
57
63
  const require = createRequire(join(__dirname, "..", "package.json"));
58
- const pkgJson = require.resolve(`${pkgName}/package.json`);
59
- const binPath = join(dirname(pkgJson), "bin", binName);
60
- if (existsSync(binPath)) return true;
64
+ const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
65
+ return dirname(pkgJsonPath);
61
66
  } catch {}
62
67
 
63
- // Check sibling scoped package
68
+ // Strategy 2: sibling scoped package
64
69
  {
65
- const siblingPath = join(__dirname, "..", "..", parts[1], "bin", binName);
66
- if (existsSync(siblingPath)) return true;
70
+ const siblingDir = join(__dirname, "..", "..", parts[1]);
71
+ const siblingPkg = join(siblingDir, "package.json");
72
+ if (existsSync(siblingPkg)) return siblingDir;
67
73
  }
68
74
 
69
- // Walk up looking for node_modules
75
+ // Strategy 3: walk up looking for node_modules
70
76
  {
71
77
  let dir = __dirname;
72
78
  for (let i = 0; i < 10; i++) {
73
- const candidate = join(dir, "node_modules", parts[0], parts[1], "bin", binName);
74
- if (existsSync(candidate)) return true;
79
+ const candidate = join(dir, "node_modules", parts[0], parts[1]);
80
+ const candidatePkg = join(candidate, "package.json");
81
+ if (existsSync(candidatePkg)) return candidate;
75
82
  const parent = dirname(dir);
76
83
  if (parent === dir) break;
77
84
  dir = parent;
78
85
  }
79
86
  }
80
87
 
81
- return false;
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Check that the platform binary exists AND its version matches ours.
93
+ * Returns: "ok" | "missing" | "stale"
94
+ */
95
+ function checkPlatformPackage() {
96
+ const pkgDir = findPlatformPkgDir();
97
+ if (!pkgDir) return "missing";
98
+
99
+ // Verify binary file exists
100
+ const binName = process.platform === "win32" ? "pizza.exe" : "pizza";
101
+ const binPath = join(pkgDir, "bin", binName);
102
+ if (!existsSync(binPath)) return "missing";
103
+
104
+ // Verify version matches
105
+ const ownVersion = getOwnVersion();
106
+ if (!ownVersion) return "ok"; // can't check — assume fine
107
+
108
+ try {
109
+ const platformPkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf-8"));
110
+ if (platformPkg.version !== ownVersion) {
111
+ return "stale";
112
+ }
113
+ } catch {
114
+ // Can't read — assume stale to be safe
115
+ return "stale";
116
+ }
117
+
118
+ return "ok";
82
119
  }
83
120
 
84
121
  /** Detect the package manager that invoked this install. */
@@ -104,47 +141,76 @@ function isGlobalInstall() {
104
141
  return false;
105
142
  }
106
143
 
107
- // --- Main ---
108
-
109
- if (binaryExists()) {
110
- // All good — optionalDependencies did its job
111
- process.exit(0);
112
- }
113
-
114
- const version = getOwnVersion();
115
- const versionSpec = version ? `${pkgName}@${version}` : pkgName;
116
- const pm = detectPackageManager();
117
- const global = isGlobalInstall();
118
-
119
- console.log(`[pizzapi] Platform package ${pkgName} was not installed automatically.`);
120
- console.log(`[pizzapi] Installing with ${pm}...`);
121
-
122
- try {
144
+ /** Run package manager install command. */
145
+ function installPlatformPackage(versionSpec, pm, isGlobal) {
123
146
  if (pm === "yarn") {
124
- const cmd = global ? `yarn global add ${versionSpec}` : `yarn add ${versionSpec}`;
147
+ const cmd = isGlobal ? `yarn global add ${versionSpec}` : `yarn add ${versionSpec}`;
125
148
  execSync(cmd, { stdio: "inherit", env: process.env });
126
149
  } else if (pm === "pnpm") {
127
- const flag = global ? " -g" : "";
150
+ const flag = isGlobal ? " -g" : "";
128
151
  execSync(`pnpm add${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
129
152
  } else if (pm === "bun") {
130
- const flag = global ? " -g" : "";
153
+ const flag = isGlobal ? " -g" : "";
131
154
  execSync(`bun add${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
132
155
  } else {
133
156
  // npm
134
- const flag = global ? " -g" : "";
157
+ const flag = isGlobal ? " -g" : "";
135
158
  execSync(`npm install${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
136
159
  }
160
+ }
161
+
162
+ // --- Main ---
163
+
164
+ const status = checkPlatformPackage();
137
165
 
138
- if (binaryExists()) {
139
- console.log(`[pizzapi] Successfully installed ${pkgName}.`);
166
+ if (status === "ok") {
167
+ // Binary exists and version matches — nothing to do
168
+ process.exit(0);
169
+ }
170
+
171
+ const version = getOwnVersion();
172
+ const versionSpec = version ? `${pkgName}@${version}` : pkgName;
173
+ const pm = detectPackageManager();
174
+ const isGlobal = isGlobalInstall();
175
+
176
+ if (status === "stale") {
177
+ console.log(`[pizzapi] Platform package ${pkgName} is outdated (expected ${version}).`);
178
+ console.log(`[pizzapi] Upgrading with ${pm}...`);
179
+ } else {
180
+ console.log(`[pizzapi] Platform package ${pkgName} was not installed automatically.`);
181
+ console.log(`[pizzapi] Installing with ${pm}...`);
182
+ }
183
+
184
+ try {
185
+ installPlatformPackage(versionSpec, pm, isGlobal);
186
+
187
+ const postStatus = checkPlatformPackage();
188
+ if (postStatus === "ok") {
189
+ console.log(`[pizzapi] Successfully installed ${pkgName}@${version}.`);
190
+ } else if (postStatus === "stale") {
191
+ // Installed but still wrong version — try removing first then reinstalling
192
+ console.log(`[pizzapi] Version mismatch persists — removing stale package and retrying...`);
193
+ try {
194
+ if (pm === "npm") {
195
+ const flag = isGlobal ? " -g" : "";
196
+ execSync(`npm rm${flag} ${pkgName}`, { stdio: "inherit", env: process.env });
197
+ }
198
+ installPlatformPackage(versionSpec, pm, isGlobal);
199
+ } catch {}
200
+
201
+ if (checkPlatformPackage() === "ok") {
202
+ console.log(`[pizzapi] Successfully installed ${pkgName}@${version}.`);
203
+ } else {
204
+ throw new Error("Version mismatch after reinstall");
205
+ }
140
206
  } else {
141
207
  throw new Error("Binary not found after install");
142
208
  }
143
209
  } catch (err) {
144
- console.error(`[pizzapi] Failed to install ${pkgName} automatically.`);
210
+ console.error(`[pizzapi] Failed to install ${pkgName}@${version} automatically.`);
145
211
  console.error(`[pizzapi] Please install it manually:`);
146
- if (global) {
147
- console.error(`[pizzapi] npm install -g ${versionSpec}`);
212
+ if (isGlobal) {
213
+ console.error(`[pizzapi] npm rm -g ${pkgName} && npm install -g ${versionSpec}`);
148
214
  } else {
149
215
  console.error(`[pizzapi] npm install ${versionSpec}`);
150
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pizzapi/pizza",
3
- "version": "0.1.35-dev.0",
3
+ "version": "0.1.35-dev.2",
4
4
  "description": "PizzaPi — a self-hosted web interface and relay server for the pi coding agent. Stream live AI coding sessions to any browser.",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -16,11 +16,11 @@
16
16
  "LICENSE"
17
17
  ],
18
18
  "optionalDependencies": {
19
- "@pizzapi/cli-linux-x64": "0.1.35-dev.0",
20
- "@pizzapi/cli-linux-arm64": "0.1.35-dev.0",
21
- "@pizzapi/cli-darwin-x64": "0.1.35-dev.0",
22
- "@pizzapi/cli-darwin-arm64": "0.1.35-dev.0",
23
- "@pizzapi/cli-win32-x64": "0.1.35-dev.0"
19
+ "@pizzapi/cli-linux-x64": "0.1.35-dev.2",
20
+ "@pizzapi/cli-linux-arm64": "0.1.35-dev.2",
21
+ "@pizzapi/cli-darwin-x64": "0.1.35-dev.2",
22
+ "@pizzapi/cli-darwin-arm64": "0.1.35-dev.2",
23
+ "@pizzapi/cli-win32-x64": "0.1.35-dev.2"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=18"