mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 23:26:39 +03:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e095c53819 | ||
|
|
3fcd656aaf | ||
|
|
6d973cdea6 |
@@ -9,3 +9,15 @@ The `skill/reference/ios.md` and `skill/reference/android.md` platform reference
|
|||||||
**Original work:** https://github.com/ehmo/platform-design-skills
|
**Original work:** https://github.com/ehmo/platform-design-skills
|
||||||
**Original license:** MIT
|
**Original license:** MIT
|
||||||
**Author:** ehmo
|
**Author:** ehmo
|
||||||
|
|
||||||
|
## Static HTML parser bundle
|
||||||
|
|
||||||
|
`cli/engine/vendor/static-html-parsers.mjs` is a generated bundle of the parser packages the static-HTML detector needs at runtime. Skill and plugin installs copy that file with the detector; they do not install these packages from npm.
|
||||||
|
|
||||||
|
| Package | License |
|
||||||
|
|---|---|
|
||||||
|
| htmlparser2 | MIT |
|
||||||
|
| css-select | BSD-2-Clause |
|
||||||
|
| css-tree | MIT |
|
||||||
|
| domutils | BSD-2-Clause |
|
||||||
|
| source-map-js (via css-tree) | BSD-3-Clause |
|
||||||
|
|||||||
@@ -423,10 +423,11 @@ async function detectCli() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else process.stderr.write(formatFindings(allFindings, false) + '\n');
|
else process.stderr.write(formatFindings(allFindings, false) + '\n');
|
||||||
process.exit(primary.length > 0 ? 2 : 0);
|
} else if (jsonMode) {
|
||||||
|
process.stdout.write('[]\n');
|
||||||
}
|
}
|
||||||
if (jsonMode) process.stdout.write('[]\n');
|
if (globalThis.__impeccableStaticHtmlDegraded) process.exit(1);
|
||||||
process.exit(0);
|
process.exit(primary.length > 0 ? 2 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { formatFindings, handleStdin, confirm, printUsage, detectCli };
|
export { formatFindings, handleStdin, confirm, printUsage, detectCli };
|
||||||
|
|||||||
@@ -122,12 +122,8 @@ async function detectHtml(filePath, options = {}) {
|
|||||||
ruleId: 'import-static-parser',
|
ruleId: 'import-static-parser',
|
||||||
target: filePath,
|
target: filePath,
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const [htmlparser2, cssSelect, csstree, domutils] = await Promise.all([
|
const parsers = await import(new URL('../../vendor/static-html-parsers.mjs', import.meta.url).href);
|
||||||
import('htmlparser2'),
|
const { htmlparser2, cssSelect, csstree, domutils } = parsers;
|
||||||
import('css-select'),
|
|
||||||
import('css-tree'),
|
|
||||||
import('domutils'),
|
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
parseDocument: htmlparser2.parseDocument,
|
parseDocument: htmlparser2.parseDocument,
|
||||||
selectAll: cssSelect.selectAll,
|
selectAll: cssSelect.selectAll,
|
||||||
@@ -137,21 +133,20 @@ async function detectHtml(filePath, options = {}) {
|
|||||||
domutils,
|
domutils,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch {
|
||||||
if (!globalThis.__impeccableStaticHtmlWarned) {
|
if (!globalThis.__impeccableStaticHtmlWarned) {
|
||||||
globalThis.__impeccableStaticHtmlWarned = true;
|
globalThis.__impeccableStaticHtmlWarned = true;
|
||||||
|
process.stderr.write(
|
||||||
process.stderr.write(
|
'impeccable detect: DEGRADED - HTML parser modules unavailable ' +
|
||||||
'impeccable detect: DEGRADED - HTML parser modules unavailable ' +
|
'(htmlparser2, css-select, css-tree, domutils).\n' +
|
||||||
'(htmlparser2, css-select, css-tree, domutils).\n' +
|
'Falling back to regex matching. Custom properties, selector matching and computed ' +
|
||||||
'Falling back to regex matching. Custom properties, selector matching and computed ' +
|
'contrast are NOT evaluated; findings are an undercount, not a clean bill of health.\n',
|
||||||
'contrast are NOT evaluated; findings are an undercount, not a clean bill of health.\n'
|
);
|
||||||
);
|
}
|
||||||
|
globalThis.__impeccableStaticHtmlDegraded = true;
|
||||||
|
return detectText(html, filePath, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectText(html, filePath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedPath = path.resolve(filePath);
|
const resolvedPath = path.resolve(filePath);
|
||||||
const fileDir = path.dirname(resolvedPath);
|
const fileDir = path.dirname(resolvedPath);
|
||||||
const root = profileStep(profile, {
|
const root = profileStep(profile, {
|
||||||
|
|||||||
+9592
File diff suppressed because one or more lines are too long
+3
-2
@@ -42,9 +42,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:skills": "bun run scripts/build.js --skip-root-sync",
|
"build:skills": "bun run scripts/build.js --skip-root-sync",
|
||||||
"build:skills:release": "bun run scripts/build.js",
|
"build:skills:release": "bun run scripts/build.js",
|
||||||
"build": "bun run build:skills && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
"build": "node scripts/build-static-html-parsers.js --check && bun run build:skills && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
||||||
"build:release": "bun run build:skills:release && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
"build:release": "node scripts/build-static-html-parsers.js --check && bun run build:skills:release && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
||||||
"build:browser": "node scripts/build-browser-detector.js",
|
"build:browser": "node scripts/build-browser-detector.js",
|
||||||
|
"build:static-html-parsers": "node scripts/build-static-html-parsers.js",
|
||||||
"build:extension": "node scripts/build-extension.js",
|
"build:extension": "node scripts/build-extension.js",
|
||||||
"clean": "rm -rf dist build",
|
"clean": "rm -rf dist build",
|
||||||
"rebuild": "bun run clean && bun run build",
|
"rebuild": "bun run clean && bun run build",
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates cli/engine/vendor/static-html-parsers.mjs
|
||||||
|
* by bundling htmlparser2, css-select, css-tree, and domutils for skill/plugin installs.
|
||||||
|
*
|
||||||
|
* Run: node scripts/build-static-html-parsers.js
|
||||||
|
* Check: node scripts/build-static-html-parsers.js --check
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const ROOT = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
const ENTRY = path.join(__dirname, 'lib/static-html-parsers.entry.mjs');
|
||||||
|
const OUT_DIR = path.join(ROOT, 'cli/engine/vendor');
|
||||||
|
const OUTPUT = path.join(OUT_DIR, 'static-html-parsers.mjs');
|
||||||
|
const PARSER_PACKAGES = ['htmlparser2', 'css-select', 'css-tree', 'domutils'];
|
||||||
|
const DIGEST_RE = /^\s*\* Source digest: ([0-9a-f]+)\s*$/m;
|
||||||
|
|
||||||
|
function sourceDigest() {
|
||||||
|
const hash = createHash('sha256');
|
||||||
|
hash.update(fs.readFileSync(ENTRY));
|
||||||
|
hash.update('\n');
|
||||||
|
for (const name of PARSER_PACKAGES) {
|
||||||
|
const pkgPath = path.join(ROOT, 'node_modules', name, 'package.json');
|
||||||
|
const version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
||||||
|
hash.update(`${name}@${version}\n`);
|
||||||
|
}
|
||||||
|
return hash.digest('hex').slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function header(digest) {
|
||||||
|
return `/**
|
||||||
|
* GENERATED -- do not edit. Source: scripts/lib/static-html-parsers.entry.mjs
|
||||||
|
* Rebuild: node scripts/build-static-html-parsers.js
|
||||||
|
* Source digest: ${digest}
|
||||||
|
*
|
||||||
|
* Bundles htmlparser2, css-select, css-tree, and domutils for skill/plugin installs.
|
||||||
|
* Third-party licenses: see NOTICE.md.
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate(outfile) {
|
||||||
|
fs.mkdirSync(path.dirname(outfile), { recursive: true });
|
||||||
|
const result = spawnSync(
|
||||||
|
'bun',
|
||||||
|
['build', ENTRY, '--outfile', outfile, '--target', 'node', '--format', 'esm'],
|
||||||
|
{ cwd: ROOT, encoding: 'utf8' },
|
||||||
|
);
|
||||||
|
if (result.status !== 0) {
|
||||||
|
process.stderr.write(result.stderr || result.stdout || 'bun build failed\n');
|
||||||
|
process.exit(result.status ?? 1);
|
||||||
|
}
|
||||||
|
const output = header(sourceDigest()) + fs.readFileSync(outfile, 'utf8');
|
||||||
|
fs.writeFileSync(outfile, output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.includes('--check')) {
|
||||||
|
const committed = fs.readFileSync(OUTPUT, 'utf8');
|
||||||
|
const found = committed.match(DIGEST_RE)?.[1];
|
||||||
|
const expected = sourceDigest();
|
||||||
|
if (found !== expected) {
|
||||||
|
process.stderr.write(
|
||||||
|
'cli/engine/vendor/static-html-parsers.mjs is stale. Run: node scripts/build-static-html-parsers.js\n',
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(OUTPUT);
|
||||||
|
console.log(`Generated ${path.relative(ROOT, OUTPUT)} (${(fs.statSync(OUTPUT).size / 1024).toFixed(1)} KB)`);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export * as htmlparser2 from 'htmlparser2';
|
||||||
|
export * as cssSelect from 'css-select';
|
||||||
|
export * as domutils from 'domutils';
|
||||||
|
import parse from 'css-tree/parser';
|
||||||
|
import generate from 'css-tree/generator';
|
||||||
|
export const csstree = { parse, generate };
|
||||||
@@ -25,7 +25,7 @@ export const SUITES = {
|
|||||||
description: 'Build, provider transforms, CLI helpers, context, and storage unit tests.',
|
description: 'Build, provider transforms, CLI helpers, context, and storage unit tests.',
|
||||||
triggers: [
|
triggers: [
|
||||||
...COMMON_INFRA_PATTERNS,
|
...COMMON_INFRA_PATTERNS,
|
||||||
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
|
/^scripts\/(?!benchmark-detector|build-browser-detector|build-static-html-parsers|build-extension|lib\/static-html-parsers\.entry)/,
|
||||||
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|generate-image|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/,
|
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|generate-image|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/,
|
||||||
/^README(\.npm)?\.md$/,
|
/^README(\.npm)?\.md$/,
|
||||||
/^cli\/bin\//,
|
/^cli\/bin\//,
|
||||||
@@ -92,7 +92,8 @@ export const SUITES = {
|
|||||||
...COMMON_INFRA_PATTERNS,
|
...COMMON_INFRA_PATTERNS,
|
||||||
/^cli\/engine\//,
|
/^cli\/engine\//,
|
||||||
/^extension\/(background|content|detector|devtools|popup|manifest\.json)/,
|
/^extension\/(background|content|detector|devtools|popup|manifest\.json)/,
|
||||||
/^scripts\/(benchmark-detector|build-browser-detector|build-extension)\.js$/,
|
/^scripts\/(benchmark-detector|build-browser-detector|build-static-html-parsers|build-extension)\.js$/,
|
||||||
|
/^scripts\/lib\/static-html-parsers\.entry\.mjs$/,
|
||||||
/^site\/(pages\/detector|public\/antipattern|data\/anti-patterns-catalog\.js)/,
|
/^site\/(pages\/detector|public\/antipattern|data\/anti-patterns-catalog\.js)/,
|
||||||
/^tests\/fixtures\/antipatterns/,
|
/^tests\/fixtures\/antipatterns/,
|
||||||
],
|
],
|
||||||
@@ -116,6 +117,7 @@ export const SUITES = {
|
|||||||
'tests/detect-cli-design-contamination.test.mjs',
|
'tests/detect-cli-design-contamination.test.mjs',
|
||||||
'tests/detect-cli-design-monorepo.test.mjs',
|
'tests/detect-cli-design-monorepo.test.mjs',
|
||||||
'tests/detect-cli-stdin-dispatch.test.mjs',
|
'tests/detect-cli-stdin-dispatch.test.mjs',
|
||||||
|
'tests/detect-static-html-skill-install.test.mjs',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { after, describe, it } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const engineDir = path.join(root, 'cli', 'engine');
|
||||||
|
const skillDetect = path.join(root, 'skill', 'scripts', 'detect.mjs');
|
||||||
|
const configDep = path.join(root, 'cli', 'lib', 'impeccable-config.mjs');
|
||||||
|
|
||||||
|
const CSS = ':root{--grad:linear-gradient(90deg,#7C3AED,#EC4899)}\n.hero h1{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}';
|
||||||
|
const HTML = '<html><head><link rel=stylesheet href=s.css></head><body><div class=hero><h1>Hi</h1></div></body></html>';
|
||||||
|
|
||||||
|
function copyDir(src, dest) {
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||||
|
const srcPath = path.join(src, entry.name);
|
||||||
|
const destPath = path.join(dest, entry.name);
|
||||||
|
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
||||||
|
else fs.copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyDetectorExternalDeps(scriptsDir) {
|
||||||
|
fs.mkdirSync(path.join(scriptsDir, 'lib'), { recursive: true });
|
||||||
|
fs.copyFileSync(configDep, path.join(scriptsDir, 'lib', 'impeccable-config.mjs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFixture(dir) {
|
||||||
|
fs.writeFileSync(path.join(dir, 's.css'), CSS);
|
||||||
|
fs.writeFileSync(path.join(dir, 'p.html'), HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runDetect(cwd, detectRel, htmlRel = 'p.html') {
|
||||||
|
const detectPath = path.join(cwd, detectRel);
|
||||||
|
return spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[detectPath, '--json', '--no-config', '--no-design-system', htmlRel],
|
||||||
|
{ cwd, encoding: 'utf8' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertFullEngine(result) {
|
||||||
|
assert.doesNotMatch(result.stderr, /DEGRADED/);
|
||||||
|
const findings = JSON.parse(result.stdout);
|
||||||
|
assert.ok(findings.some((item) => item.antipattern === 'gradient-text'));
|
||||||
|
assert.equal(result.status, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDegraded(result) {
|
||||||
|
assert.match(result.stderr, /DEGRADED/);
|
||||||
|
assert.equal(result.status, 1);
|
||||||
|
JSON.parse(result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempDirs = [];
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
for (const dir of tempDirs) fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('static HTML parsers in skill/plugin installs', () => {
|
||||||
|
it('resolves parsers from a skill-shaped scripts/ tree with no node_modules', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-skill-detect-'));
|
||||||
|
tempDirs.push(tmp);
|
||||||
|
fs.mkdirSync(path.join(tmp, 'scripts'), { recursive: true });
|
||||||
|
fs.copyFileSync(skillDetect, path.join(tmp, 'scripts', 'detect.mjs'));
|
||||||
|
copyDir(engineDir, path.join(tmp, 'scripts', 'detector'));
|
||||||
|
copyDetectorExternalDeps(path.join(tmp, 'scripts'));
|
||||||
|
writeFixture(tmp);
|
||||||
|
|
||||||
|
assertFullEngine(runDetect(tmp, path.join('scripts', 'detect.mjs')));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves parsers from a plugin cache skills/impeccable/scripts/ tree', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-plugin-detect-'));
|
||||||
|
tempDirs.push(tmp);
|
||||||
|
const scriptsDir = path.join(tmp, 'skills', 'impeccable', 'scripts');
|
||||||
|
fs.mkdirSync(scriptsDir, { recursive: true });
|
||||||
|
fs.copyFileSync(skillDetect, path.join(scriptsDir, 'detect.mjs'));
|
||||||
|
copyDir(engineDir, path.join(scriptsDir, 'detector'));
|
||||||
|
copyDetectorExternalDeps(scriptsDir);
|
||||||
|
writeFixture(tmp);
|
||||||
|
|
||||||
|
assertFullEngine(runDetect(tmp, path.join('skills', 'impeccable', 'scripts', 'detect.mjs')));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exits 1 when the vendor bundle is missing', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-skill-degraded-'));
|
||||||
|
tempDirs.push(tmp);
|
||||||
|
fs.mkdirSync(path.join(tmp, 'scripts'), { recursive: true });
|
||||||
|
fs.copyFileSync(skillDetect, path.join(tmp, 'scripts', 'detect.mjs'));
|
||||||
|
copyDir(engineDir, path.join(tmp, 'scripts', 'detector'));
|
||||||
|
copyDetectorExternalDeps(path.join(tmp, 'scripts'));
|
||||||
|
fs.unlinkSync(path.join(tmp, 'scripts', 'detector', 'vendor', 'static-html-parsers.mjs'));
|
||||||
|
writeFixture(tmp);
|
||||||
|
|
||||||
|
assertDegraded(runDetect(tmp, path.join('scripts', 'detect.mjs')));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { readSourceFiles } from '../../scripts/lib/utils.js';
|
import { readSourceFiles } from '../../scripts/lib/utils.js';
|
||||||
@@ -16,6 +17,35 @@ describe('skill detector bundle', () => {
|
|||||||
expect(scriptNames.has('detector/detect-antipatterns-browser.js')).toBe(true);
|
expect(scriptNames.has('detector/detect-antipatterns-browser.js')).toBe(true);
|
||||||
expect(scriptNames.has('detector/cli/main.mjs')).toBe(true);
|
expect(scriptNames.has('detector/cli/main.mjs')).toBe(true);
|
||||||
expect(scriptNames.has('detector/engines/static-html/detect-html.mjs')).toBe(true);
|
expect(scriptNames.has('detector/engines/static-html/detect-html.mjs')).toBe(true);
|
||||||
|
expect(scriptNames.has('detector/vendor/static-html-parsers.mjs')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('static HTML parser vendor bundle matches the source digest', () => {
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[path.join(ROOT, 'scripts/build-static-html-parsers.js'), '--check'],
|
||||||
|
{ cwd: ROOT, encoding: 'utf8' },
|
||||||
|
);
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(result.stderr || result.stdout || `--check exited ${result.status}`);
|
||||||
|
}
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('static HTML parser --check fails when the vendor bundle is stale', () => {
|
||||||
|
const vendor = path.join(ROOT, 'cli/engine/vendor/static-html-parsers.mjs');
|
||||||
|
const original = fs.readFileSync(vendor, 'utf8');
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(vendor, original.replace(/Source digest: [0-9a-f]+/, 'Source digest: deadbeefdeadbeef'));
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[path.join(ROOT, 'scripts/build-static-html-parsers.js'), '--check'],
|
||||||
|
{ cwd: ROOT, encoding: 'utf8' },
|
||||||
|
);
|
||||||
|
expect(result.status).toBe(1);
|
||||||
|
} finally {
|
||||||
|
fs.writeFileSync(vendor, original);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('critique references the bundled detector command', () => {
|
test('critique references the bundled detector command', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user