@defrex/autobuild 0.8.0 → 0.10.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/README.md +1 -1
- package/SPEC.md +323 -26
- package/bin/ab-build-runner.ts +8 -2
- package/docs/architecture.md +166 -14
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +175 -28
- package/docs/hosted-dispatcher.md +69 -13
- package/docs/operator-api.md +2 -2
- package/docs/remote-store-protocol.md +204 -16
- package/docs/remote-ticket-protocol.md +1 -1
- package/docs/setup.md +12 -9
- package/docs/website.md +14 -0
- package/package.json +45 -23
- package/packages/core/src/cli/artifact.ts +130 -6
- package/packages/core/src/cli/binary.ts +9 -1
- package/packages/core/src/cli/build-control.ts +110 -13
- package/packages/core/src/cli/context.ts +46 -0
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +141 -153
- package/packages/core/src/cli/dashboard/poll.ts +76 -12
- package/packages/core/src/cli/dashboard/render.ts +34 -5
- package/packages/core/src/cli/dispatch-frontend.ts +54 -5
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +392 -75
- package/packages/core/src/cli/harvest.ts +122 -8
- package/packages/core/src/cli/help.ts +62 -9
- package/packages/core/src/cli/init-readiness-remote.ts +110 -0
- package/packages/core/src/cli/init-readiness-shared.ts +72 -0
- package/packages/core/src/cli/init-validation.ts +266 -222
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/main.ts +70 -11
- package/packages/core/src/cli/mcp.ts +23 -1
- package/packages/core/src/cli/plugin.ts +7 -4
- package/packages/core/src/cli/remote-poll.ts +277 -0
- package/packages/core/src/cli/repo-state.ts +22 -0
- package/packages/core/src/cli/repository-status.ts +39 -1
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +150 -28
- package/packages/core/src/cli/terminals.ts +2 -1
- package/packages/core/src/cli/ticket-assets.ts +247 -0
- package/packages/core/src/cli/ticket.ts +453 -8
- package/packages/core/src/cli/wait.ts +157 -41
- package/packages/core/src/cli/watch.ts +138 -168
- package/packages/core/src/config/live.ts +23 -0
- package/packages/core/src/config/pipeline-source.ts +388 -0
- package/packages/core/src/config/schema.ts +251 -19
- package/packages/core/src/events/catalog.ts +1 -0
- package/packages/core/src/events/globs.ts +47 -0
- package/packages/core/src/events/payloads.ts +52 -2
- package/packages/core/src/events/repository.ts +52 -1
- package/packages/core/src/events/sessions.ts +69 -10
- package/packages/core/src/events/workspace-remote.ts +12 -0
- package/packages/core/src/integration/harness.ts +115 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +157 -0
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/phases.ts +16 -2
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +254 -73
- package/packages/core/src/operator/annotations.ts +6 -0
- package/packages/core/src/operator/control.ts +19 -5
- package/packages/core/src/operator/index.ts +71 -6
- package/packages/core/src/operator/query.ts +253 -24
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/operator/registry.ts +55 -10
- package/packages/core/src/operator/requests.ts +9 -6
- package/packages/core/src/operator/sandbox-host.ts +140 -0
- package/packages/core/src/operator/sandbox.ts +247 -14
- package/packages/core/src/operator/ticket-source-backend.ts +47 -0
- package/packages/core/src/orchestrator/approvals.ts +115 -0
- package/packages/core/src/orchestrator/conversation.ts +167 -0
- package/packages/core/src/orchestrator/model.ts +86 -0
- package/packages/core/src/orchestrator/prompt.ts +61 -0
- package/packages/core/src/orchestrator/tools.ts +117 -0
- package/packages/core/src/orchestrator/turn-runner.ts +457 -0
- package/packages/core/src/plugin-sdk/index.ts +51 -1
- package/packages/core/src/plugins/load.ts +135 -7
- package/packages/core/src/plugins/manifest.ts +183 -10
- package/packages/core/src/plugins/registry.ts +73 -4
- package/packages/core/src/ports/forge/create.ts +1 -1
- package/packages/core/src/ports/forge/fake.ts +31 -0
- package/packages/core/src/ports/forge/github-transport.ts +13 -2
- package/packages/core/src/ports/forge/github.ts +13 -2
- package/packages/core/src/ports/forge/local-git.ts +31 -1
- package/packages/core/src/ports/runner/claude.ts +2 -2
- package/packages/core/src/ports/runner/codex.ts +6 -2
- package/packages/core/src/ports/runner/fake.ts +7 -1
- package/packages/core/src/ports/runner/pi.ts +8 -6
- package/packages/core/src/ports/runner/provider-error.ts +13 -3
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +49 -3
- package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
- package/packages/core/src/ports/workspace/create.ts +74 -39
- package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
- package/packages/core/src/ports/workspace/fake.ts +169 -4
- package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
- package/packages/core/src/ports/workspace/github-origin.ts +36 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
- package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
- package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
- package/packages/core/src/processes/build-child.ts +17 -2
- package/packages/core/src/processes/build-execution-state.ts +68 -15
- package/packages/core/src/processes/build-runner.ts +238 -93
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +501 -201
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-child.ts +5 -0
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/harvest-runner.ts +66 -19
- package/packages/core/src/processes/harvest.ts +196 -24
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +360 -0
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +36 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +41 -0
- package/packages/core/src/store/build-scope.ts +90 -0
- package/packages/core/src/store/contract.ts +1205 -0
- package/packages/core/src/store/digest.ts +102 -0
- package/packages/core/src/store/local/schema.ts +46 -2
- package/packages/core/src/store/local/store.ts +445 -4
- package/packages/core/src/store/memory.ts +246 -0
- package/packages/core/src/store/new-build.ts +17 -0
- package/packages/core/src/store/phase-session.ts +94 -0
- package/packages/core/src/store/remote/client.ts +310 -2
- package/packages/core/src/store/remote/index.ts +14 -2
- package/packages/core/src/store/remote/protocol.ts +115 -12
- package/packages/core/src/store/remote/version.ts +6 -2
- package/packages/core/src/store/repo-state-events.ts +205 -0
- package/packages/core/src/store/retention.ts +7 -0
- package/packages/core/src/store/session-handle.ts +94 -0
- package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/streams/session-writer.ts +42 -2
- package/packages/core/src/store/streams/wait.ts +4 -5
- package/packages/core/src/store/ticket-assets.ts +458 -0
- package/packages/core/src/store/types.ts +160 -1
- package/packages/core/src/testing/index.ts +59 -0
- package/skills/code-review/SKILL.md +5 -1
- package/skills/guide/SKILL.md +152 -40
- package/skills/guide/references/operator-api.md +2 -2
- package/skills/guide/references/plugin-authoring.md +17 -0
- package/skills/guide/references/remote-store-protocol.md +204 -16
- package/skills/guide/references/remote-ticket-protocol.md +1 -1
- package/skills/guide/references/setup.md +12 -9
- package/skills/implement/SKILL.md +3 -0
- package/skills/operate/SKILL.md +126 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/plan-review/SKILL.md +5 -1
- package/skills/spec/SKILL.md +46 -3
- package/skills/tickets/SKILL.md +57 -5
- package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
- package/packages/core/src/cli/store-opening.contract.ts +0 -289
- package/packages/core/src/cli/testkit.ts +0 -194
- package/packages/core/src/markdown.ts +0 -67
- package/packages/core/src/operator/client.ts +0 -311
- package/packages/core/src/operator/server.ts +0 -616
- package/packages/core/src/operator/sessions.ts +0 -223
- package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
- package/packages/core/src/ports/tickets/remote-server.ts +0 -185
- package/packages/core/src/store/remote/server.ts +0 -923
- package/packages/core/src/testing/store-failures.ts +0 -32
|
@@ -44,7 +44,7 @@ export async function requirePostMutationTicket(
|
|
|
44
44
|
export function ticketListCriteria(
|
|
45
45
|
config: Config,
|
|
46
46
|
filters: { state?: string; labels?: string[] },
|
|
47
|
-
): { state?: string; labels?: string[] } {
|
|
47
|
+
): { state?: string; labels?: string[]; assignee?: string } {
|
|
48
48
|
return filters.state === undefined && filters.labels === undefined
|
|
49
49
|
? readyCriteria(config)
|
|
50
50
|
: filters
|
|
@@ -86,7 +86,14 @@ export class HostedTicketSource implements TicketSource {
|
|
|
86
86
|
return schema.parse(await response.json())
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
async listReady(criteria: {
|
|
89
|
+
async listReady(criteria: {
|
|
90
|
+
labels?: string[]
|
|
91
|
+
state?: string
|
|
92
|
+
assignee?: string
|
|
93
|
+
}): Promise<TicketListing> {
|
|
94
|
+
if (criteria.assignee !== undefined) {
|
|
95
|
+
throw new Error('the hosted ticket source does not support an assignee gate')
|
|
96
|
+
}
|
|
90
97
|
const listing = await this.request('list-ready', criteria, ticketListingWireSchema)
|
|
91
98
|
return { ...listing, tickets: listing.tickets.map((ticket) => this.project(ticket)) }
|
|
92
99
|
}
|
|
@@ -122,12 +122,27 @@ export interface TicketUpdate {
|
|
|
122
122
|
labels?: string[]
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** One workflow state of a source that can enumerate them. */
|
|
126
|
+
export interface TicketStateInfo {
|
|
127
|
+
name: string
|
|
128
|
+
/** Tickets currently in the state. */
|
|
129
|
+
tickets: number
|
|
130
|
+
/** One-line purpose recorded when the state was created, if any. */
|
|
131
|
+
about?: string
|
|
132
|
+
}
|
|
133
|
+
|
|
125
134
|
export interface TicketSource {
|
|
126
135
|
readonly name: string
|
|
127
136
|
/** Ready tickets matching the dispatch criteria (label/state — §3.3), plus
|
|
128
137
|
* actionable diagnostics for individual source records excluded as invalid.
|
|
129
138
|
* Fatal source failures and invariants still reject the call. */
|
|
130
|
-
listReady(criteria: {
|
|
139
|
+
listReady(criteria: {
|
|
140
|
+
labels?: string[]
|
|
141
|
+
state?: string
|
|
142
|
+
/** `me` (the API key's owner) or a user's email; only sources that can honour it
|
|
143
|
+
* accept it — others reject `[tickets].readyAssignee` at config parse. */
|
|
144
|
+
assignee?: string
|
|
145
|
+
}): Promise<TicketListing>
|
|
131
146
|
get(id: string): Promise<Ticket | null>
|
|
132
147
|
/** Claim-before-launch (§12): acquires nonterminal work and returns true.
|
|
133
148
|
* Returns false for an unknown, already-claimed/contested, or terminal ticket.
|
|
@@ -154,6 +169,22 @@ export interface TicketSource {
|
|
|
154
169
|
* are initiation, so §13's "never consulted mid-build" is untouched.
|
|
155
170
|
*/
|
|
156
171
|
dependencyStates(ids: string[]): Promise<DependencyState[]>
|
|
172
|
+
/**
|
|
173
|
+
* Optional state discovery (`ab ticket states`): every state the source has,
|
|
174
|
+
* lifecycle states first in workflow order, then the rest alphabetically.
|
|
175
|
+
* Sources that cannot enumerate their states omit it.
|
|
176
|
+
*/
|
|
177
|
+
listStates?(): Promise<TicketStateInfo[]>
|
|
178
|
+
/**
|
|
179
|
+
* Optional state creation (`ab ticket state create`). Idempotent: an existing
|
|
180
|
+
* state is reported with `created: false`, and a supplied `about` replaces its
|
|
181
|
+
* purpose. Rejects a malformed or case-colliding name without creating anything.
|
|
182
|
+
* Named `addState` because adapters already hold a `createState` option field.
|
|
183
|
+
*/
|
|
184
|
+
addState?(
|
|
185
|
+
name: string,
|
|
186
|
+
opts?: { about?: string },
|
|
187
|
+
): Promise<{ state: TicketStateInfo; created: boolean }>
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
// ── Workspace (SPEC §3.2, §7) ────────────────────────────────────────────────
|
|
@@ -177,8 +208,11 @@ export interface WorkspaceProvisionResult extends WorkspaceHandle {
|
|
|
177
208
|
|
|
178
209
|
export interface WorkspacePublication {
|
|
179
210
|
/** Observe whether the exact requested commit already reached the durable
|
|
180
|
-
* remote branch after an interrupted acknowledgement.
|
|
181
|
-
|
|
211
|
+
* remote branch after an interrupted acknowledgement. `ref` optionally
|
|
212
|
+
* names the workspace whose git database the probe runs against — used by
|
|
213
|
+
* the operator-sandbox publication path, whose provider probes from the
|
|
214
|
+
* sandbox checkout; the build-path callers omit it. */
|
|
215
|
+
isPublished?(input: { ref?: string; sha: string; branch: string }): Promise<boolean>
|
|
182
216
|
/** Publish exactly one commit to exactly one branch; no arbitrary command
|
|
183
217
|
* surface is exposed to the dispatcher or build child. */
|
|
184
218
|
publish(input: { ref: string; sha: string; branch: string }): Promise<void>
|
|
@@ -221,6 +255,14 @@ export interface WorkspaceProvider {
|
|
|
221
255
|
readonly orchestratorSandbox?: OperatorSandboxExecution
|
|
222
256
|
/** Trusted dispatcher-only publication capability for remote workspaces. */
|
|
223
257
|
readonly publication?: WorkspacePublication
|
|
258
|
+
/** Publication capability for the operator sandbox (PR-only publication):
|
|
259
|
+
* deliberately distinct from `publication`, which is the dispatcher's
|
|
260
|
+
* remote-workspace discriminator — its presence reclassifies every build
|
|
261
|
+
* workspace as remote and reroutes build publication. `sandboxPublication`
|
|
262
|
+
* is never consulted by the dispatcher; it is consumed only by the
|
|
263
|
+
* operator-sandbox publish service, and present only on providers that can
|
|
264
|
+
* host operator sandboxes. */
|
|
265
|
+
readonly sandboxPublication?: WorkspacePublication
|
|
224
266
|
/** Optional only for remote/disposable providers. */
|
|
225
267
|
readonly recovery?: WorkspaceRecovery
|
|
226
268
|
provision(opts: {
|
|
@@ -424,6 +466,10 @@ export interface AgentTurnFailure {
|
|
|
424
466
|
/** Additive structured routing cause. Legacy plugin runners may omit it;
|
|
425
467
|
* their existing permanent bit then supplies the conservative behavior. */
|
|
426
468
|
cause?: AgentFailureCause
|
|
469
|
+
/** Set with cause 'configuration' when the runtime's CLI executable is not
|
|
470
|
+
* installed. This is the one configuration failure a configured alternate
|
|
471
|
+
* may serve; every other configuration failure still stops the chain. */
|
|
472
|
+
missingExecutable?: true
|
|
427
473
|
}
|
|
428
474
|
|
|
429
475
|
/** A turn that reached a provider/runtime-declared failure while retaining an
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full capability objects for the builtin workspace providers (AUT-516).
|
|
3
|
+
* The parse-time subset lives in `config/schema.ts`'s
|
|
4
|
+
* `BUILTIN_WORKSPACE_PROVIDER_CONFIG` because config validation must be
|
|
5
|
+
* statically reachable without the registry; this module assembles the FULL
|
|
6
|
+
* declarations — including the registry-seam behaviors — and is attached to
|
|
7
|
+
* the builtin registrations by `plugins/registry.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Import graph discipline: this module imports `config/schema.ts`,
|
|
10
|
+
* `git-worktree.ts`, `vercel-sandbox.ts`, and the shared vercel capability
|
|
11
|
+
* module; nothing imports it from `schema.ts`, so no cycle exists. The
|
|
12
|
+
* `vercel-sandbox` entry's capabilities are the shared
|
|
13
|
+
* `VERCEL_SANDBOX_CAPABILITIES` object (AUT-517) — the plugin package
|
|
14
|
+
* references the same object, so the declarations cannot drift. The
|
|
15
|
+
* construction-site `configRefusal` for git-worktree is set EXPLICITLY here,
|
|
16
|
+
* not copied from the table's parse-site `configRefusalMessage` — the two
|
|
17
|
+
* strings differ by design (the parse-site message adds the remediation
|
|
18
|
+
* clause; the construction-site one does not).
|
|
19
|
+
*
|
|
20
|
+
* This `BUILTINS` record is the single source of truth for which builtins
|
|
21
|
+
* exist: `plugins/registry.ts` derives its builtin workspace-provider set
|
|
22
|
+
* from `builtinWorkspaceProviderNames()` (AUT-579), so adding an entry here
|
|
23
|
+
* (plus the config-table half that AUT-573's parity tests pin) completes
|
|
24
|
+
* the registration step — no registry edit is required.
|
|
25
|
+
*/
|
|
26
|
+
import { join, resolve } from 'node:path'
|
|
27
|
+
import type { VercelSandboxConfig, WorkspaceConfig } from '../../config/schema'
|
|
28
|
+
import type { WorkspaceProviderCapabilities } from './provider-capabilities'
|
|
29
|
+
import type { CreateWorkspaceProviderOptions } from './create'
|
|
30
|
+
import { GitWorktreeProvider } from './git-worktree'
|
|
31
|
+
import { VercelSandboxProvider } from './vercel-sandbox'
|
|
32
|
+
import { VERCEL_SANDBOX_CAPABILITIES } from './vercel-capabilities'
|
|
33
|
+
import type { WorkspaceProvider } from '../types'
|
|
34
|
+
|
|
35
|
+
/** The git-worktree construction-site refusal: no remediation clause, unlike
|
|
36
|
+
* the parse-site message in the config table. */
|
|
37
|
+
const GIT_WORKTREE_CONSTRUCTION_REFUSAL =
|
|
38
|
+
'[workspace.config] is not supported by the builtin "git-worktree" provider'
|
|
39
|
+
|
|
40
|
+
interface BuiltinWorkspaceProviderDeclaration {
|
|
41
|
+
capabilities: WorkspaceProviderCapabilities
|
|
42
|
+
builtinFactory: (
|
|
43
|
+
config: WorkspaceConfig,
|
|
44
|
+
opts: CreateWorkspaceProviderOptions,
|
|
45
|
+
parsed: unknown,
|
|
46
|
+
) => WorkspaceProvider | Promise<WorkspaceProvider>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const BUILTINS: Record<string, BuiltinWorkspaceProviderDeclaration> = {
|
|
50
|
+
'git-worktree': {
|
|
51
|
+
capabilities: {
|
|
52
|
+
configRefusal: GIT_WORKTREE_CONSTRUCTION_REFUSAL,
|
|
53
|
+
},
|
|
54
|
+
builtinFactory(_config, opts) {
|
|
55
|
+
// Its provider config is always `{}`: the refusal (parse site and
|
|
56
|
+
// construction site alike) has already rejected anything else.
|
|
57
|
+
return new GitWorktreeProvider({
|
|
58
|
+
root: resolve(opts.worktreeRoot),
|
|
59
|
+
sandboxRoot:
|
|
60
|
+
opts.sandboxRoot ?? resolve(join(opts.worktreeRoot, '..', 'orchestrator-sandboxes')),
|
|
61
|
+
setupCommand: opts.sandboxSetupCommand,
|
|
62
|
+
sandboxEnvironmentVariables: opts.sandboxEnvironmentVariables,
|
|
63
|
+
envSource: opts.env,
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
'vercel-sandbox': {
|
|
68
|
+
capabilities: VERCEL_SANDBOX_CAPABILITIES,
|
|
69
|
+
builtinFactory(_config, opts, parsed) {
|
|
70
|
+
// storeRef/storeToken are guaranteed present: the pre-split
|
|
71
|
+
// storeRequirements check in createWorkspaceProvider ran before
|
|
72
|
+
// construction.
|
|
73
|
+
return new VercelSandboxProvider({
|
|
74
|
+
config: parsed as VercelSandboxConfig,
|
|
75
|
+
env: opts.env,
|
|
76
|
+
storeRef: opts.storeRef!,
|
|
77
|
+
storeToken: opts.storeToken!,
|
|
78
|
+
repo: resolve(opts.repoRoot),
|
|
79
|
+
runtimeReferences: opts.runtimeReferences ?? [],
|
|
80
|
+
setupCommand: opts.sandboxSetupCommand,
|
|
81
|
+
sandboxEnvironmentVariables: opts.sandboxEnvironmentVariables,
|
|
82
|
+
...(opts.origin !== undefined ? { origin: opts.origin } : {}),
|
|
83
|
+
...(opts.remoteBranchHead !== undefined ? { remoteBranchHead: opts.remoteBranchHead } : {}),
|
|
84
|
+
})
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Every builtin provider name, for parity checks over the declaration
|
|
90
|
+
* tables (AUT-573): the parse-time table in `config/schema.ts` and the FULL
|
|
91
|
+
* declarations here must cover the same provider set. */
|
|
92
|
+
export function builtinWorkspaceProviderNames(): readonly string[] {
|
|
93
|
+
return Object.keys(BUILTINS)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The FULL capability object of a builtin workspace provider, `undefined`
|
|
97
|
+
* for any other name. Used by pre-registry seams (guest probe, init
|
|
98
|
+
* preflight) that cannot consult the plugin registry. */
|
|
99
|
+
export function builtinWorkspaceProviderCapabilities(
|
|
100
|
+
name: string,
|
|
101
|
+
): WorkspaceProviderCapabilities | undefined {
|
|
102
|
+
return BUILTINS[name]?.capabilities
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The builtin registration extras for `plugins/registry.ts`: the capability
|
|
106
|
+
* object plus the host-owned construction closure. */
|
|
107
|
+
export function builtinWorkspaceProviderRegistration(
|
|
108
|
+
name: string,
|
|
109
|
+
): BuiltinWorkspaceProviderDeclaration | undefined {
|
|
110
|
+
const builtin = BUILTINS[name]
|
|
111
|
+
return builtin === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: { capabilities: builtin.capabilities, builtinFactory: builtin.builtinFactory }
|
|
114
|
+
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { SANDBOX_FORBIDDEN_ENV } from './operator-sandbox'
|
|
3
|
+
import type { WorkspaceConfig } from '../../config/schema'
|
|
3
4
|
import type { PluginRegistry } from '../../plugins/registry'
|
|
4
5
|
import type { WorkspaceProvider } from '../types'
|
|
5
6
|
import type { BuildExecution } from './build-execution'
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
currentRuntimeReferences,
|
|
9
|
+
runtimeProvisioningMap,
|
|
10
|
+
runtimeProvisioningMissingMessage,
|
|
11
|
+
sandboxForbiddenEnvMessage,
|
|
12
|
+
} from './provider-capabilities'
|
|
7
13
|
import { LocalBuildExecution } from './local-build-execution'
|
|
8
|
-
import {
|
|
14
|
+
import type { RuntimeReferencesSource } from './vercel-sandbox'
|
|
9
15
|
|
|
10
16
|
export interface CreateWorkspaceProviderOptions {
|
|
11
17
|
registry: PluginRegistry
|
|
@@ -31,6 +37,11 @@ export interface CreateWorkspaceProviderOptions {
|
|
|
31
37
|
sandboxSetupCommand?: string
|
|
32
38
|
sandboxRoot?: string
|
|
33
39
|
sandboxEnvironmentVariables?: readonly string[]
|
|
40
|
+
/** Whether `[orchestrator].enabled` is set at the call site. Gates the
|
|
41
|
+
* construction-site `sandboxForbiddenEnv` check so the parse-time rule is
|
|
42
|
+
* reproduced exactly (the config validation only runs when the orchestrator
|
|
43
|
+
* is enabled). */
|
|
44
|
+
orchestratorSandboxEnabled?: boolean
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
export interface WorkspaceRuntime {
|
|
@@ -53,44 +64,56 @@ export async function createWorkspaceProvider(
|
|
|
53
64
|
)
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
// Uniform capability enforcement (AUT-516): every declaration that needs
|
|
68
|
+
// the registry is honoured here, before the builtin/plugin split, where
|
|
69
|
+
// `registration.capabilities`, `opts.storeRef`, and `opts.storeToken` are
|
|
70
|
+
// already in hand — a plugin declaring any of them is enforced too, never
|
|
71
|
+
// accepted by manifest parsing and silently ignored. Order preserves the
|
|
72
|
+
// builtin failure precedence.
|
|
73
|
+
const capabilities = registration.capabilities
|
|
74
|
+
const hasConfig = Object.keys(config.config).length > 0
|
|
75
|
+
if (capabilities?.configRefusal !== undefined && hasConfig) {
|
|
76
|
+
throw new Error(capabilities.configRefusal)
|
|
77
|
+
}
|
|
78
|
+
let parsed: { data: unknown } | undefined
|
|
79
|
+
if (capabilities?.configSchema !== undefined) {
|
|
80
|
+
const result = capabilities.configSchema.safeParse(config.config)
|
|
81
|
+
if (!result.success) {
|
|
82
|
+
throw new Error(`invalid ${config.provider} config: ${result.error.message}`)
|
|
71
83
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
parsed = result as { data: unknown }
|
|
85
|
+
}
|
|
86
|
+
if (capabilities?.requireRuntimeProvisioning === true && opts.runtimeReferences !== undefined) {
|
|
87
|
+
// `mcp.ts` passes no `runtimeReferences` because it references no runtimes;
|
|
88
|
+
// dispatch and init validation always do.
|
|
89
|
+
const provisioning = runtimeProvisioningMap(parsed !== undefined ? parsed.data : config.config)
|
|
90
|
+
for (const group of currentRuntimeReferences(opts.runtimeReferences)) {
|
|
91
|
+
if (Object.hasOwn(provisioning, group.runtime)) continue
|
|
92
|
+
throw new Error(runtimeProvisioningMissingMessage(group))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (opts.orchestratorSandboxEnabled === true && capabilities?.sandboxForbiddenEnv !== undefined) {
|
|
96
|
+
for (const name of opts.sandboxEnvironmentVariables ?? []) {
|
|
97
|
+
if (SANDBOX_FORBIDDEN_ENV.includes(name) || capabilities.sandboxForbiddenEnv.includes(name)) {
|
|
98
|
+
throw new Error(sandboxForbiddenEnvMessage(name))
|
|
77
99
|
}
|
|
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
100
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
capabilities?.storeRequirements !== undefined &&
|
|
104
|
+
(opts.storeRef === undefined || opts.storeToken === undefined)
|
|
105
|
+
) {
|
|
106
|
+
throw new Error(capabilities.storeRequirements.constructionMessage)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (registration.owner.kind === 'builtin') {
|
|
110
|
+
const builtinFactory = registration.builtinFactory
|
|
111
|
+
if (builtinFactory === undefined) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`workspace provider "${config.provider}" is registered as a builtin but has no constructor`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
return builtinFactory(config, opts, parsed !== undefined ? parsed.data : config.config)
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
const factory = registration.factory
|
|
@@ -101,10 +124,22 @@ export async function createWorkspaceProvider(
|
|
|
101
124
|
}
|
|
102
125
|
|
|
103
126
|
try {
|
|
127
|
+
// Conditional spreads keep the existing exactness guarantee: a call site
|
|
128
|
+
// that supplies no seams constructs exactly `{ config, env, repoRoot }`,
|
|
129
|
+
// so plugin factories see no `undefined`-valued additions. With seams,
|
|
130
|
+
// the context widens in place (AUT-560) — the seams the builtin factory
|
|
131
|
+
// receives through `opts` reach the plugin factory through the context.
|
|
104
132
|
return await factory({
|
|
105
133
|
config: config.config,
|
|
106
134
|
env: opts.env,
|
|
107
135
|
repoRoot: resolve(opts.repoRoot),
|
|
136
|
+
...(opts.storeRef !== undefined ? { storeRef: opts.storeRef } : {}),
|
|
137
|
+
...(opts.storeToken !== undefined ? { storeToken: opts.storeToken } : {}),
|
|
138
|
+
...(opts.runtimeReferences !== undefined
|
|
139
|
+
? { runtimeReferences: opts.runtimeReferences }
|
|
140
|
+
: {}),
|
|
141
|
+
...(opts.origin !== undefined ? { origin: opts.origin } : {}),
|
|
142
|
+
...(opts.remoteBranchHead !== undefined ? { remoteBranchHead: opts.remoteBranchHead } : {}),
|
|
108
143
|
})
|
|
109
144
|
} catch (error) {
|
|
110
145
|
throw new Error(
|
|
@@ -19,17 +19,29 @@
|
|
|
19
19
|
* version by construction, and source mode packs `distributionRoot()`, whose
|
|
20
20
|
* package.json is the same file `readDistributionIdentity()` reads. The hosted
|
|
21
21
|
* remote store enforces exact version lockstep — a client whose
|
|
22
|
-
* `x-autobuild-version` differs from the
|
|
23
|
-
* guest, store, and dispatcher versions must
|
|
24
|
-
* cut (`tools/release.ts`) plus
|
|
25
|
-
* path that safely mixes versions.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* `x-autobuild-version` or `x-autobuild-protocol-version` differs from the
|
|
23
|
+
* server's is rejected (409) — so guest, store, and dispatcher versions must
|
|
24
|
+
* move together through a release cut (`tools/release.ts`) plus
|
|
25
|
+
* store/dispatcher upgrades; there is no code path that safely mixes versions.
|
|
26
|
+
* Provisioning records the installed distribution's identity stamp
|
|
27
|
+
* (`readDistributionIdentityStamp()`) in the guest's `.distribution-version`
|
|
28
|
+
* marker and reinstalls the archive on a later mismatch, so an upgraded
|
|
29
|
+
* dispatcher retrofits its persistent guests (see `vercel-sandbox.ts`).
|
|
30
|
+
*
|
|
31
|
+
* The stamp carries the sha256 digest of the packed archive alongside the
|
|
32
|
+
* version and protocol, so a deploy that changes guest-visible code or
|
|
33
|
+
* formats without bumping either one still mismatches every marker computed
|
|
34
|
+
* over the previous archive (AUT-600). The archive and its digest are
|
|
35
|
+
* resolved together once per process (`resolveDistributionArchive()`) and
|
|
36
|
+
* the install paths reuse those same bytes, so the marker names exactly the
|
|
37
|
+
* bytes installed and resolution never adds a second pack per tick.
|
|
29
38
|
*/
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
39
|
+
import { createHash } from 'node:crypto'
|
|
40
|
+
import { access, cp, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
41
|
+
import { tmpdir } from 'node:os'
|
|
42
|
+
import { dirname, join, resolve } from 'node:path'
|
|
32
43
|
import { distributionRoot, distributionPath } from '../../distribution'
|
|
44
|
+
import { REMOTE_STORE_PROTOCOL_VERSION } from '../../store/remote/version'
|
|
33
45
|
import { registryBaseUrl, registryVersionUrl } from '../../registry'
|
|
34
46
|
import { parseRepoCoordinates } from '../forge/github'
|
|
35
47
|
import {
|
|
@@ -38,19 +50,28 @@ import {
|
|
|
38
50
|
githubTokenFromEnv,
|
|
39
51
|
type GitHubRequest,
|
|
40
52
|
} from '../forge/github-transport'
|
|
41
|
-
import {
|
|
53
|
+
import { spawnExec } from './git-worktree'
|
|
42
54
|
|
|
43
55
|
/** The canonical repository the release assets are published to. Shared with
|
|
44
56
|
* `tools/release.ts`, which uploads them. */
|
|
45
57
|
export const CANONICAL_REPOSITORY_URL = 'https://github.com/defrex/autobuild'
|
|
46
58
|
|
|
59
|
+
/** The shipped manifest `readDistributionPackage()` reads. A bundled
|
|
60
|
+
* deployment must carry this exact file into its function bundle; the hosted
|
|
61
|
+
* service's post-build trace step appends it to the dispatch route's trace. */
|
|
62
|
+
export { distributionPath, distributionRoot } from '../../distribution'
|
|
63
|
+
|
|
64
|
+
export function distributionManifestPath(): string {
|
|
65
|
+
return distributionPath('package.json')
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
/** The running distribution's package name and version, from the shipped
|
|
48
69
|
* package.json. Deployed distributions must ship `package.json` — a
|
|
49
70
|
* deployment requirement the hosted entry point owns. Absence is a hard,
|
|
50
71
|
* actionable error. */
|
|
51
72
|
export async function readDistributionPackage(): Promise<{ name: string; version: string }> {
|
|
52
73
|
try {
|
|
53
|
-
const manifest = JSON.parse(await Bun.file(
|
|
74
|
+
const manifest = JSON.parse(await Bun.file(distributionManifestPath()).text()) as {
|
|
54
75
|
name?: unknown
|
|
55
76
|
version?: unknown
|
|
56
77
|
}
|
|
@@ -85,6 +106,59 @@ export async function readDistributionIdentity(): Promise<string> {
|
|
|
85
106
|
return (await readDistributionPackage()).version
|
|
86
107
|
}
|
|
87
108
|
|
|
109
|
+
/** The distribution identity stamp the guest marker records: everything the
|
|
110
|
+
* hosted store's skew check compares — the package version
|
|
111
|
+
* (`x-autobuild-version`) *and* the remote-store protocol version
|
|
112
|
+
* (`x-autobuild-protocol-version`; either mismatch is a 409 at handshake) —
|
|
113
|
+
* plus the sha256 digest of the exact packed archive the dispatcher would
|
|
114
|
+
* install, combined into one opaque string. A protocol-only bump, a
|
|
115
|
+
* version-only release, or a code-only deploy that changes the archive bytes
|
|
116
|
+
* without touching either (AUT-600) each count as a mismatch against a guest
|
|
117
|
+
* whose marker carries the older stamp, and any future skew dimension the
|
|
118
|
+
* store's handshake gains must be folded into this stamp the same way or the
|
|
119
|
+
* marker comparison misses it. */
|
|
120
|
+
export async function distributionStampFor(archive: Uint8Array): Promise<string> {
|
|
121
|
+
const digest = createHash('sha256').update(archive).digest('hex')
|
|
122
|
+
return `${await readDistributionIdentity()}+protocol${REMOTE_STORE_PROTOCOL_VERSION}+sha256-${digest}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Per-process memo of the running distribution's archive and its digest.
|
|
126
|
+
* Holds the fulfilled promise so concurrent first callers share one
|
|
127
|
+
* resolution; a rejected resolution clears itself so the next call retries
|
|
128
|
+
* instead of a transient fetch/pack failure poisoning a long-lived process. */
|
|
129
|
+
let distributionArchiveMemo: Promise<{ archive: Uint8Array; digest: string }> | undefined
|
|
130
|
+
|
|
131
|
+
/** Resolve the archive the dispatcher would install together with its sha256
|
|
132
|
+
* digest, at most once per process: the memoized resolution wraps
|
|
133
|
+
* `defaultDistributionArchive()` (see the module docs for the source
|
|
134
|
+
* precedence), so repeated stamp reads and installs cost no second pack. */
|
|
135
|
+
export function resolveDistributionArchive(): Promise<{ archive: Uint8Array; digest: string }> {
|
|
136
|
+
if (distributionArchiveMemo === undefined) {
|
|
137
|
+
const pending = defaultDistributionArchive().then((archive) => ({
|
|
138
|
+
archive,
|
|
139
|
+
digest: createHash('sha256').update(archive).digest('hex'),
|
|
140
|
+
}))
|
|
141
|
+
distributionArchiveMemo = pending
|
|
142
|
+
pending.catch(() => {
|
|
143
|
+
if (distributionArchiveMemo === pending) distributionArchiveMemo = undefined
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
return distributionArchiveMemo
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Test-only: drop the per-process archive memo so each test resolves its own
|
|
150
|
+
* source. The memo otherwise outlives a single test. */
|
|
151
|
+
export function resetDistributionArchiveCacheForTest(): void {
|
|
152
|
+
distributionArchiveMemo = undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The identity stamp of the running distribution: the version + protocol
|
|
156
|
+
* skew stamp extended with the digest of the memoized running archive. */
|
|
157
|
+
export async function readDistributionIdentityStamp(): Promise<string> {
|
|
158
|
+
const { archive } = await resolveDistributionArchive()
|
|
159
|
+
return distributionStampFor(archive)
|
|
160
|
+
}
|
|
161
|
+
|
|
88
162
|
/** The release asset a version's distribution ships under. */
|
|
89
163
|
export function distributionAssetName(version: string): string {
|
|
90
164
|
return `autobuild-${version}.tgz`
|
|
@@ -211,7 +285,7 @@ export async function fetchDistributionReleaseAsset(
|
|
|
211
285
|
}
|
|
212
286
|
|
|
213
287
|
/** Directory, relative to a distribution root, where a deployment ships the
|
|
214
|
-
* archive it packed at build time (`ab-hosted-
|
|
288
|
+
* archive it packed at build time (`ab-hosted-dispatcher pack-distribution`). A
|
|
215
289
|
* bundled deployment — the hosted service on Vercel — has neither `bun` nor a
|
|
216
290
|
* source tree at runtime, so the archive must be produced while both exist
|
|
217
291
|
* and carried into the function bundle. */
|
|
@@ -221,6 +295,91 @@ export const PREBUILT_DISTRIBUTION_DIR = '.autobuild-dist'
|
|
|
221
295
|
* that place it somewhere other than `PREBUILT_DISTRIBUTION_DIR`. */
|
|
222
296
|
export const DISTRIBUTION_ARCHIVE_ENV = 'AB_DISTRIBUTION_ARCHIVE'
|
|
223
297
|
|
|
298
|
+
async function execOrThrow(cmd: string[], cwd: string): Promise<string> {
|
|
299
|
+
const result = await spawnExec(cmd, { cwd })
|
|
300
|
+
if (result.exitCode !== 0) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`${cmd.join(' ')} exited ${result.exitCode}: ${result.stderr.trim() || result.stdout.trim()}`,
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
return result.stdout.trim()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Manifest fields that name repo-local packaging state and must never reach
|
|
309
|
+
* the packed distribution manifest. `patchedDependencies` entries are resolved
|
|
310
|
+
* by bun against the consuming project's root, and bun 1.4.0 panics
|
|
311
|
+
* (`Option::unwrap`, exit 134) when a consumed manifest declares a patch for a
|
|
312
|
+
* package the consumer tree contains — so a packed manifest carrying the
|
|
313
|
+
* repo's better-auth patch declaration breaks any consumer that installs
|
|
314
|
+
* autobuild next to better-auth. The packed artifact ships no patched
|
|
315
|
+
* package, so the declaration is stripped from the packed manifest while the
|
|
316
|
+
* repo manifest keeps it for workspace installs. The strip is an enforced
|
|
317
|
+
* invariant, not a local detail: the `patchedDependencies` rule in
|
|
318
|
+
* `tools/workspace-manifest-check.ts` rejects a root manifest whose packed
|
|
319
|
+
* dependency set contains a patched package, and the negative control in
|
|
320
|
+
* `packages/core/src/plugin-sdk/package.test.ts` (the packed-distribution
|
|
321
|
+
* better-auth canary) pins the consumed-manifest failure shape this strip
|
|
322
|
+
* guards, so removing the field from this list fails the suite loudly.
|
|
323
|
+
*
|
|
324
|
+
* `devDependencies` is stripped for two reasons. First, the workspace link
|
|
325
|
+
* specifiers it may carry (for example the root's
|
|
326
|
+
* `@defrex/autobuild-postgres-store: workspace:*`) cannot be resolved in the
|
|
327
|
+
* staging tree — it stages only the manifest's `files` set, with no
|
|
328
|
+
* `bun.lock`, no `node_modules`, and no workspace packages — so `bun pm pack`
|
|
329
|
+
* fails there with "Failed to resolve workspace version". Second, the guest
|
|
330
|
+
* installs the distribution with `bun install --production --ignore-scripts`,
|
|
331
|
+
* which ignores devDependencies entirely; the packed artifact ships no
|
|
332
|
+
* dev-only tooling, so the declaration is dead weight in the packed manifest
|
|
333
|
+
* while the repo manifest keeps it for workspace installs. */
|
|
334
|
+
const packedManifestOmittedFields = ['patchedDependencies', 'devDependencies'] as const
|
|
335
|
+
|
|
336
|
+
export async function packageAutobuildDistribution(): Promise<Uint8Array> {
|
|
337
|
+
const staging = await mkdtemp(join(tmpdir(), 'autobuild-pack-staging-'))
|
|
338
|
+
const destination = await mkdtemp(join(tmpdir(), 'autobuild-pack-'))
|
|
339
|
+
try {
|
|
340
|
+
// Pack from a staging copy of the repo's `files` set rather than the repo
|
|
341
|
+
// root: the packed manifest must differ from the repo manifest (above), and
|
|
342
|
+
// rewriting the repo manifest in place would leave a window where tracked
|
|
343
|
+
// files are dirty.
|
|
344
|
+
const manifestPath = join(distributionRoot(), 'package.json')
|
|
345
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record<string, unknown>
|
|
346
|
+
const files = manifest.files
|
|
347
|
+
if (!Array.isArray(files) || files.some((entry) => typeof entry !== 'string')) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
'Autobuild packaging requires the root package.json to list its files as an array of paths',
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
for (const entry of files as string[]) {
|
|
353
|
+
// npm `files` entries may be negated globs (for example
|
|
354
|
+
// `!packages/core/src/**/*.test.ts`, which keeps test files out of the
|
|
355
|
+
// published package). Only positive entries name a path to stage: the
|
|
356
|
+
// negations stay in the staged manifest and `bun pm pack` applies them,
|
|
357
|
+
// so the packed set still excludes those paths.
|
|
358
|
+
if (entry.startsWith('!')) continue
|
|
359
|
+
const target = join(staging, entry)
|
|
360
|
+
await mkdir(dirname(target), { recursive: true })
|
|
361
|
+
await cp(join(distributionRoot(), entry), target, {
|
|
362
|
+
recursive: true,
|
|
363
|
+
errorOnExist: true,
|
|
364
|
+
force: false,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
for (const field of packedManifestOmittedFields) delete manifest[field]
|
|
368
|
+
await writeFile(join(staging, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`)
|
|
369
|
+
await execOrThrow(
|
|
370
|
+
['bun', 'pm', 'pack', '--ignore-scripts', '--destination', destination],
|
|
371
|
+
staging,
|
|
372
|
+
)
|
|
373
|
+
const archives = (await readdir(destination)).filter((name) => name.endsWith('.tgz'))
|
|
374
|
+
if (archives.length !== 1)
|
|
375
|
+
throw new Error('Autobuild packaging did not produce exactly one archive')
|
|
376
|
+
return new Uint8Array(await readFile(join(destination, archives[0]!)))
|
|
377
|
+
} finally {
|
|
378
|
+
await rm(staging, { recursive: true, force: true })
|
|
379
|
+
await rm(destination, { recursive: true, force: true })
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
224
383
|
/** Pack the running distribution into `<root>/.autobuild-dist/autobuild-<version>.tgz`
|
|
225
384
|
* (the same bytes `bun pm pack` produces) and return the archive path. Meant
|
|
226
385
|
* for a deployment's build step, where `bun` and the source tree exist. */
|