Enhance CI configuration and documentation; add new action palette adapter
Some checks failed
CI / validate (push) Has been cancelled

This commit is contained in:
isopropilick
2026-09-15 10:32:11 -06:00
parent 896c3a121a
commit 29899223e4
12 changed files with 296 additions and 11 deletions

View File

@@ -0,0 +1,51 @@
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;
}