build(site): [SITE-07] verify Monaco TypeScript worker and original notices

This commit is contained in:
Harvey Zhao committed 2026-09-15 08:47:23 +08:00
1 parent fb1faffbe9
commit 11296975f1
31 files changed
+6580 -8

No files matched your search

+3
View File
@@ -18,6 +18,7 @@ assert.deepEqual(manifest.groups.filter(group => group.name !== 'console').flatM
'regenerator-runtime',
'style-loader',
'svelte',
'typescript (Monaco worker)',
'webpack',
], 'Do not silently drop verified bundled component attribution')
const consoleGroup = manifest.groups.find(group => group.name === 'console')
@@ -69,6 +70,8 @@ assert.deepEqual(consoleGroup?.components?.map(component => component.name).sort
], 'Do not silently drop verified console component attribution')
assert.equal(consoleGroup?.notices.length, 47, 'Missing reviewed console notice')
const vconsoleNotices = manifest.groups.find(group => group.name === 'vconsole')?.notices.map(notice => notice.target)
const typeScriptNotices = manifest.groups.find(group => group.name === 'monaco-editor')?.components?.find(component => component.name === 'typescript (Monaco worker)')?.notices
assert.deepEqual(typeScriptNotices?.map(target => target.split('/').pop()).sort(), ['ATTRIBUTION.md', 'CopyrightNotice.txt', 'LICENSE.txt', 'ThirdPartyNoticeText.txt'], 'Missing TypeScript component notice')
for (const name of ['LICENSE', 'MIT-LICENSE', 'ATTRIBUTION.md'])
assert(vconsoleNotices?.includes(`docs/licenses/vconsole/${name}`), `Missing vConsole notice: ${name}`)
verifyConsoleNoticeSources(process.cwd(), manifest)
+6 -1
View File
@@ -22,7 +22,7 @@ files retain their exact upstream bytes, including final blank lines.
`../build-site-notices.mjs` provides `yarn build:site-notices` and read-only
`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
CLI prevents accidentally dropping any of the three groups, the eleven reviewed
Monaco/vConsole components, the 44 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.
@@ -68,3 +68,8 @@ limits are recorded in `refactor/console-notice-review.md`.
Follow-up: finish Monaco's broader bundled-component notice audit and remaining
fonts/media. Do not upgrade these assets
without verifying globals, AMD/worker paths, CSS, consoleLog and user interaction.
Monaco's actual TypeScript 4.4.4 worker now has a separate source proof and notice
supplement; its original notice's 2.7.2 label is retained and explained. See
[Monaco maintenance](monaco/README.md) for the fixed six source adaptations,
minifier reconstruction, exact-byte checks and remaining component review scope.
+34
View File
@@ -920,6 +920,26 @@
"source": "refactor/baselines/site-vendor/codicons-0.0.26/ATTRIBUTION.txt",
"target": "docs/licenses/monaco-editor/codicons/ATTRIBUTION.md",
"sha256": "ac4a57ce60aebaec1796b715d075273c4fd849853cd943fb92790c61ad038a5f"
},
{
"source": "refactor/baselines/site-vendor/monaco-typescript/LICENSE.txt",
"target": "docs/licenses/monaco-editor/typescript/LICENSE.txt",
"sha256": "a7d00bfd54525bc694b6e32f64c7ebcf5e6b7ae3657be5cc12767bce74654a47"
},
{
"source": "refactor/baselines/site-vendor/monaco-typescript/CopyrightNotice.txt",
"target": "docs/licenses/monaco-editor/typescript/CopyrightNotice.txt",
"sha256": "0d9e78b962c1f6215aa974365d263c50683b6855db45c6c0ed67ee1002974dc6"
},
{
"source": "refactor/baselines/site-vendor/monaco-typescript/ThirdPartyNoticeText.txt",
"target": "docs/licenses/monaco-editor/typescript/ThirdPartyNoticeText.txt",
"sha256": "0a64d2681a1e3c7ccd1c2a146a68812d4a23e66ca7cc1dea0e30dbd81e80c369"
},
{
"source": "refactor/baselines/site-vendor/monaco-typescript/ATTRIBUTION.txt",
"target": "docs/licenses/monaco-editor/typescript/ATTRIBUTION.md",
"sha256": "edb4b7526e86fadf7461ffda983f9f56a96472cf1ebf639947948e405ed95321"
}
],
"review": "The complete upstream Monaco LICENSE/ThirdPartyNotices are retained. The unmodified bundled Codicons font exactly matches @vscode/codicons 0.0.26; its historical README, CC BY 4.0 content license, MIT code license and added attribution are distributed below. Other site assets remain subject to their separate provenance reviews.",
@@ -937,6 +957,20 @@
"docs/licenses/monaco-editor/codicons/README.md",
"docs/licenses/monaco-editor/codicons/ATTRIBUTION.md"
]
},
{
"name": "typescript (Monaco worker)",
"version": "4.4.4",
"tarball": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"assets": [
"docs/assets/js/vs/language/typescript/tsWorker.js"
],
"notices": [
"docs/licenses/monaco-editor/typescript/LICENSE.txt",
"docs/licenses/monaco-editor/typescript/CopyrightNotice.txt",
"docs/licenses/monaco-editor/typescript/ThirdPartyNoticeText.txt",
"docs/licenses/monaco-editor/typescript/ATTRIBUTION.md"
]
}
]
},
+47
View File
@@ -0,0 +1,47 @@
# Monaco vendor maintenance
The site keeps Monaco 0.30.1 at its existing `assets/js/vs` paths. Its 99 files
match the official archive, with the recorded CSS line-ending difference. Do not
edit these assets directly or upgrade Monaco as part of a license correction.
## TypeScript worker
`typescript.ts` describes the six browser adaptations from the fixed upstream
`monaco-typescript/scripts/importTypescript.js`, source-map removal and RequireJS's
removal of the top-level strict directive. Each adaptation must occur once; strings
containing emitted strict directives are preserved. The adapted 4.4.4 service is
an exact 9,698,327-character segment in the archived development worker.
`reproduce-typescript.ts` verifies four archive SHA-512/SHA-256 fingerprints,
three fixed Git files, source/member identities, and original notice bytes. It
then executes only the pinned Terser 5.9.0 compiler with source-map 0.7.3 and the
recorded options. The full minified result and fixed header must equal the shipped
worker exactly. It does not execute TypeScript services, run npm install scripts,
or rewrite runtime assets. Historical compilers are isolated below the ignored
`refactor/.cache/monaco-review` directory, without changing root dependencies.
```sh
yarn verify:monaco-typescript-source --fetch
yarn verify:monaco-typescript-source
yarn build:site-notices
yarn check:site-notices
node --test test/monaco-provenance.test.js test/site-notices.test.js
yarn typecheck:docs-tools
```
Use the first command to populate and reverify the network cache; the second is
offline. Provenance and pinned build recipes are in
`refactor/baselines/monaco-typescript-provenance.json`. The normal site build only
copies verified notice files; it does not rerun the historical compiler.
Monaco's original ThirdPartyNotices names TypeScript 2.7.2; retain it verbatim.
The supplement identifies the actual 4.4.4 worker and delivers its LICENSE,
CopyrightNotice and ThirdPartyNoticeText. The last file includes upstream third-party
terms, not just Apache 2.0. Preserve raw bytes, including encoding and line endings.
The new attribution explains upstream modifications and the stale version label.
The other language services, localization shim, core embedded libraries and
language definitions remain under VENDOR-06 review. A match for this worker does
not close those components. Revalidate actual editor diagnostics/emission and
notice HTTP delivery after changes; use the committed editor-types and site-vendor
browser tests. Whole-site, physical-device and remote release gates remain separate.
@@ -0,0 +1,102 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import { execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import fs from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { verifyTypeScriptSource, workerHeader } from './typescript.ts'
interface Archive { name: string, version: string, tarball: string, integrity: string, sha256: string }
interface Member { archive: string, member: string, sha256: string }
interface Record {
archives: Archive[]
remotes: { source: string, sha256: string, gitBlobSha: string, apiUrl: string }[]
source: Member
worker: Member
compiler: Member & { version: string, sourceMap: string, options: { output: { comments: string } } }
target: { path: string, sha256: string }
notices: { source: string, target: string, sha256: string, member: string | null }[]
}
const root = fileURLToPath(new URL('../../../', import.meta.url))
const record: Record = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/monaco-typescript-provenance.json'), 'utf8'))
assert(process.argv.slice(2).every(arg => arg === '--fetch'), 'Use reproduce-typescript.ts [--fetch]')
assert.equal(process.version, `v${fs.readFileSync(path.join(root, '.node-version'), 'utf8').trim()}`, 'Use canonical Node')
const cache = path.join(root, 'refactor/.cache/monaco-review')
fs.mkdirSync(cache, { recursive: true })
assert.equal(fs.realpathSync(cache).toLowerCase(), path.resolve(cache).toLowerCase(), 'Redirected Monaco cache')
const hash = (bytes: Uint8Array) => createHash('sha256').update(bytes).digest('hex')
async function download(url: string): Promise<Buffer> {
const response = await fetch(url, { signal: AbortSignal.timeout(60000) })
assert(response.ok, `${url}: HTTP ${response.status}`)
return Buffer.from(await response.arrayBuffer())
}
for (const archive of record.archives) {
const file = path.join(cache, `${archive.name}-${archive.version}.tgz`)
const bytes = process.argv.includes('--fetch') ? await download(archive.tarball) : fs.readFileSync(file)
assert.equal(hash(bytes), archive.sha256, 'Monaco archive changed')
assert.equal(`sha512-${createHash('sha512').update(bytes).digest('base64')}`, archive.integrity, 'Monaco archive integrity changed')
if (process.argv.includes('--fetch'))
fs.writeFileSync(file, bytes)
}
for (const remote of record.remotes) {
const verify = (bytes: Buffer) => {
assert.equal(hash(bytes), remote.sha256, 'Monaco fixed Git content changed')
assert.equal(createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex'), remote.gitBlobSha, 'Monaco Git blob changed')
}
verify(fs.readFileSync(path.join(root, remote.source)))
if (process.argv.includes('--fetch')) {
const data: { encoding: string, content: string } = JSON.parse((await download(remote.apiUrl)).toString('utf8'))
assert.equal(data.encoding, 'base64', 'Expected Git content encoding')
verify(Buffer.from(data.content, 'base64'))
}
}
function readMember(archive: string, member: string): Buffer {
return execFileSync('tar', ['-xOzf', path.join(cache, `${archive}.tgz`), member], { maxBuffer: 20 * 1024 * 1024 })
}
function verifiedMember(member: Member): Buffer {
const bytes = readMember(member.archive, member.member)
assert.equal(hash(bytes), member.sha256, 'Monaco source member changed')
return bytes
}
for (const notice of record.notices) {
assert.equal(hash(fs.readFileSync(path.join(root, notice.source))), notice.sha256, 'TypeScript notice changed')
if (notice.member)
assert.equal(hash(readMember('typescript-4.4.4', notice.member)), notice.sha256, 'TypeScript upstream notice changed')
}
const source = verifiedMember(record.source).toString('utf8')
const worker = verifiedMember(record.worker).toString('utf8')
const matchedCharacters = verifyTypeScriptSource(source, worker)
// Only the fixed minifier and its fixed source-map library execute; no install scripts.
for (const name of ['terser', 'source-map']) {
const archive = record.archives.find(item => item.name === name)!
assert(archive, 'Missing historical compiler dependency')
const members = execFileSync('tar', ['-tzf', path.join(cache, `${name}-${archive.version}.tgz`)], { encoding: 'utf8' }).trim().split(/\r?\n/)
const directory = path.join(cache, 'compiler/node_modules', name)
fs.mkdirSync(directory, { recursive: true })
assert.equal(fs.realpathSync(directory).toLowerCase(), path.resolve(directory).toLowerCase(), 'Redirected compiler directory')
for (const member of members.filter(member => !member.endsWith('/'))) {
assert(member.startsWith('package/') && !member.includes('\\') && !member.split('/').includes('..'), 'Unsafe compiler archive member')
const destination = path.resolve(directory, member.slice('package/'.length))
assert(destination.startsWith(`${directory}${path.sep}`), 'Compiler member escapes cache')
fs.mkdirSync(path.dirname(destination), { recursive: true })
assert.equal(fs.realpathSync(path.dirname(destination)).toLowerCase(), path.dirname(destination).toLowerCase(), 'Redirected compiler member directory')
assert(!fs.existsSync(destination) || !fs.lstatSync(destination).isSymbolicLink(), 'Redirected compiler file')
fs.writeFileSync(destination, readMember(`${name}-${archive.version}`, member))
}
}
const require = createRequire(path.join(cache, 'compiler/entry.cjs'))
assert.equal(require('terser/package.json').version, record.compiler.version, 'Wrong Terser version')
assert.equal(require('source-map/package.json').version, record.compiler.sourceMap, 'Wrong source-map version')
assert.equal(hash(fs.readFileSync(path.join(cache, 'compiler/node_modules/terser/dist/bundle.min.js'))), record.compiler.sha256, 'Wrong Terser compiler bytes')
const compiler = require('terser') as { minify: (source: string, options: Record['compiler']['options']) => Promise<{ code: string }> }
const { code } = await compiler.minify(worker, record.compiler.options)
const target = fs.readFileSync(path.join(root, record.target.path))
assert.equal(hash(target), record.target.sha256, 'Current Monaco TypeScript worker changed')
assert.equal(workerHeader + code, target.toString('utf8'), 'Minified Monaco TypeScript worker differs')
console.log(JSON.stringify({ archives: record.archives.length, fixedGitSources: record.remotes.length, typeScriptVersion: '4.4.4', sourceCharacters: matchedCharacters, exactWorker: true, workerSha256: hash(target), notices: record.notices.length, otherMonacoComponentsReviewed: false }))
+39
View File
@@ -0,0 +1,39 @@
import assert from 'node:assert/strict'
// Fixed Monaco 0.30.1 importTypescript.js adaptations, followed by RequireJS's
// removal of the top-level strict directive. Never apply to arbitrary versions.
export function browserTypeScript(source: string): string {
const replacements: [RegExp, string][] = [
[/\n {4}ts\.sys =([\s\S]*)\n {4}\}\)\(\);/, '\n // MONACOCHANGE\n ts.sys = undefined;\n // END MONACOCHANGE'],
[/^( +)etwModule = require\(.*$/m, '$1// MONACOCHANGE\n$1etwModule = undefined;\n$1// END MONACOCHANGE'],
[/^( +)var result = ts\.sys\.require\(.*$/m, '$1// MONACOCHANGE\n$1var result = undefined;\n$1// END MONACOCHANGE'],
[/^( +)fs = require\("fs"\);$/m, '$1// MONACOCHANGE\n$1fs = undefined;\n$1// END MONACOCHANGE'],
[/^( +)debugger;$/m, '$1// MONACOCHANGE\n$1// debugger;\n$1// END MONACOCHANGE'],
[/= require\("perf_hooks"\)/, '/* MONACOCHANGE */= {}/* END MONACOCHANGE */'],
]
for (const [pattern, replacement] of replacements) {
assert.equal([...source.matchAll(new RegExp(pattern.source, `${pattern.flags}g`))].length, 1, 'Unexpected TypeScript adaptation boundary')
source = source.replace(pattern, replacement)
}
const map = /\/\/# sourceMappingURL[^\n]+/g
assert.equal([...source.matchAll(map)].length, 1, 'Unexpected TypeScript source map boundary')
const strict = /^"use strict";$/gm
assert.equal([...source.matchAll(strict)].length, 1, 'Unexpected TypeScript strict directive')
return source.replace(map, '').replace(strict, '')
}
export function verifyTypeScriptSource(source: string, worker: string): number {
const adapted = browserTypeScript(source)
const offset = worker.indexOf(adapted)
assert.equal(offset, 98, 'Adapted TypeScript differs from the archived worker')
assert.equal(worker.indexOf(adapted, offset + 1), -1, 'Duplicate TypeScript source in worker')
return adapted.length
}
export const workerHeader = `/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* monaco-typescript version: 0.30.1(5a7ba61be909ae9e4889768a3453ebb0dec392e2)
* Released under the MIT license
* https://github.com/Microsoft/monaco-typescript/blob/master/LICENSE.md
*-----------------------------------------------------------------------------*/
`