Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/test-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# Re-enable once the repo-level lint hang is resolved.
# - name: Lint
# run: pnpm lint
- name: Lint
run: pnpm lint

- name: Test
run: pnpm test
10 changes: 9 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export default tseslint.config(
},
},
{
ignores: ['**/dist/**', '**/node_modules/**', '**/worktrees/**', '**/workspaces/**'],
ignores: [
'**/dist/**',
'**/node_modules/**',
'**/.next/**',
'**/.vite/**',
'**/coverage/**',
'**/worktrees/**',
'**/workspaces/**',
],
}
)
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"scripts": {
"build": "pnpm -r build",
"test": "pnpm -r test",
"lint": "pnpm exec eslint .",
"lint:fix": "pnpm exec eslint . --fix",
"lint": "pnpm --filter './packages/*' -r lint && pnpm exec eslint eslint.config.js",
"lint:fix": "pnpm --filter './packages/*' -r lint:fix && pnpm exec eslint eslint.config.js --fix",
"parallax": "NODE_ENV=dev pnpm --filter parallax-cli start",
"clean": "rm -rf packages/orchestrator/parallax.db packages/orchestrator/workspaces",
"release:pack": "pnpm --filter parallax-cli pack:tarball",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
},
"scripts": {
"build": "rm -rf dist && tsc",
"lint": "eslint src test scripts vitest.config.ts",
"lint:fix": "eslint src test scripts vitest.config.ts --fix",
"prepack": "node ./scripts/prepare-package.mjs",
"postpack": "node ./scripts/restore-workspace-links.mjs",
"pack:check": "npm_config_cache=.npm-cache npm pack --dry-run",
Expand Down
19 changes: 8 additions & 11 deletions packages/cli/src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ export function parseStartOptions(args: string[]): StartCommandOptions {
const apiPort = parseStrictPort(args, 'server-api-port', 3000)
const uiPort = parseStrictPort(args, 'server-ui-port', 8080)
const rawConcurrency = parseOptionalArg(args, 'concurrency')
const concurrency =
rawConcurrency === undefined ? 2 : Number.parseInt(rawConcurrency, 10)
const concurrency = rawConcurrency === undefined ? 2 : Number.parseInt(rawConcurrency, 10)

if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 16) {
throw new Error('--concurrency must be an integer between 1 and 16.')
Expand Down Expand Up @@ -260,15 +259,13 @@ export function parseRegisterOptions(
throw new Error('parallax unregister does not accept flags.')
}

const positionalArgs = args
.slice(1)
.filter((entry, index, entries) => {
const previous = entries[index - 1]
if (previous === '--env-file') {
return false
}
return !entry.startsWith('--')
})
const positionalArgs = args.slice(1).filter((entry, index, entries) => {
const previous = entries[index - 1]
if (previous === '--env-file') {
return false
}
return !entry.startsWith('--')
})
if (positionalArgs.length > 0) {
throw new Error(`parallax ${command} accepts exactly one <config-file>.`)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/commands/pending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ async function postJson(url: string, body: unknown) {

function printPendingSummary(tasks: TaskPendingState[]) {
for (const task of tasks) {
console.log(`- ${task.id} | project=${task.projectId} | plan=${task.planState} | agent=${task.lastAgent ?? 'n/a'}`)
console.log(
`- ${task.id} | project=${task.projectId} | plan=${task.planState} | agent=${task.lastAgent ?? 'n/a'}`
)
console.log(` title: ${task.title ?? '(no title)'}`)
const snippet = task.planMarkdown ?? task.planResult
if (snippet) {
Expand Down
13 changes: 10 additions & 3 deletions packages/cli/src/commands/pr-review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ async function postJson<T>(url: string, body: unknown): Promise<T> {

if (!response.ok) {
const payload = (await response.json().catch(() => undefined)) as { error?: string } | undefined
throw new Error(payload?.error ?? `Request failed: ${url} ${response.status} ${response.statusText}`)
throw new Error(
payload?.error ?? `Request failed: ${url} ${response.status} ${response.statusText}`
)
}

return response.json() as Promise<T>
Expand All @@ -26,12 +28,17 @@ export async function runPrReview(args: string[], context: CliContext) {

console.log('')
console.log(`${YELLOW}${BOLD}⚠ Experimental: pr-review is an early on-demand workflow.${RESET}`)
console.log(`${YELLOW}It will try to apply open human PR review comments to the existing PR branch.${RESET}`)
console.log(
`${YELLOW}It will try to apply open human PR review comments to the existing PR branch.${RESET}`
)
console.log('')

let queuedTask: { reviewTaskId: string; prNumber: number }
try {
queuedTask = await postJson(`${apiBase}/tasks/${encodeURIComponent(options.taskId)}/pr-review`, {})
queuedTask = await postJson(
`${apiBase}/tasks/${encodeURIComponent(options.taskId)}/pr-review`,
{}
)
} catch (error) {
throw new Error(
`Failed to queue PR review for task ${options.taskId}: ${
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/commands/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export async function runPreflight(args: string[]) {
name: 'claude CLI',
ok: claudeOk,
required: false,
detail: claudeOk ? undefined : 'Install Claude Code CLI (npm i -g @anthropic-ai/claude-code).',
detail: claudeOk
? undefined
: 'Install Claude Code CLI (npm i -g @anthropic-ai/claude-code).',
})

checks.push({
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/commands/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ async function reloadRunningRuntime(context: CliContext) {
})
if (!response.ok) {
const payload = (await response.json().catch(() => undefined)) as { error?: string } | undefined
throw new Error(payload?.error ?? `Failed to reload running Parallax instance (${response.status}).`)
throw new Error(
payload?.error ?? `Failed to reload running Parallax instance (${response.status}).`
)
}

return true
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,7 @@ export async function runStart(args: string[], context: CliContext) {
if (workspaceDevMode) {
orchestratorPid = spawnDetached(
process.execPath,
[
'--import',
'tsx',
path.resolve(context.rootDir, 'packages/orchestrator/src/index.ts'),
],
['--import', 'tsx', path.resolve(context.rootDir, 'packages/orchestrator/src/index.ts')],
context.rootDir,
env,
{
Expand All @@ -117,7 +113,16 @@ export async function runStart(args: string[], context: CliContext) {

uiPid = spawnDetached(
'pnpm',
['--filter', '@parallax/ui', 'start', '--', '--host', '0.0.0.0', '--port', String(options.uiPort)],
[
'--filter',
'@parallax/ui',
'start',
'--',
'--host',
'0.0.0.0',
'--port',
String(options.uiPort),
],
context.rootDir,
{
VITE_PARALLAX_API_BASE: `http://localhost:${options.apiPort}`,
Expand Down
14 changes: 10 additions & 4 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ export function parseRegistryState(raw: string, source: string): RegistryState {
)
}

if (!parsed || typeof parsed !== 'object' || !Array.isArray((parsed as { configs?: unknown }).configs)) {
if (
!parsed ||
typeof parsed !== 'object' ||
!Array.isArray((parsed as { configs?: unknown }).configs)
) {
throw new Error(`Invalid config registry at ${source}.`)
}

Expand All @@ -128,8 +132,7 @@ export function parseRegistryState(raw: string, source: string): RegistryState {
return {
configPath: (entry as { configPath: string }).configPath,
addedAt: (entry as { addedAt: number }).addedAt,
envFilePath:
(entry as { envFilePath?: string }).envFilePath?.trim() || undefined,
envFilePath: (entry as { envFilePath?: string }).envFilePath?.trim() || undefined,
}
}),
}
Expand Down Expand Up @@ -165,7 +168,10 @@ export async function resolveServerPorts(configPath: string): Promise<ServerConf
return parseServerPortsFromConfig(await fs.readFile(configPath, 'utf8'), configPath)
}

export async function loadRunningState(dataDir: string, manifestFile: string): Promise<RunningState> {
export async function loadRunningState(
dataDir: string,
manifestFile: string
): Promise<RunningState> {
const manifestPath = path.join(dataDir, manifestFile)
if (!(await ensureFileExists(manifestPath))) {
throw new Error(`No running instance found at ${manifestPath}. Run parallax start first.`)
Expand Down
20 changes: 18 additions & 2 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@ import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { DEFAULT_API_PORT } from '@parallax/common'
import { hasFlag, parseCancelOptions, parseLogsOptions, parsePendingOptions, parsePreflightOptions, parsePrReviewOptions, parseRegisterOptions, parseRetryOptions, parseStartOptions, parseStatusOptions, parseStopOptions as parseStopOptionsInternal, resolvePath } from './args.js'
import {
hasFlag,
parseCancelOptions,
parseLogsOptions,
parsePendingOptions,
parsePreflightOptions,
parsePrReviewOptions,
parseRegisterOptions,
parseRetryOptions,
parseStartOptions,
parseStatusOptions,
parseStopOptions as parseStopOptionsInternal,
resolvePath,
} from './args.js'
import {
ensureFileExists,
loadRegistry as loadRegistryFromDisk,
Expand Down Expand Up @@ -56,7 +69,10 @@ function resolvePackageVersion(rootDir: string): string {
continue
}

const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8')) as { version?: string; name?: string }
const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8')) as {
version?: string
name?: string
}
if (
typeof parsed.version === 'string' &&
(parsed.name === 'parallax-cli' || candidate.endsWith('/packages/cli/package.json'))
Expand Down
18 changes: 15 additions & 3 deletions packages/cli/src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ export async function waitForExit(pid: number, timeoutMs: number): Promise<boole
return !isProcessAlive(pid)
}

export async function stopProcessOrThrow(pid: number, label: string, force: boolean): Promise<void> {
export async function stopProcessOrThrow(
pid: number,
label: string,
force: boolean
): Promise<void> {
if (!isProcessAlive(pid)) {
throw new Error(`${label} process ${pid} is not running.`)
}
Expand All @@ -110,7 +114,11 @@ export async function stopProcessOrThrow(pid: number, label: string, force: bool
}
}

export async function stopProcessBestEffort(pid: number | undefined, label: string, force: boolean) {
export async function stopProcessBestEffort(
pid: number | undefined,
label: string,
force: boolean
) {
if (!pid || !Number.isFinite(pid) || pid <= 0 || !isProcessAlive(pid)) {
return
}
Expand Down Expand Up @@ -146,7 +154,11 @@ export async function waitForUrlHealth(url: string, name: string): Promise<void>
throw new Error(`${name} failed to become ready at ${url}: ${lastError ?? 'timeout'}`)
}

export async function readFileTail(filePath: string, ensureFileExists: (filePath: string) => Promise<boolean>, maxLines: number = 30) {
export async function readFileTail(
filePath: string,
ensureFileExists: (filePath: string) => Promise<boolean>,
maxLines: number = 30
) {
if (!(await ensureFileExists(filePath))) {
return '(log file not found)'
}
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/test/logs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ describe('runLogs', () => {
vi.spyOn(Date, 'now').mockReturnValue(5_000)
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

sleepMock
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(stopLoop)
sleepMock.mockResolvedValueOnce(undefined).mockRejectedValueOnce(stopLoop)

vi.mocked(fetch)
.mockResolvedValueOnce({
Expand Down
2 changes: 2 additions & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
},
"scripts": {
"build": "rm -rf dist && tsc",
"lint": "eslint src test",
"lint:fix": "eslint src test --fix",
"test": "vitest run"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/marketing/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["dist"] },
{ ignores: ["dist", ".next", ".vite", "coverage"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
Expand Down
1 change: 1 addition & 0 deletions packages/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "node ./scripts/sync-docs.mjs && vite build",
"build:dev": "node ./scripts/sync-docs.mjs && vite build --mode development",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"preview": "node ./scripts/sync-docs.mjs && vite preview",
"test": "vitest run",
"test:watch": "vitest"
Expand Down
16 changes: 12 additions & 4 deletions packages/marketing/src/components/IntegrationsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import type { ReactNode } from "react";
import { MessageSquareCode, RotateCcw } from "lucide-react";

type IntegrationItem = {
href: string;
logo: string | ReactNode;
label: string;
imgClass?: string;
};

const IconCard = ({
href,
logo,
label,
imgClass,
}: {
href: string;
logo: string | React.ReactNode;
logo: string | ReactNode;
label: string;
imgClass?: string;
}) => (
Expand All @@ -30,7 +38,7 @@ const IconCard = ({
</a>
);

const steps = [
const steps: Array<{ label: string; items: IntegrationItem[] }> = [
{
label: "Pulling",
items: [
Expand Down Expand Up @@ -100,7 +108,7 @@ const IntegrationsSection = () => {
href={item.href}
logo={item.logo}
label={item.label}
imgClass={"imgClass" in item ? (item as any).imgClass : undefined}
imgClass={item.imgClass}
/>
))}
</div>
Expand Down Expand Up @@ -130,7 +138,7 @@ const IntegrationsSection = () => {
href={item.href}
logo={item.logo}
label={item.label}
imgClass={"imgClass" in item ? (item as any).imgClass : undefined}
imgClass={item.imgClass}
/>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/marketing/src/components/ui/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Command = React.forwardRef<
));
Command.displayName = CommandPrimitive.displayName;

interface CommandDialogProps extends DialogProps {}
type CommandDialogProps = DialogProps;

const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/marketing/src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";

import { cn } from "@/lib/utils";

export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
return (
Expand Down
Loading
Loading