Skip to content

Commit b9ff07f

Browse files
chore: merge #755 webpack production manifest certificate bridge
Refs #686 and #694 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2 parents 30f88e2 + c91e404 commit b9ff07f

4 files changed

Lines changed: 492 additions & 0 deletions

File tree

‎packages/webpack-plugin/src/__tests__/index.test.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { spawnSync } from 'node:child_process'
2+
import { fileURLToPath } from 'node:url'
3+
14
import { describe, expect, it } from 'bun:test'
25

36
describe('export', () => {
@@ -6,6 +9,57 @@ describe('export', () => {
69
expect({ ...index }).toEqual({
710
DevupUIWebpackPlugin: expect.any(Function),
811
createWebpackGeneration: expect.any(Function),
12+
registerWebpackReturnedConfig: expect.any(Function),
13+
readWebpackProductionManifest: expect.any(Function),
14+
sealWebpackProductionManifest: expect.any(Function),
15+
WebpackProductionManifestError: expect.any(Function),
916
})
1017
})
1118
})
19+
20+
it('shares actual owner authority when advertised built CJS and ESM entries interleave', () => {
21+
// Given
22+
const cwd = fileURLToPath(new URL('../..', import.meta.url))
23+
const script = `
24+
import assert from 'node:assert/strict'
25+
import { createRequire } from 'node:module'
26+
const require = createRequire(process.cwd() + '/package.json')
27+
const cjs = require('@devup-ui/webpack-plugin')
28+
const esm = await import('@devup-ui/webpack-plugin')
29+
assert.equal(require.resolve('@devup-ui/webpack-plugin').endsWith('.cjs'), true)
30+
assert.equal(import.meta.resolve('@devup-ui/webpack-plugin').endsWith('.mjs'), true)
31+
assert.notEqual(cjs.registerWebpackReturnedConfig, esm.registerWebpackReturnedConfig)
32+
for (const [creator, writer] of [[cjs, esm], [esm, cjs]]) {
33+
const owner = creator.createWebpackGeneration()
34+
const config = { context: '/built-actual', name: 'native', entry: { main: './first.ts' } }
35+
writer.registerWebpackReturnedConfig(owner, 'client', config)
36+
assert.equal(creator.readWebpackProductionManifest(owner).configs.client[0], config)
37+
const sealed = creator.sealWebpackProductionManifest(owner)
38+
assert.equal(writer.readWebpackProductionManifest(owner), sealed)
39+
assert.throws(() => writer.registerWebpackReturnedConfig(owner, 'client', config),
40+
{ code: 'sealed', operation: 'register', coordinates: [{ role: 'client', context: '/built-actual', name: 'native' }] })
41+
config.entry.main = './changed.ts'
42+
assert.equal(sealed.configs.client[0].entry.main, './changed.ts')
43+
owner.acquire(true)()
44+
assert.equal(owner.disposed, true)
45+
for (const api of [creator, writer]) {
46+
assert.throws(() => api.readWebpackProductionManifest(owner), { code: 'disposed', operation: 'read' })
47+
assert.throws(() => api.sealWebpackProductionManifest(owner), { code: 'disposed', operation: 'seal' })
48+
assert.throws(() => api.registerWebpackReturnedConfig(owner, 'edge', {}), { code: 'disposed', operation: 'register' })
49+
}
50+
}
51+
console.log('mixed-entry-authority: passed')
52+
`
53+
// When
54+
const result = spawnSync('node', ['--input-type=module', '-e', script], {
55+
cwd,
56+
encoding: 'utf8',
57+
})
58+
// Then
59+
expect(result.error).toBeUndefined()
60+
expect({ status: result.status, stderr: result.stderr }).toEqual({
61+
status: 0,
62+
stderr: '',
63+
})
64+
expect(result.stdout.trim()).toBe('mixed-entry-authority: passed')
65+
})
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import * as wasm from '@devup-ui/wasm'
2+
import { expect, it } from 'bun:test'
3+
import type { Configuration } from 'webpack'
4+
5+
import { bindCompilerScope, createWebpackGeneration } from '../build-scope'
6+
import {
7+
readWebpackProductionManifest as read,
8+
registerWebpackReturnedConfig as register,
9+
sealWebpackProductionManifest as seal,
10+
WebpackProductionManifestError,
11+
type WebpackProductionRole,
12+
} from '../production-manifest'
13+
14+
function rejection(action: () => unknown): WebpackProductionManifestError {
15+
try {
16+
action()
17+
} catch (error) {
18+
if (error instanceof WebpackProductionManifestError) return error
19+
throw error
20+
}
21+
throw new Error('Expected registry rejection')
22+
}
23+
24+
it.each([
25+
['client', 'nodejs', 'edge'],
26+
['edge', 'nodejs', 'client'],
27+
] satisfies WebpackProductionRole[][])(
28+
'retains exact per-role membership when arrivals are %s/%s/%s',
29+
(...roles) => {
30+
// Given
31+
const owner = createWebpackGeneration()
32+
const first: Configuration = {}
33+
const second: Configuration = {}
34+
// When
35+
for (const role of roles) register(owner, role, first)
36+
register(owner, 'client', first)
37+
register(owner, 'client', second)
38+
const result = read(owner)
39+
// Then
40+
expect(result.phase).toBe('open')
41+
expect(result.configs.client).toEqual([first, second])
42+
expect(result.configs.client[0]).toBe(first)
43+
expect(result.configs.client[1]).toBe(second)
44+
expect(result.configs.nodejs[0]).toBe(first)
45+
expect(result.configs.edge[0]).toBe(first)
46+
},
47+
)
48+
49+
it('leaves unseen owners absent when reading empty open metadata', () => {
50+
// Given
51+
const owner = createWebpackGeneration()
52+
// When
53+
const result = read(owner)
54+
// Then
55+
expect(result).toEqual({
56+
phase: 'open',
57+
configs: { client: [], nodejs: [], edge: [] },
58+
})
59+
expect(
60+
globalThis.__devupUiWebpackProductionManifests?.has(owner) ?? false,
61+
).toBe(false)
62+
})
63+
64+
it.each([false, true])(
65+
'seals only current membership when partial=%s',
66+
(partial) => {
67+
// Given
68+
const owner = createWebpackGeneration()
69+
const empty = read(owner)
70+
const config: Configuration = {}
71+
if (partial) register(owner, 'nodejs', config)
72+
const open = read(owner)
73+
// When
74+
const result = seal(owner)
75+
// Then
76+
expect(result.phase).toBe('sealed')
77+
expect(result.configs).toEqual({
78+
client: [],
79+
nodejs: partial ? [config] : [],
80+
edge: [],
81+
})
82+
expect(read(owner)).toBe(result)
83+
expect(seal(owner)).toBe(result)
84+
expect(empty.configs.nodejs).toEqual([])
85+
expect(open.phase).toBe('open')
86+
expect(owner.disposed).toBe(false)
87+
for (const snapshot of [empty, open, result]) {
88+
expect(Object.isFrozen(snapshot)).toBe(true)
89+
expect(Object.isFrozen(snapshot.configs)).toBe(true)
90+
for (const configs of Object.values(snapshot.configs))
91+
expect(Object.isFrozen(configs)).toBe(true)
92+
}
93+
},
94+
)
95+
96+
it('retains native mutability when entries change before and after seal', () => {
97+
// Given
98+
const owner = createWebpackGeneration()
99+
const entry = { main: './first.ts' }
100+
const config: Configuration = { entry }
101+
register(owner, 'client', config)
102+
const before = read(owner)
103+
entry.main = './second.ts'
104+
// When
105+
const result = seal(owner)
106+
entry.main = './third.ts'
107+
config.entry = { other: './fourth.ts' }
108+
owner.dispose()
109+
// Then
110+
expect(before.configs.client[0]).toBe(config)
111+
expect(result.configs.client[0]).toBe(config)
112+
expect(result.configs.client[0]?.entry).toEqual({ other: './fourth.ts' })
113+
expect(entry.main).toBe('./third.ts')
114+
expect(Object.isFrozen(config)).toBe(false)
115+
expect(Object.isFrozen(entry)).toBe(false)
116+
expect(Object.isFrozen(result)).toBe(true)
117+
})
118+
119+
it('never evaluates entries when registering, reading, sealing or rejecting', () => {
120+
// Given
121+
const owner = createWebpackGeneration()
122+
const config: Configuration = {
123+
get entry(): Configuration['entry'] {
124+
throw new Error('Entry evaluated')
125+
},
126+
}
127+
// When
128+
register(owner, 'edge', config)
129+
const open = read(owner)
130+
const sealed = seal(owner)
131+
const late = rejection(() => register(owner, 'edge', config))
132+
owner.dispose()
133+
const disposed = rejection(() => read(owner))
134+
// Then
135+
expect(open.configs.edge[0]).toBe(config)
136+
expect(sealed.configs.edge[0]).toBe(config)
137+
expect(late.code).toBe('sealed')
138+
expect(disposed.coordinates).toEqual([{ role: 'edge' }])
139+
})
140+
141+
it.each([false, true])(
142+
'rejects every registration when sealed and identical=%s',
143+
(identical) => {
144+
// Given
145+
const owner = createWebpackGeneration()
146+
const config: Configuration = { context: '/actual', name: 'actual-client' }
147+
register(owner, 'client', config)
148+
const snapshot = seal(owner)
149+
const attempted = identical ? config : {}
150+
// When
151+
const error = rejection(() => register(owner, 'client', attempted))
152+
// Then
153+
expect(error.code).toBe('sealed')
154+
expect(error.operation).toBe('register')
155+
expect(error.coordinates).toEqual([
156+
identical
157+
? { role: 'client', context: '/actual', name: 'actual-client' }
158+
: { role: 'client' },
159+
])
160+
expect(Object.hasOwn(error, 'cause')).toBe(false)
161+
expect(Object.isFrozen(error.coordinates)).toBe(true)
162+
expect(Object.isFrozen(error.coordinates[0])).toBe(true)
163+
expect(read(owner)).toBe(snapshot)
164+
},
165+
)
166+
167+
it.each(['unseen', 'open', 'sealed'] as const)(
168+
'rejects all access with disposed precedence when previously %s',
169+
(state) => {
170+
// Given
171+
const owner = createWebpackGeneration()
172+
const config: Configuration = { context: '/stored', name: 'stored' }
173+
if (state !== 'unseen') {
174+
register(owner, 'nodejs', config)
175+
register(owner, 'edge', {})
176+
}
177+
if (state === 'sealed') seal(owner)
178+
owner.dispose()
179+
const attempted: Configuration = { name: 'attempted' }
180+
// When
181+
const errors = [
182+
rejection(() => register(owner, 'client', attempted)),
183+
rejection(() => read(owner)),
184+
rejection(() => seal(owner)),
185+
]
186+
// Then
187+
expect(errors.map((error) => [error.code, error.operation])).toEqual([
188+
['disposed', 'register'],
189+
['disposed', 'read'],
190+
['disposed', 'seal'],
191+
])
192+
expect(errors[0]?.coordinates).toEqual([
193+
{ role: 'client', name: 'attempted' },
194+
])
195+
for (const error of errors.slice(1)) {
196+
expect(error.coordinates).toEqual(
197+
state === 'unseen'
198+
? []
199+
: [
200+
{ role: 'nodejs', context: '/stored', name: 'stored' },
201+
{ role: 'edge' },
202+
],
203+
)
204+
expect(Object.isFrozen(error.coordinates)).toBe(true)
205+
expect(error.coordinates.every(Object.isFrozen)).toBe(true)
206+
expect(Object.hasOwn(error, 'cause')).toBe(false)
207+
}
208+
},
209+
)
210+
211+
it('does not pin completion or alias fresh owners when unused roles are registered', () => {
212+
// Given
213+
const owner = createWebpackGeneration()
214+
const config: Configuration = { context: '/same', name: 'same' }
215+
register(owner, 'edge', config)
216+
register(owner, 'nodejs', config)
217+
const independent = createWebpackGeneration()
218+
const scope = bindCompilerScope({}, { owner, complete: true })
219+
scope.start()
220+
// When
221+
scope.close()
222+
const fresh = createWebpackGeneration()
223+
register(fresh, 'client', config)
224+
// Then
225+
expect(owner.disposed).toBe(true)
226+
expect(read(independent).configs).toEqual({
227+
client: [],
228+
nodejs: [],
229+
edge: [],
230+
})
231+
expect(read(fresh).configs).toEqual({
232+
client: [config],
233+
nodejs: [],
234+
edge: [],
235+
})
236+
expect(rejection(() => read(owner)).code).toBe('disposed')
237+
})
238+
239+
it('preserves actual engine bytes and extraction when metadata runs in a bound scope', () => {
240+
// Given
241+
const owner = createWebpackGeneration()
242+
const scope = bindCompilerScope({}, { owner, complete: true })
243+
const capture = () => [
244+
wasm.exportSheet(),
245+
wasm.exportClassMap(),
246+
wasm.exportFileMap(),
247+
wasm.exportCanonicalMap(),
248+
]
249+
const extract = () => {
250+
const output = wasm.codeExtract(
251+
'manifest.tsx',
252+
"import {Box} from '@devup-ui/react';export const x=<Box bg='red'/>",
253+
'@devup-ui/react',
254+
'df',
255+
true,
256+
false,
257+
false,
258+
{},
259+
)
260+
try {
261+
return [output.code, wasm.getCss(null, false)]
262+
} finally {
263+
output.free()
264+
}
265+
}
266+
const expected = scope.run(extract)
267+
// When
268+
const actual = scope.run(() => {
269+
const before = capture()
270+
register(owner, 'client', {})
271+
read(owner)
272+
seal(owner)
273+
expect(capture()).toEqual(before)
274+
return extract()
275+
})
276+
// Then
277+
expect(actual).toEqual(expected)
278+
expect(actual[1]).toContain('background:red')
279+
scope.abort()
280+
})

‎packages/webpack-plugin/src/index.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,12 @@ export {
66
DevupUIWebpackPlugin,
77
type DevupUIWebpackPluginOptions,
88
} from './plugin'
9+
export {
10+
readWebpackProductionManifest,
11+
registerWebpackReturnedConfig,
12+
sealWebpackProductionManifest,
13+
type WebpackManifestCoordinate,
14+
type WebpackProductionManifest,
15+
WebpackProductionManifestError,
16+
type WebpackProductionRole,
17+
} from './production-manifest'

0 commit comments

Comments
 (0)