mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-14 00:06:17 -08:00
build(site): [SITE-07] reproduce Monaco CSS sources and verify styled editor
This commit is contained in:
1 parent
23ac6f7128
commit
d2b2f76db8
23 files changed
+16271
-5
No files matched your search
@@ -277,3 +277,51 @@ foreign modules, missing/repeated/already-injected dependencies and false entry
|
||||
strings. Browser regressions exercise real diagnostics/formatting and all pinned
|
||||
tokenizer cases. This does not close original core TS compilation, CSS reproduction,
|
||||
remaining embedded-origin review, or the broader SITE-07 release requirements.
|
||||
|
||||
## CSS sources and historical compression
|
||||
|
||||
`reproduce-css.ts` separately covers all 68 core stylesheets. The AMD string table
|
||||
does not give their assembly order: `coreStyleOrder` follows actual registrations.
|
||||
81 fixed VS Code Git members include styles, three inline images, the codicon font,
|
||||
original transport/build recipes, package/lock and license. Original `transportCSS`
|
||||
is extracted syntactically from the verified recipe and executed with reads limited
|
||||
to verified inputs. It removes the font query, normalizes URL quotes and embeds the
|
||||
images. Every prepared result equals the corresponding core ESM archive member;
|
||||
the copied font also matches. This harness does not run the full VS Code build.
|
||||
|
||||
The original CSS plugin rewrites paths and joins 70 separators with CRLF. The npm
|
||||
development CSS is LF-only. That publication normalization is explicit and counted;
|
||||
its exact upstream pipeline stage has not been established. After the fixed release
|
||||
header and this documented transformation, the whole 114,942-byte development file
|
||||
matches. This is source/output equivalence with an observed newline adaptation,
|
||||
not an assertion that the original publishing command was reproduced unchanged.
|
||||
|
||||
Historical cssnano 4.1.11 and PostCSS 7.0.35 reproduce the complete 72,052-byte
|
||||
minified file. Each run creates a fresh ignored tool directory, writes the fixed
|
||||
historical lock, installs with Yarn 1.22.22 `--frozen-lockfile --ignore-scripts`,
|
||||
and checks that the lock remains identical. The recorded closure has 153 selectors
|
||||
and 143 package versions, all with registry integrity. `--fetch` downloads/verifies
|
||||
source archives and permits registry access; the default uses cached archives and
|
||||
an offline frozen install. No project dependency or root lock changes are needed.
|
||||
|
||||
The private manifest explicitly selects historical Browserslist defaults to prevent
|
||||
the parent project's `last 1 Chrome version` target leaking into the archived build.
|
||||
That leakage changes `transparent` to `initial`. Ambient BROWSERSLIST/config/env
|
||||
overrides are rejected. Do not update historical caniuse data to silence its age
|
||||
warning: these inputs exist only to reproduce the pinned asset. VS Code's archive
|
||||
also obeys `LICENSE.txt eol=crlf`; only that member is converted to LF for its Git
|
||||
blob check, while its frozen license bytes stay intact.
|
||||
|
||||
The site's existing 72,057-byte CSS differs solely by five CRLF header newlines;
|
||||
the verifier requires that exact transformation and leaves the asset unchanged.
|
||||
|
||||
```sh
|
||||
yarn verify:monaco-css --fetch
|
||||
yarn verify:monaco-css
|
||||
yarn test:browser test/browser/editor-styles.spec.js --workers=1
|
||||
```
|
||||
|
||||
Browser checks cover light/dark theme colors, layout and resize, find-widget bounds,
|
||||
successful CSS/font requests, loaded codicon glyphs and model disposal in all three
|
||||
engines. The pinned light theme is intentionally #fffffe. Full original core TS
|
||||
compilation and further embedded-origin review remain separate open work.
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { Buffer } from 'node:buffer'
|
||||
import assert from 'node:assert/strict'
|
||||
import path from 'node:path'
|
||||
import vm from 'node:vm'
|
||||
import ts from 'typescript'
|
||||
|
||||
export function coreStyleOrder(source: string): string[] {
|
||||
const ast = ts.createSourceFile('core.js', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS)
|
||||
const tables = new Map<string, string[]>()
|
||||
const visitTables = (node: ts.Node) => {
|
||||
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isArrayLiteralExpression(node.initializer)
|
||||
&& node.initializer.elements.every(item => ts.isStringLiteral(item))) { tables.set(node.name.text, node.initializer.elements.map(item => (item as ts.StringLiteral).text)) }
|
||||
ts.forEachChild(node, visitTables)
|
||||
}
|
||||
visitTables(ast)
|
||||
const styles: string[] = []
|
||||
const visitCalls = (node: ts.Node) => {
|
||||
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'define') {
|
||||
const name = node.arguments[0]
|
||||
if (name && ts.isElementAccessExpression(name) && ts.isIdentifier(name.expression) && ts.isNumericLiteral(name.argumentExpression)) {
|
||||
const id = tables.get(name.expression.text)?.[Number(name.argumentExpression.text)]
|
||||
if (id?.startsWith('vs/css!') && id !== 'vs/css!vs/editor/editor.main')
|
||||
styles.push(id.slice('vs/css!'.length))
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visitCalls)
|
||||
}
|
||||
visitCalls(ast)
|
||||
assert(styles.length && new Set(styles).size === styles.length, 'Missing or repeated core CSS modules')
|
||||
return styles
|
||||
}
|
||||
|
||||
interface PreparedStyle { id: string, contents: string, moduleName: string, fsPath: string }
|
||||
interface CssPlugin {
|
||||
writeFile: (plugin: string, entry: string, request: { toUrl: (name: string) => string }, write: (file: string, source: string) => void, config: object) => void
|
||||
}
|
||||
|
||||
// Execute only the pinned transport function and CSS plugin, with reads restricted
|
||||
// to the caller's verified source map. VM is an isolation aid, not a security sandbox.
|
||||
export function prepareCoreStyles(transportRecipe: string, cssRecipe: string, ids: string[], read: (file: string) => Buffer) {
|
||||
const ast = ts.createSourceFile('standalone.ts', transportRecipe, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
||||
const functions = ast.statements.filter(node => ts.isFunctionDeclaration(node) && node.name?.text === 'transportCSS')
|
||||
assert.equal(functions.length, 1, 'Expected one transportCSS function')
|
||||
const code = ts.transpileModule(functions[0]!.getText(ast), { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.None } }).outputText
|
||||
const assets = new Set<string>()
|
||||
const context = vm.createContext({
|
||||
SRC_DIR: '/source',
|
||||
path: path.posix,
|
||||
fs: { readFileSync: (file: string) => {
|
||||
assert(file.startsWith('/source/') && !file.split('/').includes('..'), 'CSS source escaped verified inputs')
|
||||
const source = `src/${file.slice('/source/'.length)}`
|
||||
assets.add(source)
|
||||
return read(source)
|
||||
} },
|
||||
})
|
||||
vm.runInContext(code, context, { timeout: 1000 })
|
||||
const transport = context.transportCSS as (module: string, enqueue: (font: string) => void, write: (file: string, contents: string) => void) => boolean
|
||||
const fonts = new Set<string>()
|
||||
const prepared: PreparedStyle[] = ids.map((id) => {
|
||||
assert(/^vs\/[\w./-]+$/.test(id) && !id.split('/').includes('..'), 'Invalid CSS module')
|
||||
let contents: string | undefined
|
||||
assert(transport(`${id}.css`, font => fonts.add(font), (file, source) => {
|
||||
assert.equal(file, `${id}.css`)
|
||||
assert.equal(contents, undefined, 'Repeated CSS output')
|
||||
contents = source
|
||||
}), 'CSS transport rejected input')
|
||||
assert(typeof contents === 'string', 'Missing transported CSS')
|
||||
return { id, contents, moduleName: id, fsPath: `/source/${id}.css` }
|
||||
})
|
||||
let plugin: CssPlugin | undefined
|
||||
const build = vm.createContext({
|
||||
process: { versions: { node: '24' } },
|
||||
require: { nodeRequire: (name: string) => {
|
||||
if (name === 'path')
|
||||
return path.posix
|
||||
assert.equal(name, 'fs', 'Unexpected CSS build import')
|
||||
return {
|
||||
readFileSync: () => { throw new Error('Unprepared CSS resource') },
|
||||
}
|
||||
} },
|
||||
define: (name: string, value: CssPlugin) => {
|
||||
assert.equal(name, 'vs/css')
|
||||
assert.equal(plugin, undefined)
|
||||
plugin = value
|
||||
},
|
||||
inlineResources: true,
|
||||
inlineResourcesLimit: 5000,
|
||||
cssPluginEntryPoints: { 'vs/editor/editor.main': prepared },
|
||||
})
|
||||
vm.runInContext(cssRecipe, build, { timeout: 1000 })
|
||||
assert(plugin, 'Missing CSS build plugin')
|
||||
let joined: string | undefined
|
||||
plugin.writeFile('vs/css', 'vs/editor/editor.main', { toUrl: name => name }, (file, source) => {
|
||||
assert.equal(file, 'vs/editor/editor.main.css')
|
||||
assert.equal(joined, undefined)
|
||||
joined = source
|
||||
}, {})
|
||||
assert(typeof joined === 'string', 'Missing CSS bundle')
|
||||
return { prepared, assets: [...assets], fonts: [...fonts], joined, normalizedSeparators: (joined.match(/\r\n/g) || []).length }
|
||||
}
|
||||
|
||||
export function coreCssHeader(commit: string): string {
|
||||
assert(/^[a-f0-9]{40}$/.test(commit))
|
||||
return `/*!-----------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Version: 0.30.1(${commit})\n * Released under the MIT license\n * https://github.com/microsoft/vscode/blob/main/LICENSE.txt\n *-----------------------------------------------------------*/\n`
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import type { Archive, Member } from './archives.ts'
|
||||
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 { ArchiveCache, hash } from './archives.ts'
|
||||
import { coreCssHeader, coreStyleOrder, prepareCoreStyles } from './css.ts'
|
||||
|
||||
interface Selector { selector: string, name: string, version: string, resolved: string, integrity: string }
|
||||
interface LockedDependency {
|
||||
version: string
|
||||
resolved?: string
|
||||
integrity?: string
|
||||
dependencies?: Record<string, string>
|
||||
optionalDependencies?: Record<string, string>
|
||||
}
|
||||
interface Provenance {
|
||||
commit: string
|
||||
archives: Archive[]
|
||||
sourceArchive: { url: string, file: string, bytes: number, sha256: string }
|
||||
members: { path: string, member: string, sha256: string, gitBlobSha: string, gitNormalizeCRLF?: boolean, frozen?: string }[]
|
||||
tooling: { manifest: object, selectors: Selector[] }
|
||||
coreJavaScript: Member
|
||||
styles: { id: string, prepared: Member }[]
|
||||
fonts: string[]
|
||||
normalizedSeparators: number
|
||||
development: Member
|
||||
minified: Member
|
||||
editor: Member
|
||||
target: { path: string, sha256: string, crlf: number }
|
||||
}
|
||||
|
||||
const root = fileURLToPath(new URL('../../../', import.meta.url))
|
||||
assert(process.argv.slice(2).every(arg => arg === '--fetch'), 'Use yarn verify:monaco-css [--fetch]')
|
||||
assert.equal(process.version, `v${fs.readFileSync(path.join(root, '.node-version'), 'utf8').trim()}`, 'Use canonical Node')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && process.env.npm_config_user_agent?.split(' ')[0] === 'yarn/1.22.22', 'Use Yarn Classic 1.22.22')
|
||||
assert.equal(execFileSync(process.execPath, [yarn, '--version'], { encoding: 'utf8' }).trim(), '1.22.22')
|
||||
for (const variable of ['BROWSERSLIST', 'BROWSERSLIST_CONFIG', 'BROWSERSLIST_ENV'])
|
||||
assert(!process.env[variable], `Historical CSS target must not inherit ${variable}`)
|
||||
const record: Provenance = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/monaco-css-build-provenance.json'), 'utf8'))
|
||||
const cache = new ArchiveCache(root, path.join(root, 'refactor/.cache/monaco-review'))
|
||||
const online = process.argv.includes('--fetch')
|
||||
await cache.verify(record.archives, [], online)
|
||||
assert.equal(record.sourceArchive.file, 'vscode-source.tgz')
|
||||
assert.equal(record.sourceArchive.url, `https://codeload.github.com/microsoft/vscode/tar.gz/${record.commit}`)
|
||||
const archivePath = path.join(cache.directory, record.sourceArchive.file)
|
||||
let archive: Buffer
|
||||
if (online) {
|
||||
const response = await fetch(record.sourceArchive.url, { signal: AbortSignal.timeout(60000) })
|
||||
assert(response.ok, `VS Code archive HTTP ${response.status}`)
|
||||
archive = Buffer.from(await response.arrayBuffer())
|
||||
}
|
||||
else {
|
||||
archive = fs.readFileSync(archivePath)
|
||||
}
|
||||
assert.equal(archive.length, record.sourceArchive.bytes)
|
||||
assert.equal(hash(archive), record.sourceArchive.sha256, 'VS Code archive changed')
|
||||
if (online)
|
||||
fs.writeFileSync(archivePath, archive)
|
||||
const sources = new Map<string, Buffer>()
|
||||
for (const member of record.members) {
|
||||
assert.equal(member.member, `vscode-${record.commit}/${member.path}`)
|
||||
assert(!member.path.split('/').includes('..'), 'Invalid VS Code source')
|
||||
const bytes = execFileSync('tar', ['-xOzf', archivePath, member.member], { maxBuffer: 5 * 1024 * 1024 })
|
||||
assert.equal(hash(bytes), member.sha256, `Source changed: ${member.path}`)
|
||||
assert.equal(Boolean(member.gitNormalizeCRLF), member.path === 'LICENSE.txt', 'Unexpected Git line-ending adaptation')
|
||||
const gitBytes = member.gitNormalizeCRLF ? Buffer.from(bytes.toString('utf8').replace(/\r\n/g, '\n')) : bytes
|
||||
assert.equal(createHash('sha1').update(`blob ${gitBytes.length}\0`).update(gitBytes).digest('hex'), member.gitBlobSha, 'Git blob changed')
|
||||
if (member.frozen)
|
||||
assert.deepEqual(fs.readFileSync(path.join(root, member.frozen)), bytes, 'Frozen CSS recipe changed')
|
||||
assert(!sources.has(member.path), 'Duplicate CSS source')
|
||||
sources.set(member.path, bytes)
|
||||
}
|
||||
function read(file: string): Buffer {
|
||||
const source = sources.get(file)
|
||||
assert(source, `Unverified CSS source: ${file}`)
|
||||
return source
|
||||
}
|
||||
const ids = coreStyleOrder(cache.member(record.coreJavaScript).toString('utf8'))
|
||||
assert(read('.gitattributes').toString('utf8').includes('LICENSE.txt eol=crlf'), 'Unexplained archive license line endings')
|
||||
assert.deepEqual(ids, record.styles.map(style => style.id))
|
||||
assert.equal(ids.length, 68)
|
||||
const styles = prepareCoreStyles(read('build/lib/standalone.ts').toString('utf8'), read('src/vs/css.build.js').toString('utf8'), ids, read)
|
||||
for (const [index, style] of styles.prepared.entries())
|
||||
assert(Buffer.from(style.contents).equals(cache.member(record.styles[index]!.prepared)), `Prepared CSS differs: ${style.id}`)
|
||||
assert.deepEqual(styles.fonts, record.fonts)
|
||||
for (const font of styles.fonts)
|
||||
assert.deepEqual(read(`src/${font}`), cache.read('monaco-editor-core-0.30.1', `package/esm/${font}`), 'Copied font differs')
|
||||
assert.equal(styles.normalizedSeparators, record.normalizedSeparators)
|
||||
assert.equal(styles.normalizedSeparators, 70)
|
||||
// The archived dev CSS is LF-only; the fixed plugin joins 70 separators with CRLF.
|
||||
// Record this observed publication normalization instead of ignoring whitespace.
|
||||
const development = `${coreCssHeader(record.commit)}\n${styles.joined}`.replace(/\r\n/g, '\n')
|
||||
assert(Buffer.from(development).equals(cache.member(record.development)), 'Complete CSS development bundle differs')
|
||||
|
||||
// The existing Yarn lock parser has no declarations; keep its used boundary local.
|
||||
const lockfile = createRequire(import.meta.url)('@yarnpkg/lockfile') as { parse: (source: string) => { type: string, object: Record<string, LockedDependency> } }
|
||||
const parsed = lockfile.parse(read('yarn.lock').toString('utf8'))
|
||||
assert.equal(parsed.type, 'success')
|
||||
const closure = new Map<string, Selector>()
|
||||
function visit(selector: string) {
|
||||
if (closure.has(selector))
|
||||
return
|
||||
const dependency = parsed.object[selector]
|
||||
assert(dependency?.resolved && dependency.integrity, `Unpinned historical CSS dependency: ${selector}`)
|
||||
closure.set(selector, { selector, name: selector.slice(0, selector.lastIndexOf('@')), version: dependency.version, resolved: dependency.resolved, integrity: dependency.integrity })
|
||||
for (const [name, range] of Object.entries({ ...dependency.dependencies, ...dependency.optionalDependencies }))
|
||||
visit(`${name}@${range}`)
|
||||
}
|
||||
visit('cssnano@^4.1.11')
|
||||
assert.deepEqual([...closure.values()], record.tooling.selectors)
|
||||
assert.equal(JSON.parse(read('package.json').toString('utf8')).devDependencies.cssnano, '^4.1.11')
|
||||
const manifest = { name: 'monaco-css-historical-tools', private: true, browserslist: ['defaults'], dependencies: { cssnano: '^4.1.11' } }
|
||||
assert.deepEqual(manifest, record.tooling.manifest)
|
||||
const compiler = fs.mkdtempSync(path.join(cache.directory, 'css-build-'))
|
||||
fs.writeFileSync(path.join(compiler, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`)
|
||||
fs.writeFileSync(path.join(compiler, 'yarn.lock'), read('yarn.lock'))
|
||||
const install = execFileSync(process.execPath, [yarn, 'install', '--frozen-lockfile', '--ignore-scripts', '--non-interactive', ...online ? [] : ['--offline']], { cwd: compiler, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 })
|
||||
fs.writeFileSync(path.join(cache.directory, 'css-install-last.log'), install)
|
||||
assert.deepEqual(fs.readFileSync(path.join(compiler, 'yarn.lock')), read('yarn.lock'), 'Frozen historical lock changed')
|
||||
const require = createRequire(path.join(compiler, 'entry.cjs'))
|
||||
const cssnanoPath = require.resolve('cssnano/package.json')
|
||||
const cssRequire = createRequire(cssnanoPath)
|
||||
assert.equal(require(cssnanoPath).version, '4.1.11')
|
||||
assert.equal(cssRequire('postcss/package.json').version, '7.0.35')
|
||||
const minified: { css: string } = await cssRequire('postcss')([require('cssnano')({ preset: 'default' })]).process(development, { from: path.join(compiler, 'editor.main.css'), map: false })
|
||||
|
||||
assert(Buffer.from(minified.css).equals(cache.member(record.minified)), 'Complete CSS minification differs')
|
||||
assert.deepEqual(cache.member(record.minified), cache.member(record.editor), 'Editor CSS archive differs')
|
||||
const target = fs.readFileSync(path.join(root, record.target.path))
|
||||
assert.equal(hash(target), record.target.sha256, 'Site CSS changed')
|
||||
assert.equal((target.toString('utf8').match(/\r\n/g) || []).length, record.target.crlf)
|
||||
assert.equal(record.target.crlf, 5)
|
||||
assert(Buffer.from(minified.css.replace(/\n/g, '\r\n')).equals(target), 'Site CSS differs beyond five header newlines')
|
||||
console.log(JSON.stringify({ gitSources: sources.size, styles: ids.length, inlinedImages: styles.assets.filter(file => !file.endsWith('.css')), fonts: styles.fonts, dependencySelectors: closure.size, normalizedSeparators: styles.normalizedSeparators, developmentBytes: Buffer.byteLength(development), minifiedBytes: Buffer.byteLength(minified.css), siteBytes: target.length, exactDevelopment: true, exactMinification: true, preservedSiteCrlf: 5, compiler: { cssnano: '4.1.11', postcss: '7.0.35' }, originalCoreTypeScriptCompiled: false }))
|
||||
Reference in new issue
Block a user