174 lines
6.9 KiB
JavaScript
174 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// scripts/vendor-playground-libs.mjs
|
|
// Reproducible vendor script for v4.2 playground render-pipeline.
|
|
//
|
|
// Usage: node scripts/vendor-playground-libs.mjs
|
|
//
|
|
// Pins (locked per plan-critic B3 — never use highlightjs.org website builder
|
|
// or any other interactive UI; this script is fully headless):
|
|
// - markdown-it@14.1.0 (UMD bundle copied verbatim)
|
|
// - markdown-it-front-matter@0.2.4 (CommonJS module wrapped in IIFE)
|
|
// - highlight.js@11.11.1 (5-lang bundle assembled from CommonJS sources)
|
|
// - dompurify@3.2.6 (UMD bundle copied verbatim) — v4.3 Step 24
|
|
//
|
|
// Output: playground/lib/{markdown-it.min.js, markdown-it-front-matter.min.js,
|
|
// highlight.min.js, dompurify.min.js}
|
|
//
|
|
// All three output files are zero-network browser-loadable scripts that
|
|
// expose globals (`window.markdownit`, `window.markdownitFrontMatter`,
|
|
// `window.hljs`). They also work under Node.js dynamic-import via the
|
|
// pattern in scripts/render-artifact.mjs (UMD + global-eval).
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = resolve(HERE, '..');
|
|
const OUT = join(ROOT, 'playground', 'lib');
|
|
|
|
const PINS = {
|
|
'markdown-it': '14.1.0',
|
|
'markdown-it-front-matter': '0.2.4',
|
|
'highlight.js': '11.11.1',
|
|
// v4.3 Step 24 — pinned ≥ 3.1.1 (PortSwigger HTML-comment mutation-XSS bypass
|
|
// was fixed in 3.1.x; 3.2.6 is the current stable line as of 2026-05-10).
|
|
'dompurify': '3.2.6',
|
|
};
|
|
|
|
const HL_LANGS = ['yaml', 'json', 'javascript', 'bash', 'markdown', 'diff'];
|
|
|
|
function vendor() {
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const tmp = mkdtempSync(join(tmpdir(), 'voyage-vendor-'));
|
|
const log = (msg) => process.stdout.write(`[vendor] ${msg}\n`);
|
|
|
|
try {
|
|
// 1. markdown-it — copy UMD min bundle directly
|
|
log('packing markdown-it@' + PINS['markdown-it']);
|
|
execSync(`npm pack markdown-it@${PINS['markdown-it']} --silent`, { cwd: tmp });
|
|
execSync(`tar xzf markdown-it-${PINS['markdown-it']}.tgz`, { cwd: tmp });
|
|
copyFileSync(
|
|
join(tmp, 'package', 'dist', 'markdown-it.min.js'),
|
|
join(OUT, 'markdown-it.min.js'),
|
|
);
|
|
log(`wrote ${join(OUT, 'markdown-it.min.js')}`);
|
|
|
|
// 2. markdown-it-front-matter — wrap CommonJS in IIFE that exposes a global
|
|
log('packing markdown-it-front-matter@' + PINS['markdown-it-front-matter']);
|
|
execSync(`npm pack markdown-it-front-matter@${PINS['markdown-it-front-matter']} --silent`, { cwd: tmp });
|
|
execSync(`tar xzf markdown-it-front-matter-${PINS['markdown-it-front-matter']}.tgz`, { cwd: tmp });
|
|
const fmSrc = readFileSync(join(tmp, 'package', 'index.js'), 'utf-8');
|
|
const fmBundle = wrapCommonJS('markdownitFrontMatter', fmSrc);
|
|
writeFileSync(join(OUT, 'markdown-it-front-matter.min.js'), fmBundle);
|
|
log(`wrote ${join(OUT, 'markdown-it-front-matter.min.js')}`);
|
|
|
|
// 3. highlight.js — assemble core + 5 languages from CommonJS sources
|
|
log('packing highlight.js@' + PINS['highlight.js']);
|
|
execSync(`npm pack highlight.js@${PINS['highlight.js']} --silent`, { cwd: tmp });
|
|
execSync(`tar xzf highlight.js-${PINS['highlight.js']}.tgz`, { cwd: tmp });
|
|
|
|
const coreSrc = readFileSync(join(tmp, 'package', 'lib', 'core.js'), 'utf-8');
|
|
const langSrcs = HL_LANGS.map((lang) => ({
|
|
lang,
|
|
src: readFileSync(join(tmp, 'package', 'lib', 'languages', `${lang}.js`), 'utf-8'),
|
|
}));
|
|
|
|
const hlBundle = assembleHighlight(coreSrc, langSrcs);
|
|
writeFileSync(join(OUT, 'highlight.min.js'), hlBundle);
|
|
log(`wrote ${join(OUT, 'highlight.min.js')} (${HL_LANGS.length} langs)`);
|
|
|
|
// 4. dompurify — copy UMD min bundle directly (v4.3 Step 24).
|
|
// Mirrors markdown-it-vendoring: npm pack → tar xzf → copy
|
|
// dist/purify.min.js → playground/lib/dompurify.min.js. The UMD bundle
|
|
// exposes `window.DOMPurify` for browser-loadable use.
|
|
log('packing dompurify@' + PINS['dompurify']);
|
|
execSync(`npm pack dompurify@${PINS['dompurify']} --silent`, { cwd: tmp });
|
|
execSync(`tar xzf dompurify-${PINS['dompurify']}.tgz`, { cwd: tmp });
|
|
copyFileSync(
|
|
join(tmp, 'package', 'dist', 'purify.min.js'),
|
|
join(OUT, 'dompurify.min.js'),
|
|
);
|
|
log(`wrote ${join(OUT, 'dompurify.min.js')}`);
|
|
|
|
// 5. MANIFEST — record the vendored versions for audit
|
|
const manifest = {
|
|
generated_at: new Date().toISOString(),
|
|
pins: PINS,
|
|
highlight_languages: HL_LANGS,
|
|
output_files: [
|
|
'markdown-it.min.js',
|
|
'markdown-it-front-matter.min.js',
|
|
'highlight.min.js',
|
|
'dompurify.min.js',
|
|
],
|
|
};
|
|
writeFileSync(
|
|
join(OUT, 'VENDOR-MANIFEST.json'),
|
|
JSON.stringify(manifest, null, 2) + '\n',
|
|
);
|
|
log(`wrote ${join(OUT, 'VENDOR-MANIFEST.json')}`);
|
|
} finally {
|
|
rmSync(tmp, { recursive: true, force: true });
|
|
}
|
|
|
|
log('done');
|
|
}
|
|
|
|
/**
|
|
* Wrap a CommonJS module body (uses `module.exports = ...`) in an IIFE
|
|
* that exposes the export as a global on `window` (browser) or
|
|
* `globalThis` (Node).
|
|
*/
|
|
function wrapCommonJS(globalName, src) {
|
|
return [
|
|
`// vendored by scripts/vendor-playground-libs.mjs — DO NOT EDIT`,
|
|
`// global: ${globalName}`,
|
|
`(function (root, factory) {`,
|
|
` var __mod = { exports: {} };`,
|
|
` (function (module, exports) {`,
|
|
` ${src.replace(/\n/g, '\n ')}`,
|
|
` })(__mod, __mod.exports);`,
|
|
` root[${JSON.stringify(globalName)}] = __mod.exports;`,
|
|
`})(typeof window !== 'undefined' ? window : globalThis);`,
|
|
``,
|
|
].join('\n');
|
|
}
|
|
|
|
/**
|
|
* Assemble a self-contained highlight.js IIFE with core + N languages.
|
|
*
|
|
* Output exposes `window.hljs` (and `globalThis.hljs` under Node).
|
|
*/
|
|
function assembleHighlight(coreSrc, langSrcs) {
|
|
const parts = [
|
|
`// vendored by scripts/vendor-playground-libs.mjs — DO NOT EDIT`,
|
|
`// global: hljs (highlight.js@${PINS['highlight.js']} — core + ${langSrcs.map(l => l.lang).join('/')})`,
|
|
`(function (root) {`,
|
|
` function loadCommonJS(src) {`,
|
|
` var __mod = { exports: {} };`,
|
|
` var fn = new Function('module', 'exports', src);`,
|
|
` fn(__mod, __mod.exports);`,
|
|
` return __mod.exports;`,
|
|
` }`,
|
|
` var coreSrc = ${JSON.stringify(coreSrc)};`,
|
|
` var hljs = loadCommonJS(coreSrc);`,
|
|
];
|
|
for (const { lang, src } of langSrcs) {
|
|
parts.push(` var lang_${lang.replace(/\W/g, '_')} = loadCommonJS(${JSON.stringify(src)});`);
|
|
parts.push(` hljs.registerLanguage(${JSON.stringify(lang)}, lang_${lang.replace(/\W/g, '_')});`);
|
|
}
|
|
parts.push(` root.hljs = hljs;`);
|
|
parts.push(`})(typeof window !== 'undefined' ? window : globalThis);`);
|
|
parts.push('');
|
|
return parts.join('\n');
|
|
}
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
vendor();
|
|
}
|
|
|
|
export { vendor, wrapCommonJS, assembleHighlight };
|