Skip to content

Commit c3b32f5

Browse files
authored
fix(release): harden install version smokes
Fix npm launcher execution when invoked through npm bin symlinks, make source-built versions use Go build info instead of a stale fallback, and require release npm install smokes to assert exact tag output.
1 parent 58ca234 commit c3b32f5

5 files changed

Lines changed: 87 additions & 19 deletions

File tree

.github/workflows/release.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@ jobs:
121121
tmp_prefix="$(mktemp -d)"
122122
trap 'rm -rf "${tmp_prefix}"' EXIT
123123
npm install --global --prefix "${tmp_prefix}" "./${package_tarball}"
124-
PATH="${tmp_prefix}/bin:${PATH}" nightward --version
125-
PATH="${tmp_prefix}/bin:${PATH}" nw --version
124+
nightward_version="$(PATH="${tmp_prefix}/bin:${PATH}" nightward --version)"
125+
if [[ "${nightward_version}" != "${version}" ]]; then
126+
echo "nightward --version returned ${nightward_version}, expected ${version}" >&2
127+
exit 1
128+
fi
129+
nw_version="$(PATH="${tmp_prefix}/bin:${PATH}" nw --version)"
130+
if [[ "${nw_version}" != "${version}" ]]; then
131+
echo "nw --version returned ${nw_version}, expected ${version}" >&2
132+
exit 1
133+
fi
126134
127135
- name: Publish npm package
128136
run: npm publish --access public --provenance

internal/cli/cli.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"os/exec"
1010
"path/filepath"
11+
"runtime/debug"
1112
"strings"
1213
"time"
1314

@@ -23,7 +24,7 @@ import (
2324
"github.com/jsonbored/nightward/internal/tui"
2425
)
2526

26-
var version = "0.1.0"
27+
var version = "devel"
2728

