boxdown 1.2.0 → 1.3.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 +10 -0
- package/assets/devcontainer/README.md +29 -6
- package/assets/devcontainer/devcontainer.json +4 -9
- package/assets/devcontainer/hooks/initialize.sh +49 -27
- package/assets/devcontainer/hooks/post-create.sh +23 -1
- package/assets/devcontainer/hooks/post-start.sh +0 -10
- package/assets/devcontainer/utils/git-signing-bootstrap.sh +58 -5
- package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
- package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
- package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/{main-Df4E8ARj.cjs → main-CT2n9qcb.cjs} +907 -266
- package/dist/{main-XMBsKjIK.mjs → main-O__JaiXe.mjs} +891 -268
- package/dist/main-O__JaiXe.mjs.map +1 -0
- package/dist/main.cjs +4 -1
- package/dist/main.d.cts +98 -26
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +98 -26
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/features/commit-signing.md +71 -0
- package/docs/features/generated-config-and-state.md +19 -4
- package/docs/features/github-auth-refresh.md +3 -2
- package/docs/features/lifecycle.md +19 -0
- package/docs/features/setup.md +5 -0
- package/docs/features/start-and-shell.md +10 -0
- package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
- package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
- package/docs/superpowers/plans/2026-07-18-architecture-aware-1password-installer.md +163 -0
- package/docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md +341 -0
- package/docs/superpowers/plans/2026-07-19-container-runtime-readiness.md +1536 -0
- package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +20 -0
- package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
- package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
- package/docs/superpowers/specs/2026-07-18-architecture-aware-1password-installer-design.md +63 -0
- package/docs/superpowers/specs/2026-07-18-devcontainer-node-image-digest-design.md +108 -0
- package/docs/superpowers/specs/2026-07-19-container-runtime-readiness-design.md +353 -0
- package/package.json +1 -1
- package/src/config.ts +14 -2
- package/src/constants.ts +7 -0
- package/src/container-runtime.ts +295 -0
- package/src/devcontainer.ts +57 -30
- package/src/doctor.ts +169 -46
- package/src/git-signing.ts +205 -25
- package/src/logging.ts +11 -1
- package/src/main.ts +97 -12
- package/src/paths.ts +21 -1
- package/src/process.ts +50 -10
- package/src/progress.ts +63 -8
- package/src/purge.ts +8 -0
- package/src/shell.ts +6 -0
- package/dist/main-XMBsKjIK.mjs.map +0 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks'
|
|
2
|
+
|
|
3
|
+
import { runBuffered, type CommandResult } from './process.ts'
|
|
4
|
+
|
|
5
|
+
export type ContainerRuntimeReason =
|
|
6
|
+
| 'docker-cli-unavailable'
|
|
7
|
+
| 'docker-daemon-unavailable'
|
|
8
|
+
| 'buildx-builder-unavailable'
|
|
9
|
+
|
|
10
|
+
export type ContainerRuntimeMode = 'buildx' | 'fallback'
|
|
11
|
+
export type ContainerRuntimeCommandResult = CommandResult
|
|
12
|
+
export type ContainerRuntimeCommandRunner = (
|
|
13
|
+
command: string,
|
|
14
|
+
args: string[],
|
|
15
|
+
timeoutMs?: number
|
|
16
|
+
) => Promise<ContainerRuntimeCommandResult>
|
|
17
|
+
|
|
18
|
+
export interface ContainerRuntimeFailure {
|
|
19
|
+
reason: ContainerRuntimeReason
|
|
20
|
+
command: string[]
|
|
21
|
+
detail: string
|
|
22
|
+
timedOut?: true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ContainerRuntimeProbe =
|
|
26
|
+
| { state: 'ready', mode: ContainerRuntimeMode, warnings: string[] }
|
|
27
|
+
| { state: 'waiting', failure: ContainerRuntimeFailure }
|
|
28
|
+
| { state: 'failed', failure: ContainerRuntimeFailure }
|
|
29
|
+
|
|
30
|
+
const BUILDX_FALLBACK_WARNING = 'Docker Buildx is unavailable; the Dev Containers CLI will use its classic-build fallback.'
|
|
31
|
+
|
|
32
|
+
async function runContainerRuntimeCommand (
|
|
33
|
+
command: string,
|
|
34
|
+
args: string[],
|
|
35
|
+
timeoutMs?: number
|
|
36
|
+
): Promise<ContainerRuntimeCommandResult> {
|
|
37
|
+
return runBuffered(command, args, {
|
|
38
|
+
mirrorStdout: false,
|
|
39
|
+
mirrorStderr: false,
|
|
40
|
+
timeoutMs
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function compactCommandOutput (result: ContainerRuntimeCommandResult): string {
|
|
45
|
+
const output = result.stderr.trim().length > 0 ? result.stderr : result.stdout
|
|
46
|
+
const compact = output.trim().replace(/\s+/gu, ' ').slice(0, 500)
|
|
47
|
+
return compact.length > 0 ? compact : `Command exited with code ${result.code}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function failure (
|
|
51
|
+
reason: ContainerRuntimeReason,
|
|
52
|
+
command: string[],
|
|
53
|
+
result: ContainerRuntimeCommandResult
|
|
54
|
+
): ContainerRuntimeFailure {
|
|
55
|
+
return {
|
|
56
|
+
reason,
|
|
57
|
+
command,
|
|
58
|
+
detail: compactCommandOutput(result),
|
|
59
|
+
...(result.timedOut === true ? { timedOut: true as const } : {})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function probeContainerRuntime (
|
|
64
|
+
runCommand: ContainerRuntimeCommandRunner = runContainerRuntimeCommand
|
|
65
|
+
): Promise<ContainerRuntimeProbe> {
|
|
66
|
+
const dockerVersionCommand = ['docker', '--version']
|
|
67
|
+
const dockerVersion = await runCommand(dockerVersionCommand[0] as string, dockerVersionCommand.slice(1))
|
|
68
|
+
if (dockerVersion.code !== 0) {
|
|
69
|
+
return {
|
|
70
|
+
state: 'failed',
|
|
71
|
+
failure: failure('docker-cli-unavailable', dockerVersionCommand, dockerVersion)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const dockerInfoCommand = ['docker', 'info']
|
|
76
|
+
const dockerInfo = await runCommand(dockerInfoCommand[0] as string, dockerInfoCommand.slice(1))
|
|
77
|
+
if (dockerInfo.code !== 0) {
|
|
78
|
+
return {
|
|
79
|
+
state: 'waiting',
|
|
80
|
+
failure: failure('docker-daemon-unavailable', dockerInfoCommand, dockerInfo)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const buildxVersionCommand = ['docker', 'buildx', 'version']
|
|
85
|
+
const buildxVersion = await runCommand(buildxVersionCommand[0] as string, buildxVersionCommand.slice(1))
|
|
86
|
+
if (buildxVersion.timedOut === true) {
|
|
87
|
+
return {
|
|
88
|
+
state: 'waiting',
|
|
89
|
+
failure: failure('buildx-builder-unavailable', buildxVersionCommand, buildxVersion)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (buildxVersion.code !== 0) {
|
|
93
|
+
return {
|
|
94
|
+
state: 'ready',
|
|
95
|
+
mode: 'fallback',
|
|
96
|
+
warnings: [BUILDX_FALLBACK_WARNING]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const buildxInspectCommand = ['docker', 'buildx', 'inspect', '--bootstrap']
|
|
101
|
+
const buildxInspect = await runCommand(buildxInspectCommand[0] as string, buildxInspectCommand.slice(1))
|
|
102
|
+
if (buildxInspect.code !== 0) {
|
|
103
|
+
return {
|
|
104
|
+
state: 'waiting',
|
|
105
|
+
failure: failure('buildx-builder-unavailable', buildxInspectCommand, buildxInspect)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { state: 'ready', mode: 'buildx', warnings: [] }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type ContainerRuntimeWaitResult =
|
|
113
|
+
| { state: 'ready', mode: ContainerRuntimeMode, warnings: string[] }
|
|
114
|
+
| {
|
|
115
|
+
state: 'failed'
|
|
116
|
+
failure: ContainerRuntimeFailure
|
|
117
|
+
timedOut: boolean
|
|
118
|
+
timeoutMs: number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface WaitForContainerRuntimeOptions {
|
|
122
|
+
runCommand?: ContainerRuntimeCommandRunner
|
|
123
|
+
onTransition?: (probe: ContainerRuntimeProbe) => void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @internal */
|
|
127
|
+
export interface InternalContainerRuntimeWaitOptions extends WaitForContainerRuntimeOptions {
|
|
128
|
+
timeoutMs?: number
|
|
129
|
+
pollIntervalMs?: number
|
|
130
|
+
now?: () => number
|
|
131
|
+
sleep?: (milliseconds: number) => Promise<void>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function defaultSleep (milliseconds: number): Promise<void> {
|
|
135
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function transitionKey (probe: ContainerRuntimeProbe): string {
|
|
139
|
+
return probe.state === 'ready' ? 'ready' : `${probe.state}:${probe.failure.reason}`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class ContainerRuntimeDeadlineError extends Error {
|
|
143
|
+
readonly command: string[]
|
|
144
|
+
|
|
145
|
+
constructor (command: string[]) {
|
|
146
|
+
super(`Container runtime deadline expired before ${command.join(' ')}`)
|
|
147
|
+
this.command = command
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function deadlineFailure (command: string[]): ContainerRuntimeFailure {
|
|
152
|
+
const reason: ContainerRuntimeReason = command[1] === 'info'
|
|
153
|
+
? 'docker-daemon-unavailable'
|
|
154
|
+
: command[1] === 'buildx'
|
|
155
|
+
? 'buildx-builder-unavailable'
|
|
156
|
+
: 'docker-cli-unavailable'
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
reason,
|
|
160
|
+
command,
|
|
161
|
+
detail: `Readiness deadline expired before ${command.join(' ')} could start.`,
|
|
162
|
+
timedOut: true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @internal */
|
|
167
|
+
export async function waitForContainerRuntimeInternal (
|
|
168
|
+
options: InternalContainerRuntimeWaitOptions = {}
|
|
169
|
+
): Promise<ContainerRuntimeWaitResult> {
|
|
170
|
+
const timeoutMs = options.timeoutMs ?? 60_000
|
|
171
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1_000
|
|
172
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 60_000) {
|
|
173
|
+
throw new Error('timeoutMs must be a finite number between 0 and 60000 milliseconds')
|
|
174
|
+
}
|
|
175
|
+
if (pollIntervalMs !== 1_000) {
|
|
176
|
+
throw new Error('pollIntervalMs must be exactly 1000 milliseconds')
|
|
177
|
+
}
|
|
178
|
+
const now = options.now ?? Date.now
|
|
179
|
+
const sleep = options.sleep ?? defaultSleep
|
|
180
|
+
const deadline = now() + timeoutMs
|
|
181
|
+
let lastTransition: string | undefined
|
|
182
|
+
let lastWaitingFailure: ContainerRuntimeFailure | undefined
|
|
183
|
+
|
|
184
|
+
while (true) {
|
|
185
|
+
if (lastWaitingFailure !== undefined && deadline - now() <= 0) {
|
|
186
|
+
return { state: 'failed', failure: lastWaitingFailure, timedOut: true, timeoutMs }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const runCommand = options.runCommand ?? runContainerRuntimeCommand
|
|
190
|
+
let lastStartedCommand: string[] | undefined
|
|
191
|
+
let probe: ContainerRuntimeProbe
|
|
192
|
+
try {
|
|
193
|
+
probe = await probeContainerRuntime(async (command, args) => {
|
|
194
|
+
const commandArgs = [command, ...args]
|
|
195
|
+
const remainingMs = Math.floor(deadline - now())
|
|
196
|
+
if (remainingMs <= 0) throw new ContainerRuntimeDeadlineError(commandArgs)
|
|
197
|
+
lastStartedCommand = commandArgs
|
|
198
|
+
return runCommand(command, args, remainingMs)
|
|
199
|
+
})
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (!(error instanceof ContainerRuntimeDeadlineError)) throw error
|
|
202
|
+
return {
|
|
203
|
+
state: 'failed',
|
|
204
|
+
failure: lastWaitingFailure ?? deadlineFailure(error.command),
|
|
205
|
+
timedOut: true,
|
|
206
|
+
timeoutMs
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (deadline - now() <= 0) {
|
|
211
|
+
const currentFailure = probe.state === 'ready'
|
|
212
|
+
? deadlineFailure(lastStartedCommand ?? ['docker', '--version'])
|
|
213
|
+
: probe.failure
|
|
214
|
+
return {
|
|
215
|
+
state: 'failed',
|
|
216
|
+
failure: lastWaitingFailure ?? currentFailure,
|
|
217
|
+
timedOut: true,
|
|
218
|
+
timeoutMs
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (probe.state === 'ready') return probe
|
|
223
|
+
|
|
224
|
+
const currentTransition = transitionKey(probe)
|
|
225
|
+
if (currentTransition !== lastTransition) {
|
|
226
|
+
options.onTransition?.(probe)
|
|
227
|
+
lastTransition = currentTransition
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (probe.state === 'failed') {
|
|
231
|
+
return {
|
|
232
|
+
state: 'failed',
|
|
233
|
+
failure: probe.failure,
|
|
234
|
+
timedOut: probe.failure.timedOut === true,
|
|
235
|
+
timeoutMs
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
lastWaitingFailure = probe.failure
|
|
240
|
+
|
|
241
|
+
const remainingMs = deadline - now()
|
|
242
|
+
if (remainingMs <= 0) {
|
|
243
|
+
return { state: 'failed', failure: probe.failure, timedOut: true, timeoutMs }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await sleep(Math.min(pollIntervalMs, remainingMs))
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function waitForContainerRuntime (
|
|
251
|
+
options: WaitForContainerRuntimeOptions = {}
|
|
252
|
+
): Promise<ContainerRuntimeWaitResult> {
|
|
253
|
+
return waitForContainerRuntimeInternal({
|
|
254
|
+
runCommand: options.runCommand,
|
|
255
|
+
onTransition: options.onTransition,
|
|
256
|
+
timeoutMs: 60_000,
|
|
257
|
+
pollIntervalMs: 1_000,
|
|
258
|
+
now: () => performance.now(),
|
|
259
|
+
sleep: defaultSleep
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function commandText (command: readonly string[]): string {
|
|
264
|
+
return command.join(' ')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function formatContainerRuntimeFailure (
|
|
268
|
+
result: Extract<ContainerRuntimeWaitResult, { state: 'failed' }>,
|
|
269
|
+
options: { logPath?: string } = {}
|
|
270
|
+
): string {
|
|
271
|
+
const seconds = result.timeoutMs / 1_000
|
|
272
|
+
const descriptions: Record<ContainerRuntimeReason, string> = {
|
|
273
|
+
'docker-cli-unavailable': 'Docker CLI is required but was not available.',
|
|
274
|
+
'docker-daemon-unavailable': result.timedOut
|
|
275
|
+
? `Docker daemon did not become ready within ${seconds} seconds.`
|
|
276
|
+
: 'Docker daemon is required but was not reachable.',
|
|
277
|
+
'buildx-builder-unavailable': result.timedOut
|
|
278
|
+
? `Docker Buildx builder did not become ready within ${seconds} seconds.`
|
|
279
|
+
: 'Docker Buildx builder was not operational.'
|
|
280
|
+
}
|
|
281
|
+
const manualCheck = result.failure.reason === 'buildx-builder-unavailable'
|
|
282
|
+
? 'docker buildx inspect'
|
|
283
|
+
: result.failure.reason === 'docker-daemon-unavailable'
|
|
284
|
+
? 'docker info'
|
|
285
|
+
: 'docker --version'
|
|
286
|
+
const lines = [
|
|
287
|
+
descriptions[result.failure.reason],
|
|
288
|
+
`Last check: ${commandText(result.failure.command)}`,
|
|
289
|
+
`Detail: ${result.failure.detail}`,
|
|
290
|
+
`Check ${result.failure.reason === 'buildx-builder-unavailable' ? 'Buildx' : 'Docker'} with: ${manualCheck}`
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
if (options.logPath !== undefined) lines.push(`Command log: ${options.logPath}`)
|
|
294
|
+
return lines.join('\n')
|
|
295
|
+
}
|
package/src/devcontainer.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { buildGeneratedDevcontainerConfig, publishContainerPortFromConfig, write
|
|
|
5
5
|
import { codingAgentBinary, type CodingAgentCli } from './coding-agents.ts'
|
|
6
6
|
import { resolveDevcontainerCli } from './devcontainer-cli.ts'
|
|
7
7
|
import { configureWorkspaceGithubGitAuth } from './github-git-auth.ts'
|
|
8
|
-
import { resolveGitSigningPlan } from './git-signing.ts'
|
|
8
|
+
import { reportGitSigningPlan, resolveGitSigningPlan } from './git-signing.ts'
|
|
9
9
|
import type { WorkspaceCommandLogger } from './logging.ts'
|
|
10
10
|
import { recordWorkspaceDockerImage } from './metadata.ts'
|
|
11
11
|
import type { WorkspaceContext } from './paths.ts'
|
|
@@ -21,6 +21,7 @@ export interface StartOptions {
|
|
|
21
21
|
progress?: ProgressReporter
|
|
22
22
|
logger?: WorkspaceCommandLogger
|
|
23
23
|
reuseRunning?: boolean
|
|
24
|
+
runDevcontainerUp?: typeof runProgressCommand
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export interface ContainerCommandOptions {
|
|
@@ -43,10 +44,6 @@ export interface DockerImageInfo {
|
|
|
43
44
|
name?: string
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
interface DockerInspectContainer {
|
|
47
|
-
Image?: unknown
|
|
48
|
-
Config?: unknown
|
|
49
|
-
}
|
|
50
47
|
|
|
51
48
|
function devcontainerWorkspaceArgs (context: WorkspaceContext): string[] {
|
|
52
49
|
return [
|
|
@@ -142,36 +139,28 @@ export async function findRunningContainerId (context: WorkspaceContext, options
|
|
|
142
139
|
return result.stdout.split(/\r?\n/).find((line) => line.length > 0)
|
|
143
140
|
}
|
|
144
141
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function parseDockerInspectImage (output: string, containerId: string): DockerImageInfo | undefined {
|
|
150
|
-
const trimmed = output.trim()
|
|
142
|
+
export function parseDockerInspectImage (output: string, containerId: string): DockerImageInfo | undefined {
|
|
143
|
+
const [rawId, rawName] = output.trim().split('|')
|
|
151
144
|
|
|
152
|
-
if (
|
|
145
|
+
if (rawId === undefined || rawId.length === 0) {
|
|
153
146
|
return undefined
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
let
|
|
149
|
+
let id: unknown
|
|
150
|
+
let name: unknown
|
|
157
151
|
|
|
158
152
|
try {
|
|
159
|
-
|
|
153
|
+
id = JSON.parse(rawId)
|
|
154
|
+
name = rawName === undefined || rawName.length === 0 ? undefined : JSON.parse(rawName)
|
|
160
155
|
} catch (error) {
|
|
161
156
|
throw new Error(`Could not parse docker inspect output for ${containerId}`, { cause: error })
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
if (typeof
|
|
165
|
-
return undefined
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const configImage = isRecord(parsed.Config) && typeof parsed.Config.Image === 'string' && parsed.Config.Image.length > 0
|
|
169
|
-
? parsed.Config.Image
|
|
170
|
-
: undefined
|
|
159
|
+
if (typeof id !== 'string' || id.length === 0) return undefined
|
|
171
160
|
|
|
172
161
|
return {
|
|
173
|
-
id
|
|
174
|
-
...(
|
|
162
|
+
id,
|
|
163
|
+
...(typeof name === 'string' && name.length > 0 ? { name } : {})
|
|
175
164
|
}
|
|
176
165
|
}
|
|
177
166
|
|
|
@@ -179,7 +168,7 @@ export async function inspectContainerImage (containerId: string, options: { log
|
|
|
179
168
|
const result = await runBuffered('docker', [
|
|
180
169
|
'inspect',
|
|
181
170
|
'--format',
|
|
182
|
-
'{{json .}}',
|
|
171
|
+
'{{json .Image}}|{{json .Config.Image}}',
|
|
183
172
|
containerId
|
|
184
173
|
], {
|
|
185
174
|
logger: options.logger,
|
|
@@ -320,7 +309,19 @@ export async function startDevcontainer (context: WorkspaceContext, options: Sta
|
|
|
320
309
|
progress.detail(context.generatedConfigPath)
|
|
321
310
|
}
|
|
322
311
|
try {
|
|
323
|
-
|
|
312
|
+
const signingPlan = await resolveGitSigningPlan(context)
|
|
313
|
+
reportGitSigningPlan(signingPlan, {
|
|
314
|
+
logger: options.logger,
|
|
315
|
+
quiet: proxyMode,
|
|
316
|
+
...(progress === undefined
|
|
317
|
+
? {}
|
|
318
|
+
: {
|
|
319
|
+
writeWarning: (message: string) => {
|
|
320
|
+
progress.warn(message.trimEnd())
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
writeGeneratedDevcontainerConfig(context, signingPlan)
|
|
324
325
|
if (hasConfigStep) {
|
|
325
326
|
progress?.completeStep('devcontainer-config')
|
|
326
327
|
}
|
|
@@ -374,7 +375,7 @@ export async function startDevcontainer (context: WorkspaceContext, options: Sta
|
|
|
374
375
|
mirrorStderr: 'stderr',
|
|
375
376
|
logger: options.logger
|
|
376
377
|
})
|
|
377
|
-
: await runProgressCommand('devcontainer up', cli.command, [...cli.argsPrefix, ...args], {
|
|
378
|
+
: await (options.runDevcontainerUp ?? runProgressCommand)('devcontainer up', cli.command, [...cli.argsPrefix, ...args], {
|
|
378
379
|
progress,
|
|
379
380
|
spinnerLabel: 'Starting devcontainer',
|
|
380
381
|
stepId: 'devcontainer-start',
|
|
@@ -388,7 +389,12 @@ export async function startDevcontainer (context: WorkspaceContext, options: Sta
|
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
if (progress !== undefined) {
|
|
391
|
-
assertProgressCommandSucceeded(
|
|
392
|
+
assertProgressCommandSucceeded(
|
|
393
|
+
'devcontainer up',
|
|
394
|
+
result,
|
|
395
|
+
`devcontainer up failed for ${context.workspaceFolder}`,
|
|
396
|
+
{ logPath: options.logger?.logPath }
|
|
397
|
+
)
|
|
392
398
|
}
|
|
393
399
|
|
|
394
400
|
const containerId = parseContainerIdFromUpOutput(`${result.stdout}\n${result.stderr}`) ?? await findRunningContainerId(context, { logger: options.logger })
|
|
@@ -503,7 +509,12 @@ export async function ensureContainerSshRuntime (context: WorkspaceContext, opti
|
|
|
503
509
|
}
|
|
504
510
|
|
|
505
511
|
if (options.progress !== undefined) {
|
|
506
|
-
assertProgressCommandSucceeded(
|
|
512
|
+
assertProgressCommandSucceeded(
|
|
513
|
+
'prepare SSH runtime',
|
|
514
|
+
result,
|
|
515
|
+
'Failed to prepare devcontainer SSH runtime',
|
|
516
|
+
{ logPath: options.logger?.logPath }
|
|
517
|
+
)
|
|
507
518
|
}
|
|
508
519
|
}
|
|
509
520
|
|
|
@@ -590,7 +601,12 @@ export async function ensureContainerCodingAgentCli (
|
|
|
590
601
|
}
|
|
591
602
|
|
|
592
603
|
if (options.progress !== undefined) {
|
|
593
|
-
assertProgressCommandSucceeded(
|
|
604
|
+
assertProgressCommandSucceeded(
|
|
605
|
+
`prepare ${codingAgentBinary(agent)}`,
|
|
606
|
+
result,
|
|
607
|
+
`Could not install or refresh ${codingAgentBinary(agent)} inside the devcontainer`,
|
|
608
|
+
{ logPath: options.logger?.logPath }
|
|
609
|
+
)
|
|
594
610
|
}
|
|
595
611
|
}
|
|
596
612
|
|
|
@@ -669,7 +685,18 @@ export async function refreshContainerGhAuth (context: WorkspaceContext, options
|
|
|
669
685
|
quiet: true,
|
|
670
686
|
progress
|
|
671
687
|
})
|
|
672
|
-
|
|
688
|
+
const signingPlan = await resolveGitSigningPlan(context)
|
|
689
|
+
reportGitSigningPlan(signingPlan, {
|
|
690
|
+
logger: options.logger,
|
|
691
|
+
...(progress === undefined
|
|
692
|
+
? {}
|
|
693
|
+
: {
|
|
694
|
+
writeWarning: (message: string) => {
|
|
695
|
+
progress.warn(message.trimEnd())
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
writeGeneratedDevcontainerConfig(context, signingPlan)
|
|
673
700
|
if (hasConfigStep) {
|
|
674
701
|
progress?.completeStep('gh-auth-config')
|
|
675
702
|
}
|