Skip to content

Commit bbbfe47

Browse files
author
DevBot
committed
test(tools): gate published artifacts against dead v0.43 residue (#1273, B2.13)
check-package-artifacts.ts scans the packed npm tarballs (which ship src/**), so it is the cheapest honest place to keep the B2.13 deletions from silently reappearing in a release: - FORBIDDEN_LEGACY_PATHS: the five proven-dead @openelement/element modules removed in the B2.13 audit fail the gate if they ever re-enter the packed artifact. - FORBIDDEN_LEGACY_SOURCE_PATTERNS: the removed marker-hydration channel (DATA_SSR_PROPS export, data-eid/data-signal* attributes, oe-branch:/oe-for-item: comment markers) is rejected in comment-stripped packed sources, extending the check-v044-legacy-absence.ts dist contract to the published src payload. The gate is already wired at ci+release tiers in tools/autoflow/policy.ts (package-artifacts:check) and runs before consumer:packaged. Unit tests cover both rules plus comment/other-package false-positive guards. Refs #1273. Stage #1288 risk #8.
1 parent 86b5ef9 commit bbbfe47

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

tools/check-package-artifacts.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,76 @@ Deno.test('package artifacts: rejects adapter tests and fixtures', async () => {
148148
},
149149
);
150150
});
151+
152+
Deno.test('package artifacts: rejects dead v0.43 residue paths (#1273/B2.13)', async () => {
153+
await withPackage(
154+
'@openelement/element',
155+
{
156+
'index.js': 'export {};',
157+
'src/types.ts': 'export interface ElementDefinition {}',
158+
'src/internal/protocol/vnode.ts': 'export interface VNode {}',
159+
'src/internal/protocol/prop.ts': 'export type PropDecl = never;',
160+
'src/internal/core/dom-utils.ts': 'export function clearChildren() {}',
161+
'src/internal/core/dsd-shadow-root.ts': 'export function hasPopulatedShadowRoot() {}',
162+
},
163+
(root) => {
164+
const messages = scanExtractedPackage('@openelement/element', root).violations.map((v) =>
165+
v.message
166+
);
167+
assertEquals(
168+
messages.filter((message) =>
169+
message === 'dead v0.43 residue must not be published (#1273/B2.13)'
170+
).length,
171+
5,
172+
);
173+
},
174+
);
175+
});
176+
177+
Deno.test('package artifacts: rejects legacy hydration markers in packed sources', async () => {
178+
await withPackage(
179+
'@openelement/element',
180+
{
181+
'index.js': 'export {};',
182+
'src/internal/protocol/hydration-markers.ts': `
183+
export const DATA_SSR_PROPS = 'data-ssr-props';
184+
export const DATA_SIGNAL = 'data-signal';
185+
export const BRANCH_MARKER_PREFIX = 'oe-branch:';
186+
`,
187+
},
188+
(root) => {
189+
const messages = scanExtractedPackage('@openelement/element', root).violations.map((v) =>
190+
v.message
191+
);
192+
assert(messages.includes('dead data-ssr-props channel export (#836, removed in 0.44)'));
193+
assert(messages.includes('legacy marker-based hydration attribute'));
194+
assert(messages.includes('legacy branch/list hydration comment marker'));
195+
},
196+
);
197+
});
198+
199+
Deno.test('package artifacts: marker scan ignores comments and other packages', async () => {
200+
await withPackage(
201+
'@openelement/element',
202+
{
203+
'index.js': 'export {};',
204+
'src/migration-note.ts': `
205+
// The removed channel was documented as data-ssr-props; do not re-add.
206+
export const DATA_OE_LIGHT = 'data-oe-light';
207+
`,
208+
},
209+
(root) => {
210+
assertEquals(scanExtractedPackage('@openelement/element', root).violations, []);
211+
},
212+
);
213+
await withPackage(
214+
'@openelement/app',
215+
{
216+
'index.js': 'export {};',
217+
'src/notes.ts': `export const marker = 'data-signal';`,
218+
},
219+
(root) => {
220+
assertEquals(scanExtractedPackage('@openelement/app', root).violations, []);
221+
},
222+
);
223+
});

tools/check-package-artifacts.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,42 @@ const HOST_PATTERNS: Array<[RegExp, string]> = [
3838
[/\bclearImmediate\b/, 'Node clearImmediate global'],
3939
];
4040

41+
// #1273 / B2.13 (stage #1288 risk #8): dead v0.43 renderer/binding/hydration
42+
// residue must not silently reappear in PUBLISHED artifacts (the packages
43+
// ship src/**, so a removed module that comes back would go straight to
44+
// npm). Paths are relative to the extracted package root; the audit table in
45+
// the B2.13 PR records the dead-code proof for each entry.
46+
const FORBIDDEN_LEGACY_PATHS: Record<string, ReadonlyArray<string>> = {
47+
'@openelement/element': [
48+
// Legacy ElementDefinition / runtime-renderer typing (VNode model).
49+
'src/types.ts',
50+
'src/internal/protocol/vnode.ts',
51+
// Legacy static prop-declaration typing (ADR-0052 era), superseded by the
52+
// compiler's __compiledProperties metadata.
53+
'src/internal/protocol/prop.ts',
54+
// Legacy renderer DOM helpers with no consumer in the compiled model.
55+
'src/internal/core/dom-utils.ts',
56+
'src/internal/core/dsd-shadow-root.ts',
57+
],
58+
};
59+
60+
// Marker strings of the removed v0.43 marker-hydration channel, scanned in
61+
// comment-stripped packed sources. The same literals are forbidden in built
62+
// artifacts by tools/check-v044-legacy-absence.ts; this rule extends that
63+
// absence contract to the published src/** payload.
64+
const FORBIDDEN_LEGACY_SOURCE_PATTERNS: Record<string, ReadonlyArray<[RegExp, string]>> = {
65+
'@openelement/element': [
66+
[/\bDATA_SSR_PROPS\b/u, 'dead data-ssr-props channel export (#836, removed in 0.44)'],
67+
[
68+
/['"]data-(?:eid|signal)(?:-[^'"]*)?['"]/u,
69+
'legacy marker-based hydration attribute',
70+
],
71+
[/['"]oe-(?:branch|for-item):/u, 'legacy branch/list hydration comment marker'],
72+
],
73+
};
74+
75+
const SOURCE_SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs', '.cjs']);
76+
4177
export interface ArtifactViolation {
4278
path: string;
4379
message: string;
@@ -137,6 +173,8 @@ export function scanExtractedPackage(packageName: string, packageRoot: string):
137173
pushPackageJsonViolations(packageName, `${packageRoot}/package.json`, violations);
138174

139175
const runtimeFree = RUNTIME_FREE_PACKAGES.has(packageName);
176+
const forbiddenPaths = FORBIDDEN_LEGACY_PATHS[packageName] ?? [];
177+
const forbiddenSourcePatterns = FORBIDDEN_LEGACY_SOURCE_PATTERNS[packageName] ?? [];
140178
const files = new Set<string>();
141179
for (
142180
const entry of walkSync(packageRoot, {
@@ -146,6 +184,22 @@ export function scanExtractedPackage(packageName: string, packageRoot: string):
146184
) {
147185
const relative = entry.path.slice(packageRoot.length + 1);
148186
files.add(relative);
187+
if (forbiddenPaths.includes(relative)) {
188+
violations.push({
189+
path: `${packageName}/${relative}`,
190+
message: 'dead v0.43 residue must not be published (#1273/B2.13)',
191+
});
192+
}
193+
if (
194+
forbiddenSourcePatterns.length > 0 && SOURCE_SCAN_EXTENSIONS.has(extension(entry.path))
195+
) {
196+
const text = stripComments(Deno.readTextFileSync(entry.path));
197+
for (const [pattern, message] of forbiddenSourcePatterns) {
198+
if (pattern.test(text)) {
199+
violations.push({ path: `${packageName}/${relative}`, message });
200+
}
201+
}
202+
}
149203
if (
150204
packageName === '@openelement/adapter-vite' &&
151205
relative.split('/').some((segment) =>

0 commit comments

Comments
 (0)