2829
type Check struct {
2930
ID string `json:"id"`
@@ -71,7 +72,7 @@ func RunWithName(commandName string, args []string, stdout, stderr io.Writer) in
7172
case "-h", "--help", "help":
7273
printHelp(stdout, commandName)
7374
case "--version", "version":
74-
fmt.Fprintln(stdout, version)
75+
fmt.Fprintln(stdout, currentVersion())
7576
case "scan":
7677
return runScan(home, args[1:], stdout, stderr)
7778
case "doctor":
@@ -826,7 +827,7 @@ func doctor(home string) DoctorReport {
826827
}
827828
return DoctorReport{
828829
GeneratedAt: time.Now().UTC(),
829-
Version: version,
830+
Version: currentVersion(),
830831
Home: home,
831832
Executable: exe,
832833
Checks: checks,
@@ -835,6 +836,19 @@ func doctor(home string) DoctorReport {
835836
}
836837
}
837838

839+
func currentVersion() string {
840+
if v := strings.TrimSpace(version); v != "" && v != "devel" {
841+
return v
842+
}
843+
if info, ok := debug.ReadBuildInfo(); ok {
844+
moduleVersion := strings.TrimSpace(info.Main.Version)
845+
if moduleVersion != "" && moduleVersion != "(devel)" {
846+
return strings.TrimPrefix(moduleVersion, "v")
847+
}
848+
}
849+
return "devel"
850+
}
851+
838852
func commandCheck(name, detail string) Check {
839853
path, err := exec.LookPath(name)
840854
if err != nil {

internal/cli/cli_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,15 @@ func TestHelpVersionAndCommandErrors(t *testing.T) {
221221
t.Fatalf("version failed: code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
222222
}
223223

224+
stdout.Reset()
225+
stderr.Reset()
226+
oldVersion := version
227+
version = "9.8.7-test"
228+
t.Cleanup(func() { version = oldVersion })
229+
if code := RunWithName("nightward", []string{"--version"}, &stdout, &stderr); code != 0 || strings.TrimSpace(stdout.String()) != "9.8.7-test" {
230+
t.Fatalf("version override failed: code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
231+
}
232+
224233
stdout.Reset()
225234
stderr.Reset()
226235
if code := RunWithName("nw", []string{"providers", "bogus"}, &stdout, &stderr); code == 0 || !strings.Contains(stderr.String(), "usage: nightward providers") {

packages/npm/bin/nightward.mjs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import { createHash } from "node:crypto";
3-
import { createWriteStream, existsSync } from "node:fs";
3+
import { createWriteStream, existsSync, realpathSync } from "node:fs";
44
import { chmod, mkdir, readFile, rm } from "node:fs/promises";
55
import { get } from "node:https";
66
import { homedir, platform as osPlatform, tmpdir } from "node:os";
@@ -183,21 +183,31 @@ export async function ensureBinary(command = commandName()) {
183183
async function main() {
184184
const command = commandName();
185185
const binary = await ensureBinary(command);
186-
const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
187-
child.on("exit", (code, signal) => {
188-
if (signal) {
189-
process.kill(process.pid, signal);
190-
return;
191-
}
192-
process.exit(code ?? 1);
193-
});
194-
child.on("error", (error) => {
195-
console.error(`nightward launcher failed: ${error.message}`);
196-
process.exit(1);
186+
const result = await new Promise((resolve, reject) => {
187+
const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
188+
child.on("error", reject);
189+
child.on("exit", (code, signal) => resolve({ code: code ?? 1, signal }));
197190
});
191+
192+
if (result.signal) {
193+
process.kill(process.pid, result.signal);
194+
return;
195+
}
196+
process.exit(result.code);
197+
}
198+
199+
function isMainModule() {
200+
if (!process.argv[1]) {
201+
return false;
202+
}
203+
try {
204+
return realpathSync(modulePath) === realpathSync(process.argv[1]);
205+
} catch {
206+
return import.meta.url === pathToFileURL(process.argv[1]).href;
207+
}
198208
}
199209

200-
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) {
210+
if (isMainModule()) {
201211
main().catch((error) => {
202212
console.error(`nightward launcher failed: ${error.message}`);
203213
process.exit(1);

packages/npm/test/launcher.test.mjs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { mkdtemp, writeFile } from "node:fs/promises";
1+
import { chmod, mkdtemp, symlink, writeFile } from "node:fs/promises";
22
import { tmpdir } from "node:os";
33
import path from "node:path";
4+
import { spawnSync } from "node:child_process";
5+
import { fileURLToPath } from "node:url";
46
import test from "node:test";
57
import assert from "node:assert/strict";
68

@@ -58,3 +60,28 @@ test("uses invocation name and environment overrides", () => {
5860
delete process.env.NIGHTWARD_NPM_DOWNLOAD_BASE;
5961
}
6062
});
63+
64+
test("runs through npm bin symlink and waits for child output", async () => {
65+
const dir = await mkdtemp(path.join(tmpdir(), "nightward-npm-bin-"));
66+
const fakeBinary = path.join(dir, "fake-nightward.mjs");
67+
const launcherLink = path.join(dir, "nightward");
68+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
69+
70+
await writeFile(fakeBinary, `#!/usr/bin/env node
71+
console.log("fake-nightward " + process.argv.slice(2).join(" "));
72+
`, "utf8");
73+
await chmod(fakeBinary, 0o755);
74+
await symlink(path.join(packageRoot, "bin/nightward.mjs"), launcherLink);
75+
76+
const result = spawnSync(process.execPath, [launcherLink, "--version"], {
77+
cwd: packageRoot,
78+
env: {
79+
...process.env,
80+
NIGHTWARD_BIN: fakeBinary
81+
},
82+
encoding: "utf8"
83+
});
84+
85+
assert.equal(result.status, 0, result.stderr);
86+
assert.equal(result.stdout.trim(), "fake-nightward --version");
87+
});

0 commit comments

Comments
 (0)