Run prettier across the repo, exclude the lockfile from it
Prettier had never been run in --check mode here before, so this had drifted across most files (markdown tables, long option() chains, line wrapping). Purely formatting, no logic changes - needed so a CI format:check gate can actually pass. Adds .prettierignore for pnpm-lock.yaml specifically: prettier's YAML formatter rewrites every quoted key (single -> double quotes) producing an ~8700-line diff of pure noise on a file pnpm itself owns the formatting of. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,10 @@ import { test } from "node:test";
|
||||
import { loadDotenv, upsertEnvVar } from "./env-file";
|
||||
|
||||
function tmpEnvPath(): string {
|
||||
return path.join(fs.mkdtempSync(path.join(os.tmpdir(), "triggershell-env-")), ".env");
|
||||
return path.join(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), "triggershell-env-")),
|
||||
".env",
|
||||
);
|
||||
}
|
||||
|
||||
test("upsertEnvVar appends a new key", () => {
|
||||
|
||||
@@ -20,13 +20,19 @@ function parseEnvLines(content: string): EnvEntry[] {
|
||||
/** Loads a `.env` file into `process.env`, without overriding vars already set. */
|
||||
export function loadDotenv(envPath: string): void {
|
||||
if (!fs.existsSync(envPath)) return;
|
||||
for (const { key, value } of parseEnvLines(fs.readFileSync(envPath, "utf-8"))) {
|
||||
for (const { key, value } of parseEnvLines(
|
||||
fs.readFileSync(envPath, "utf-8"),
|
||||
)) {
|
||||
if (process.env[key] === undefined) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets `key=value` in a `.env` file, replacing an existing line for that key rather than duplicating it. */
|
||||
export function upsertEnvVar(envPath: string, key: string, value: string): void {
|
||||
export function upsertEnvVar(
|
||||
envPath: string,
|
||||
key: string,
|
||||
value: string,
|
||||
): void {
|
||||
const lines = fs.existsSync(envPath)
|
||||
? fs.readFileSync(envPath, "utf-8").split("\n")
|
||||
: [];
|
||||
|
||||
+10
-2
@@ -51,10 +51,18 @@ export async function isServerReachable(url: string): Promise<boolean> {
|
||||
|
||||
export function openBrowser(url: string): void {
|
||||
const command =
|
||||
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
||||
process.platform === "darwin"
|
||||
? "open"
|
||||
: process.platform === "win32"
|
||||
? "start"
|
||||
: "xdg-open";
|
||||
const args = process.platform === "win32" ? ["", url] : [url];
|
||||
try {
|
||||
spawn(command, args, { detached: true, stdio: "ignore", shell: process.platform === "win32" }).unref();
|
||||
spawn(command, args, {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
shell: process.platform === "win32",
|
||||
}).unref();
|
||||
} catch {
|
||||
// best-effort - not fatal if no browser opener is available
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ const DEFAULT_CONFIG_NAME = "triggershell.yml";
|
||||
|
||||
/** Root of the installed `triggershell` package - one level up from `src/cli/lib`. */
|
||||
export function resolveAppRoot(): string {
|
||||
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
||||
return path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveConfigPath(configArg?: string): string {
|
||||
|
||||
@@ -5,7 +5,8 @@ import { renderUnit } from "./systemd";
|
||||
test("renderUnit builds an absolute-path ExecStart with the given args", () => {
|
||||
const unit = renderUnit({
|
||||
execPath: "/usr/bin/node",
|
||||
binPath: "/home/user/.local/share/pnpm/global/5/node_modules/.bin/triggershell",
|
||||
binPath:
|
||||
"/home/user/.local/share/pnpm/global/5/node_modules/.bin/triggershell",
|
||||
configPath: "/home/user/project/triggershell.yml",
|
||||
configDir: "/home/user/project",
|
||||
port: 8080,
|
||||
@@ -31,5 +32,8 @@ test("renderUnit uses multi-user.target for the system scope", () => {
|
||||
});
|
||||
|
||||
assert.match(unit, /WantedBy=multi-user\.target/);
|
||||
assert.match(unit, /ExecStart=\/usr\/bin\/node .*start --config .*--no-browser$/m);
|
||||
assert.match(
|
||||
unit,
|
||||
/ExecStart=\/usr\/bin\/node .*start --config .*--no-browser$/m,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,8 @@ export function renderUnit(opts: UnitOptions): string {
|
||||
.map((part) => (part.includes(" ") ? `"${part}"` : part))
|
||||
.join(" ");
|
||||
|
||||
const wantedBy = opts.scope === "user" ? "default.target" : "multi-user.target";
|
||||
const wantedBy =
|
||||
opts.scope === "user" ? "default.target" : "multi-user.target";
|
||||
|
||||
return `[Unit]
|
||||
Description=TriggerShell - self-hosted script runner
|
||||
@@ -42,7 +43,13 @@ WantedBy=${wantedBy}
|
||||
}
|
||||
|
||||
export function userUnitPath(): string {
|
||||
return path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
||||
return path.join(
|
||||
os.homedir(),
|
||||
".config",
|
||||
"systemd",
|
||||
"user",
|
||||
`${SERVICE_NAME}.service`,
|
||||
);
|
||||
}
|
||||
|
||||
export function systemUnitPath(): string {
|
||||
|
||||
@@ -3,7 +3,10 @@ import { test } from "node:test";
|
||||
import type { VariableConfig } from "../../lib/config/schema";
|
||||
import { coerceVariables, parseVarFlags } from "./variables";
|
||||
|
||||
function stringVar(name: string, overrides: Partial<VariableConfig> = {}): VariableConfig {
|
||||
function stringVar(
|
||||
name: string,
|
||||
overrides: Partial<VariableConfig> = {},
|
||||
): VariableConfig {
|
||||
return {
|
||||
type: "string",
|
||||
name,
|
||||
@@ -62,18 +65,32 @@ test("parseVarFlags rejects a flag with no '='", () => {
|
||||
});
|
||||
|
||||
test("coerceVariables coerces booleans and numbers, passes strings through", () => {
|
||||
const variables = [stringVar("environment"), boolVar("dryRun"), numberVar("replicas")];
|
||||
const variables = [
|
||||
stringVar("environment"),
|
||||
boolVar("dryRun"),
|
||||
numberVar("replicas"),
|
||||
];
|
||||
const values = coerceVariables(variables, {
|
||||
environment: ["staging"],
|
||||
dryRun: ["true"],
|
||||
replicas: ["3"],
|
||||
});
|
||||
assert.deepEqual(values, { environment: "staging", dryRun: true, replicas: 3 });
|
||||
assert.deepEqual(values, {
|
||||
environment: "staging",
|
||||
dryRun: true,
|
||||
replicas: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test("coerceVariables rejects an invalid boolean/number", () => {
|
||||
assert.throws(() => coerceVariables([boolVar("dryRun")], { dryRun: ["yes"] }), /must be 'true' or 'false'/);
|
||||
assert.throws(() => coerceVariables([numberVar("replicas")], { replicas: ["abc"] }), /not a valid number/);
|
||||
assert.throws(
|
||||
() => coerceVariables([boolVar("dryRun")], { dryRun: ["yes"] }),
|
||||
/must be 'true' or 'false'/,
|
||||
);
|
||||
assert.throws(
|
||||
() => coerceVariables([numberVar("replicas")], { replicas: ["abc"] }),
|
||||
/not a valid number/,
|
||||
);
|
||||
});
|
||||
|
||||
test("coerceVariables collects a multiselect variable's repeats into an array", () => {
|
||||
@@ -85,7 +102,8 @@ test("coerceVariables collects a multiselect variable's repeats into an array",
|
||||
|
||||
test("coerceVariables rejects a non-multiselect variable given more than once", () => {
|
||||
assert.throws(
|
||||
() => coerceVariables([stringVar("environment")], { environment: ["a", "b"] }),
|
||||
() =>
|
||||
coerceVariables([stringVar("environment")], { environment: ["a", "b"] }),
|
||||
/given 2 times/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@ export function parseVarFlags(pairs: string[]): Record<string, string[]> {
|
||||
for (const pair of pairs) {
|
||||
const eq = pair.indexOf("=");
|
||||
if (eq === -1) {
|
||||
throw new Error(`--var ${pair} is missing '=' - expected --var name=value`);
|
||||
throw new Error(
|
||||
`--var ${pair} is missing '=' - expected --var name=value`,
|
||||
);
|
||||
}
|
||||
const name = pair.slice(0, eq);
|
||||
const value = pair.slice(eq + 1);
|
||||
@@ -29,7 +31,9 @@ export function coerceVariables(
|
||||
|
||||
for (const name of Object.keys(grouped)) {
|
||||
if (!known.has(name)) {
|
||||
throw new Error(`--var ${name}=... does not match any variable on this script`);
|
||||
throw new Error(
|
||||
`--var ${name}=... does not match any variable on this script`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +56,16 @@ export function coerceVariables(
|
||||
if (variable.type === "boolean") {
|
||||
if (value === "true") values[variable.name] = true;
|
||||
else if (value === "false") values[variable.name] = false;
|
||||
else throw new Error(`--var ${variable.name}=${value} must be 'true' or 'false'`);
|
||||
else
|
||||
throw new Error(
|
||||
`--var ${variable.name}=${value} must be 'true' or 'false'`,
|
||||
);
|
||||
} else if (variable.type === "number") {
|
||||
const n = Number(value);
|
||||
if (Number.isNaN(n)) {
|
||||
throw new Error(`--var ${variable.name}=${value} is not a valid number`);
|
||||
throw new Error(
|
||||
`--var ${variable.name}=${value} is not a valid number`,
|
||||
);
|
||||
}
|
||||
values[variable.name] = n;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user