@agent-facets/core 0.1.1
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/CHANGELOG.md +20 -0
- package/bunfig.toml +2 -0
- package/package.json +32 -0
- package/src/__tests__/build-pipeline.test.ts +427 -0
- package/src/__tests__/content-hash.test.ts +226 -0
- package/src/__tests__/facet-loader.test.ts +264 -0
- package/src/__tests__/facet-manifest.test.ts +208 -0
- package/src/__tests__/lockfile.test.ts +166 -0
- package/src/__tests__/server-loader.test.ts +99 -0
- package/src/__tests__/server-manifest.test.ts +92 -0
- package/src/build/content-hash.ts +102 -0
- package/src/build/detect-collisions.ts +36 -0
- package/src/build/pipeline.ts +120 -0
- package/src/build/validate-facets.ts +34 -0
- package/src/build/validate-platforms.ts +89 -0
- package/src/build/write-output.ts +34 -0
- package/src/index.ts +35 -0
- package/src/loaders/facet.ts +180 -0
- package/src/loaders/server.ts +37 -0
- package/src/loaders/validate.ts +64 -0
- package/src/schemas/build-manifest.ts +15 -0
- package/src/schemas/facet-manifest.ts +113 -0
- package/src/schemas/lockfile.ts +37 -0
- package/src/schemas/server-manifest.ts +17 -0
- package/src/types.ts +20 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @agent-facets/core
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5813b90: Small test for change set management in CI
|
|
8
|
+
|
|
9
|
+
## 0.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 2243bbf: Added basic create command to CLI
|
|
14
|
+
|
|
15
|
+
## 0.0.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 74e3d25: Should be 0.0.1 now
|
|
20
|
+
- 74e3d25: Initial publishing
|
package/bunfig.toml
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-facets/core",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/agent-facets/facets",
|
|
6
|
+
"directory": "packages/core"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.1.1",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"types": "tsc --noEmit",
|
|
15
|
+
"test": "bun test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"arktype": "2.1.29",
|
|
19
|
+
"comment-json": "^4.2.5",
|
|
20
|
+
"nanotar": "0.3.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/bun": "1.3.10"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"provenance": false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { detectNamingCollisions } from '../build/detect-collisions.ts'
|
|
6
|
+
import { runBuildPipeline } from '../build/pipeline.ts'
|
|
7
|
+
import { validateCompactFacets } from '../build/validate-facets.ts'
|
|
8
|
+
import { validatePlatformConfigs } from '../build/validate-platforms.ts'
|
|
9
|
+
import { writeBuildOutput } from '../build/write-output.ts'
|
|
10
|
+
import type { FacetManifest } from '../schemas/facet-manifest.ts'
|
|
11
|
+
|
|
12
|
+
let testDir: string
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
testDir = await mkdtemp(join(tmpdir(), 'build-pipeline-test-'))
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
await rm(testDir, { recursive: true, force: true })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
async function createFixtureDir(name: string): Promise<string> {
|
|
23
|
+
const dir = join(testDir, name)
|
|
24
|
+
await Bun.write(join(dir, '.keep'), '')
|
|
25
|
+
return dir
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --- Compact facets validation ---
|
|
29
|
+
|
|
30
|
+
describe('validateCompactFacets', () => {
|
|
31
|
+
test('valid compact entry passes', () => {
|
|
32
|
+
const manifest = {
|
|
33
|
+
name: 'test',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
facets: ['base@1.0.0'],
|
|
36
|
+
} as FacetManifest
|
|
37
|
+
const errors = validateCompactFacets(manifest)
|
|
38
|
+
expect(errors).toHaveLength(0)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('scoped compact entry passes', () => {
|
|
42
|
+
const manifest = {
|
|
43
|
+
name: 'test',
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
facets: ['@acme/base@2.0.0'],
|
|
46
|
+
} as FacetManifest
|
|
47
|
+
const errors = validateCompactFacets(manifest)
|
|
48
|
+
expect(errors).toHaveLength(0)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('malformed compact entry fails', () => {
|
|
52
|
+
const manifest = {
|
|
53
|
+
name: 'test',
|
|
54
|
+
version: '1.0.0',
|
|
55
|
+
facets: ['no-version-here'],
|
|
56
|
+
} as FacetManifest
|
|
57
|
+
const errors = validateCompactFacets(manifest)
|
|
58
|
+
expect(errors).toHaveLength(1)
|
|
59
|
+
expect(errors[0]?.path).toBe('facets[0]')
|
|
60
|
+
expect(errors[0]?.message).toContain('name@version')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('selective entries are skipped', () => {
|
|
64
|
+
const manifest = {
|
|
65
|
+
name: 'test',
|
|
66
|
+
version: '1.0.0',
|
|
67
|
+
facets: [{ name: 'other', version: '1.0.0', skills: ['x'] }],
|
|
68
|
+
} as FacetManifest
|
|
69
|
+
const errors = validateCompactFacets(manifest)
|
|
70
|
+
expect(errors).toHaveLength(0)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('no facets section passes', () => {
|
|
74
|
+
const manifest = {
|
|
75
|
+
name: 'test',
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
skills: { x: { description: 'A skill' } },
|
|
78
|
+
} as FacetManifest
|
|
79
|
+
const errors = validateCompactFacets(manifest)
|
|
80
|
+
expect(errors).toHaveLength(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// --- Naming collision detection ---
|
|
85
|
+
|
|
86
|
+
describe('detectNamingCollisions', () => {
|
|
87
|
+
test('no collisions with distinct names', () => {
|
|
88
|
+
const manifest = {
|
|
89
|
+
name: 'test',
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
skills: { review: { description: 'Review skill' } },
|
|
92
|
+
agents: { helper: { description: 'Helper agent' } },
|
|
93
|
+
commands: { deploy: { description: 'Deploy command' } },
|
|
94
|
+
} as FacetManifest
|
|
95
|
+
const errors = detectNamingCollisions(manifest)
|
|
96
|
+
expect(errors).toHaveLength(0)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('skill and command sharing a name is allowed (cross-type)', () => {
|
|
100
|
+
const manifest = {
|
|
101
|
+
name: 'test',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
skills: { review: { description: 'Review skill' } },
|
|
104
|
+
commands: { review: { description: 'Run review' } },
|
|
105
|
+
} as FacetManifest
|
|
106
|
+
const errors = detectNamingCollisions(manifest)
|
|
107
|
+
expect(errors).toHaveLength(0)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('agent and skill sharing a name is allowed (cross-type)', () => {
|
|
111
|
+
const manifest = {
|
|
112
|
+
name: 'test',
|
|
113
|
+
version: '1.0.0',
|
|
114
|
+
skills: { helper: { description: 'Helper skill' } },
|
|
115
|
+
agents: { helper: { description: 'Helper agent' } },
|
|
116
|
+
} as FacetManifest
|
|
117
|
+
const errors = detectNamingCollisions(manifest)
|
|
118
|
+
expect(errors).toHaveLength(0)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('same name across all three types is allowed (cross-type)', () => {
|
|
122
|
+
const manifest = {
|
|
123
|
+
name: 'test',
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
skills: { deploy: { description: 'Deploy skill' } },
|
|
126
|
+
agents: { deploy: { description: 'Deploy agent' } },
|
|
127
|
+
commands: { deploy: { description: 'Deploy command' } },
|
|
128
|
+
} as FacetManifest
|
|
129
|
+
const errors = detectNamingCollisions(manifest)
|
|
130
|
+
expect(errors).toHaveLength(0)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// --- Platform config validation ---
|
|
135
|
+
|
|
136
|
+
describe('validatePlatformConfigs', () => {
|
|
137
|
+
test('valid opencode config passes', () => {
|
|
138
|
+
const manifest = {
|
|
139
|
+
name: 'test',
|
|
140
|
+
version: '1.0.0',
|
|
141
|
+
agents: {
|
|
142
|
+
reviewer: {
|
|
143
|
+
description: 'Reviewer agent',
|
|
144
|
+
platforms: {
|
|
145
|
+
opencode: { tools: { grep: true, bash: true } },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
} as FacetManifest
|
|
150
|
+
const result = validatePlatformConfigs(manifest)
|
|
151
|
+
expect(result.errors).toHaveLength(0)
|
|
152
|
+
expect(result.warnings).toHaveLength(0)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('unknown platform produces warning', () => {
|
|
156
|
+
const manifest = {
|
|
157
|
+
name: 'test',
|
|
158
|
+
version: '1.0.0',
|
|
159
|
+
skills: {
|
|
160
|
+
review: {
|
|
161
|
+
description: 'Review skill',
|
|
162
|
+
platforms: {
|
|
163
|
+
'unknown-platform': { foo: 'bar' },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
} as FacetManifest
|
|
168
|
+
const result = validatePlatformConfigs(manifest)
|
|
169
|
+
expect(result.errors).toHaveLength(0)
|
|
170
|
+
expect(result.warnings).toHaveLength(1)
|
|
171
|
+
expect(result.warnings[0]).toContain('unknown-platform')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('invalid opencode config fails', () => {
|
|
175
|
+
const manifest = {
|
|
176
|
+
name: 'test',
|
|
177
|
+
version: '1.0.0',
|
|
178
|
+
agents: {
|
|
179
|
+
reviewer: {
|
|
180
|
+
description: 'Reviewer agent',
|
|
181
|
+
platforms: {
|
|
182
|
+
opencode: { tools: 'not-a-record' },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
} as FacetManifest
|
|
187
|
+
const result = validatePlatformConfigs(manifest)
|
|
188
|
+
expect(result.errors.length).toBeGreaterThan(0)
|
|
189
|
+
expect(result.errors[0]?.message).toContain('opencode')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('no platforms on any asset passes', () => {
|
|
193
|
+
const manifest = {
|
|
194
|
+
name: 'test',
|
|
195
|
+
version: '1.0.0',
|
|
196
|
+
skills: { x: { description: 'A skill' } },
|
|
197
|
+
} as FacetManifest
|
|
198
|
+
const result = validatePlatformConfigs(manifest)
|
|
199
|
+
expect(result.errors).toHaveLength(0)
|
|
200
|
+
expect(result.warnings).toHaveLength(0)
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// --- Build pipeline (end-to-end) ---
|
|
205
|
+
|
|
206
|
+
describe('runBuildPipeline', () => {
|
|
207
|
+
test('successful build with valid facet', async () => {
|
|
208
|
+
const dir = await createFixtureDir('valid-build')
|
|
209
|
+
await Bun.write(join(dir, 'skills/example.md'), '# Example skill')
|
|
210
|
+
await Bun.write(
|
|
211
|
+
join(dir, 'facet.json'),
|
|
212
|
+
JSON.stringify({
|
|
213
|
+
name: 'test-facet',
|
|
214
|
+
version: '1.0.0',
|
|
215
|
+
skills: {
|
|
216
|
+
example: {
|
|
217
|
+
description: 'An example skill',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
const result = await runBuildPipeline(dir)
|
|
224
|
+
expect(result.ok).toBe(true)
|
|
225
|
+
if (result.ok) {
|
|
226
|
+
expect(result.data.name).toBe('test-facet')
|
|
227
|
+
expect(result.data.skills?.example?.prompt).toBe('# Example skill')
|
|
228
|
+
|
|
229
|
+
// Content hashing fields
|
|
230
|
+
expect(result.archiveFilename).toBe('test-facet-1.0.0.facet')
|
|
231
|
+
expect(result.archiveBytes.length).toBeGreaterThan(0)
|
|
232
|
+
expect(Object.keys(result.assetHashes)).toContain('facet.json')
|
|
233
|
+
expect(Object.keys(result.assetHashes)).toContain('skills/example.md')
|
|
234
|
+
expect(result.assetHashes['skills/example.md']).toMatchInlineSnapshot(
|
|
235
|
+
`"sha256:ded8057927e03783371d0d929e4a6e92da66eb9dd164377ad6845a5a1c0cb5ba"`,
|
|
236
|
+
)
|
|
237
|
+
expect(result.integrity).toMatch(/^sha256:[a-f0-9]{64}$/)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('build fails on missing manifest', async () => {
|
|
242
|
+
const dir = await createFixtureDir('no-manifest')
|
|
243
|
+
const result = await runBuildPipeline(dir)
|
|
244
|
+
expect(result.ok).toBe(false)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('build fails on missing asset file', async () => {
|
|
248
|
+
const dir = await createFixtureDir('missing-file')
|
|
249
|
+
await Bun.write(
|
|
250
|
+
join(dir, 'facet.json'),
|
|
251
|
+
JSON.stringify({
|
|
252
|
+
name: 'test-facet',
|
|
253
|
+
version: '1.0.0',
|
|
254
|
+
skills: {
|
|
255
|
+
example: {
|
|
256
|
+
description: 'An example skill',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
}),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const result = await runBuildPipeline(dir)
|
|
263
|
+
expect(result.ok).toBe(false)
|
|
264
|
+
if (!result.ok) {
|
|
265
|
+
expect(result.errors[0]?.path).toBe('skills.example')
|
|
266
|
+
expect(result.errors[0]?.message).toContain('skills/example.md')
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('build succeeds with cross-type name sharing', async () => {
|
|
271
|
+
const dir = await createFixtureDir('cross-type')
|
|
272
|
+
await Bun.write(join(dir, 'skills/review.md'), '# Review skill')
|
|
273
|
+
await Bun.write(join(dir, 'commands/review.md'), '# Review command')
|
|
274
|
+
await Bun.write(
|
|
275
|
+
join(dir, 'facet.json'),
|
|
276
|
+
JSON.stringify({
|
|
277
|
+
name: 'test-facet',
|
|
278
|
+
version: '1.0.0',
|
|
279
|
+
skills: {
|
|
280
|
+
review: { description: 'A review skill' },
|
|
281
|
+
},
|
|
282
|
+
commands: {
|
|
283
|
+
review: { description: 'A review command' },
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
const result = await runBuildPipeline(dir)
|
|
289
|
+
expect(result.ok).toBe(true)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('build with all asset types includes all hashes', async () => {
|
|
293
|
+
const dir = await createFixtureDir('all-types')
|
|
294
|
+
await Bun.write(join(dir, 'skills/alpha.md'), '# Alpha skill')
|
|
295
|
+
await Bun.write(join(dir, 'skills/beta.md'), '# Beta skill')
|
|
296
|
+
await Bun.write(join(dir, 'agents/helper.md'), '# Helper agent')
|
|
297
|
+
await Bun.write(join(dir, 'commands/deploy.md'), '# Deploy command')
|
|
298
|
+
await Bun.write(
|
|
299
|
+
join(dir, 'facet.json'),
|
|
300
|
+
JSON.stringify({
|
|
301
|
+
name: 'multi-facet',
|
|
302
|
+
version: '2.0.0',
|
|
303
|
+
skills: {
|
|
304
|
+
alpha: { description: 'Alpha skill' },
|
|
305
|
+
beta: { description: 'Beta skill' },
|
|
306
|
+
},
|
|
307
|
+
agents: {
|
|
308
|
+
helper: { description: 'Helper agent' },
|
|
309
|
+
},
|
|
310
|
+
commands: {
|
|
311
|
+
deploy: { description: 'Deploy command' },
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
const result = await runBuildPipeline(dir)
|
|
317
|
+
expect(result.ok).toBe(true)
|
|
318
|
+
if (result.ok) {
|
|
319
|
+
expect(result.archiveFilename).toBe('multi-facet-2.0.0.facet')
|
|
320
|
+
const assetPaths = Object.keys(result.assetHashes).sort()
|
|
321
|
+
expect(assetPaths).toEqual([
|
|
322
|
+
'agents/helper.md',
|
|
323
|
+
'commands/deploy.md',
|
|
324
|
+
'facet.json',
|
|
325
|
+
'skills/alpha.md',
|
|
326
|
+
'skills/beta.md',
|
|
327
|
+
])
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('build fails on malformed compact facets entry', async () => {
|
|
332
|
+
const dir = await createFixtureDir('bad-facets')
|
|
333
|
+
await Bun.write(join(dir, 'skills/x.md'), '# Skill')
|
|
334
|
+
await Bun.write(
|
|
335
|
+
join(dir, 'facet.json'),
|
|
336
|
+
JSON.stringify({
|
|
337
|
+
name: 'test-facet',
|
|
338
|
+
version: '1.0.0',
|
|
339
|
+
skills: {
|
|
340
|
+
x: { description: 'A skill' },
|
|
341
|
+
},
|
|
342
|
+
facets: ['no-version-here'],
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const result = await runBuildPipeline(dir)
|
|
347
|
+
expect(result.ok).toBe(false)
|
|
348
|
+
if (!result.ok) {
|
|
349
|
+
expect(result.errors[0]?.message).toContain('name@version')
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
// --- Build output generation ---
|
|
355
|
+
|
|
356
|
+
describe('writeBuildOutput', () => {
|
|
357
|
+
test('writes archive and build manifest to dist/', async () => {
|
|
358
|
+
const dir = await createFixtureDir('write-output')
|
|
359
|
+
await Bun.write(join(dir, 'skills/example.md'), '# Resolved content')
|
|
360
|
+
await Bun.write(
|
|
361
|
+
join(dir, 'facet.json'),
|
|
362
|
+
JSON.stringify({
|
|
363
|
+
name: 'test-facet',
|
|
364
|
+
version: '1.0.0',
|
|
365
|
+
skills: {
|
|
366
|
+
example: { description: 'A skill' },
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
const result = await runBuildPipeline(dir)
|
|
372
|
+
expect(result.ok).toBe(true)
|
|
373
|
+
if (!result.ok) return
|
|
374
|
+
|
|
375
|
+
await writeBuildOutput(result, dir)
|
|
376
|
+
|
|
377
|
+
// Archive exists
|
|
378
|
+
const archiveExists = await Bun.file(join(dir, 'dist/test-facet-1.0.0.facet')).exists()
|
|
379
|
+
expect(archiveExists).toBe(true)
|
|
380
|
+
|
|
381
|
+
// Build manifest exists and has correct structure
|
|
382
|
+
const manifestText = await Bun.file(join(dir, 'dist/build-manifest.json')).text()
|
|
383
|
+
const manifest = JSON.parse(manifestText)
|
|
384
|
+
expect(manifest.facetVersion).toBe(1)
|
|
385
|
+
expect(manifest.archive).toBe('test-facet-1.0.0.facet')
|
|
386
|
+
expect(manifest.integrity).toMatch(/^sha256:[a-f0-9]{64}$/)
|
|
387
|
+
expect(manifest.assets['facet.json']).toMatch(/^sha256:[a-f0-9]{64}$/)
|
|
388
|
+
expect(manifest.assets['skills/example.md']).toMatch(/^sha256:[a-f0-9]{64}$/)
|
|
389
|
+
|
|
390
|
+
// No loose files
|
|
391
|
+
const looseManifest = await Bun.file(join(dir, 'dist/facet.json')).exists()
|
|
392
|
+
expect(looseManifest).toBe(false)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test('cleans previous dist/ before writing', async () => {
|
|
396
|
+
const dir = await createFixtureDir('clean-dist')
|
|
397
|
+
await Bun.write(join(dir, 'skills/x.md'), '# Skill')
|
|
398
|
+
await Bun.write(
|
|
399
|
+
join(dir, 'facet.json'),
|
|
400
|
+
JSON.stringify({
|
|
401
|
+
name: 'test',
|
|
402
|
+
version: '1.0.0',
|
|
403
|
+
skills: {
|
|
404
|
+
x: { description: 'A skill' },
|
|
405
|
+
},
|
|
406
|
+
}),
|
|
407
|
+
)
|
|
408
|
+
// Write a stale file in dist/
|
|
409
|
+
await Bun.write(join(dir, 'dist/stale.txt'), 'stale')
|
|
410
|
+
|
|
411
|
+
const result = await runBuildPipeline(dir)
|
|
412
|
+
expect(result.ok).toBe(true)
|
|
413
|
+
if (!result.ok) return
|
|
414
|
+
|
|
415
|
+
await writeBuildOutput(result, dir)
|
|
416
|
+
|
|
417
|
+
// Stale file should be gone
|
|
418
|
+
const staleExists = await Bun.file(join(dir, 'dist/stale.txt')).exists()
|
|
419
|
+
expect(staleExists).toBe(false)
|
|
420
|
+
|
|
421
|
+
// Archive and manifest should exist
|
|
422
|
+
const archiveExists = await Bun.file(join(dir, 'dist/test-1.0.0.facet')).exists()
|
|
423
|
+
expect(archiveExists).toBe(true)
|
|
424
|
+
const manifestExists = await Bun.file(join(dir, 'dist/build-manifest.json')).exists()
|
|
425
|
+
expect(manifestExists).toBe(true)
|
|
426
|
+
})
|
|
427
|
+
})
|