52 lines
2.6 KiB
JavaScript
52 lines
2.6 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
export const GENERATED_ROOT = 'test/qkforce';
|
|
export const GENERATED_MANIFEST = `${GENERATED_ROOT}/manifest.json`;
|
|
const fail = message => { throw new Error(`Invalid QKForce generated workspace: ${message}`); };
|
|
|
|
function localFile(root, relative) {
|
|
if (typeof relative !== 'string' || !relative.startsWith(`${GENERATED_ROOT}/`)
|
|
|| relative.includes('\\') || relative.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
fail('unsafe generated file path');
|
|
}
|
|
let current = path.resolve(root);
|
|
for (const segment of relative.split('/')) {
|
|
current = path.join(current, segment);
|
|
let stat;
|
|
try { stat = fs.lstatSync(current); } catch { fail('declared generated file is missing'); }
|
|
if (stat.isSymbolicLink()) fail('generated paths must not contain symlinks');
|
|
}
|
|
if (!fs.statSync(current).isFile()) fail('declared generated input must be a file');
|
|
return current;
|
|
}
|
|
|
|
export function loadGeneratedWorkspace(root = process.cwd()) {
|
|
const manifestPath = path.join(root, GENERATED_MANIFEST);
|
|
if (!fs.existsSync(manifestPath)) {
|
|
// A local template checkout may run the example. An API package never may.
|
|
if (fs.existsSync(path.join(root, 'qkforce-package-manifest.json'))) fail('regenerate the project before execution');
|
|
return null;
|
|
}
|
|
let manifest;
|
|
try { manifest = JSON.parse(fs.readFileSync(localFile(root, GENERATED_MANIFEST), 'utf8')); }
|
|
catch { fail('generated manifest is not readable JSON'); }
|
|
if (manifest?.schemaVersion !== 1 || !Array.isArray(manifest.features)
|
|
|| manifest.features.length === 0) fail('select at least one project test case');
|
|
if (new Set(manifest.features).size !== manifest.features.length) fail('duplicate feature paths');
|
|
for (const file of manifest.features) {
|
|
if (typeof file !== 'string' || !file.startsWith(`${GENERATED_ROOT}/features/`) || !file.endsWith('.feature')) fail('invalid feature path');
|
|
localFile(root, file);
|
|
}
|
|
if (manifest.steps !== `${GENERATED_ROOT}/steps.mjs` || manifest.selectors !== `${GENERATED_ROOT}/selectors.json`) fail('unsupported generated module paths');
|
|
localFile(root, manifest.steps);
|
|
localFile(root, manifest.selectors);
|
|
return Object.freeze({ specs: manifest.features.map(file => `./${file}`), imports: [`./${manifest.steps}`] });
|
|
}
|
|
|
|
export function loadGeneratedSelectors(root = process.cwd()) {
|
|
const value = JSON.parse(fs.readFileSync(localFile(root, `${GENERATED_ROOT}/selectors.json`), 'utf8'));
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) fail('selectors must be an object');
|
|
return value;
|
|
}
|