boxdown 1.2.1 → 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 +12 -1
- package/assets/devcontainer/devcontainer.json +1 -1
- package/assets/devcontainer/hooks/post-create.sh +12 -1
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/{main-BDgyf2t5.cjs → main-CT2n9qcb.cjs} +448 -67
- package/dist/{main-J4_2Up3o.mjs → main-O__JaiXe.mjs} +431 -68
- package/dist/main-O__JaiXe.mjs.map +1 -0
- package/dist/main.cjs +4 -1
- package/dist/main.d.cts +92 -26
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +92 -26
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/features/lifecycle.md +13 -0
- package/docs/features/setup.md +5 -0
- package/docs/features/start-and-shell.md +5 -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-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/container-runtime.ts +295 -0
- package/src/devcontainer.ts +20 -4
- package/src/doctor.ts +48 -22
- package/src/main.ts +95 -11
- package/src/process.ts +50 -10
- package/src/progress.ts +63 -8
- package/dist/main-J4_2Up3o.mjs.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Architecture-Aware 1Password Installer Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Install the pinned prebuilt 1Password CLI archive for the current Linux architecture and exercise amd64, arm64, and unsupported paths in CI.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep architecture selection inside the existing `install_1password_cli` shell function. Behavioral Node tests source the hook and replace only external commands, so CI runs the real selection logic without network access, privileged writes, Buildx, QEMU, or source builds.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Bash, Node.js test runner, TypeScript, `node:assert`, pnpm
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- Keep 1Password CLI pinned to version `2.32.1`.
|
|
14
|
+
- Fetch the prebuilt Linux zip; do not build 1Password from source.
|
|
15
|
+
- Support `x86_64|amd64` with the `amd64` archive and `aarch64|arm64` with the `arm64` archive.
|
|
16
|
+
- Warn and return successfully without downloading on unsupported architectures.
|
|
17
|
+
- Do not change the CI runner matrix, Snyk installer, devcontainer image, or unrelated lifecycle behavior.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Task 1: Select and Test the 1Password Archive Architecture
|
|
22
|
+
|
|
23
|
+
**Files:**
|
|
24
|
+
|
|
25
|
+
- Modify: `assets/devcontainer/hooks/post-create.sh:112-117`
|
|
26
|
+
- Test: `__tests__/app.test.ts` near the existing post-create hook tests
|
|
27
|
+
|
|
28
|
+
**Interfaces:**
|
|
29
|
+
|
|
30
|
+
- Consumes: `install_1password_cli()` and the machine name returned by `uname -m`.
|
|
31
|
+
- Produces: A download request for `op_linux_<amd64|arm64>_v2.32.1.zip`, or a successful warning-only skip for an unsupported machine name.
|
|
32
|
+
|
|
33
|
+
- [ ] **Step 1: Add a shell smoke-test helper and failing architecture tests**
|
|
34
|
+
|
|
35
|
+
Add this helper near the existing `tempDir` test helper:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
function runOnePasswordInstallForArchitecture (arch: string): { status: number | null, stderr: string, downloads: string } {
|
|
39
|
+
const postCreatePath = join(assetsDevcontainerDir, 'hooks', 'post-create.sh')
|
|
40
|
+
const curlLogPath = join(tempDir('onepassword-curl-log'), 'calls.log')
|
|
41
|
+
const script = [
|
|
42
|
+
'source "$1"',
|
|
43
|
+
'uname() { printf "%s\\n" "$BOXDOWN_TEST_ARCH"; }',
|
|
44
|
+
'curl() { printf "%s\\n" "$*" >> "$BOXDOWN_TEST_CURL_LOG"; }',
|
|
45
|
+
'python3() { :; }',
|
|
46
|
+
'sudo() { :; }',
|
|
47
|
+
'chmod() { :; }',
|
|
48
|
+
'rm() { :; }',
|
|
49
|
+
'install_1password_cli'
|
|
50
|
+
].join('\n')
|
|
51
|
+
const result = spawnSync('bash', ['-c', script, 'bash', postCreatePath], {
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
env: {
|
|
54
|
+
...process.env,
|
|
55
|
+
BOXDOWN_TEST_ARCH: arch,
|
|
56
|
+
BOXDOWN_TEST_CURL_LOG: curlLogPath
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
status: result.status,
|
|
62
|
+
stderr: result.stderr,
|
|
63
|
+
downloads: existsSync(curlLogPath) ? readFileSync(curlLogPath, 'utf8') : ''
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add these focused tests near `post-create local git config is idempotent with multiple GitHub helpers`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
test('1Password installer selects the amd64 archive on x86_64', () => {
|
|
72
|
+
const result = runOnePasswordInstallForArchitecture('x86_64')
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(result.status, 0)
|
|
75
|
+
assert.match(result.downloads, /op_linux_amd64_v2\.32\.1\.zip/)
|
|
76
|
+
assert.doesNotMatch(result.downloads, /op_linux_arm64/)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('1Password installer selects the arm64 archive on aarch64', () => {
|
|
80
|
+
const result = runOnePasswordInstallForArchitecture('aarch64')
|
|
81
|
+
|
|
82
|
+
assert.strictEqual(result.status, 0)
|
|
83
|
+
assert.match(result.downloads, /op_linux_arm64_v2\.32\.1\.zip/)
|
|
84
|
+
assert.doesNotMatch(result.downloads, /op_linux_amd64/)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('1Password installer skips unsupported architectures', () => {
|
|
88
|
+
const result = runOnePasswordInstallForArchitecture('riscv64')
|
|
89
|
+
|
|
90
|
+
assert.strictEqual(result.status, 0)
|
|
91
|
+
assert.strictEqual(result.downloads, '')
|
|
92
|
+
assert.match(result.stderr, /skipping 1Password CLI \(unsupported arch: riscv64\)/)
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- [ ] **Step 2: Run the focused tests and verify the red phase**
|
|
97
|
+
|
|
98
|
+
Run:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm exec c8 --clean=false node --import tsx --test \
|
|
102
|
+
--test-name-pattern='1Password installer' __tests__/app.test.ts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Expected: FAIL. The amd64 test records the hard-coded arm64 URL, and the unsupported-architecture test records a download instead of a warning-only skip. The existing arm64 behavior may already pass.
|
|
106
|
+
|
|
107
|
+
- [ ] **Step 3: Implement architecture-aware archive selection**
|
|
108
|
+
|
|
109
|
+
Replace `install_1password_cli` with:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
install_1password_cli() {
|
|
113
|
+
local op_version="2.32.1"
|
|
114
|
+
local op_arch
|
|
115
|
+
|
|
116
|
+
case "$(uname -m)" in
|
|
117
|
+
aarch64 | arm64) op_arch="arm64" ;;
|
|
118
|
+
x86_64 | amd64) op_arch="amd64" ;;
|
|
119
|
+
*)
|
|
120
|
+
echo "post-create: skipping 1Password CLI (unsupported arch: $(uname -m))" >&2
|
|
121
|
+
return 0
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
|
|
125
|
+
curl -fsSL "https://cache.agilebits.com/dist/1P/op2/pkg/v${op_version}/op_linux_${op_arch}_v${op_version}.zip" -o /tmp/op.zip
|
|
126
|
+
python3 -c "import zipfile; zipfile.ZipFile('/tmp/op.zip').extract('op', '/tmp')"
|
|
127
|
+
sudo mv /tmp/op /usr/local/bin/op && chmod +x /usr/local/bin/op && rm /tmp/op.zip
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- [ ] **Step 4: Rerun the focused tests and shell syntax check**
|
|
132
|
+
|
|
133
|
+
Run:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pnpm exec c8 --clean=false node --import tsx --test \
|
|
137
|
+
--test-name-pattern='1Password installer' __tests__/app.test.ts
|
|
138
|
+
bash -n assets/devcontainer/hooks/post-create.sh
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Expected: All three focused tests pass and Bash reports no syntax errors.
|
|
142
|
+
|
|
143
|
+
- [ ] **Step 5: Run full repository verification**
|
|
144
|
+
|
|
145
|
+
Run:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pnpm run test
|
|
149
|
+
pnpm run lint
|
|
150
|
+
pnpm run build
|
|
151
|
+
git diff --check
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Expected: The full test suite, lint, and build pass; the diff check produces no output.
|
|
155
|
+
|
|
156
|
+
- [ ] **Step 6: Commit the implementation**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git add assets/devcontainer/hooks/post-create.sh __tests__/app.test.ts
|
|
160
|
+
git commit -m "fix: select 1password installer architecture"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Expected: One implementation commit containing the architecture selector and its behavioral smoke tests.
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Devcontainer Node Image Digest Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Pin Boxdown's packaged Node 24 devcontainer image to its verified multi-platform index digest and configure Renovate to refresh that digest through monthly pull requests.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep the existing `node:24-trixie-slim` update track but append the immutable OCI index digest resolved from Docker Hub. Add a narrowly scoped Renovate configuration that enables only the devcontainer manager, covers Boxdown's nonstandard packaged-template path, disables Feature updates, and permits only the Node image digest rule.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Dev Container JSONC, Renovate JSON, Node.js built-in test runner, TypeScript, Docker Buildx registry inspection, Markdown.
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- The image tag remains exactly `node:24-trixie-slim`.
|
|
14
|
+
- The pinned digest is the multi-platform OCI index digest `sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573` resolved on 2026-07-18.
|
|
15
|
+
- Renovate manages only the packaged devcontainer and does not overlap with Dependabot's npm or GitHub Actions responsibilities.
|
|
16
|
+
- Dev Container Feature updates remain disabled in Renovate.
|
|
17
|
+
- Renovate uses `exact` versioning for the Node image so only its digest can change; the `24-trixie-slim` tag cannot be upgraded.
|
|
18
|
+
- Node image digest updates are eligible only on the first day of each month before 04:00 UTC.
|
|
19
|
+
- The Renovate GitHub app is an external operational prerequisite; no repository token or scheduled workflow is added.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Task 1: Enforce and Pin the Multi-Platform Node Image
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
|
|
27
|
+
- Create: `__tests__/devcontainer-image-policy.test.ts`
|
|
28
|
+
- Modify: `assets/devcontainer/devcontainer.json:3`
|
|
29
|
+
|
|
30
|
+
**Interfaces:**
|
|
31
|
+
|
|
32
|
+
- Consumes: `parseJsonc<T>(input: string): T` from `src/jsonc.ts`.
|
|
33
|
+
- Produces: a static repository invariant requiring the exact Node 24 trixie slim tag plus a 64-hex-character SHA-256 digest.
|
|
34
|
+
|
|
35
|
+
- [ ] **Step 1: Write the failing image-policy test**
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import assert from 'node:assert'
|
|
39
|
+
import { readFileSync } from 'node:fs'
|
|
40
|
+
import { fileURLToPath } from 'node:url'
|
|
41
|
+
import { test } from 'node:test'
|
|
42
|
+
|
|
43
|
+
import { parseJsonc } from '../src/jsonc.ts'
|
|
44
|
+
|
|
45
|
+
const devcontainerPath = fileURLToPath(new URL('../assets/devcontainer/devcontainer.json', import.meta.url))
|
|
46
|
+
|
|
47
|
+
test('pins the packaged Node 24 devcontainer image to a SHA-256 digest', () => {
|
|
48
|
+
const devcontainer = parseJsonc<{ image: string }>(readFileSync(devcontainerPath, 'utf8'))
|
|
49
|
+
|
|
50
|
+
assert.match(devcontainer.image, /^node:24-trixie-slim@sha256:[a-f0-9]{64}$/)
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- [ ] **Step 2: Run the focused test and verify RED**
|
|
55
|
+
|
|
56
|
+
Run:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node --test __tests__/devcontainer-image-policy.test.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Expected: FAIL because `node:24-trixie-slim` does not include `@sha256:` and a digest.
|
|
63
|
+
|
|
64
|
+
- [ ] **Step 3: Pin the verified OCI index digest**
|
|
65
|
+
|
|
66
|
+
Change the image property in `assets/devcontainer/devcontainer.json` to:
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
"image": "node:24-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573",
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- [ ] **Step 4: Run the focused test and verify GREEN**
|
|
73
|
+
|
|
74
|
+
Run:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node --test __tests__/devcontainer-image-policy.test.ts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Expected: PASS with 1 test and 0 failures.
|
|
81
|
+
|
|
82
|
+
- [ ] **Step 5: Reinspect the registry reference**
|
|
83
|
+
|
|
84
|
+
Run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
docker buildx imagetools inspect node:24-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Expected: `MediaType: application/vnd.oci.image.index.v1+json`, the same top-level digest, and platform manifests including `linux/amd64` and `linux/arm64/v8`.
|
|
91
|
+
|
|
92
|
+
- [ ] **Step 6: Commit the image pin**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git add __tests__/devcontainer-image-policy.test.ts assets/devcontainer/devcontainer.json
|
|
96
|
+
git commit -m "chore: pin devcontainer Node image digest"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Task 2: Scope Renovate to Monthly Node Image Digest Updates
|
|
100
|
+
|
|
101
|
+
**Files:**
|
|
102
|
+
|
|
103
|
+
- Modify: `__tests__/devcontainer-image-policy.test.ts`
|
|
104
|
+
- Create: `renovate.json`
|
|
105
|
+
|
|
106
|
+
**Interfaces:**
|
|
107
|
+
|
|
108
|
+
- Consumes: the `devcontainer` manager's `image` and `feature` dependency types and the packaged template path from Task 1.
|
|
109
|
+
- Produces: Renovate configuration restricted to the Node image digest rule, with Feature management disabled and a monthly UTC schedule.
|
|
110
|
+
|
|
111
|
+
- [ ] **Step 1: Add the failing Renovate-policy test**
|
|
112
|
+
|
|
113
|
+
Append the following declarations and test to `__tests__/devcontainer-image-policy.test.ts`, adding `existsSync` to the existing `node:fs` import:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const renovatePath = fileURLToPath(new URL('../renovate.json', import.meta.url))
|
|
117
|
+
|
|
118
|
+
interface RenovatePackageRule {
|
|
119
|
+
description?: string
|
|
120
|
+
matchManagers?: string[]
|
|
121
|
+
matchDepTypes?: string[]
|
|
122
|
+
matchPackageNames?: string[]
|
|
123
|
+
matchFileNames?: string[]
|
|
124
|
+
versioning?: string
|
|
125
|
+
pinDigests?: boolean
|
|
126
|
+
schedule?: string[]
|
|
127
|
+
enabled?: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface RenovateConfig {
|
|
131
|
+
enabledManagers?: string[]
|
|
132
|
+
devcontainer?: {
|
|
133
|
+
managerFilePatterns?: string[]
|
|
134
|
+
}
|
|
135
|
+
packageRules?: RenovatePackageRule[]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
test('scopes Renovate to monthly packaged Node image digest updates', () => {
|
|
139
|
+
assert.equal(existsSync(renovatePath), true, 'renovate.json must exist')
|
|
140
|
+
|
|
141
|
+
const renovate = JSON.parse(readFileSync(renovatePath, 'utf8')) as RenovateConfig
|
|
142
|
+
assert.deepEqual(renovate.enabledManagers, ['devcontainer'])
|
|
143
|
+
assert.deepEqual(renovate.devcontainer?.managerFilePatterns, [
|
|
144
|
+
'/^assets\\/devcontainer\\/devcontainer\\.json$/'
|
|
145
|
+
])
|
|
146
|
+
|
|
147
|
+
const featureRule = renovate.packageRules?.find(rule => rule.matchDepTypes?.includes('feature'))
|
|
148
|
+
assert.deepEqual(featureRule?.matchManagers, ['devcontainer'])
|
|
149
|
+
assert.equal(featureRule?.enabled, false)
|
|
150
|
+
|
|
151
|
+
const imageRule = renovate.packageRules?.find(rule => rule.matchDepTypes?.includes('image'))
|
|
152
|
+
assert.deepEqual(imageRule?.matchManagers, ['devcontainer'])
|
|
153
|
+
assert.deepEqual(imageRule?.matchPackageNames, ['node'])
|
|
154
|
+
assert.deepEqual(imageRule?.matchFileNames, ['assets/devcontainer/devcontainer.json'])
|
|
155
|
+
assert.equal(imageRule?.versioning, 'exact')
|
|
156
|
+
assert.equal(imageRule?.pinDigests, true)
|
|
157
|
+
assert.deepEqual(imageRule?.schedule, ['* 0-3 1 * *'])
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- [ ] **Step 2: Run the focused test and verify RED**
|
|
162
|
+
|
|
163
|
+
Run:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
node --test __tests__/devcontainer-image-policy.test.ts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Expected: the image-policy test passes and the Renovate-policy test fails with `renovate.json must exist`.
|
|
170
|
+
|
|
171
|
+
- [ ] **Step 3: Add the scoped Renovate configuration**
|
|
172
|
+
|
|
173
|
+
Create `renovate.json` with:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
178
|
+
"enabledManagers": [
|
|
179
|
+
"devcontainer"
|
|
180
|
+
],
|
|
181
|
+
"devcontainer": {
|
|
182
|
+
"managerFilePatterns": [
|
|
183
|
+
"/^assets\\/devcontainer\\/devcontainer\\.json$/"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"packageRules": [
|
|
187
|
+
{
|
|
188
|
+
"description": "Keep digest-pinned Dev Container Features outside Renovate",
|
|
189
|
+
"matchManagers": [
|
|
190
|
+
"devcontainer"
|
|
191
|
+
],
|
|
192
|
+
"matchDepTypes": [
|
|
193
|
+
"feature"
|
|
194
|
+
],
|
|
195
|
+
"enabled": false
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"description": "Refresh the Boxdown Node 24 devcontainer image digest monthly",
|
|
199
|
+
"matchManagers": [
|
|
200
|
+
"devcontainer"
|
|
201
|
+
],
|
|
202
|
+
"matchDepTypes": [
|
|
203
|
+
"image"
|
|
204
|
+
],
|
|
205
|
+
"matchPackageNames": [
|
|
206
|
+
"node"
|
|
207
|
+
],
|
|
208
|
+
"matchFileNames": [
|
|
209
|
+
"assets/devcontainer/devcontainer.json"
|
|
210
|
+
],
|
|
211
|
+
"versioning": "exact",
|
|
212
|
+
"pinDigests": true,
|
|
213
|
+
"schedule": [
|
|
214
|
+
"* 0-3 1 * *"
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- [ ] **Step 4: Run the focused test and verify GREEN**
|
|
222
|
+
|
|
223
|
+
Run:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
node --test __tests__/devcontainer-image-policy.test.ts
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Expected: PASS with 2 tests and 0 failures.
|
|
230
|
+
|
|
231
|
+
- [ ] **Step 5: Validate the Renovate JSON and schema URL**
|
|
232
|
+
|
|
233
|
+
Run:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
node -e "const fs=require('node:fs'); const config=JSON.parse(fs.readFileSync('renovate.json','utf8')); if (config['$schema'] !== 'https://docs.renovatebot.com/renovate-schema.json') process.exit(1)"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Expected: exit 0 with no output.
|
|
240
|
+
|
|
241
|
+
- [ ] **Step 6: Commit the automation policy**
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
git add __tests__/devcontainer-image-policy.test.ts renovate.json
|
|
245
|
+
git commit -m "chore: automate devcontainer image digest updates"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Task 3: Document Reproducibility and Verify the Complete Change
|
|
249
|
+
|
|
250
|
+
**Files:**
|
|
251
|
+
|
|
252
|
+
- Modify: `assets/devcontainer/README.md:25-45`
|
|
253
|
+
|
|
254
|
+
**Interfaces:**
|
|
255
|
+
|
|
256
|
+
- Consumes: the pinned index digest and Renovate scope delivered by Tasks 1 and 2.
|
|
257
|
+
- Produces: contributor-facing explanation of the immutable cross-platform image reference and automated PR update path.
|
|
258
|
+
|
|
259
|
+
- [ ] **Step 1: Update the Base image documentation**
|
|
260
|
+
|
|
261
|
+
Replace the first paragraph under `## Base image` with:
|
|
262
|
+
|
|
263
|
+
```markdown
|
|
264
|
+
Boxdown uses `node:24-trixie-slim` as the base-image update track to keep the
|
|
265
|
+
shared image smaller than the full Dev Containers TypeScript/Node image. The
|
|
266
|
+
template appends the upstream multi-platform OCI index digest, making rebuilds
|
|
267
|
+
immutable while allowing AMD64 and ARM64 hosts to select the matching platform
|
|
268
|
+
manifest from the same pinned release set. Renovate checks the packaged
|
|
269
|
+
template monthly and opens a pull request when that tag's index digest changes,
|
|
270
|
+
so base-image updates remain explicit and auditable.
|
|
271
|
+
|
|
272
|
+
The devcontainer then installs the required operating-system tools through
|
|
273
|
+
pinned Dev Container features. `common-utils` and `git` run first so later
|
|
274
|
+
features and lifecycle hooks can rely on shell basics, `sudo`, package
|
|
275
|
+
metadata, Git, and related utilities.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- [ ] **Step 2: Run focused policy and documentation checks**
|
|
279
|
+
|
|
280
|
+
Run:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
node --test __tests__/devcontainer-image-policy.test.ts
|
|
284
|
+
./node_modules/.bin/markdownlint -c .github/.markdownlint.yml assets/devcontainer/README.md docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md
|
|
285
|
+
git diff --check
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Expected: 2 tests pass, Markdown lint exits 0, and `git diff --check` exits 0.
|
|
289
|
+
|
|
290
|
+
- [ ] **Step 3: Run the full test suite outside the socket-restricted sandbox**
|
|
291
|
+
|
|
292
|
+
Run:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
node --test __tests__/**/*.test.ts
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Expected: 203 tests pass with 0 failures. This uses Node's built-in TypeScript support because the host Node 26 runtime is incompatible with the installed `c8`/`tsx` execution path; the test contents are unchanged.
|
|
299
|
+
|
|
300
|
+
- [ ] **Step 4: Run lint and build using installed binaries**
|
|
301
|
+
|
|
302
|
+
Run:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
./node_modules/.bin/eslint .
|
|
306
|
+
./node_modules/.bin/markdownlint -c .github/.markdownlint.yml -i 'apm_modules/**' -i '.git' -i '__tests__' -i '.github' -i '.changeset' -i 'CODE_OF_CONDUCT.md' -i 'CHANGELOG.md' -i 'node_modules' -i 'dist' '**/**.md'
|
|
307
|
+
./node_modules/.bin/tsc
|
|
308
|
+
./node_modules/.bin/tsdown
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Expected: every command exits 0. `tsdown` reports successful ESM and CJS builds.
|
|
312
|
+
|
|
313
|
+
- [ ] **Step 5: Reverify the pinned registry object and final diff**
|
|
314
|
+
|
|
315
|
+
Run:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
docker buildx imagetools inspect node:24-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573
|
|
319
|
+
git diff --check
|
|
320
|
+
git status --short
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Expected: the registry object is an OCI image index with AMD64 and ARM64 manifests, whitespace validation exits 0, and status lists only the planned documentation change before its commit.
|
|
324
|
+
|
|
325
|
+
- [ ] **Step 6: Commit the documentation**
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
git add assets/devcontainer/README.md
|
|
329
|
+
git commit -m "docs: explain devcontainer image digest updates"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
- [ ] **Step 7: Confirm branch state**
|
|
333
|
+
|
|
334
|
+
Run:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
git status --short --branch
|
|
338
|
+
git log --oneline --decorate -4
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Expected: a clean `work/devcontainer-node-digest` branch with the implementation-plan commit followed by the three implementation commits.
|