build(site): [SITE-07] preserve exact Stack Overflow revision and attribution

This commit is contained in:
Harvey Zhao committed 2026-09-15 08:09:53 +08:00
1 parent 3136eb2402
commit 0503fe8aa8
30 files changed
+1941 -27

No files matched your search

+2 -1
View File
@@ -60,11 +60,12 @@ assert.deepEqual(consoleGroup?.components?.map(component => component.name).sort
'scheduler',
'shallowequal',
'simple-html-tokenizer',
'stackoverflow-custom-stringify',
'styled-components',
'stylis (Emotion fork)',
'stylis-rule-sheet',
], 'Do not silently drop verified console component attribution')
assert.equal(consoleGroup?.notices.length, 42, 'Missing reviewed console notice')
assert.equal(consoleGroup?.notices.length, 45, 'Missing reviewed console notice')
const vconsoleNotices = manifest.groups.find(group => group.name === 'vconsole')?.notices.map(notice => notice.target)
for (const name of ['LICENSE', 'MIT-LICENSE', 'ATTRIBUTION.md'])
assert(vconsoleNotices?.includes(`docs/licenses/vconsole/${name}`), `Missing vConsole notice: ${name}`)
+2 -2
View File
@@ -23,7 +23,7 @@ files retain their exact upstream bytes, including final blank lines.
`yarn check:site-notices`. The write command cannot bless altered vendor assets;
review the new archive and license evidence before changing the manifest. The
CLI prevents accidentally dropping any of the three groups, the ten reviewed
Monaco/vConsole components, the 42 identified console components or their notice
Monaco/vConsole components, the 43 identified console components or their notice
count, and vConsole's original license, supplemental MIT body and attribution.
Component references require their runtime assets and every notice before writing.
Source notices
@@ -60,7 +60,7 @@ The desktop console's owned TS entry/view and lifecycle now build through
`build:console` / `check:console`; see [console maintenance](console/README.md).
Its other 100 Parcel modules remain frozen and now reproduce exactly from fixed
archives, including the Parcel loader. Its package/loader licenses, two embedded
headers, embedded dependency licenses and upstream author notices now ship as 42 files
headers, embedded dependency licenses and upstream author notices now ship as 45 files
under `docs/licenses/console/`. Full embedded
attribution remains open; the generated index explicitly preserves that boundary.
+17 -3
View File
@@ -20,6 +20,8 @@ The fixed upstream is `refactor/baselines/site-vendor/console-original.js`, chec
by SHA-256. Only W5CS (view) and Focm (entry) bodies change. The other 100 modules,
their dependency maps and Parcel runtime remain byte-for-byte intact. The dead
`/index.js.map` trailer is removed; a complete upstream source map is unavailable.
A non-executable attribution comment is appended for the matched Stack Overflow
snippet. The build test compares every other byte outside the two owned bodies.
The entry injects existing React/ReactDOM 17.0.2, console-feed and
styled-components 5.3.3. RxPG supplies the historical parser and V2JG the method
list. Root `@types/react` 19.1.10 is used for type checking only; no React 19 runtime
@@ -185,6 +187,18 @@ Gary Court's original README/MIT text and Austin Appleby's public-domain header
are preserved for the two explicit MurmurHash source references. Original sources
and excerpts are hashed; attribution is never inferred from the outer Emotion MIT.
There are now 42 console notice outputs. Remaining follow-ups are the Component
Stack Overflow reference and final embedded-source review. Do not close VENDOR-08
solely because all Parcel modules reproduce.
`stackoverflow.ts` verifies a fixed answer revision's author, timestamp, license,
body and code, then locates the unique module declaration for comparison.
The customStringify function matches answer 48254637 revision 5, dated 2018-09-19,
under CC BY-SA 4.0. Revision 1 used Map and the current revision changes duplicate
handling; neither is the matching source. TypeScript 4.1.2 reproduces the function
exactly before minification. `console-stackoverflow-provenance.json` retains the
minimal revision response, source and full license; the public attribution credits
Alexander Mills and Rob W. A generated bundle comment retains the license and
links even when the script is read separately from the site notice index.
There are now 45 console notice outputs for 43 components. This includes a
CC BY-SA snippet; never describe the bundle as MIT-only. Final embedded-source
and mixed-license distribution review remains open. The shallowequal 1.1.0 README
explicitly identifies react-pure-render as its code origin; review that original
notice before closing VENDOR-08. Exact Parcel module reproduction alone is insufficient.
+3 -1
View File
@@ -26,6 +26,8 @@ export function moduleRanges(code: string) {
return ranges
}
export const attributionBanner = '\n/*! customStringify: Alexander Mills, with credit to Rob W. CC BY-SA 4.0: https://creativecommons.org/licenses/by-sa/4.0/ . Source revision: https://api.stackexchange.com/2.3/revisions/8FD5F52A-B16F-4C66-AB48-830EABB36CC0?site=stackoverflow&filter=withbody . ES3 compilation and minification; notices and source: /licenses/console/stackoverflow/ATTRIBUTION.md . Other component notices: /THIRD_PARTY_NOTICES.md */\n'
export async function generateConsole(root: string) {
const upstream = fs.readFileSync(path.join(root, 'refactor/baselines/site-vendor/console-original.js'), 'utf8')
assert.equal(createHash('sha256').update(upstream).digest('hex'), upstreamSha256, 'Frozen console source changed')
@@ -44,5 +46,5 @@ export async function generateConsole(root: string) {
for (const [id, range] of [...ranges].sort((a, b) => b[1].start - a[1].start))
output = output.slice(0, range.start) + bodies.get(id) + output.slice(range.end)
assert(output.endsWith(obsoleteMap), 'Unexpected original console source map trailer')
return output.slice(0, -obsoleteMap.length)
return output.slice(0, -obsoleteMap.length) + attributionBanner
}
+26 -1
View File
@@ -3,6 +3,7 @@ import type { EmbeddedNotice } from './embedded-notices.ts'
import type { Member, SourceMapRecord, TransformedSource } from './embedded-sources.ts'
import type { Archive, External, Source } from './provenance.ts'
import type { BabelRuntime, EsmSource } from './reconstruction.ts'
import type { Revision } from './stackoverflow.ts'
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import { execFileSync } from 'node:child_process'
@@ -16,6 +17,7 @@ import { extractNotice } from './embedded-notices.ts'
import { verifyMappedSources, verifyTransformedSources } from './embedded-sources.ts'
import { hash, parcelModules, verifyArchive, verifyModules, verifyPackageEdges } from './provenance.ts'
import { reconstructModule, verifyPrelude } from './reconstruction.ts'
import { moduleStatement, revisionSnippet } from './stackoverflow.ts'
interface CompilerOptions { warnings: boolean, safari10: boolean, mangle: { toplevel: boolean }, output?: { comments: boolean } }
interface Provenance {
@@ -58,6 +60,16 @@ interface DerivedProvenance {
cache: { reference: Member, upstreamUrl: string, notice: string }
}
interface HistoricalTypeScript { version: string, transpileModule: (source: string, options: { compilerOptions: DerivedProvenance['compiler']['options'] }) => { outputText: string } }
interface StackOverflowProvenance {
revision: Revision
apiUrl: string
response: { source: string, sha256: string }
source: string
target: Member
binding: string
compiledSha256: string
license: { url: string, source: string, sha256: string }
}
const root = fileURLToPath(new URL('../../../', import.meta.url))
const record: Provenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/console-feed-provenance.json'), 'utf8'))
@@ -65,6 +77,7 @@ const common: SourceGroup<CommonSource> = JSON.parse(fs.readFileSync(path.join(r
const esm: EsmProvenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/console-esm-provenance.json'), 'utf8'))
const embeddedSources: EmbeddedProvenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/console-embedded-sources.json'), 'utf8'))
const derived: DerivedProvenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/console-derived-attribution.json'), 'utf8'))
const stackOverflow: StackOverflowProvenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/console-stackoverflow-provenance.json'), 'utf8'))
assert(process.argv.slice(2).every(arg => arg === '--fetch'), 'Use reproduce.ts [--fetch]')
assert.equal(process.version, `v${fs.readFileSync(path.join(root, '.node-version'), 'utf8').trim()}`, 'Use canonical Node')
const cacheRoot = fs.realpathSync(path.join(root, 'refactor/.cache'))
@@ -275,4 +288,16 @@ const publicDomain = extractNotice(gitSource(derived.hash.source), derived.hash.
assert.equal(fs.readFileSync(path.join(root, derived.hash.notice.source), 'utf8'), publicDomain, 'Public domain notice changed')
assert(derivedMember(derived.cache.reference).toString('utf8').includes(derived.cache.upstreamUrl), 'Rule-sheet source attribution changed')
assert(fs.readFileSync(path.join(root, derived.cache.notice), 'utf8').includes('Copyright (c) 2016 Sultan Tarimo'), 'Rule-sheet attribution missing')
console.log(JSON.stringify({ exactModules: identified.size, consoleFeed: count, commonjs: commonCount, esm: esmCount, parcelPrelude: true, packageEdges: true, embeddedNotices: embedded.notices.length, embeddedMappedSources: mappedCount, embeddedTransformedSources: transformedCount, derivedGitSources: gitSources.size, replicatorFork: true, emotionStylisSource: true, unresolved: 0, licenseClosure: false }))
const answerSnapshot = fs.readFileSync(path.join(root, stackOverflow.response.source))
assert.equal(hash(answerSnapshot), stackOverflow.response.sha256, 'Frozen answer revision changed')
const snippet = revisionSnippet(answerSnapshot, stackOverflow.revision)
assert.equal(fs.readFileSync(path.join(root, stackOverflow.source), 'utf8'), snippet, 'Frozen answer snippet changed')
const compiledSnippet = historicalTs.transpileModule(snippet, { compilerOptions: tsCompiler.options }).outputText.trimEnd()
assert.equal(hash(compiledSnippet), stackOverflow.compiledSha256, 'Compiled answer snippet changed')
assert.equal(compiledSnippet, moduleStatement(derivedMember(stackOverflow.target).toString('utf8'), stackOverflow.binding), 'Answer snippet differs from archived function')
assert.equal(hash(fs.readFileSync(path.join(root, stackOverflow.license.source))), stackOverflow.license.sha256, 'Frozen CC license changed')
if (process.argv.includes('--fetch')) {
revisionSnippet(await download(stackOverflow.apiUrl), stackOverflow.revision)
assert.equal(hash(await download(stackOverflow.license.url)), stackOverflow.license.sha256, 'Upstream CC license changed')
}
console.log(JSON.stringify({ exactModules: identified.size, consoleFeed: count, commonjs: commonCount, esm: esmCount, parcelPrelude: true, packageEdges: true, embeddedNotices: embedded.notices.length, embeddedMappedSources: mappedCount, embeddedTransformedSources: transformedCount, derivedGitSources: gitSources.size, replicatorFork: true, emotionStylisSource: true, stackOverflowRevision: stackOverflow.revision.number, unresolved: 0, licenseClosure: false }))
@@ -0,0 +1,35 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import ts from 'typescript'
import { hash } from './provenance.ts'
export interface Revision {
guid: string
postId: number
number: number
created: number
userId: number
license: string
bodySha256: string
snippetSha256: string
}
export function revisionSnippet(response: Uint8Array, expected: Revision): string {
const data = JSON.parse(Buffer.from(response).toString('utf8')) as { items: { revision_guid: string, post_id: number, revision_number: number, creation_date: number, user: { user_id: number }, content_license: string, body: string }[] }
assert.equal(data.items.length, 1, 'Expected one fixed answer revision')
const item = data.items[0]!
assert.deepEqual([item.revision_guid, item.post_id, item.revision_number, item.creation_date, item.user.user_id, item.content_license], [expected.guid, expected.postId, expected.number, expected.created, expected.userId, expected.license], 'Answer revision identity changed')
assert.equal(hash(item.body), expected.bodySha256, 'Answer revision body changed')
const blocks = [...item.body.matchAll(/<pre><code>([\s\S]*?)<\/code><\/pre>/g)]
assert.equal(blocks.length, 1, 'Expected one answer code block')
const snippet = blocks[0]![1]!.replaceAll('&amp;', '&')
assert.equal(hash(snippet), expected.snippetSha256, 'Answer snippet changed')
return snippet
}
export function moduleStatement(source: string, name: string): string {
const file = ts.createSourceFile('source.js', source, ts.ScriptTarget.Latest, true)
const matches = file.statements.filter(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => ts.isIdentifier(declaration.name) && declaration.name.text === name))
assert.equal(matches.length, 1, 'Expected one module declaration')
return matches[0]!.getText(file)
}
+30 -2
View File
@@ -944,14 +944,14 @@
"name": "console",
"version": "legacy-vendor-with-TS-adapter",
"tarball": "https://github.com/zhw2590582/ArtPlayer/blob/5d6b2f22bc75213ac97cff0bcffa4a1ce2bc786c/docs/assets/js/console.js",
"review": "The 100 third-party Parcel modules and loader have exact source reproduction. The two owned adapter modules are built from TypeScript. Preserved notices also cover two embedded license headers, all 17 react-inspector external source-map members, and all seven HTML tokenizer sources from its fixed Git dependency. Embedded attribution review is still incomplete; this inventory is not publication clearance. Modified replicator and Emotion/Stylis copies retain upstream author licenses; the original Emotion online Closure stage is not claimed to be reproducible. MurmurHash credits include Gary Court and Austin Appleby.",
"review": "The 100 third-party Parcel modules and loader have exact source reproduction. The two owned adapter modules are built from TypeScript. Preserved notices also cover two embedded license headers, all 17 react-inspector external source-map members, and all seven HTML tokenizer sources from its fixed Git dependency. Embedded attribution review is still incomplete; this inventory is not publication clearance. Modified replicator and Emotion/Stylis copies retain upstream author licenses; the original Emotion online Closure stage is not claimed to be reproducible. MurmurHash credits include Gary Court and Austin Appleby. The customStringify snippet matches Stack Overflow answer revision 5 under CC BY-SA 4.0; its separate attribution, source and full license are supplied. This is a mixed-license bundle, not MIT-only.",
"roots": [
"docs/assets/js/console.js"
],
"files": [
{
"path": "docs/assets/js/console.js",
"sha256": "5644af7bb35bb6d0bcfa7eeae9a21b406ac4fc1b499a6c797a6534c7d33f0678",
"sha256": "a5f1388175fd885c03dd21b35fff97dca0b548745588dfca3172579a96537163",
"mode": "lf-text"
}
],
@@ -1165,6 +1165,21 @@
"source": "refactor/baselines/site-vendor/console-attribution/murmurhash2-public-domain.txt",
"target": "docs/licenses/console/murmurhash2/PUBLIC-DOMAIN.txt",
"sha256": "50e13b0a1963f0b382255d2425df245dbdfd209201621e8c157687caedbbb949"
},
{
"source": "refactor/baselines/site-vendor/console-stackoverflow/CC-BY-SA-4.0.txt",
"target": "docs/licenses/console/stackoverflow/LICENSE",
"sha256": "28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5"
},
{
"source": "refactor/baselines/site-vendor/console-stackoverflow/source.txt",
"target": "docs/licenses/console/stackoverflow/SOURCE.js",
"sha256": "966fdc4a37e65e90c2dc1f6ba9385e572c75ce4c0647d35f3ce566ed9ef7f6bb"
},
{
"source": "refactor/baselines/site-vendor/console-stackoverflow/ATTRIBUTION.txt",
"target": "docs/licenses/console/stackoverflow/ATTRIBUTION.md",
"sha256": "4ac7ae9b19a1201b6cfe6af1d35ec85d015a2de8fb1aac6b03796145e648a667"
}
],
"components": [
@@ -1629,6 +1644,19 @@
"notices": [
"docs/licenses/console/murmurhash2/PUBLIC-DOMAIN.txt"
]
},
{
"name": "stackoverflow-custom-stringify",
"version": "answer 48254637 revision 5 (CC BY-SA 4.0)",
"tarball": "https://api.stackexchange.com/2.3/revisions/8FD5F52A-B16F-4C66-AB48-830EABB36CC0?site=stackoverflow&filter=withbody",
"assets": [
"docs/assets/js/console.js"
],
"notices": [
"docs/licenses/console/stackoverflow/LICENSE",
"docs/licenses/console/stackoverflow/SOURCE.js",
"docs/licenses/console/stackoverflow/ATTRIBUTION.md"
]
}
]
}