@promptster/cli 1.8.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.
- package/bin/promptster.js +84 -0
- package/binaries/promptster-darwin-arm64 +0 -0
- package/binaries/promptster-darwin-x64 +0 -0
- package/binaries/promptster-linux-arm64 +0 -0
- package/binaries/promptster-linux-x64 +0 -0
- package/binaries/promptster-win32-x64.exe +0 -0
- package/package.json +3 -2
- package/scripts/check-install.js +182 -0
|
@@ -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.8.
|
|
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
|
+
}
|