feat(graph-api): Add schema review notes for upcoming Graph API changes
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat(sbomservice): Add placeholder for SHA256SUMS in LNM v1 fixtures docs(devportal): Create README for SDK archives in public directory build(devportal): Implement offline bundle build script test(devportal): Add link checker script for validating links in documentation test(devportal): Create performance check script for dist folder size test(devportal): Implement accessibility check script using Playwright and Axe docs(devportal): Add SDK quickstart guide with examples for Node.js, Python, and cURL feat(excititor): Implement MongoDB storage for airgap import records test(findings): Add unit tests for export filters hash determinism feat(findings): Define attestation contracts for ledger web service feat(graph): Add MongoDB options and service collection extensions for graph indexing test(graph): Implement integration tests for MongoDB provider and service collection extensions feat(zastava): Define configuration options for Zastava surface secrets build(tests): Create script to run Concelier linkset tests with TRX output
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const moduleRoot = path.resolve(__dirname, '..');
|
||||
const outDir = path.join(moduleRoot, 'dist');
|
||||
const bundleDir = path.join(moduleRoot, 'out');
|
||||
const bundleFile = path.join(bundleDir, 'devportal-offline.tar.gz');
|
||||
const specPath = path.join(moduleRoot, 'public', 'api', 'stella.yaml');
|
||||
const sdkDir = path.join(moduleRoot, 'public', 'sdk');
|
||||
|
||||
function ensureSpec() {
|
||||
if (!fs.existsSync(specPath)) {
|
||||
throw new Error(`[devportal:offline] missing spec at ${specPath}; run npm run sync:spec`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSdkFolder() {
|
||||
if (!fs.existsSync(sdkDir)) {
|
||||
fs.mkdirSync(sdkDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(sdkDir, 'README.txt'),
|
||||
'Place SDK archives here (e.g., stellaops-sdk-node-vX.Y.Z.tgz, stellaops-sdk-python-vX.Y.Z.tar.gz).\n'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function runBuild() {
|
||||
console.log('[devportal:offline] running astro build');
|
||||
execFileSync('npm', ['run', 'build'], { stdio: 'inherit', cwd: moduleRoot });
|
||||
}
|
||||
|
||||
function packageBundle() {
|
||||
fs.mkdirSync(bundleDir, { recursive: true });
|
||||
if (fs.existsSync(bundleFile)) {
|
||||
fs.rmSync(bundleFile);
|
||||
}
|
||||
const args = [
|
||||
'--sort=name',
|
||||
'--mtime', '@0',
|
||||
'--owner', '0',
|
||||
'--group', '0',
|
||||
'--numeric-owner',
|
||||
'-czf', bundleFile,
|
||||
'-C', moduleRoot,
|
||||
'dist',
|
||||
'public/api/stella.yaml',
|
||||
'public/sdk'
|
||||
];
|
||||
console.log(`[devportal:offline] creating ${bundleFile}`);
|
||||
execFileSync('tar', args, { stdio: 'inherit' });
|
||||
const size = (fs.statSync(bundleFile).size / 1024 / 1024).toFixed(2);
|
||||
console.log(`[devportal:offline] bundle ready (${size} MiB)`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
ensureSpec();
|
||||
ensureSdkFolder();
|
||||
runBuild();
|
||||
packageBundle();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { setTimeout as wait } from 'node:timers/promises';
|
||||
import { LinkChecker } from 'linkinator';
|
||||
|
||||
const HOST = process.env.DEVPORT_HOST ?? '127.0.0.1';
|
||||
const PORT = process.env.DEVPORT_PORT ?? '4321';
|
||||
const BASE = `http://${HOST}:${PORT}`;
|
||||
|
||||
async function startPreview() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn('npm', ['run', 'preview', '--', '--host', HOST, '--port', PORT], {
|
||||
cwd: new URL('..', import.meta.url).pathname,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
child.once('error', reject);
|
||||
resolve(child);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForServer() {
|
||||
const url = `${BASE}/`;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
try {
|
||||
const res = await fetch(url, { method: 'GET' });
|
||||
if (res.ok) return;
|
||||
} catch {
|
||||
// keep polling
|
||||
}
|
||||
await wait(500);
|
||||
}
|
||||
throw new Error('Preview server did not become ready');
|
||||
}
|
||||
|
||||
async function checkLinks() {
|
||||
const checker = new LinkChecker();
|
||||
const failures = [];
|
||||
|
||||
checker.on('link', (event) => {
|
||||
if (event.state !== 'BROKEN') return;
|
||||
failures.push({ url: event.url, status: event.status });
|
||||
});
|
||||
|
||||
await checker.check({ path: BASE, recurse: true, maxDepth: 3, concurrency: 16, skip: [/mailto:/, /tel:/] });
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('[links] broken links found');
|
||||
failures.forEach((f) => console.error(`- ${f.status} ${f.url}`));
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('[links] no broken links detected');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const server = await startPreview();
|
||||
try {
|
||||
await waitForServer();
|
||||
await checkLinks();
|
||||
} finally {
|
||||
server.kill('SIGINT');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const moduleRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
||||
const distDir = path.join(moduleRoot, 'dist');
|
||||
|
||||
function folderSize(dir) {
|
||||
let total = 0;
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
total += folderSize(full);
|
||||
} else {
|
||||
total += fs.statSync(full).size;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function largestFile(dir) {
|
||||
let max = { size: 0, file: '' };
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
const child = largestFile(full);
|
||||
if (child.size > max.size) max = child;
|
||||
} else {
|
||||
const size = fs.statSync(full).size;
|
||||
if (size > max.size) {
|
||||
max = { size, file: full };
|
||||
}
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
function formatMB(bytes) {
|
||||
return (bytes / 1024 / 1024).toFixed(2);
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(distDir)) {
|
||||
console.error('[budget] dist/ not found; run `npm run build` first');
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const total = folderSize(distDir);
|
||||
const largest = largestFile(distDir);
|
||||
|
||||
const budgetTotal = 30 * 1024 * 1024; // 30 MB
|
||||
const budgetSingle = 1 * 1024 * 1024; // 1 MB
|
||||
|
||||
console.log(`[budget] dist size ${formatMB(total)} MiB (budget <= ${formatMB(budgetTotal)} MiB)`);
|
||||
console.log(`[budget] largest file ${formatMB(largest.size)} MiB -> ${path.relative(moduleRoot, largest.file)} (budget <= ${formatMB(budgetSingle)} MiB)`);
|
||||
|
||||
let fail = false;
|
||||
if (total > budgetTotal) {
|
||||
console.error('[budget] total size exceeds budget');
|
||||
fail = true;
|
||||
}
|
||||
if (largest.size > budgetSingle) {
|
||||
console.error('[budget] single-asset size exceeds budget');
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('[budget] budgets satisfied');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
81
src/DevPortal/StellaOps.DevPortal.Site/scripts/run-a11y.mjs
Normal file
81
src/DevPortal/StellaOps.DevPortal.Site/scripts/run-a11y.mjs
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { setTimeout as wait } from 'node:timers/promises';
|
||||
import { chromium } from 'playwright';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
|
||||
const HOST = process.env.DEVPORT_HOST ?? '127.0.0.1';
|
||||
const PORT = process.env.DEVPORT_PORT ?? '4321';
|
||||
const BASE = `http://${HOST}:${PORT}`;
|
||||
const PAGES = ['/docs/', '/docs/api-reference/', '/docs/try-it-console/'];
|
||||
|
||||
async function startPreview() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn('npm', ['run', 'preview', '--', '--host', HOST, '--port', PORT], {
|
||||
cwd: new URL('..', import.meta.url).pathname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
child.once('error', reject);
|
||||
resolve(child);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForServer() {
|
||||
const url = `${BASE}/`;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
try {
|
||||
const res = await fetch(url, { method: 'GET' });
|
||||
if (res.ok) return;
|
||||
} catch (err) {
|
||||
// keep polling
|
||||
}
|
||||
await wait(500);
|
||||
}
|
||||
throw new Error('Preview server did not become ready');
|
||||
}
|
||||
|
||||
async function runA11y() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
const violationsAll = [];
|
||||
|
||||
for (const path of PAGES) {
|
||||
const url = `${BASE}${path}`;
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
const axe = new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']);
|
||||
const results = await axe.analyze();
|
||||
if (results.violations.length > 0) {
|
||||
violationsAll.push({ path, violations: results.violations });
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
|
||||
if (violationsAll.length > 0) {
|
||||
console.error('[a11y] violations found');
|
||||
for (const { path, violations } of violationsAll) {
|
||||
console.error(`- ${path}`);
|
||||
violations.forEach((v) => {
|
||||
console.error(` • ${v.id}: ${v.description}`);
|
||||
});
|
||||
}
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('[a11y] no violations detected');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const server = await startPreview();
|
||||
try {
|
||||
await waitForServer();
|
||||
await runA11y();
|
||||
} finally {
|
||||
server.kill('SIGINT');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user