fix(server): isolate paseo.json parse failures so they don't block fetch_workspaces
A malformed paseo.json (e.g. git-conflict markers) in any registered
workspace caused readPaseoConfig to throw, which propagated through
getScriptConfigs -> buildWorkspaceScriptPayloads ->
Session.describeWorkspaceRecord and failed the entire
fetch_workspaces_request with code=fetch_workspaces_failed. The client
showed no workspaces at all.
Reshape: separate loading (mechanism) from policy (per-caller).
- readPaseoConfig now returns a discriminated result
({ ok: true; config } | { ok: false; configPath; error }). Pure load,
no logging, no opinions.
- Workspace projection owns its own policy via readPaseoConfigForProjection
in script-status-projection.ts: warn via pino with configPath +
workspaceDirectory + err, treat workspace as having no scripts, keep the
list alive.
- spawnWorkspaceScript throws with cause so the existing
handleStartWorkspaceScriptRequest catch logs it once with full context.
- The setup/teardown/terminal wrappers retain their pre-existing silent
degradation on missing/broken config.
- getScriptConfigs takes a parsed PaseoConfig | null instead of repoRoot
so the projection can pass the result through without double-reading.
Closes https://github.com/getpaseo/paseo/issues/598
This commit is contained in:
@@ -269,6 +269,7 @@ export async function createPaseoDaemon(
|
|||||||
daemonPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
|
daemonPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
|
||||||
resolveWorkspaceDirectory: async (workspaceId) =>
|
resolveWorkspaceDirectory: async (workspaceId) =>
|
||||||
(await workspaceRegistry?.get(workspaceId))?.cwd ?? null,
|
(await workspaceRegistry?.get(workspaceId))?.cwd ?? null,
|
||||||
|
logger,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const handleBranchChange = createBranchChangeRouteHandler({
|
const handleBranchChange = createBranchChangeRouteHandler({
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import {
|
|||||||
import { WorkspaceScriptPayloadSchema } from "../shared/messages.js";
|
import { WorkspaceScriptPayloadSchema } from "../shared/messages.js";
|
||||||
import type { ScriptHealthState } from "./script-health-monitor.js";
|
import type { ScriptHealthState } from "./script-health-monitor.js";
|
||||||
import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
||||||
|
import { readPaseoConfig } from "../utils/worktree.js";
|
||||||
|
import type { PaseoConfig } from "../utils/paseo-config-schema.js";
|
||||||
|
import { createTestLogger } from "../test-utils/test-logger.js";
|
||||||
|
|
||||||
function createWorkspaceRepo(options?: {
|
function createWorkspaceRepo(options?: {
|
||||||
branchName?: string;
|
branchName?: string;
|
||||||
@@ -41,13 +44,21 @@ function createWorkspaceRepo(options?: {
|
|||||||
function buildPayloads(input: {
|
function buildPayloads(input: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
workspaceDirectory: string;
|
workspaceDirectory: string;
|
||||||
|
paseoConfig?: PaseoConfig | null;
|
||||||
routeStore: ScriptRouteStore;
|
routeStore: ScriptRouteStore;
|
||||||
runtimeStore: WorkspaceScriptRuntimeStore;
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
||||||
daemonPort: number | null;
|
daemonPort: number | null;
|
||||||
gitMetadata?: { projectSlug: string; currentBranch: string | null };
|
gitMetadata?: { projectSlug: string; currentBranch: string | null };
|
||||||
resolveHealth?: (hostname: string) => ScriptHealthState | null;
|
resolveHealth?: (hostname: string) => ScriptHealthState | null;
|
||||||
}) {
|
}) {
|
||||||
return buildWorkspaceScriptPayloads(input);
|
const paseoConfig =
|
||||||
|
input.paseoConfig !== undefined ? input.paseoConfig : loadConfig(input.workspaceDirectory);
|
||||||
|
return buildWorkspaceScriptPayloads({ ...input, paseoConfig });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig(repoRoot: string): PaseoConfig | null {
|
||||||
|
const result = readPaseoConfig(repoRoot);
|
||||||
|
return result.ok ? result.config : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("script-status-projection", () => {
|
describe("script-status-projection", () => {
|
||||||
@@ -369,6 +380,67 @@ describe("script-status-projection", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("readPaseoConfig fails with configPath and error when paseo.json is malformed", () => {
|
||||||
|
const workspace = createWorkspaceRepo();
|
||||||
|
const configPath = path.join(workspace.repoDir, "paseo.json");
|
||||||
|
writeFileSync(
|
||||||
|
configPath,
|
||||||
|
'{\n<<<<<<< HEAD\n "scripts": {}\n=======\n "scripts": {}\n>>>>>>> origin/main\n}\n',
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = readPaseoConfig(workspace.repoDir);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) throw new Error("unreachable");
|
||||||
|
expect(result.configPath).toBe(configPath);
|
||||||
|
expect(result.error).toBeInstanceOf(SyntaxError);
|
||||||
|
} finally {
|
||||||
|
workspace.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("buildWorkspaceScriptPayloads given paseoConfig=null still surfaces orphaned runtime scripts", () => {
|
||||||
|
const workspaceId = "workspace-null-config";
|
||||||
|
const workspace = createWorkspaceRepo();
|
||||||
|
const routeStore = new ScriptRouteStore();
|
||||||
|
const runtimeStore = new WorkspaceScriptRuntimeStore();
|
||||||
|
runtimeStore.set({
|
||||||
|
workspaceId,
|
||||||
|
scriptName: "typecheck",
|
||||||
|
type: "script",
|
||||||
|
lifecycle: "running",
|
||||||
|
terminalId: "term-typecheck",
|
||||||
|
exitCode: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(
|
||||||
|
buildPayloads({
|
||||||
|
workspaceId,
|
||||||
|
workspaceDirectory: workspace.repoDir,
|
||||||
|
paseoConfig: null,
|
||||||
|
routeStore,
|
||||||
|
runtimeStore,
|
||||||
|
daemonPort: 6767,
|
||||||
|
}),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
scriptName: "typecheck",
|
||||||
|
type: "script",
|
||||||
|
hostname: "typecheck",
|
||||||
|
port: null,
|
||||||
|
proxyUrl: null,
|
||||||
|
lifecycle: "running",
|
||||||
|
health: null,
|
||||||
|
exitCode: null,
|
||||||
|
terminalId: "term-typecheck",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
workspace.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("createScriptStatusEmitter overlays health onto the projected workspace script list", async () => {
|
it("createScriptStatusEmitter overlays health onto the projected workspace script list", async () => {
|
||||||
const workspaceId = "workspace-emitter";
|
const workspaceId = "workspace-emitter";
|
||||||
const workspace = createWorkspaceRepo({
|
const workspace = createWorkspaceRepo({
|
||||||
@@ -405,6 +477,7 @@ describe("script-status-projection", () => {
|
|||||||
daemonPort: 6767,
|
daemonPort: 6767,
|
||||||
resolveWorkspaceDirectory: async (requestedWorkspaceId) =>
|
resolveWorkspaceDirectory: async (requestedWorkspaceId) =>
|
||||||
requestedWorkspaceId === "workspace-emitter" ? workspace.repoDir : null,
|
requestedWorkspaceId === "workspace-emitter" ? workspace.repoDir : null,
|
||||||
|
logger: createTestLogger(),
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import type { Logger } from "pino";
|
||||||
import type {
|
import type {
|
||||||
ScriptStatusUpdateMessage,
|
ScriptStatusUpdateMessage,
|
||||||
SessionOutboundMessage,
|
SessionOutboundMessage,
|
||||||
WorkspaceScriptPayload,
|
WorkspaceScriptPayload,
|
||||||
} from "../shared/messages.js";
|
} from "../shared/messages.js";
|
||||||
|
import type { PaseoConfig } from "../utils/paseo-config-schema.js";
|
||||||
import { buildScriptHostname } from "../utils/script-hostname.js";
|
import { buildScriptHostname } from "../utils/script-hostname.js";
|
||||||
import { getScriptConfigs, isServiceScript } from "../utils/worktree.js";
|
import { getScriptConfigs, isServiceScript, readPaseoConfig } from "../utils/worktree.js";
|
||||||
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
||||||
import type { ScriptHealthEntry, ScriptHealthState } from "./script-health-monitor.js";
|
import type { ScriptHealthEntry, ScriptHealthState } from "./script-health-monitor.js";
|
||||||
import type { ScriptRouteStore } from "./script-proxy.js";
|
import type { ScriptRouteStore } from "./script-proxy.js";
|
||||||
@@ -17,6 +19,7 @@ interface SessionEmitter {
|
|||||||
interface BuildWorkspaceScriptPayloadsOptions {
|
interface BuildWorkspaceScriptPayloadsOptions {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
workspaceDirectory: string;
|
workspaceDirectory: string;
|
||||||
|
paseoConfig: PaseoConfig | null;
|
||||||
routeStore: ScriptRouteStore;
|
routeStore: ScriptRouteStore;
|
||||||
runtimeStore: WorkspaceScriptRuntimeStore;
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
||||||
daemonPort: number | null;
|
daemonPort: number | null;
|
||||||
@@ -27,6 +30,21 @@ interface BuildWorkspaceScriptPayloadsOptions {
|
|||||||
resolveHealth?: (hostname: string) => ScriptHealthState | null;
|
resolveHealth?: (hostname: string) => ScriptHealthState | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readPaseoConfigForProjection(
|
||||||
|
workspaceDirectory: string,
|
||||||
|
logger: Logger,
|
||||||
|
): PaseoConfig | null {
|
||||||
|
const result = readPaseoConfig(workspaceDirectory);
|
||||||
|
if (result.ok) {
|
||||||
|
return result.config;
|
||||||
|
}
|
||||||
|
logger.warn(
|
||||||
|
{ configPath: result.configPath, workspaceDirectory, err: result.error },
|
||||||
|
"Failed to parse paseo.json; treating workspace as having no scripts",
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveDaemonPort(daemonPort: number | null | (() => number | null)): number | null {
|
function resolveDaemonPort(daemonPort: number | null | (() => number | null)): number | null {
|
||||||
if (typeof daemonPort === "function") {
|
if (typeof daemonPort === "function") {
|
||||||
return daemonPort();
|
return daemonPort();
|
||||||
@@ -136,7 +154,7 @@ export function buildWorkspaceScriptPayloads(
|
|||||||
const workspaceDirectory = options.workspaceDirectory;
|
const workspaceDirectory = options.workspaceDirectory;
|
||||||
const projectSlug = options.gitMetadata?.projectSlug ?? deriveProjectSlug(workspaceDirectory);
|
const projectSlug = options.gitMetadata?.projectSlug ?? deriveProjectSlug(workspaceDirectory);
|
||||||
const branchName = options.gitMetadata?.currentBranch ?? null;
|
const branchName = options.gitMetadata?.currentBranch ?? null;
|
||||||
const scriptConfigs = getScriptConfigs(workspaceDirectory);
|
const scriptConfigs = getScriptConfigs(options.paseoConfig);
|
||||||
const runtimeEntries = new Map(
|
const runtimeEntries = new Map(
|
||||||
options.runtimeStore
|
options.runtimeStore
|
||||||
.listForWorkspace(workspaceId)
|
.listForWorkspace(workspaceId)
|
||||||
@@ -193,12 +211,14 @@ export function createScriptStatusEmitter({
|
|||||||
runtimeStore,
|
runtimeStore,
|
||||||
daemonPort,
|
daemonPort,
|
||||||
resolveWorkspaceDirectory,
|
resolveWorkspaceDirectory,
|
||||||
|
logger,
|
||||||
}: {
|
}: {
|
||||||
sessions: () => SessionEmitter[];
|
sessions: () => SessionEmitter[];
|
||||||
routeStore: ScriptRouteStore;
|
routeStore: ScriptRouteStore;
|
||||||
runtimeStore: WorkspaceScriptRuntimeStore;
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
||||||
daemonPort: number | null | (() => number | null);
|
daemonPort: number | null | (() => number | null);
|
||||||
resolveWorkspaceDirectory: (workspaceId: string) => string | null | Promise<string | null>;
|
resolveWorkspaceDirectory: (workspaceId: string) => string | null | Promise<string | null>;
|
||||||
|
logger: Logger;
|
||||||
}): (workspaceId: string, scripts: ScriptHealthEntry[]) => void {
|
}): (workspaceId: string, scripts: ScriptHealthEntry[]) => void {
|
||||||
return (workspaceId, scripts) => {
|
return (workspaceId, scripts) => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@@ -215,6 +235,7 @@ export function createScriptStatusEmitter({
|
|||||||
const projected = buildWorkspaceScriptPayloads({
|
const projected = buildWorkspaceScriptPayloads({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceDirectory,
|
workspaceDirectory,
|
||||||
|
paseoConfig: readPaseoConfigForProjection(workspaceDirectory, logger),
|
||||||
routeStore,
|
routeStore,
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
daemonPort: resolvedDaemonPort,
|
daemonPort: resolvedDaemonPort,
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ import { sendPromptToAgent, unarchiveAgentState } from "./agent/mcp-shared.js";
|
|||||||
import { experimental_createMCPClient } from "ai";
|
import { experimental_createMCPClient } from "ai";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import type { VoiceCallerContext, VoiceSpeakHandler } from "./voice-types.js";
|
import type { VoiceCallerContext, VoiceSpeakHandler } from "./voice-types.js";
|
||||||
import { buildWorkspaceScriptPayloads } from "./script-status-projection.js";
|
import {
|
||||||
|
buildWorkspaceScriptPayloads,
|
||||||
|
readPaseoConfigForProjection,
|
||||||
|
} from "./script-status-projection.js";
|
||||||
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
||||||
import type { ScriptHealthState } from "./script-health-monitor.js";
|
import type { ScriptHealthState } from "./script-health-monitor.js";
|
||||||
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
||||||
@@ -6069,6 +6072,7 @@ export class Session {
|
|||||||
? buildWorkspaceScriptPayloads({
|
? buildWorkspaceScriptPayloads({
|
||||||
workspaceId: workspace.workspaceId,
|
workspaceId: workspace.workspaceId,
|
||||||
workspaceDirectory: workspace.cwd,
|
workspaceDirectory: workspace.cwd,
|
||||||
|
paseoConfig: readPaseoConfigForProjection(workspace.cwd, this.sessionLogger),
|
||||||
routeStore: this.scriptRouteStore,
|
routeStore: this.scriptRouteStore,
|
||||||
runtimeStore: this.scriptRuntimeStore,
|
runtimeStore: this.scriptRuntimeStore,
|
||||||
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
||||||
@@ -7133,6 +7137,7 @@ export class Session {
|
|||||||
return buildWorkspaceScriptPayloads({
|
return buildWorkspaceScriptPayloads({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceDirectory,
|
workspaceDirectory,
|
||||||
|
paseoConfig: readPaseoConfigForProjection(workspaceDirectory, this.sessionLogger),
|
||||||
routeStore: this.scriptRouteStore,
|
routeStore: this.scriptRouteStore,
|
||||||
runtimeStore: this.scriptRuntimeStore,
|
runtimeStore: this.scriptRuntimeStore,
|
||||||
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
getWorktreeTerminalSpecs,
|
getWorktreeTerminalSpecs,
|
||||||
isServiceScript,
|
isServiceScript,
|
||||||
processCarriageReturns,
|
processCarriageReturns,
|
||||||
|
readPaseoConfig,
|
||||||
resolveWorktreeRuntimeEnv,
|
resolveWorktreeRuntimeEnv,
|
||||||
runWorktreeSetupCommands,
|
runWorktreeSetupCommands,
|
||||||
WorktreeSetupError,
|
WorktreeSetupError,
|
||||||
@@ -832,7 +833,13 @@ export async function spawnWorkspaceScript(
|
|||||||
logger,
|
logger,
|
||||||
onLifecycleChanged,
|
onLifecycleChanged,
|
||||||
} = options;
|
} = options;
|
||||||
const scriptConfigs = getScriptConfigs(repoRoot);
|
const configResult = readPaseoConfig(repoRoot);
|
||||||
|
if (!configResult.ok) {
|
||||||
|
throw new Error(`Failed to parse ${configResult.configPath}`, {
|
||||||
|
cause: configResult.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const scriptConfigs = getScriptConfigs(configResult.config);
|
||||||
const config = scriptConfigs.get(scriptName);
|
const config = scriptConfigs.get(scriptName);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error(`Script '${scriptName}' is not configured in paseo.json`);
|
throw new Error(`Script '${scriptName}' is not configured in paseo.json`);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
isServiceScript,
|
isServiceScript,
|
||||||
isPaseoOwnedWorktreeCwd,
|
isPaseoOwnedWorktreeCwd,
|
||||||
listPaseoWorktrees,
|
listPaseoWorktrees,
|
||||||
|
readPaseoConfig,
|
||||||
resolveWorktreeRuntimeEnv,
|
resolveWorktreeRuntimeEnv,
|
||||||
type WorktreeSetupCommandProgressEvent,
|
type WorktreeSetupCommandProgressEvent,
|
||||||
runWorktreeSetupCommands,
|
runWorktreeSetupCommands,
|
||||||
@@ -18,6 +19,12 @@ import {
|
|||||||
type CreateWorktreeOptions,
|
type CreateWorktreeOptions,
|
||||||
type WorktreeConfig,
|
type WorktreeConfig,
|
||||||
} from "./worktree";
|
} from "./worktree";
|
||||||
|
import type { PaseoConfig } from "./paseo-config-schema.js";
|
||||||
|
|
||||||
|
function loadConfigForTest(repoRoot: string): PaseoConfig | null {
|
||||||
|
const result = readPaseoConfig(repoRoot);
|
||||||
|
return result.ok ? result.config : null;
|
||||||
|
}
|
||||||
import { getPaseoWorktreeMetadataPath } from "./worktree-metadata.js";
|
import { getPaseoWorktreeMetadataPath } from "./worktree-metadata.js";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import {
|
import {
|
||||||
@@ -707,7 +714,7 @@ describe.skipIf(process.platform === "win32")("createWorktree", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const scriptConfigs = getScriptConfigs(repoDir);
|
const scriptConfigs = getScriptConfigs(loadConfigForTest(repoDir));
|
||||||
const typecheck = scriptConfigs.get("typecheck");
|
const typecheck = scriptConfigs.get("typecheck");
|
||||||
|
|
||||||
expect(typecheck).toEqual({
|
expect(typecheck).toEqual({
|
||||||
@@ -731,7 +738,7 @@ describe.skipIf(process.platform === "win32")("createWorktree", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const scriptConfigs = getScriptConfigs(repoDir);
|
const scriptConfigs = getScriptConfigs(loadConfigForTest(repoDir));
|
||||||
const server = scriptConfigs.get("server");
|
const server = scriptConfigs.get("server");
|
||||||
|
|
||||||
expect(server).toEqual({
|
expect(server).toEqual({
|
||||||
@@ -771,7 +778,7 @@ describe.skipIf(process.platform === "win32")("createWorktree", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(getScriptConfigs(repoDir)).toEqual(
|
expect(getScriptConfigs(loadConfigForTest(repoDir))).toEqual(
|
||||||
new Map([
|
new Map([
|
||||||
["valid", { command: "npm run valid" }],
|
["valid", { command: "npm run valid" }],
|
||||||
["invalidType", { command: "npm run worker" }],
|
["invalidType", { command: "npm run worker" }],
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { createHash } from "node:crypto";
|
|||||||
import * as pty from "node-pty";
|
import * as pty from "node-pty";
|
||||||
import stripAnsi from "strip-ansi";
|
import stripAnsi from "strip-ansi";
|
||||||
import { buildStringCommandShellInvocation } from "./string-command-shell.js";
|
import { buildStringCommandShellInvocation } from "./string-command-shell.js";
|
||||||
import { readPaseoConfigJson } from "./paseo-config-file.js";
|
import { readPaseoConfigJson, resolvePaseoConfigPath } from "./paseo-config-file.js";
|
||||||
export {
|
export {
|
||||||
PaseoConfigRawSchema,
|
PaseoConfigRawSchema,
|
||||||
PaseoLifecycleCommandRawSchema,
|
PaseoLifecycleCommandRawSchema,
|
||||||
@@ -197,31 +197,35 @@ export class UnknownBranchError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readPaseoConfig(repoRoot: string): PaseoConfig | null {
|
export type ReadPaseoConfigResult =
|
||||||
|
| { ok: true; config: PaseoConfig | null }
|
||||||
|
| { ok: false; configPath: string; error: unknown };
|
||||||
|
|
||||||
|
export function readPaseoConfig(repoRoot: string): ReadPaseoConfigResult {
|
||||||
try {
|
try {
|
||||||
const json = readPaseoConfigJson(repoRoot);
|
const json = readPaseoConfigJson(repoRoot);
|
||||||
if (json === null) {
|
if (json === null) {
|
||||||
return null;
|
return { ok: true, config: null };
|
||||||
}
|
}
|
||||||
return PaseoConfigSchema.parse(json);
|
return { ok: true, config: PaseoConfigSchema.parse(json) };
|
||||||
} catch {
|
} catch (error) {
|
||||||
throw new Error(`Failed to parse paseo.json`);
|
return { ok: false, configPath: resolvePaseoConfigPath(repoRoot), error };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorktreeSetupCommands(repoRoot: string): string[] {
|
export function getWorktreeSetupCommands(repoRoot: string): string[] {
|
||||||
const config = readPaseoConfig(repoRoot);
|
const result = readPaseoConfig(repoRoot);
|
||||||
return config?.worktree?.setup ?? [];
|
return result.ok ? (result.config?.worktree?.setup ?? []) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorktreeTeardownCommands(repoRoot: string): string[] {
|
export function getWorktreeTeardownCommands(repoRoot: string): string[] {
|
||||||
const config = readPaseoConfig(repoRoot);
|
const result = readPaseoConfig(repoRoot);
|
||||||
return config?.worktree?.teardown ?? [];
|
return result.ok ? (result.config?.worktree?.teardown ?? []) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorktreeTerminalSpecs(repoRoot: string): WorktreeTerminalConfig[] {
|
export function getWorktreeTerminalSpecs(repoRoot: string): WorktreeTerminalConfig[] {
|
||||||
const config = readPaseoConfig(repoRoot);
|
const result = readPaseoConfig(repoRoot);
|
||||||
const terminals = config?.worktree?.terminals;
|
const terminals = result.ok ? result.config?.worktree?.terminals : undefined;
|
||||||
if (!Array.isArray(terminals) || terminals.length === 0) {
|
if (!Array.isArray(terminals) || terminals.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -254,8 +258,7 @@ export function getWorktreeTerminalSpecs(repoRoot: string): WorktreeTerminalConf
|
|||||||
return specs;
|
return specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScriptConfigs(repoRoot: string): Map<string, ScriptConfig> {
|
export function getScriptConfigs(config: PaseoConfig | null): Map<string, ScriptConfig> {
|
||||||
const config = readPaseoConfig(repoRoot);
|
|
||||||
const scripts = config?.scripts;
|
const scripts = config?.scripts;
|
||||||
if (!scripts || typeof scripts !== "object") {
|
if (!scripts || typeof scripts !== "object") {
|
||||||
return new Map();
|
return new Map();
|
||||||
|
|||||||
Reference in New Issue
Block a user