Merge remote-tracking branch 'origin/main' into nwparker/upgrade-to-local-build

This commit is contained in:
Neil
2026-07-27 16:26:53 -07:00
6 changed files with 212 additions and 1 deletions
+17
View File
@@ -31,9 +31,15 @@ jobs:
- name: Lint
run: pnpm exec oxlint --format github
- name: Check switch exhaustiveness
run: pnpm run lint:switch-exhaustiveness
- name: Check styled scrollbars
run: pnpm check:styled-scrollbars
- name: Check quadratic buffer concatenation
run: pnpm run check:quadratic-buffer-concat
- name: Check reliability gate manifest
run: pnpm run check:reliability-gates
@@ -46,6 +52,17 @@ jobs:
- name: Verify skill freshness manifest
run: pnpm run verify:skill-bundle-manifest
- name: Verify localization catalog
run: pnpm run verify:localization-catalog
- name: Verify localization coverage
run: pnpm run verify:localization-coverage
# Why: project-owned type declarations must live in .ts so tsc
# actually checks them. TypeScript's skipLibCheck: true (inherited
# from @electron-toolkit/tsconfig) silently widens unresolved names
# in .d.ts to `any`, which is how #1186 shipped a broken IPC signature
# past typecheck. See docs/preload-typecheck-hole.md.
- name: Guard against project-owned .d.ts in preload/shared
run: |
matches=$(find src/preload src/shared -name '*.d.ts' 2>/dev/null || true)
@@ -40,5 +40,33 @@
"text": "Idioma",
"dynamic": false,
"count": 1
},
{
"filePath": "src/renderer/src/components/settings/terminal-advanced-platform-search.ts",
"kind": "object-property:keywords",
"text": "Ghostty",
"dynamic": false,
"count": 2
},
{
"filePath": "src/renderer/src/components/settings/terminal-advanced-platform-search.ts",
"kind": "object-property:keywords",
"text": "ghostty",
"dynamic": false,
"count": 2
},
{
"filePath": "src/renderer/src/components/settings/terminal-pane-appearance-search.ts",
"kind": "object-property:keywords",
"text": "Ghostty",
"dynamic": false,
"count": 1
},
{
"filePath": "src/renderer/src/components/settings/terminal-pane-appearance-search.ts",
"kind": "object-property:keywords",
"text": "ghostty",
"dynamic": false,
"count": 1
}
]
@@ -0,0 +1,86 @@
import { readFileSync } from 'node:fs'
import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
// Why: pr.yml re-lists the `lint` chain as individual steps so a single failure
// does not mask the rest and oxlint keeps its `--format github` annotations.
// Hand-maintained mirrors drift (#10601: three verifiers never ran on PRs), so
// this gate fails the moment a `lint` step has no counterpart in pr.yml.
// Flags that only change reporting, so they must not split two otherwise identical commands.
const REPORTING_FLAGS_WITH_VALUE = new Set(['--format', '--reporter'])
const REPORTING_FLAGS = new Set(['--quiet'])
const PACKAGE_RUNNER_TOKENS = new Set(['pnpm', 'npm', 'yarn', 'npx', 'run', 'exec', 'node'])
function splitCommandChain(command) {
return command
.split(/\n|&&|;/)
.map((part) => part.trim())
.filter(Boolean)
}
function canonicalize(command) {
const tokens = command.split(/\s+/)
const canonical = []
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index]
if (canonical.length === 0 && PACKAGE_RUNNER_TOKENS.has(token)) {
continue
}
if (REPORTING_FLAGS.has(token)) {
continue
}
if (REPORTING_FLAGS_WITH_VALUE.has(token)) {
index += 1
continue
}
canonical.push(token)
}
return canonical.join(' ')
}
/** Expands `pnpm run x` indirection until every entry is a real binary invocation. */
function resolveLeafCommands(command, scripts, seen = new Set()) {
const leaves = []
for (const part of splitCommandChain(command)) {
const scriptName = part.match(/^(?:pnpm|npm|yarn)(?:\s+run)?\s+([\w:-]+)$/)?.[1]
if (scriptName && scripts[scriptName] && !seen.has(scriptName)) {
leaves.push(
...resolveLeafCommands(scripts[scriptName], scripts, new Set([...seen, scriptName]))
)
continue
}
leaves.push(canonicalize(part))
}
return leaves
}
describe('PR workflow lint parity', () => {
it('runs every `pnpm lint` step on pull requests', () => {
const { scripts } = JSON.parse(readFileSync('package.json', 'utf8'))
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
// Scan every job: which one hosts the lint steps is an organizational
// detail that has already been renamed once (verify -> static_analysis).
const workflowCommands = new Set(
Object.values(workflow.jobs)
.flatMap((job) => job.steps ?? [])
.filter((step) => typeof step.run === 'string')
.flatMap((step) => resolveLeafCommands(step.run, scripts))
)
const missing = resolveLeafCommands(scripts.lint, scripts).filter(
(leaf) => !workflowCommands.has(leaf)
)
expect(
missing,
`.github/workflows/pr.yml is missing lint steps: ${missing.join(', ')}. ` +
'Add a step for each one so PR CI matches `pnpm lint`.'
).toEqual([])
})
})
+4 -1
View File
@@ -27,7 +27,10 @@ export const PROJECT_COMMAND_SPECS: CommandSpec[] = [
usage:
'orca project setup-existing-folder --project <id> --host <host-id> --path <path> [--kind git|folder] [--display-name <name>] [--json]',
allowedFlags: [...GLOBAL_FLAGS, 'project', 'host', 'path', 'kind', 'display-name'],
notes: ['For remote runtimes, --path must be an absolute path on the remote server.'],
notes: [
'For remote runtimes, --path must be an absolute path on the remote server.',
'SSH targets are set up through the desktop UI because the desktop client owns SSH connections.'
],
examples: [
'orca project setup-existing-folder --project github:stablyai/orca --host local --path ~/orca',
'orca project setup-existing-folder --project github:stablyai/orca --host runtime:gpu --path /home/me/orca --kind git --json'
+61
View File
@@ -7160,6 +7160,67 @@ describe('OrcaRuntimeService', () => {
}
})
it('refuses SSH hosts instead of setting the project up on the local machine', async () => {
// Why: both inputs must be paths the pre-guard code would have accepted. An unwritable
// destination fails at mkdir and a non-repo path fails at isGitRepo, which would leave the
// side-effect assertions below unable to observe the local clone/probe they exist to catch.
const destination = await mkdtemp(join(tmpdir(), 'orca-runtime-ssh-guard-'))
const existingFolder = join(destination, 'orca')
mkdirSync(existingFolder, { recursive: true })
execFileSync('git', ['init'], { cwd: existingFolder, stdio: 'ignore' })
const spawnSpy = vi.spyOn(gitRunner, 'gitSpawn').mockImplementation(() => {
// Why: unreachable while the guard holds; stubbed so a regression records the call
// instead of shelling out to a real network clone.
const proc = new EventEmitter() as EventEmitter & { stderr: EventEmitter }
proc.stderr = new EventEmitter()
queueMicrotask(() => proc.emit('close', 1, null))
return proc as never
})
const repos: Record<string, unknown>[] = []
const runtimeStore = {
...store,
getRepos: () => [...repos] as never,
addRepo: (repo: Record<string, unknown>) => {
repos.push(repo)
}
}
const runtime = new OrcaRuntimeService(runtimeStore as never)
try {
const cloneError = await runtime
.setupProjectClone({
projectId: 'github:stablyai/orca',
hostId: 'ssh:openclaw',
url: 'https://example.com/orca.git',
destination
})
.catch((error: unknown) => error)
const existingFolderError = await runtime
.setupProjectExistingFolder({
projectId: 'github:stablyai/orca',
hostId: 'ssh:openclaw',
path: existingFolder,
kind: 'git'
})
.catch((error: unknown) => error)
// Why: the defect was a silent local clone/probe recorded as remote, not a bad message,
// so the absent side effects are asserted before the wording. Both calls are awaited
// first so a regression reports the corruption rather than stopping at the first throw.
expect(spawnSpy).not.toHaveBeenCalled()
expect(repos).toHaveLength(0)
expect(cloneError).toMatchObject({
message: expect.stringMatching(/SSH hosts are not supported/)
})
expect(existingFolderError).toMatchObject({
message: expect.stringMatching(/SSH hosts are not supported/)
})
} finally {
spawnSpy.mockRestore()
await rm(destination, { recursive: true, force: true })
}
})
it('adopts public clone repos into host-qualified project setup', async () => {
const destination = await mkdtemp(join(tmpdir(), 'orca-runtime-project-clone-'))
const clonePath = join(destination, 'orca')
+16
View File
@@ -1823,6 +1823,19 @@ function runtimeRepoMatchesExecutionHost(
return repo.connectionId == null
}
// Why: this runtime only has local git and local fs, so an ssh: host here would clone and
// probe the wrong machine and then register the result as remote. SSH setup is owned by the
// desktop IPC path (addRemoteRepoFromPath / cloneRemoteRepo), which the renderer routes to;
// only `local` and `runtime:` legitimately reach these RPCs.
function assertProjectHostSetupHostIsSupported(hostId: ExecutionHostId | null | undefined): void {
if (parseExecutionHostId(hostId)?.kind !== 'ssh') {
return
}
throw new Error(
'SSH hosts are not supported by this operation. Set the project up from the Orca desktop app, which owns the SSH connection.'
)
}
function getRuntimeFolderWorkspaceInstanceId(repo: Repo, instanceId: string): string {
return `${getRuntimeFolderWorkspaceRootId(repo)}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}${instanceId}`
}
@@ -15309,6 +15322,7 @@ export class OrcaRuntimeService {
if (!this.store) {
throw new Error('runtime_unavailable')
}
assertProjectHostSetupHostIsSupported(args.hostId)
let repo = await this.addRepo(args.path, args.kind === 'folder' ? 'folder' : 'git', args.hostId)
let setup = getProjectHostSetupForRepo(this.listProjectHostSetups(), repo)
if (setup.projectId !== args.projectId) {
@@ -15351,6 +15365,8 @@ export class OrcaRuntimeService {
}
async setupProjectClone(args: ProjectHostSetupCloneArgs): Promise<ProjectHostSetupResult> {
// Why: guard before cloneRepo, which would otherwise clone to the local disk.
assertProjectHostSetupHostIsSupported(args.hostId)
const repo = await this.cloneRepo(args.url, args.destination, args.hostId)
return await this.setupProjectExistingFolder({
projectId: args.projectId,