diff --git a/__tests__/production-smoke.test.ts b/__tests__/production-smoke.test.ts
index 0a6c131..0004e4a 100644
--- a/__tests__/production-smoke.test.ts
+++ b/__tests__/production-smoke.test.ts
@@ -1,3 +1,9 @@
+import { spawn } from 'node:child_process';
+import { copyFile, mkdtemp, rm } from 'node:fs/promises';
+import { createServer } from 'node:http';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
import { describe, expect, it, vi } from 'vitest';
import {
@@ -122,4 +128,111 @@ describe('production smoke harness', () => {
errors: ['connection refused'],
});
});
+
+ it('runs the CLI from a path containing spaces and exits nonzero when a public check fails', async () => {
+ const requestedPaths: string[] = [];
+ const server = createServer((request, response) => {
+ const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;
+ requestedPaths.push(path);
+
+ if (path === '/api/proof/truehire-preview') {
+ response.writeHead(400, { 'content-type': 'application/json' });
+ response.end(JSON.stringify({ ok: false, error: 'Invalid preview URL.' }));
+ return;
+ }
+
+ if (path === '/jobs') {
+ response.writeHead(503, { 'content-type': 'text/html' });
+ response.end('
Temporarily unavailable
');
+ return;
+ }
+
+ response.writeHead(200, { 'content-type': 'text/html' });
+ response.end(
+ {
+ '/': 'RolePatch
',
+ '/pricing': 'Tokens
',
+ '/proof': 'TrueHire proof project
Candidate proof profile
',
+ '/settings': 'Operational readiness
Chrome extension
',
+ }[path] ?? 'Unexpected route
'
+ );
+ });
+
+ await new Promise((resolve, reject) => {
+ server.once('error', reject);
+ server.listen(0, '127.0.0.1', resolve);
+ });
+
+ const address = server.address();
+ if (!address || typeof address === 'string') throw new Error('Expected an ephemeral TCP port');
+
+ const tempDirectory = await mkdtemp(join(process.cwd(), '.rolepatch smoke cli '));
+ try {
+ const sourcePath = join(
+ dirname(fileURLToPath(import.meta.url)),
+ '../scripts/production-smoke.mjs'
+ );
+ const spacedScriptPath = join(tempDirectory, 'production-smoke.mjs');
+ await copyFile(sourcePath, spacedScriptPath);
+
+ const { code, stdout, stderr } = await new Promise<{
+ code: number | null;
+ stdout: string;
+ stderr: string;
+ }>((resolve, reject) => {
+ const child = spawn(
+ process.execPath,
+ [spacedScriptPath, '--base-url', `http://127.0.0.1:${address.port}`],
+ {
+ env: { PATH: process.env.PATH ?? '', NODE_ENV: 'test' },
+ stdio: ['ignore', 'pipe', 'pipe'],
+ }
+ );
+ let childStdout = '';
+ let childStderr = '';
+ child.stdout.setEncoding('utf8').on('data', (chunk: string) => {
+ childStdout += chunk;
+ });
+ child.stderr.setEncoding('utf8').on('data', (chunk: string) => {
+ childStderr += chunk;
+ });
+ child.once('error', reject);
+ child.once('close', (exitCode) =>
+ resolve({ code: exitCode, stdout: childStdout, stderr: childStderr })
+ );
+ });
+
+ expect(spacedScriptPath).toContain(' ');
+ expect(stdout).toContain('RolePatch production smoke: 5/6 passed');
+ expect(code).toBe(1);
+ expect(stderr).toBe('');
+ expect(
+ stdout
+ .split(/\r?\n/)
+ .filter((line) => /^(PASS|FAIL) /.test(line))
+ .map((line) => line.replace(/^(PASS|FAIL) /, '').replace(/ \d+ \d+ms.*$/, ''))
+ ).toEqual([
+ 'landing',
+ 'jobs browser',
+ 'pricing',
+ 'proof project',
+ 'truehire proof preview guard',
+ 'settings readiness',
+ ]);
+ expect(stdout).toContain('FAIL jobs browser 503');
+ expect(requestedPaths).toEqual([
+ '/',
+ '/jobs',
+ '/pricing',
+ '/proof',
+ '/api/proof/truehire-preview',
+ '/settings',
+ ]);
+ } finally {
+ await new Promise((resolve, reject) =>
+ server.close((error) => (error ? reject(error) : resolve()))
+ );
+ await rm(tempDirectory, { recursive: true, force: true });
+ }
+ });
});
diff --git a/scripts/production-smoke.mjs b/scripts/production-smoke.mjs
index 71e519e..9d73aa0 100644
--- a/scripts/production-smoke.mjs
+++ b/scripts/production-smoke.mjs
@@ -1,5 +1,7 @@
#!/usr/bin/env node
+import { pathToFileURL } from 'node:url';
+
const DEFAULT_BASE_URL = 'https://rolepatch.com';
const DEFAULT_TIMEOUT_MS = 10_000;
@@ -208,7 +210,7 @@ async function main() {
if (summary.failed > 0) process.exitCode = 1;
}
-if (import.meta.url === `file://${process.argv[1]}`) {
+if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch((err) => {
console.error(err instanceof Error ? err.message : String(err));
process.exitCode = 1;