@defrex/autobuild 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { RepositoryEvent } from '../events/repository'
|
|
2
|
+
import { reduceDispatchSettings, type DispatchSettings } from '../kernel/dispatch-settings'
|
|
3
|
+
import { sandboxStates } from '../processes/sandbox-state'
|
|
4
|
+
import type { Exec } from '../ports/workspace/git-worktree'
|
|
5
|
+
import { withSessionlessStore, type StoreOpener } from './store-opening'
|
|
6
|
+
|
|
7
|
+
/** Script-facing projection returned by `ab repository status`. */
|
|
8
|
+
export interface RepositoryStatus extends DispatchSettings {
|
|
9
|
+
repo: string
|
|
10
|
+
/** Live operator sandboxes (AUT-340); released environments are omitted. */
|
|
11
|
+
sandboxes: Array<{
|
|
12
|
+
operator: string
|
|
13
|
+
environmentId: string
|
|
14
|
+
provider: string
|
|
15
|
+
state: 'live' | 'stopped'
|
|
16
|
+
lastEvidenceAt: string
|
|
17
|
+
}>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RepositoryStatusOpts {
|
|
21
|
+
/** The cwd, resolved to the canonical main repository before store access. */
|
|
22
|
+
targetRepo: string
|
|
23
|
+
/** Raw process environment for AB_STORE / AB_TOKEN selection. */
|
|
24
|
+
env: Record<string, string | undefined>
|
|
25
|
+
exec: Exec
|
|
26
|
+
stdout: (line: string) => void
|
|
27
|
+
json?: boolean
|
|
28
|
+
/** Explicit --store reference; precedence is owned by repo-state.ts. */
|
|
29
|
+
storeRef?: string
|
|
30
|
+
openStore?: StoreOpener
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function projectRepositoryStatus(repo: string, events: RepositoryEvent[]): RepositoryStatus {
|
|
34
|
+
return {
|
|
35
|
+
repo,
|
|
36
|
+
...reduceDispatchSettings(events),
|
|
37
|
+
sandboxes: sandboxStates(events)
|
|
38
|
+
.filter((state) => state.state !== 'released')
|
|
39
|
+
.map((state) => ({
|
|
40
|
+
operator: state.operator,
|
|
41
|
+
environmentId: state.environmentId,
|
|
42
|
+
provider: state.provider,
|
|
43
|
+
state: state.state === 'stopped' ? ('stopped' as const) : ('live' as const),
|
|
44
|
+
lastEvidenceAt: state.lastEvidenceTs,
|
|
45
|
+
})),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderRepositoryStatus(status: RepositoryStatus): string[] {
|
|
50
|
+
const setting = (enabled: boolean): string => (enabled ? 'ON' : 'OFF')
|
|
51
|
+
return [
|
|
52
|
+
`repository: ${status.repo}`,
|
|
53
|
+
`intake: ${setting(status.intake)}`,
|
|
54
|
+
`repository pause: ${setting(status.paused)}`,
|
|
55
|
+
`default auto-merge: ${setting(status.defaultAutoMerge)}`,
|
|
56
|
+
...status.sandboxes.map(
|
|
57
|
+
(sandbox) =>
|
|
58
|
+
`operator sandbox ${sandbox.environmentId} (${sandbox.provider}) — ${sandbox.state}, idle since ${sandbox.lastEvidenceAt}`,
|
|
59
|
+
),
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `ab repository status` — read the durable dispatcher controls without
|
|
65
|
+
* creating a repository stream or starting any dispatcher work.
|
|
66
|
+
*/
|
|
67
|
+
export async function abRepositoryStatus(opts: RepositoryStatusOpts): Promise<void> {
|
|
68
|
+
await withSessionlessStore(opts, async ({ store, repo }) => {
|
|
69
|
+
// A fresh store has no repository row. Do not call ensureRepo: this query's
|
|
70
|
+
// empty-stream defaults must remain a genuinely read-only operation.
|
|
71
|
+
const record = await store.getRepo(repo)
|
|
72
|
+
const events = record === null ? [] : await store.getRepoEvents(repo)
|
|
73
|
+
const status = projectRepositoryStatus(repo, events)
|
|
74
|
+
if (opts.json === true) {
|
|
75
|
+
opts.stdout(JSON.stringify(status, null, 2))
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
for (const line of renderRepositoryStatus(status)) opts.stdout(line)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import semver from 'semver'
|
|
2
|
+
import type { ExecResult } from '../ports/workspace/git-worktree'
|
|
3
|
+
import { registryBaseUrl, registryVersionUrl } from '../registry'
|
|
4
|
+
import { defaultDistRoot } from './init'
|
|
5
|
+
import { addUpgradeSelfUpdatePaths, UPGRADE_COMMIT_CONTEXT_ENV } from './upgrade-commit'
|
|
6
|
+
import {
|
|
7
|
+
inspectInstallation,
|
|
8
|
+
readDistributionIdentity,
|
|
9
|
+
type BunForgeInstallation,
|
|
10
|
+
type DistributionIdentity,
|
|
11
|
+
type ManagedInstallation,
|
|
12
|
+
} from './installation'
|
|
13
|
+
|
|
14
|
+
export const SELF_UPDATE_HANDOFF_ENV = 'AB_SELF_UPDATE_HANDOFF'
|
|
15
|
+
|
|
16
|
+
export interface SelfUpdateCommandOptions {
|
|
17
|
+
cwd?: string
|
|
18
|
+
env?: Record<string, string | undefined>
|
|
19
|
+
signal?: AbortSignal
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SelfUpdateCommand = (
|
|
23
|
+
command: string[],
|
|
24
|
+
options: SelfUpdateCommandOptions,
|
|
25
|
+
) => Promise<ExecResult>
|
|
26
|
+
|
|
27
|
+
export type SelfUpdateResult =
|
|
28
|
+
| { kind: 'continue' }
|
|
29
|
+
| { kind: 'handoff'; exitCode: number }
|
|
30
|
+
| { kind: 'failed' }
|
|
31
|
+
|
|
32
|
+
/** Registry metadata read seam: one GET returning status and body text. */
|
|
33
|
+
export type RegistryLookup = (
|
|
34
|
+
url: string,
|
|
35
|
+
options: { signal?: AbortSignal },
|
|
36
|
+
) => Promise<{ status: number; body: string }>
|
|
37
|
+
|
|
38
|
+
export { registryBaseUrl, registryVersionUrl } from '../registry'
|
|
39
|
+
|
|
40
|
+
const fetchRegistry: RegistryLookup = async (url, options) => {
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
headers: { accept: 'application/json' },
|
|
43
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
44
|
+
})
|
|
45
|
+
return { status: response.status, body: await response.text() }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Parse a registry version document into its exact semver version. */
|
|
49
|
+
export function registryVersion(response: { status: number; body: string }): string {
|
|
50
|
+
if (response.status !== 200) {
|
|
51
|
+
throw new Error(`npm registry returned HTTP ${response.status}`)
|
|
52
|
+
}
|
|
53
|
+
let parsed: unknown
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(response.body)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`npm registry returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
const version =
|
|
62
|
+
typeof parsed === 'object' && parsed !== null && 'version' in parsed
|
|
63
|
+
? (parsed as { version?: unknown }).version
|
|
64
|
+
: undefined
|
|
65
|
+
if (typeof version !== 'string' || semver.valid(version) !== version) {
|
|
66
|
+
throw new Error('npm registry document has no exact semver version')
|
|
67
|
+
}
|
|
68
|
+
return version
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SelfUpdateOptions {
|
|
72
|
+
targetRepo: string
|
|
73
|
+
version?: string
|
|
74
|
+
distRoot?: string
|
|
75
|
+
env?: Record<string, string | undefined>
|
|
76
|
+
stdout: (line: string) => void
|
|
77
|
+
stderr: (line: string) => void
|
|
78
|
+
command?: SelfUpdateCommand
|
|
79
|
+
/** Registry read seam for npm-channel installs. */
|
|
80
|
+
registry?: RegistryLookup
|
|
81
|
+
/** Baseline captured before this function may rewrite a local owner project. */
|
|
82
|
+
upgradeCommitContextPath?: string
|
|
83
|
+
/** Forward the operator's commit opt-out to the replacement binary. */
|
|
84
|
+
noCommit?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function signalAborted(signal: AbortSignal | undefined): boolean {
|
|
88
|
+
return signal?.aborted ?? false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const processCommand: SelfUpdateCommand = async (command, options) => {
|
|
92
|
+
if (signalAborted(options.signal)) throw new Error('command aborted')
|
|
93
|
+
const proc = Bun.spawn(command, {
|
|
94
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
95
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
96
|
+
stdin: 'ignore',
|
|
97
|
+
stdout: 'pipe',
|
|
98
|
+
stderr: 'pipe',
|
|
99
|
+
})
|
|
100
|
+
const abort = (): void => {
|
|
101
|
+
try {
|
|
102
|
+
proc.kill()
|
|
103
|
+
} catch {
|
|
104
|
+
// The process may have exited between the abort and this callback.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
options.signal?.addEventListener('abort', abort, { once: true })
|
|
108
|
+
// Abort may have raced the spawn/listener boundary.
|
|
109
|
+
if (signalAborted(options.signal)) abort()
|
|
110
|
+
try {
|
|
111
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
112
|
+
new Response(proc.stdout).text(),
|
|
113
|
+
new Response(proc.stderr).text(),
|
|
114
|
+
proc.exited,
|
|
115
|
+
])
|
|
116
|
+
return { stdout, stderr, exitCode }
|
|
117
|
+
} finally {
|
|
118
|
+
options.signal?.removeEventListener('abort', abort)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function commandFailure(label: string, result: ExecResult): string {
|
|
123
|
+
return `${label} failed (exit ${result.exitCode}): ${
|
|
124
|
+
result.stderr.trim() || result.stdout.trim() || '(no output)'
|
|
125
|
+
}`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function releaseVersion(response: string): string {
|
|
129
|
+
let parsed: unknown
|
|
130
|
+
try {
|
|
131
|
+
parsed = JSON.parse(response)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`GitHub returned invalid release JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
const tag =
|
|
138
|
+
typeof parsed === 'object' && parsed !== null && 'tag_name' in parsed
|
|
139
|
+
? (parsed as { tag_name?: unknown }).tag_name
|
|
140
|
+
: undefined
|
|
141
|
+
if (typeof tag !== 'string' || !tag.startsWith('v')) {
|
|
142
|
+
throw new Error('GitHub release has no v<semver> tag_name')
|
|
143
|
+
}
|
|
144
|
+
const version = tag.slice(1)
|
|
145
|
+
if (semver.valid(version) !== version) {
|
|
146
|
+
throw new Error(`GitHub release tag is not v<semver>: ${tag}`)
|
|
147
|
+
}
|
|
148
|
+
return version
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function releaseApiPath(
|
|
152
|
+
installation: Pick<BunForgeInstallation, 'owner' | 'repository'>,
|
|
153
|
+
version?: string,
|
|
154
|
+
): string {
|
|
155
|
+
return version === undefined
|
|
156
|
+
? `repos/${installation.owner}/${installation.repository}/releases/latest`
|
|
157
|
+
: `repos/${installation.owner}/${installation.repository}/releases/tags/v${version}`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function exactRequestedVersion(value: string): string {
|
|
161
|
+
if (semver.valid(value) !== value) {
|
|
162
|
+
throw new Error(`--version must be an exact semver version: ${value}`)
|
|
163
|
+
}
|
|
164
|
+
return value
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function warn(options: SelfUpdateOptions, reason: string): SelfUpdateResult {
|
|
168
|
+
options.stderr(`self-update skipped: ${reason}; merging skills with the installed distribution`)
|
|
169
|
+
return { kind: 'continue' }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function fail(options: SelfUpdateOptions, reason: string): SelfUpdateResult {
|
|
173
|
+
options.stderr(`self-update failed: ${reason}`)
|
|
174
|
+
return { kind: 'failed' }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function forward(output: string, sink: (line: string) => void): void {
|
|
178
|
+
const text = output.replace(/\n$/, '')
|
|
179
|
+
if (text !== '') sink(text)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface AvailableReleaseOptions {
|
|
183
|
+
distRoot?: string
|
|
184
|
+
signal?: AbortSignal
|
|
185
|
+
command?: SelfUpdateCommand
|
|
186
|
+
registry?: RegistryLookup
|
|
187
|
+
env?: Record<string, string | undefined>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Resolve a published version for a managed install: the GitHub Release for
|
|
191
|
+
* a `github:` forge install, or the registry version document for an npm
|
|
192
|
+
* install. Throws on any lookup or parse failure. */
|
|
193
|
+
async function publishedVersion(
|
|
194
|
+
install: ManagedInstallation,
|
|
195
|
+
requested: string | undefined,
|
|
196
|
+
seams: {
|
|
197
|
+
command: SelfUpdateCommand
|
|
198
|
+
registry: RegistryLookup
|
|
199
|
+
env: Record<string, string | undefined> | undefined
|
|
200
|
+
signal?: AbortSignal
|
|
201
|
+
},
|
|
202
|
+
): Promise<string> {
|
|
203
|
+
if (install.channel === 'github') {
|
|
204
|
+
const release = await seams.command(['gh', 'api', releaseApiPath(install, requested)], {
|
|
205
|
+
...(seams.signal === undefined ? {} : { signal: seams.signal }),
|
|
206
|
+
})
|
|
207
|
+
if (release.exitCode !== 0) {
|
|
208
|
+
throw new Error(commandFailure('resolving the GitHub release', release))
|
|
209
|
+
}
|
|
210
|
+
const resolved = releaseVersion(release.stdout)
|
|
211
|
+
if (requested !== undefined && resolved !== requested) {
|
|
212
|
+
throw new Error(`GitHub returned v${resolved} for requested release v${requested}`)
|
|
213
|
+
}
|
|
214
|
+
return resolved
|
|
215
|
+
}
|
|
216
|
+
const url = registryVersionUrl(registryBaseUrl(seams.env), install.packageName, requested)
|
|
217
|
+
const resolved = registryVersion(
|
|
218
|
+
await seams.registry(url, seams.signal === undefined ? {} : { signal: seams.signal }),
|
|
219
|
+
)
|
|
220
|
+
if (requested !== undefined && resolved !== requested) {
|
|
221
|
+
throw new Error(`npm registry returned ${resolved} for requested version ${requested}`)
|
|
222
|
+
}
|
|
223
|
+
return resolved
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The specifier Bun installs for a resolved version on the install's channel. */
|
|
227
|
+
export function updateDependency(install: ManagedInstallation, version: string): string {
|
|
228
|
+
return install.channel === 'github'
|
|
229
|
+
? `github:${install.owner}/${install.repository}#v${version}`
|
|
230
|
+
: `${install.packageName}@${version}`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Silently determine whether this installation has a newer published release.
|
|
234
|
+
* This read-only courtesy intentionally collapses every unsupported install,
|
|
235
|
+
* command failure, malformed response, and cancellation to no result. */
|
|
236
|
+
export async function availableRelease(
|
|
237
|
+
options: AvailableReleaseOptions = {},
|
|
238
|
+
): Promise<string | undefined> {
|
|
239
|
+
const command = options.command ?? processCommand
|
|
240
|
+
const registry = options.registry ?? fetchRegistry
|
|
241
|
+
try {
|
|
242
|
+
if (signalAborted(options.signal)) return undefined
|
|
243
|
+
const distRoot = options.distRoot ?? defaultDistRoot()
|
|
244
|
+
const identity = await readDistributionIdentity(distRoot)
|
|
245
|
+
if (identity.sourceCheckout || signalAborted(options.signal)) return undefined
|
|
246
|
+
|
|
247
|
+
const globalBin = await command(['bun', 'pm', 'bin', '-g'], { signal: options.signal })
|
|
248
|
+
if (
|
|
249
|
+
globalBin.exitCode !== 0 ||
|
|
250
|
+
globalBin.stdout.trim() === '' ||
|
|
251
|
+
signalAborted(options.signal)
|
|
252
|
+
) {
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const inspection = await inspectInstallation({
|
|
257
|
+
distRoot,
|
|
258
|
+
globalBin: globalBin.stdout.trim(),
|
|
259
|
+
})
|
|
260
|
+
if (
|
|
261
|
+
(inspection.kind !== 'bun-forge' && inspection.kind !== 'npm-registry') ||
|
|
262
|
+
signalAborted(options.signal)
|
|
263
|
+
) {
|
|
264
|
+
return undefined
|
|
265
|
+
}
|
|
266
|
+
const install = inspection.installation
|
|
267
|
+
const published = await publishedVersion(install, undefined, {
|
|
268
|
+
command,
|
|
269
|
+
registry,
|
|
270
|
+
env: options.env,
|
|
271
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
272
|
+
})
|
|
273
|
+
if (signalAborted(options.signal)) return undefined
|
|
274
|
+
return semver.gt(published, install.version) ? published : undefined
|
|
275
|
+
} catch {
|
|
276
|
+
return undefined
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Update the running Bun forge distribution, or explicitly decide that the
|
|
281
|
+
* caller may continue with the current one. A successful install has exactly
|
|
282
|
+
* one legal continuation: a fresh process from the replaced distribution. */
|
|
283
|
+
export async function selfUpdate(options: SelfUpdateOptions): Promise<SelfUpdateResult> {
|
|
284
|
+
const command = options.command ?? processCommand
|
|
285
|
+
const registry = options.registry ?? fetchRegistry
|
|
286
|
+
const invoke: SelfUpdateCommand = async (argv, commandOptions) => {
|
|
287
|
+
try {
|
|
288
|
+
return await command(argv, commandOptions)
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return {
|
|
291
|
+
stdout: '',
|
|
292
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
293
|
+
exitCode: 127,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const distRoot = options.distRoot ?? defaultDistRoot()
|
|
298
|
+
const explicit = options.version !== undefined
|
|
299
|
+
let requested: string | undefined
|
|
300
|
+
try {
|
|
301
|
+
requested = options.version === undefined ? undefined : exactRequestedVersion(options.version)
|
|
302
|
+
} catch (error) {
|
|
303
|
+
options.stderr(error instanceof Error ? error.message : String(error))
|
|
304
|
+
return { kind: 'failed' }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let identity: DistributionIdentity
|
|
308
|
+
try {
|
|
309
|
+
identity = await readDistributionIdentity(distRoot)
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
312
|
+
if (explicit) {
|
|
313
|
+
options.stderr(`self-update failed: ${reason}`)
|
|
314
|
+
return { kind: 'failed' }
|
|
315
|
+
}
|
|
316
|
+
return warn(options, reason)
|
|
317
|
+
}
|
|
318
|
+
if (identity.sourceCheckout) {
|
|
319
|
+
return warn(options, 'running from a source checkout (.git is present)')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const globalBinResult = await invoke(['bun', 'pm', 'bin', '-g'], {})
|
|
323
|
+
if (globalBinResult.exitCode !== 0 || globalBinResult.stdout.trim() === '') {
|
|
324
|
+
const reason = commandFailure('determining Bun global installation', globalBinResult)
|
|
325
|
+
return explicit ? fail(options, reason) : warn(options, reason)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const inspection = await inspectInstallation({
|
|
329
|
+
distRoot,
|
|
330
|
+
globalBin: globalBinResult.stdout.trim(),
|
|
331
|
+
})
|
|
332
|
+
if (inspection.kind !== 'bun-forge' && inspection.kind !== 'npm-registry') {
|
|
333
|
+
return explicit ? fail(options, inspection.reason) : warn(options, inspection.reason)
|
|
334
|
+
}
|
|
335
|
+
const install = inspection.installation
|
|
336
|
+
|
|
337
|
+
let resolved: string
|
|
338
|
+
try {
|
|
339
|
+
resolved = await publishedVersion(install, requested, {
|
|
340
|
+
command: invoke,
|
|
341
|
+
registry,
|
|
342
|
+
env: options.env,
|
|
343
|
+
})
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
346
|
+
const reason = `resolving ${requested === undefined ? 'latest' : `v${requested}`} release: ${detail}`
|
|
347
|
+
if (explicit) {
|
|
348
|
+
options.stderr(`self-update failed: ${reason}`)
|
|
349
|
+
return { kind: 'failed' }
|
|
350
|
+
}
|
|
351
|
+
return warn(options, reason)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (resolved === install.version || (!explicit && semver.gte(install.version, resolved))) {
|
|
355
|
+
options.stdout(
|
|
356
|
+
resolved === install.version
|
|
357
|
+
? `Autobuild ${install.version} is already installed.`
|
|
358
|
+
: `Autobuild ${install.version} is newer than the latest release v${resolved}; keeping it.`,
|
|
359
|
+
)
|
|
360
|
+
return { kind: 'continue' }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const dependency = updateDependency(install, resolved)
|
|
364
|
+
let updateCommand: string[]
|
|
365
|
+
if (install.scope === 'global') {
|
|
366
|
+
options.stdout(`Updating Autobuild's global Bun installation to v${resolved}.`)
|
|
367
|
+
updateCommand = ['bun', 'add', '--global', dependency]
|
|
368
|
+
} else {
|
|
369
|
+
options.stdout(
|
|
370
|
+
`Updating Autobuild to v${resolved} in ${install.ownerRoot}; Bun will rewrite ${install.ownerManifest} and ${install.ownerLock}.`,
|
|
371
|
+
)
|
|
372
|
+
updateCommand = ['bun', 'add', '--cwd', install.ownerRoot, dependency]
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const update = await invoke(updateCommand, {})
|
|
376
|
+
if (update.exitCode !== 0) {
|
|
377
|
+
const reason = commandFailure(`installing v${resolved}`, update)
|
|
378
|
+
if (explicit) {
|
|
379
|
+
options.stderr(`self-update failed: ${reason}`)
|
|
380
|
+
return { kind: 'failed' }
|
|
381
|
+
}
|
|
382
|
+
return warn(options, reason)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let updated: DistributionIdentity
|
|
386
|
+
try {
|
|
387
|
+
updated = await readDistributionIdentity(distRoot)
|
|
388
|
+
// Injected command fixtures do not rewrite disk; a real successful Bun
|
|
389
|
+
// operation must. Keep the check injectable by accepting the old identity
|
|
390
|
+
// only when the command seam itself was supplied.
|
|
391
|
+
if (options.command === undefined && updated.version !== resolved) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Bun reported success but the installed package is ${updated.version}, expected ${resolved}`,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
} catch (error) {
|
|
397
|
+
options.stderr(
|
|
398
|
+
`self-update failed after installation: ${error instanceof Error ? error.message : String(error)}`,
|
|
399
|
+
)
|
|
400
|
+
return { kind: 'failed' }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let handoffCommitContextPath = options.upgradeCommitContextPath
|
|
404
|
+
let handoffNoCommit = options.noCommit === true
|
|
405
|
+
if (install.scope === 'global') {
|
|
406
|
+
options.stdout(`Installed Autobuild v${resolved} in Bun's global package-manager scope.`)
|
|
407
|
+
} else {
|
|
408
|
+
options.stdout(
|
|
409
|
+
`Installed Autobuild v${resolved}; Bun updated ${install.ownerManifest} and ${install.ownerLock}.`,
|
|
410
|
+
)
|
|
411
|
+
if (options.upgradeCommitContextPath !== undefined) {
|
|
412
|
+
try {
|
|
413
|
+
await addUpgradeSelfUpdatePaths(options.upgradeCommitContextPath, [
|
|
414
|
+
install.ownerManifest,
|
|
415
|
+
install.ownerLock,
|
|
416
|
+
])
|
|
417
|
+
} catch (error) {
|
|
418
|
+
options.stderr(
|
|
419
|
+
`ab upgrade did not commit: could not preserve the pre-update Git baseline for handoff: ${
|
|
420
|
+
error instanceof Error ? error.message : String(error)
|
|
421
|
+
}`,
|
|
422
|
+
)
|
|
423
|
+
handoffCommitContextPath = undefined
|
|
424
|
+
handoffNoCommit = true
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const childArgs = ['bun', updated.binaryPath, 'upgrade', options.targetRepo]
|
|
430
|
+
if (handoffNoCommit) childArgs.push('--no-commit')
|
|
431
|
+
const child = await invoke(childArgs, {
|
|
432
|
+
env: {
|
|
433
|
+
...options.env,
|
|
434
|
+
[SELF_UPDATE_HANDOFF_ENV]: '1',
|
|
435
|
+
...(handoffCommitContextPath === undefined
|
|
436
|
+
? {}
|
|
437
|
+
: { [UPGRADE_COMMIT_CONTEXT_ENV]: handoffCommitContextPath }),
|
|
438
|
+
},
|
|
439
|
+
})
|
|
440
|
+
forward(child.stdout, options.stdout)
|
|
441
|
+
forward(child.stderr, options.stderr)
|
|
442
|
+
return { kind: 'handoff', exitCode: child.exitCode }
|
|
443
|
+
}
|