@pizzapi/pizza 0.1.34-dev.3 → 0.1.34-dev.4

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,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PizzaPi postinstall script.
5
+ *
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).
10
+ *
11
+ * 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
14
+ *
15
+ * This mirrors the approach used by esbuild, rollup, swc, etc.
16
+ */
17
+
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { join, dirname } from "node:path";
20
+ import { execSync } from "node:child_process";
21
+ import { createRequire } from "node:module";
22
+ import { fileURLToPath } from "node:url";
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+
26
+ const PLATFORM_PACKAGES = {
27
+ "linux-x64": "@pizzapi/cli-linux-x64",
28
+ "linux-arm64": "@pizzapi/cli-linux-arm64",
29
+ "darwin-x64": "@pizzapi/cli-darwin-x64",
30
+ "darwin-arm64": "@pizzapi/cli-darwin-arm64",
31
+ "win32-x64": "@pizzapi/cli-win32-x64",
32
+ };
33
+
34
+ const platformKey = `${process.platform}-${process.arch}`;
35
+ const pkgName = PLATFORM_PACKAGES[platformKey];
36
+
37
+ if (!pkgName) {
38
+ // Unsupported platform — nothing we can do at install time
39
+ process.exit(0);
40
+ }
41
+
42
+ function getOwnVersion() {
43
+ try {
44
+ return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
45
+ } catch {
46
+ return undefined;
47
+ }
48
+ }
49
+
50
+ /** Check if the platform binary is already installed. */
51
+ function binaryExists() {
52
+ const binName = process.platform === "win32" ? "pizza.exe" : "pizza";
53
+ const parts = pkgName.split("/");
54
+
55
+ // Check via require.resolve
56
+ try {
57
+ 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;
61
+ } catch {}
62
+
63
+ // Check sibling scoped package
64
+ {
65
+ const siblingPath = join(__dirname, "..", "..", parts[1], "bin", binName);
66
+ if (existsSync(siblingPath)) return true;
67
+ }
68
+
69
+ // Walk up looking for node_modules
70
+ {
71
+ let dir = __dirname;
72
+ 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;
75
+ const parent = dirname(dir);
76
+ if (parent === dir) break;
77
+ dir = parent;
78
+ }
79
+ }
80
+
81
+ return false;
82
+ }
83
+
84
+ /** Detect the package manager that invoked this install. */
85
+ function detectPackageManager() {
86
+ const ua = process.env.npm_config_user_agent || "";
87
+ if (ua.startsWith("yarn")) return "yarn";
88
+ if (ua.startsWith("pnpm")) return "pnpm";
89
+ if (ua.startsWith("bun")) return "bun";
90
+ return "npm";
91
+ }
92
+
93
+ /** Check if this is a global install. */
94
+ function isGlobalInstall() {
95
+ // npm sets this env var during global installs
96
+ if (process.env.npm_config_global === "true") return true;
97
+ // Heuristic: check if we're inside a global-looking path
98
+ const globalMarkers = ["node_modules/.global", "nvm", "volta", "fnm", "nodenv"];
99
+ const loc = __dirname.toLowerCase();
100
+ if (loc.includes("lib/node_modules") || loc.includes("lib\\node_modules")) return true;
101
+ for (const marker of globalMarkers) {
102
+ if (loc.includes(marker)) return true;
103
+ }
104
+ return false;
105
+ }
106
+
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 {
123
+ if (pm === "yarn") {
124
+ const cmd = global ? `yarn global add ${versionSpec}` : `yarn add ${versionSpec}`;
125
+ execSync(cmd, { stdio: "inherit", env: process.env });
126
+ } else if (pm === "pnpm") {
127
+ const flag = global ? " -g" : "";
128
+ execSync(`pnpm add${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
129
+ } else if (pm === "bun") {
130
+ const flag = global ? " -g" : "";
131
+ execSync(`bun add${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
132
+ } else {
133
+ // npm
134
+ const flag = global ? " -g" : "";
135
+ execSync(`npm install${flag} ${versionSpec}`, { stdio: "inherit", env: process.env });
136
+ }
137
+
138
+ if (binaryExists()) {
139
+ console.log(`[pizzapi] Successfully installed ${pkgName}.`);
140
+ } else {
141
+ throw new Error("Binary not found after install");
142
+ }
143
+ } catch (err) {
144
+ console.error(`[pizzapi] Failed to install ${pkgName} automatically.`);
145
+ console.error(`[pizzapi] Please install it manually:`);
146
+ if (global) {
147
+ console.error(`[pizzapi] npm install -g ${versionSpec}`);
148
+ } else {
149
+ console.error(`[pizzapi] npm install ${versionSpec}`);
150
+ }
151
+ // Don't fail the overall install — this is best-effort
152
+ process.exit(0);
153
+ }
package/bin/pizza.mjs CHANGED
@@ -4,13 +4,14 @@
4
4
  * PizzaPi CLI launcher.
5
5
  *
6
6
  * This script locates the platform-specific binary installed via
7
- * optionalDependencies and executes it, passing through all arguments.
7
+ * optionalDependencies (or the postinstall fallback) and executes it,
8
+ * passing through all arguments.
8
9
  *
9
10
  * Pattern borrowed from esbuild, turbo, swc, etc.
10
11
  */
11
12
 
12
- import { execFileSync, execSync } from "node:child_process";
13
- import { existsSync, readFileSync } from "node:fs";
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync } from "node:fs";
14
15
  import { join, dirname, resolve } from "node:path";
15
16
  import { createRequire } from "node:module";
16
17
  import { fileURLToPath } from "node:url";
@@ -39,16 +40,6 @@ if (!pkgName) {
39
40
  process.exit(1);
40
41
  }
41
42
 
42
- /** Read our own package.json version. */
43
- function getOwnVersion() {
44
- try {
45
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
46
- return pkg.version;
47
- } catch {
48
- return undefined;
49
- }
50
- }
51
-
52
43
  /** Try to find the platform binary via multiple strategies. */
53
44
  function findBinary() {
54
45
  const binName = process.platform === "win32" ? "pizza.exe" : "pizza";
@@ -97,62 +88,12 @@ function findBinary() {
97
88
  }
98
89
  }
99
90
 
100
- // Strategy 5: Use npm root -g to find global node_modules
101
- try {
102
- const globalRoot = execSync("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
103
- if (globalRoot) {
104
- const binPath = join(globalRoot, parts[0], parts[1], "bin", binName);
105
- if (existsSync(binPath)) return binPath;
106
- }
107
- } catch {
108
- // npm not available or failed
109
- }
110
-
111
91
  return null;
112
92
  }
113
93
 
114
- /**
115
- * Attempt to install the missing platform package alongside our package.
116
- * Returns true if the install succeeded.
117
- */
118
- function tryAutoInstall(version) {
119
- const versionSpec = version ? `${pkgName}@${version}` : pkgName;
120
-
121
- // Determine whether we're in a global install by checking if our path
122
- // is inside a global-looking node_modules (not inside a project).
123
- const isGlobal = __dirname.includes("node_modules");
124
-
125
- const globalFlag = isGlobal ? " -g" : "";
126
- console.error(
127
- `\nThe platform package "${pkgName}" was not installed automatically.\n` +
128
- `Attempting to install it now...\n`,
129
- );
130
-
131
- try {
132
- execSync(`npm install${globalFlag} ${versionSpec}`, {
133
- stdio: "inherit",
134
- env: process.env,
135
- });
136
- return true;
137
- } catch {
138
- return false;
139
- }
140
- }
141
-
142
- // --- Main ---
143
-
144
- let binaryPath = findBinary();
94
+ const binaryPath = findBinary();
145
95
 
146
- // If not found, try to auto-install the platform package and retry
147
96
  if (!binaryPath) {
148
- const version = getOwnVersion();
149
- if (tryAutoInstall(version)) {
150
- binaryPath = findBinary();
151
- }
152
- }
153
-
154
- if (!binaryPath) {
155
- // Collect diagnostic info for troubleshooting
156
97
  const parts = pkgName.split("/");
157
98
  const diag = [
158
99
  ` launcher: ${__filename}`,
@@ -161,19 +102,14 @@ if (!binaryPath) {
161
102
  ];
162
103
  const siblingDir = join(__dirname, "..", "..", parts[1]);
163
104
  diag.push(` sibling dir exists: ${existsSync(siblingDir)} (${siblingDir})`);
164
- try {
165
- const globalRoot = execSync("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
166
- const globalPkg = join(globalRoot, parts[0], parts[1]);
167
- diag.push(` global root: ${globalRoot}`);
168
- diag.push(` global pkg exists: ${existsSync(globalPkg)} (${globalPkg})`);
169
- } catch {}
170
105
 
171
106
  console.error(
172
107
  `Error: Could not find the PizzaPi binary for your platform (${platformKey}).\n\n` +
173
- `The platform-specific package "${pkgName}" could not be installed\n` +
174
- `automatically.\n\n` +
175
- `Try installing it manually:\n` +
176
- ` npm install -g ${pkgName}@${getOwnVersion() || "latest"}\n\n` +
108
+ `The platform-specific package "${pkgName}" was not installed.\n\n` +
109
+ `Install it manually:\n` +
110
+ ` npm install -g ${pkgName}\n\n` +
111
+ `Or reinstall PizzaPi:\n` +
112
+ ` npm install -g @pizzapi/pizza\n\n` +
177
113
  `Or build from source: https://github.com/Pizzaface/PizzaPi\n\n` +
178
114
  `Diagnostics:\n${diag.join("\n")}`,
179
115
  );
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@pizzapi/pizza",
3
- "version": "0.1.34-dev.3",
3
+ "version": "0.1.34-dev.4",
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
+ "scripts": {
7
+ "postinstall": "node bin/install.mjs"
8
+ },
6
9
  "bin": {
7
10
  "pizza": "bin/pizza.mjs",
8
11
  "pizzapi": "bin/pizza.mjs"
@@ -13,11 +16,11 @@
13
16
  "LICENSE"
14
17
  ],
15
18
  "optionalDependencies": {
16
- "@pizzapi/cli-linux-x64": "0.1.34-dev.3",
17
- "@pizzapi/cli-linux-arm64": "0.1.34-dev.3",
18
- "@pizzapi/cli-darwin-x64": "0.1.34-dev.3",
19
- "@pizzapi/cli-darwin-arm64": "0.1.34-dev.3",
20
- "@pizzapi/cli-win32-x64": "0.1.34-dev.3"
19
+ "@pizzapi/cli-linux-x64": "0.1.34-dev.4",
20
+ "@pizzapi/cli-linux-arm64": "0.1.34-dev.4",
21
+ "@pizzapi/cli-darwin-x64": "0.1.34-dev.4",
22
+ "@pizzapi/cli-darwin-arm64": "0.1.34-dev.4",
23
+ "@pizzapi/cli-win32-x64": "0.1.34-dev.4"
21
24
  },
22
25
  "engines": {
23
26
  "node": ">=18"