@promptster/cli 1.7.0 → 1.8.1

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,84 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * The `promptster` command installed by `npm install -g @promptster/cli`.
6
+ *
7
+ * package.json's "bin" has always pointed here, but this file did not exist
8
+ * until now: it was never committed (the root .gitignore's `bin/` pattern
9
+ * matched `npm/bin/` at any depth), so the published tarball shipped
10
+ * `binaries/` with nothing to dispatch to them. npm creates no link for a
11
+ * "bin" target that is absent from the tarball and does not fail the install,
12
+ * so `npm install -g @promptster/cli@1.7.0` reported success and left no
13
+ * `promptster` on PATH at all. npm/scripts/check-install.js is the gate that
14
+ * now blocks a publish in that state.
15
+ *
16
+ * This shim only picks the right prebuilt Go binary and hands it the argv.
17
+ * Keep it dependency-free and Node >=16 compatible (see "engines").
18
+ */
19
+
20
+ const { spawnSync } = require("child_process");
21
+ const fs = require("fs");
22
+ const path = require("path");
23
+
24
+ // Keep in sync with the TARGETS table in scripts/build.js and the EXPECTED
25
+ // list in scripts/check-binaries.js.
26
+ const BINARY_BY_PLATFORM = {
27
+ "darwin-arm64": "promptster-darwin-arm64",
28
+ "darwin-x64": "promptster-darwin-x64",
29
+ "linux-arm64": "promptster-linux-arm64",
30
+ "linux-x64": "promptster-linux-x64",
31
+ "win32-x64": "promptster-win32-x64.exe",
32
+ };
33
+
34
+ const platformKey = `${process.platform}-${process.arch}`;
35
+ const binaryName = BINARY_BY_PLATFORM[platformKey];
36
+
37
+ if (!binaryName) {
38
+ console.error(`promptster: unsupported platform ${platformKey}`);
39
+ console.error("Supported: " + Object.keys(BINARY_BY_PLATFORM).sort().join(", "));
40
+ console.error("Build from source instead: go install github.com/pa-arth/promptster-cli@latest");
41
+ process.exit(1);
42
+ }
43
+
44
+ const binaryPath = path.join(__dirname, "..", "binaries", binaryName);
45
+
46
+ if (!fs.existsSync(binaryPath)) {
47
+ console.error(`promptster: missing bundled binary ${binaryName}`);
48
+ console.error(`Looked in: ${path.dirname(binaryPath)}`);
49
+ console.error("This install is incomplete — reinstall with: npm install -g @promptster/cli");
50
+ process.exit(1);
51
+ }
52
+
53
+ // npm rewrites the mode of every packed file to 0644 except the entries named
54
+ // in "bin", so the Go binaries land on disk without an executable bit and
55
+ // spawning them fails with EACCES. Restore it here rather than in a
56
+ // postinstall, because a global install run with --ignore-scripts (common in
57
+ // locked-down environments) would skip a postinstall entirely.
58
+ if (process.platform !== "win32") {
59
+ try {
60
+ const mode = fs.statSync(binaryPath).mode & 0o7777;
61
+ if ((mode & 0o111) !== 0o111) {
62
+ fs.chmodSync(binaryPath, mode | 0o755);
63
+ }
64
+ } catch (err) {
65
+ console.error(`promptster: could not make ${binaryPath} executable: ${err.message}`);
66
+ console.error(`Fix it manually with: chmod +x ${binaryPath}`);
67
+ process.exit(1);
68
+ }
69
+ }
70
+
71
+ const result = spawnSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
72
+
73
+ if (result.error) {
74
+ console.error(`promptster: failed to run ${binaryPath}: ${result.error.message}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ // Re-raise the child's terminating signal so a Ctrl+C during an assessment
79
+ // looks like a signal death to the caller's shell, not exit 0.
80
+ if (result.signal) {
81
+ process.kill(process.pid, result.signal);
82
+ }
83
+
84
+ process.exit(result.status === null ? 1 : result.status);
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptster/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Promptster CLI — capture and submit developer assessments",
5
5
  "keywords": [
6
6
  "promptster",
@@ -31,6 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "scripts": {
34
- "build": "node scripts/build.js"
34
+ "build": "node scripts/build.js",
35
+ "check": "node scripts/check-binaries.js && node scripts/check-install.js"
35
36
  }
36
37
  }
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Publish gate: prove that the tarball we are about to push to npm actually
6
+ * yields a runnable `promptster` on PATH.
7
+ *
8
+ * It packs the package, installs the tarball into a throwaway global prefix,
9
+ * and EXECUTES the resulting command. That distinction is the whole point:
10
+ * check-binaries.js reads a file list, and a file list was green for v1.7.0 —
11
+ * every binary was present and there was simply nothing to dispatch to them,
12
+ * so `npm install -g @promptster/cli@1.7.0` installed no command at all and
13
+ * still exited 0.
14
+ *
15
+ * Two details this gate is deliberately picky about, because each one on its
16
+ * own is enough to ship an install that does nothing:
17
+ *
18
+ * - It packs with the SAME packer the release workflow publishes with. `pnpm
19
+ * pack` rewrites every file to mode 0644; `npm pack` preserves 0755. The
20
+ * release runs `pnpm publish`, so a gate that packed with npm would be
21
+ * green over a tarball whose Go binaries arrive non-executable.
22
+ * - It runs the command a second time with the bundled binary forced to 0644,
23
+ * which is the state pnpm ships. That asserts the shim restores the
24
+ * executable bit itself rather than depending on the packer's mood.
25
+ *
26
+ * The install passes --ignore-scripts, so a package that needs a postinstall
27
+ * to become runnable fails here rather than on the machines of users whose npm
28
+ * config disables scripts.
29
+ *
30
+ * Usage: node scripts/check-install.js
31
+ */
32
+
33
+ const { execFileSync, spawnSync } = require("child_process");
34
+ const fs = require("fs");
35
+ const os = require("os");
36
+ const path = require("path");
37
+
38
+ // The gate packs, and a packer runs lifecycle scripts. prepublishOnly is not
39
+ // one of them today, but this makes a future packer change a clear message
40
+ // instead of an infinite loop inside `pnpm publish`.
41
+ if (process.env.PROMPTSTER_INSTALL_CHECK === "1") {
42
+ console.error("ERROR: check-install.js re-entered itself — the packer is running prepublishOnly");
43
+ process.exit(1);
44
+ }
45
+ process.env.PROMPTSTER_INSTALL_CHECK = "1";
46
+
47
+ const packageDir = path.resolve(__dirname, "..");
48
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf8"));
49
+
50
+ const BINARY_BY_PLATFORM = {
51
+ "darwin-arm64": "promptster-darwin-arm64",
52
+ "darwin-x64": "promptster-darwin-x64",
53
+ "linux-arm64": "promptster-linux-arm64",
54
+ "linux-x64": "promptster-linux-x64",
55
+ "win32-x64": "promptster-win32-x64.exe",
56
+ };
57
+
58
+ function fail(message, ...details) {
59
+ console.error(`ERROR: ${message}`);
60
+ for (const line of details) console.error(` ${line}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ function hasCommand(name) {
65
+ const probe = spawnSync(name, ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
66
+ return !probe.error && probe.status === 0;
67
+ }
68
+
69
+ const platformKey = `${process.platform}-${process.arch}`;
70
+ const hostBinary = BINARY_BY_PLATFORM[platformKey];
71
+ if (!hostBinary) {
72
+ fail(
73
+ `cannot verify the install on unsupported platform ${platformKey}`,
74
+ `supported: ${Object.keys(BINARY_BY_PLATFORM).sort().join(", ")}`,
75
+ );
76
+ }
77
+ if (!fs.existsSync(path.join(packageDir, "binaries", hostBinary))) {
78
+ fail(
79
+ `binaries/${hostBinary} is missing, so the packed tarball cannot be run here`,
80
+ "run: node scripts/build.js",
81
+ );
82
+ }
83
+
84
+ // Match .github/workflows/release.yml, which publishes with pnpm.
85
+ const packer = hasCommand("pnpm") ? "pnpm" : "npm";
86
+
87
+ const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "promptster-install-check-"));
88
+ const prefixDir = path.join(workDir, "prefix");
89
+ fs.mkdirSync(prefixDir);
90
+
91
+ function runPromptster(command, label) {
92
+ const run = spawnSync(command, ["--version"], { encoding: "utf8" });
93
+ if (run.error) {
94
+ fail(`could not execute the installed promptster (${label}): ${run.error.message}`, `path: ${command}`);
95
+ }
96
+ if (run.status !== 0) {
97
+ fail(
98
+ `\`promptster --version\` from the packed tarball exited ${run.status} (${label})`,
99
+ ...String(run.stderr || "").trim().split("\n").filter(Boolean),
100
+ );
101
+ }
102
+ const reported = String(run.stdout || "").trim();
103
+ if (reported !== pkg.version) {
104
+ fail(
105
+ `\`promptster --version\` printed ${JSON.stringify(reported)}, expected ${JSON.stringify(pkg.version)}`,
106
+ "the bundled binaries were built from a different version stamp",
107
+ `run: node scripts/build.js ${pkg.version}`,
108
+ );
109
+ }
110
+ return reported;
111
+ }
112
+
113
+ let ok = false;
114
+ try {
115
+ execFileSync(packer, ["pack", "--pack-destination", workDir], {
116
+ cwd: packageDir,
117
+ encoding: "utf8",
118
+ stdio: ["ignore", "pipe", "inherit"],
119
+ });
120
+ const tarballs = fs.readdirSync(workDir).filter((entry) => entry.endsWith(".tgz"));
121
+ if (tarballs.length !== 1) {
122
+ fail(`expected exactly one tarball from \`${packer} pack\`, got ${tarballs.length}`);
123
+ }
124
+ const tarball = path.join(workDir, tarballs[0]);
125
+
126
+ execFileSync(
127
+ "npm",
128
+ [
129
+ "install",
130
+ "--global",
131
+ "--prefix",
132
+ prefixDir,
133
+ "--ignore-scripts",
134
+ "--no-audit",
135
+ "--no-fund",
136
+ "--loglevel",
137
+ "error",
138
+ tarball,
139
+ ],
140
+ { cwd: workDir, encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] },
141
+ );
142
+
143
+ // On Windows npm writes promptster.cmd at the prefix root; elsewhere it
144
+ // links into <prefix>/bin.
145
+ const candidates =
146
+ process.platform === "win32"
147
+ ? [path.join(prefixDir, "promptster.cmd"), path.join(prefixDir, "promptster")]
148
+ : [path.join(prefixDir, "bin", "promptster")];
149
+ const command = candidates.find((candidate) => fs.existsSync(candidate));
150
+ if (!command) {
151
+ fail(
152
+ "the packed tarball installed no `promptster` command",
153
+ `packed with: ${packer}`,
154
+ `looked for: ${candidates.join(", ")}`,
155
+ `package.json "bin" is ${JSON.stringify(pkg.bin)} — is that file committed and matched by "files"?`,
156
+ );
157
+ }
158
+
159
+ const reported = runPromptster(command, `packed with ${packer}`);
160
+
161
+ if (process.platform !== "win32") {
162
+ const installedBinary = path.join(
163
+ prefixDir,
164
+ "lib",
165
+ "node_modules",
166
+ pkg.name,
167
+ "binaries",
168
+ hostBinary,
169
+ );
170
+ if (!fs.existsSync(installedBinary)) {
171
+ fail(`installed package is missing binaries/${hostBinary}`, `looked at: ${installedBinary}`);
172
+ }
173
+ fs.chmodSync(installedBinary, 0o644);
174
+ runPromptster(command, "bundled binary forced to mode 0644");
175
+ }
176
+
177
+ console.log(`✓ packed tarball (${packer}) installs a runnable promptster (${reported})`);
178
+ ok = true;
179
+ } finally {
180
+ fs.rmSync(workDir, { recursive: true, force: true });
181
+ if (!ok) process.exitCode = process.exitCode || 1;
182
+ }