@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,136 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { isAbsolute, join } from 'node:path'
|
|
4
|
+
import type { WorkspaceBase } from '../../ontology'
|
|
5
|
+
import type { WorkspaceProvider } from '../types'
|
|
6
|
+
|
|
7
|
+
export interface WorkspaceProviderContractHarness {
|
|
8
|
+
provider: WorkspaceProvider
|
|
9
|
+
provision: Parameters<WorkspaceProvider['provision']>[0]
|
|
10
|
+
/** The exact commit-selection evidence the first provision must return. */
|
|
11
|
+
expectedBase: WorkspaceBase
|
|
12
|
+
fixture: { relativePath: string; content: string }
|
|
13
|
+
cleanup?: () => Promise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type WorkspaceProviderContractFactory = () => Promise<WorkspaceProviderContractHarness>
|
|
17
|
+
|
|
18
|
+
async function withWorkspace(
|
|
19
|
+
factory: WorkspaceProviderContractFactory,
|
|
20
|
+
run: (harness: WorkspaceProviderContractHarness) => Promise<void>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const harness = await factory()
|
|
23
|
+
let failure: unknown
|
|
24
|
+
try {
|
|
25
|
+
await run(harness)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
failure = error
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await harness.cleanup?.()
|
|
32
|
+
} catch (cleanupError) {
|
|
33
|
+
if (failure !== undefined) {
|
|
34
|
+
throw new AggregateError(
|
|
35
|
+
[failure, cleanupError],
|
|
36
|
+
'WorkspaceProvider contract assertion and cleanup both failed',
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
throw cleanupError
|
|
40
|
+
}
|
|
41
|
+
if (failure !== undefined) throw failure
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function exists(path: string): Promise<boolean> {
|
|
45
|
+
try {
|
|
46
|
+
await stat(path)
|
|
47
|
+
return true
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Common working-copy semantics for both the fake and git-worktree adapter. */
|
|
55
|
+
export function describeWorkspaceProviderContract(
|
|
56
|
+
name: string,
|
|
57
|
+
factory: WorkspaceProviderContractFactory,
|
|
58
|
+
): void {
|
|
59
|
+
describe(`WorkspaceProvider contract: ${name}`, () => {
|
|
60
|
+
test('provision returns a usable requested-branch working copy and selected base', async () => {
|
|
61
|
+
await withWorkspace(factory, async (harness) => {
|
|
62
|
+
const result = await harness.provider.provision(harness.provision)
|
|
63
|
+
expect(result.provider).toBe(harness.provider.name)
|
|
64
|
+
expect(result.branch).toBe(harness.provision.branch)
|
|
65
|
+
expect(result.ref).not.toBe('')
|
|
66
|
+
expect(isAbsolute(result.path)).toBe(true)
|
|
67
|
+
expect((await stat(result.path)).isDirectory()).toBe(true)
|
|
68
|
+
expect(await readFile(join(result.path, harness.fixture.relativePath), 'utf8')).toBe(
|
|
69
|
+
harness.fixture.content,
|
|
70
|
+
)
|
|
71
|
+
expect(result.base).toEqual(harness.expectedBase)
|
|
72
|
+
|
|
73
|
+
const writeProbe = join(result.path, 'contract-write-probe.txt')
|
|
74
|
+
await writeFile(writeProbe, 'workspace is writable\n')
|
|
75
|
+
expect(await readFile(writeProbe, 'utf8')).toBe('workspace is writable\n')
|
|
76
|
+
await harness.provider.release(result)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('intact reprovision is non-destructive and reports the existing branch base', async () => {
|
|
81
|
+
await withWorkspace(factory, async (harness) => {
|
|
82
|
+
const first = await harness.provider.provision(harness.provision)
|
|
83
|
+
const writeProbe = join(first.path, 'contract-reprovision-probe.txt')
|
|
84
|
+
await writeFile(writeProbe, 'preserve in-progress work\n')
|
|
85
|
+
|
|
86
|
+
const reprovisioned = await harness.provider.provision(harness.provision)
|
|
87
|
+
expect(reprovisioned.path).toBe(first.path)
|
|
88
|
+
expect(reprovisioned.branch).toBe(harness.provision.branch)
|
|
89
|
+
expect(reprovisioned.base).toEqual({
|
|
90
|
+
source: 'existing',
|
|
91
|
+
sha: first.base.sha,
|
|
92
|
+
})
|
|
93
|
+
expect(await readFile(writeProbe, 'utf8')).toBe('preserve in-progress work\n')
|
|
94
|
+
|
|
95
|
+
await harness.provider.release(reprovisioned)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('reprovision rematerializes a working copy deleted out of band', async () => {
|
|
100
|
+
await withWorkspace(factory, async (harness) => {
|
|
101
|
+
const first = await harness.provider.provision(harness.provision)
|
|
102
|
+
await rm(first.path, { recursive: true, force: true })
|
|
103
|
+
expect(await exists(first.path)).toBe(false)
|
|
104
|
+
|
|
105
|
+
const reprovisioned = await harness.provider.provision(harness.provision)
|
|
106
|
+
expect(reprovisioned.path).toBe(first.path)
|
|
107
|
+
expect(reprovisioned.branch).toBe(harness.provision.branch)
|
|
108
|
+
expect((await stat(reprovisioned.path)).isDirectory()).toBe(true)
|
|
109
|
+
expect(await readFile(join(reprovisioned.path, harness.fixture.relativePath), 'utf8')).toBe(
|
|
110
|
+
harness.fixture.content,
|
|
111
|
+
)
|
|
112
|
+
expect(reprovisioned.base).toEqual({
|
|
113
|
+
source: 'existing',
|
|
114
|
+
sha: first.base.sha,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const writeProbe = join(reprovisioned.path, 'contract-recovery-probe.txt')
|
|
118
|
+
await writeFile(writeProbe, 'recovered workspace is writable\n')
|
|
119
|
+
expect(await readFile(writeProbe, 'utf8')).toBe('recovered workspace is writable\n')
|
|
120
|
+
await harness.provider.release(reprovisioned)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('release removes/releases the handle and is safe to call twice', async () => {
|
|
125
|
+
await withWorkspace(factory, async (harness) => {
|
|
126
|
+
const result = await harness.provider.provision(harness.provision)
|
|
127
|
+
expect(await exists(result.path)).toBe(true)
|
|
128
|
+
|
|
129
|
+
await harness.provider.release(result)
|
|
130
|
+
expect(await exists(result.path)).toBe(false)
|
|
131
|
+
await harness.provider.release(result)
|
|
132
|
+
expect(await exists(result.path)).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path'
|
|
2
|
+
import { vercelSandboxConfigSchema, type WorkspaceConfig } from '../../config/schema'
|
|
3
|
+
import type { PluginRegistry } from '../../plugins/registry'
|
|
4
|
+
import type { WorkspaceProvider } from '../types'
|
|
5
|
+
import type { BuildExecution } from './build-execution'
|
|
6
|
+
import { GitWorktreeProvider } from './git-worktree'
|
|
7
|
+
import { LocalBuildExecution } from './local-build-execution'
|
|
8
|
+
import { type RuntimeReferencesSource, VercelSandboxProvider } from './vercel-sandbox'
|
|
9
|
+
|
|
10
|
+
export interface CreateWorkspaceProviderOptions {
|
|
11
|
+
registry: PluginRegistry
|
|
12
|
+
/** Selected local scratch root. Remote stores pass the repository default. */
|
|
13
|
+
worktreeRoot: string
|
|
14
|
+
/** Absolute repository root supplied to plugin factories. */
|
|
15
|
+
repoRoot: string
|
|
16
|
+
env: Record<string, string | undefined>
|
|
17
|
+
/** Required only by remote builtins. */
|
|
18
|
+
storeRef?: string
|
|
19
|
+
storeToken?: string
|
|
20
|
+
/** Host-derived effective routes; consumed only by the built-in sandbox. */
|
|
21
|
+
runtimeReferences?: RuntimeReferencesSource
|
|
22
|
+
/** Checkout-less seams for the built-in sandbox: the repository's HTTPS
|
|
23
|
+
* origin, and a remote branch-head reader returning `undefined` for an
|
|
24
|
+
* absent branch. Absent seams fall back to host `git` from `repoRoot`. */
|
|
25
|
+
origin?: () => Promise<string>
|
|
26
|
+
remoteBranchHead?: (branch: string) => Promise<string | undefined>
|
|
27
|
+
/** Operator-sandbox options (AUT-340), set once at construction: the raw
|
|
28
|
+
* `[commands].setup` shell string, the local-state-tree sandbox root, and
|
|
29
|
+
* the forwarded non-secret variable names. The service never re-receives
|
|
30
|
+
* setup, root, or variables. */
|
|
31
|
+
sandboxSetupCommand?: string
|
|
32
|
+
sandboxRoot?: string
|
|
33
|
+
sandboxEnvironmentVariables?: readonly string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WorkspaceRuntime {
|
|
37
|
+
provider: WorkspaceProvider
|
|
38
|
+
execution: BuildExecution
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Resolve and lazily construct the configured WorkspaceProvider. Builtin
|
|
42
|
+
* construction remains host-owned because its scratch root comes from store
|
|
43
|
+
* selection rather than plugin configuration. */
|
|
44
|
+
export async function createWorkspaceProvider(
|
|
45
|
+
config: WorkspaceConfig,
|
|
46
|
+
opts: CreateWorkspaceProviderOptions,
|
|
47
|
+
): Promise<WorkspaceProvider> {
|
|
48
|
+
const available = [...opts.registry.workspaceProviders.keys()].sort()
|
|
49
|
+
const registration = opts.registry.workspaceProviders.get(config.provider)
|
|
50
|
+
if (registration === undefined) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`unknown workspace provider "${config.provider}"; available providers: ${available.join(', ')}`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (registration.owner.kind === 'builtin') {
|
|
57
|
+
if (config.provider === 'git-worktree') {
|
|
58
|
+
if (Object.keys(config.config).length > 0) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'[workspace.config] is not supported by the builtin "git-worktree" provider',
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
return new GitWorktreeProvider({
|
|
64
|
+
root: resolve(opts.worktreeRoot),
|
|
65
|
+
sandboxRoot:
|
|
66
|
+
opts.sandboxRoot ?? resolve(join(opts.worktreeRoot, '..', 'orchestrator-sandboxes')),
|
|
67
|
+
setupCommand: opts.sandboxSetupCommand,
|
|
68
|
+
sandboxEnvironmentVariables: opts.sandboxEnvironmentVariables,
|
|
69
|
+
envSource: opts.env,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
if (config.provider === 'vercel-sandbox') {
|
|
73
|
+
const parsed = vercelSandboxConfigSchema.safeParse(config.config)
|
|
74
|
+
if (!parsed.success) throw new Error(`invalid vercel-sandbox config: ${parsed.error.message}`)
|
|
75
|
+
if (opts.storeRef === undefined || opts.storeToken === undefined) {
|
|
76
|
+
throw new Error('vercel-sandbox requires an HTTPS BuildStore and scoped AB_TOKEN authority')
|
|
77
|
+
}
|
|
78
|
+
return new VercelSandboxProvider({
|
|
79
|
+
config: parsed.data,
|
|
80
|
+
env: opts.env,
|
|
81
|
+
storeRef: opts.storeRef,
|
|
82
|
+
storeToken: opts.storeToken,
|
|
83
|
+
repo: resolve(opts.repoRoot),
|
|
84
|
+
runtimeReferences: opts.runtimeReferences ?? [],
|
|
85
|
+
setupCommand: opts.sandboxSetupCommand,
|
|
86
|
+
sandboxEnvironmentVariables: opts.sandboxEnvironmentVariables,
|
|
87
|
+
...(opts.origin !== undefined ? { origin: opts.origin } : {}),
|
|
88
|
+
...(opts.remoteBranchHead !== undefined ? { remoteBranchHead: opts.remoteBranchHead } : {}),
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
throw new Error(
|
|
92
|
+
`workspace provider "${config.provider}" is registered as a builtin but has no constructor`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const factory = registration.factory
|
|
97
|
+
if (factory === undefined) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`workspace provider "${config.provider}" from plugin "${registration.owner.name}" has no factory`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
return await factory({
|
|
105
|
+
config: config.config,
|
|
106
|
+
env: opts.env,
|
|
107
|
+
repoRoot: resolve(opts.repoRoot),
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`workspace provider "${config.provider}" failed to initialize: ${
|
|
112
|
+
error instanceof Error ? error.message : String(error)
|
|
113
|
+
}`,
|
|
114
|
+
{ cause: error },
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Resolve provisioning and execution as one workspace-owned runtime. Existing
|
|
120
|
+
* providers remain source-compatible: locally reachable implementations that
|
|
121
|
+
* omit execution receive the shipped child-process capability, while a future
|
|
122
|
+
* sandbox provider can replace it without a local proxy process. */
|
|
123
|
+
export async function createWorkspaceRuntime(
|
|
124
|
+
config: WorkspaceConfig,
|
|
125
|
+
opts: CreateWorkspaceProviderOptions,
|
|
126
|
+
): Promise<WorkspaceRuntime> {
|
|
127
|
+
const provider = await createWorkspaceProvider(config, opts)
|
|
128
|
+
return {
|
|
129
|
+
provider,
|
|
130
|
+
execution: provider.buildExecution ?? new LocalBuildExecution({ env: opts.env }),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Autobuild distribution archive installed into guests.
|
|
3
|
+
*
|
|
4
|
+
* Two sources, one archive layout (`autobuild-<version>.tgz`, the same bytes
|
|
5
|
+
* `bun pm pack` produces and the guest bootstrap extracts):
|
|
6
|
+
*
|
|
7
|
+
* - Usable source checkout: `bun pm pack` from the host tree (local-git
|
|
8
|
+
* consumers, and any dispatcher running from a checkout with a `bun`
|
|
9
|
+
* executable on `PATH` — byte-identical to today). A checkout without `bun`
|
|
10
|
+
* cannot be packed, so a vestigial `.git` directory in a bundled deployment
|
|
11
|
+
* routes to the release asset instead of attempting a pack it cannot run.
|
|
12
|
+
* - Checkout-less origin mode: the dispatcher's own published release asset,
|
|
13
|
+
* fetched from GitHub by the running version, so the guest installs exactly
|
|
14
|
+
* the version the launching dispatcher reports even when no source tree
|
|
15
|
+
* exists on disk.
|
|
16
|
+
*
|
|
17
|
+
* The guest's installed version therefore equals the running distribution's
|
|
18
|
+
* version in both modes: origin mode's release-asset tag is the running
|
|
19
|
+
* version by construction, and source mode packs `distributionRoot()`, whose
|
|
20
|
+
* package.json is the same file `readDistributionIdentity()` reads. The hosted
|
|
21
|
+
* remote store enforces exact version lockstep — a client whose
|
|
22
|
+
* `x-autobuild-version` differs from the server's is rejected (409) — so
|
|
23
|
+
* guest, store, and dispatcher versions must move together through a release
|
|
24
|
+
* cut (`tools/release.ts`) plus store/dispatcher upgrades; there is no code
|
|
25
|
+
* path that safely mixes versions. Provisioning records the installed version
|
|
26
|
+
* in the guest's `.distribution-version` marker and reinstalls the archive on
|
|
27
|
+
* a later mismatch, so an upgraded dispatcher retrofits its persistent guests
|
|
28
|
+
* (see `vercel-sandbox.ts`).
|
|
29
|
+
*/
|
|
30
|
+
import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
|
|
31
|
+
import { join, resolve } from 'node:path'
|
|
32
|
+
import { distributionRoot, distributionPath } from '../../distribution'
|
|
33
|
+
import { registryBaseUrl, registryVersionUrl } from '../../registry'
|
|
34
|
+
import { parseRepoCoordinates } from '../forge/github'
|
|
35
|
+
import {
|
|
36
|
+
createGitHubFetchTransport,
|
|
37
|
+
GitHubApiError,
|
|
38
|
+
githubTokenFromEnv,
|
|
39
|
+
type GitHubRequest,
|
|
40
|
+
} from '../forge/github-transport'
|
|
41
|
+
import { packageAutobuildDistribution } from './vercel-sandbox'
|
|
42
|
+
|
|
43
|
+
/** The canonical repository the release assets are published to. Shared with
|
|
44
|
+
* `tools/release.ts`, which uploads them. */
|
|
45
|
+
export const CANONICAL_REPOSITORY_URL = 'https://github.com/defrex/autobuild'
|
|
46
|
+
|
|
47
|
+
/** The running distribution's package name and version, from the shipped
|
|
48
|
+
* package.json. Deployed distributions must ship `package.json` — a
|
|
49
|
+
* deployment requirement the hosted entry point owns. Absence is a hard,
|
|
50
|
+
* actionable error. */
|
|
51
|
+
export async function readDistributionPackage(): Promise<{ name: string; version: string }> {
|
|
52
|
+
try {
|
|
53
|
+
const manifest = JSON.parse(await Bun.file(distributionPath('package.json')).text()) as {
|
|
54
|
+
name?: unknown
|
|
55
|
+
version?: unknown
|
|
56
|
+
}
|
|
57
|
+
if (typeof manifest.version !== 'string' || manifest.version === '') {
|
|
58
|
+
throw new Error('the distribution package.json has no usable version field')
|
|
59
|
+
}
|
|
60
|
+
if (typeof manifest.name !== 'string' || manifest.name === '') {
|
|
61
|
+
throw new Error('the distribution package.json has no usable name field')
|
|
62
|
+
}
|
|
63
|
+
return { name: manifest.name, version: manifest.version }
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
66
|
+
throw new Error(
|
|
67
|
+
`could not read the running Autobuild version from the distribution package.json: ${detail}; ` +
|
|
68
|
+
'a deployed distribution must ship package.json — install Autobuild from its packed archive or release',
|
|
69
|
+
{ cause: error },
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
75
|
+
try {
|
|
76
|
+
await access(path)
|
|
77
|
+
return true
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The running distribution's version alone. */
|
|
84
|
+
export async function readDistributionIdentity(): Promise<string> {
|
|
85
|
+
return (await readDistributionPackage()).version
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The release asset a version's distribution ships under. */
|
|
89
|
+
export function distributionAssetName(version: string): string {
|
|
90
|
+
return `autobuild-${version}.tgz`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Minimal fetch seam for registry reads: the global `fetch` in production. */
|
|
94
|
+
export type RegistryFetch = (
|
|
95
|
+
url: string,
|
|
96
|
+
init: { headers: Record<string, string> },
|
|
97
|
+
) => Promise<{ status: number; text(): Promise<string>; arrayBuffer(): Promise<ArrayBuffer> }>
|
|
98
|
+
|
|
99
|
+
const registryFetch: RegistryFetch = (url, init) => fetch(url, init)
|
|
100
|
+
|
|
101
|
+
/** Fetch the published npm tarball for `packageName@version`: read the
|
|
102
|
+
* registry's version document, follow its `dist.tarball`, and return the
|
|
103
|
+
* bytes. The tarball roots at `package/`, exactly like `bun pm pack`, so the
|
|
104
|
+
* guest bootstrap extracts it unchanged. Honors `NPM_CONFIG_REGISTRY`. */
|
|
105
|
+
export async function fetchDistributionRegistryTarball(
|
|
106
|
+
packageName: string,
|
|
107
|
+
version: string,
|
|
108
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
109
|
+
fetchImpl: RegistryFetch = registryFetch,
|
|
110
|
+
): Promise<Uint8Array> {
|
|
111
|
+
const url = registryVersionUrl(registryBaseUrl(env), packageName, version)
|
|
112
|
+
const document = await fetchImpl(url, { headers: { accept: 'application/json' } })
|
|
113
|
+
if (document.status !== 200) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`npm registry has no ${packageName}@${version} (HTTP ${document.status} from ${url}); ` +
|
|
116
|
+
'publish the release first',
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
let tarball: unknown
|
|
120
|
+
try {
|
|
121
|
+
tarball = (JSON.parse(await document.text()) as { dist?: { tarball?: unknown } }).dist?.tarball
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`npm registry returned an unreadable document for ${packageName}@${version}`, {
|
|
124
|
+
cause: error,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
if (typeof tarball !== 'string' || tarball === '') {
|
|
128
|
+
throw new Error(`npm registry document for ${packageName}@${version} names no dist.tarball`)
|
|
129
|
+
}
|
|
130
|
+
const download = await fetchImpl(tarball, { headers: { accept: 'application/octet-stream' } })
|
|
131
|
+
if (download.status !== 200) {
|
|
132
|
+
throw new Error(`downloading ${tarball} failed with HTTP ${download.status}`)
|
|
133
|
+
}
|
|
134
|
+
const bytes = new Uint8Array(await download.arrayBuffer())
|
|
135
|
+
if (bytes.byteLength === 0) throw new Error(`${tarball} downloaded no bytes`)
|
|
136
|
+
return bytes
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The published distribution for the running version: the npm registry
|
|
140
|
+
* tarball first, then the GitHub release asset (the route a fork or an
|
|
141
|
+
* unpublished channel still has). A failure names both sources. */
|
|
142
|
+
async function fetchPublishedDistribution(
|
|
143
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
144
|
+
transport: GitHubRequest | undefined,
|
|
145
|
+
fetchImpl: RegistryFetch,
|
|
146
|
+
): Promise<Uint8Array> {
|
|
147
|
+
const { name, version } = await readDistributionPackage()
|
|
148
|
+
try {
|
|
149
|
+
return await fetchDistributionRegistryTarball(name, version, env, fetchImpl)
|
|
150
|
+
} catch (registryError) {
|
|
151
|
+
try {
|
|
152
|
+
return await fetchDistributionReleaseAsset(version, env, transport)
|
|
153
|
+
} catch (releaseError) {
|
|
154
|
+
const detail = (error: unknown): string =>
|
|
155
|
+
error instanceof Error ? error.message : String(error)
|
|
156
|
+
throw new Error(
|
|
157
|
+
`no published Autobuild ${version} distribution: npm registry — ${detail(registryError)}; ` +
|
|
158
|
+
`GitHub release asset — ${detail(releaseError)}`,
|
|
159
|
+
{ cause: releaseError },
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Fetch the published `autobuild-<version>.tgz` release asset for the
|
|
166
|
+
* running version. Uses the dispatcher's GitHub credentials when present
|
|
167
|
+
* (required for a private canonical repository). */
|
|
168
|
+
export async function fetchDistributionReleaseAsset(
|
|
169
|
+
version: string,
|
|
170
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
171
|
+
transport: GitHubRequest = createGitHubFetchTransport({
|
|
172
|
+
token: githubTokenFromEnv(env),
|
|
173
|
+
}),
|
|
174
|
+
): Promise<Uint8Array> {
|
|
175
|
+
const coordinates = parseRepoCoordinates(CANONICAL_REPOSITORY_URL)
|
|
176
|
+
if (coordinates === null) {
|
|
177
|
+
throw new Error(`the canonical repository URL is not parseable: ${CANONICAL_REPOSITORY_URL}`)
|
|
178
|
+
}
|
|
179
|
+
const repo = `repos/${encodeURIComponent(coordinates.owner)}/${encodeURIComponent(coordinates.name)}`
|
|
180
|
+
const tag = `v${version}`
|
|
181
|
+
const release = await transport('GET', `${repo}/releases/tags/${tag}`).catch((error: unknown) => {
|
|
182
|
+
if (error instanceof GitHubApiError && error.status === 404) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Autobuild release ${tag} has no published GitHub release; origin-mode dispatch ` +
|
|
185
|
+
'installs the guest distribution from the release asset autobuild-' +
|
|
186
|
+
`${version}.tgz — cut a release with tools/release.ts first`,
|
|
187
|
+
{ cause: error },
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
throw error
|
|
191
|
+
})
|
|
192
|
+
const assets = (
|
|
193
|
+
release.json as { assets?: { name?: unknown; browser_download_url?: unknown }[] } | undefined
|
|
194
|
+
)?.assets
|
|
195
|
+
const assetName = distributionAssetName(version)
|
|
196
|
+
const asset = (assets ?? []).find((candidate) => candidate.name === assetName)
|
|
197
|
+
if (asset === undefined || typeof asset.browser_download_url !== 'string') {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Autobuild release ${tag} does not carry the ${assetName} distribution asset; ` +
|
|
200
|
+
'origin-mode dispatch cannot install a guest distribution without it — ' +
|
|
201
|
+
'cut a release with tools/release.ts first',
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
const download = await transport('GET', asset.browser_download_url, {
|
|
205
|
+
headers: { Accept: 'application/octet-stream' },
|
|
206
|
+
})
|
|
207
|
+
if (download.bytes === undefined) {
|
|
208
|
+
throw new Error(`the ${assetName} release asset downloaded no bytes`)
|
|
209
|
+
}
|
|
210
|
+
return download.bytes
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Directory, relative to a distribution root, where a deployment ships the
|
|
214
|
+
* archive it packed at build time (`ab-hosted-store pack-distribution`). A
|
|
215
|
+
* bundled deployment — the hosted service on Vercel — has neither `bun` nor a
|
|
216
|
+
* source tree at runtime, so the archive must be produced while both exist
|
|
217
|
+
* and carried into the function bundle. */
|
|
218
|
+
export const PREBUILT_DISTRIBUTION_DIR = '.autobuild-dist'
|
|
219
|
+
|
|
220
|
+
/** The environment variable naming an explicit archive file, for deployments
|
|
221
|
+
* that place it somewhere other than `PREBUILT_DISTRIBUTION_DIR`. */
|
|
222
|
+
export const DISTRIBUTION_ARCHIVE_ENV = 'AB_DISTRIBUTION_ARCHIVE'
|
|
223
|
+
|
|
224
|
+
/** Pack the running distribution into `<root>/.autobuild-dist/autobuild-<version>.tgz`
|
|
225
|
+
* (the same bytes `bun pm pack` produces) and return the archive path. Meant
|
|
226
|
+
* for a deployment's build step, where `bun` and the source tree exist. */
|
|
227
|
+
export async function writePrebuiltDistributionArchive(
|
|
228
|
+
root: string = distributionRoot(),
|
|
229
|
+
): Promise<string> {
|
|
230
|
+
const version = await readDistributionIdentity()
|
|
231
|
+
const archive = await packageAutobuildDistribution()
|
|
232
|
+
const dir = join(root, PREBUILT_DISTRIBUTION_DIR)
|
|
233
|
+
await mkdir(dir, { recursive: true })
|
|
234
|
+
const path = join(dir, distributionAssetName(version))
|
|
235
|
+
await writeFile(path, archive)
|
|
236
|
+
return path
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Locate a prebuilt archive: `AB_DISTRIBUTION_ARCHIVE` when set, else the
|
|
240
|
+
* single `autobuild-*.tgz` under `.autobuild-dist/` of the distribution root
|
|
241
|
+
* or the working directory (a bundled function's root). Null when none. */
|
|
242
|
+
export async function findPrebuiltDistributionArchive(
|
|
243
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
244
|
+
roots: readonly string[] = [distributionRoot(), process.cwd()],
|
|
245
|
+
): Promise<string | null> {
|
|
246
|
+
const explicit = env[DISTRIBUTION_ARCHIVE_ENV]?.trim()
|
|
247
|
+
if (explicit) {
|
|
248
|
+
const path = resolve(explicit)
|
|
249
|
+
if (!(await fileExists(path))) {
|
|
250
|
+
throw new Error(`${DISTRIBUTION_ARCHIVE_ENV} names a file that does not exist: ${path}`)
|
|
251
|
+
}
|
|
252
|
+
return path
|
|
253
|
+
}
|
|
254
|
+
for (const root of [...new Set(roots)]) {
|
|
255
|
+
const dir = join(root, PREBUILT_DISTRIBUTION_DIR)
|
|
256
|
+
let names: string[]
|
|
257
|
+
try {
|
|
258
|
+
names = (await readdir(dir)).filter(
|
|
259
|
+
(name) => name.startsWith('autobuild-') && name.endsWith('.tgz'),
|
|
260
|
+
)
|
|
261
|
+
} catch {
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
if (names.length === 0) continue
|
|
265
|
+
if (names.length > 1) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`${dir} holds more than one distribution archive (${names.sort().join(', ')}); ` +
|
|
268
|
+
'a deployment ships exactly one',
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
return join(dir, names[0]!)
|
|
272
|
+
}
|
|
273
|
+
return null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Whether a `bun` executable is available to pack a source checkout with.
|
|
277
|
+
* Injectable in tests via `defaultDistributionArchive`. */
|
|
278
|
+
const hasBunExecutable = (): boolean => Bun.which('bun') !== null
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The default guest distribution source, in precedence order: an archive
|
|
282
|
+
* prebuilt for this deployment (`AB_DISTRIBUTION_ARCHIVE` or
|
|
283
|
+
* `.autobuild-dist/`), then a usable source checkout — a `.git` directory
|
|
284
|
+
* *and* a `bun` executable to pack with — then the running version's
|
|
285
|
+
* published distribution: the npm registry tarball, else the GitHub release
|
|
286
|
+
* asset (see module docs). The prebuilt archive comes first because a
|
|
287
|
+
* bundled deployment can carry a vestigial `.git` directory without a `bun`
|
|
288
|
+
* executable to pack with; in that case the published distribution is the
|
|
289
|
+
* route, and when it too is missing the error names the prebuilt archive the
|
|
290
|
+
* deployment should have shipped.
|
|
291
|
+
*/
|
|
292
|
+
export async function defaultDistributionArchive(
|
|
293
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
294
|
+
transport?: GitHubRequest,
|
|
295
|
+
hasBun: () => boolean = hasBunExecutable,
|
|
296
|
+
fetchImpl: RegistryFetch = registryFetch,
|
|
297
|
+
): Promise<Uint8Array> {
|
|
298
|
+
const prebuilt = await findPrebuiltDistributionArchive(env)
|
|
299
|
+
if (prebuilt !== null) return new Uint8Array(await readFile(prebuilt))
|
|
300
|
+
if (await fileExists(join(distributionRoot(), '.git'))) {
|
|
301
|
+
if (hasBun()) return packageAutobuildDistribution()
|
|
302
|
+
// Vestigial `.git` in a bundled deployment: there is no usable source
|
|
303
|
+
// checkout to pack, so fall through to the release asset — and when that
|
|
304
|
+
// also fails, name the prebuilt archive the deployment should ship
|
|
305
|
+
// instead of leaving an unactionable failure behind.
|
|
306
|
+
const version = await readDistributionIdentity()
|
|
307
|
+
return fetchPublishedDistribution(env, transport, fetchImpl).catch((error: unknown) => {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`no usable source checkout to pack and no prebuilt distribution archive for this deployment; ` +
|
|
310
|
+
`ship .autobuild-dist/autobuild-${version}.tgz with the deployment or point ` +
|
|
311
|
+
`${DISTRIBUTION_ARCHIVE_ENV} at one (see the deployment pack step)`,
|
|
312
|
+
{ cause: error },
|
|
313
|
+
)
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
return fetchPublishedDistribution(env, transport, fetchImpl)
|
|
317
|
+
}
|