@askrjs/cli 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyze.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/create.js +1 -1
- package/dist/generate.js +3 -2
- package/dist/{guardrails-CVle3CCm.js → guardrails-BVxzrcBX.js} +1 -1
- package/dist/openapi.js +2 -1
- package/dist/{registry-BVliEbUv.js → registry-BmGIkoO0.js} +2 -1
- package/dist/{runner-CYvonJ0C.js → runner-D2iNzz9g.js} +72 -6
- package/dist/{runner-u214Evez.js → runner-N2qHCBar.js} +2 -2
- package/dist/{skills-CSGdAZHN.js → skills-C72Z3-Cr.js} +2 -2
- package/dist/skills.js +1 -1
- package/dist/ssg-config.d.ts +2 -0
- package/dist/ssg.js +37 -5
- package/dist/templates/full-stack/package.json +1 -1
- package/dist/templates/spa/package.json +1 -1
- package/dist/templates/spa/tests/app.test.tsx +1 -1
- package/dist/templates/spa/tests/components/shared.test.tsx +1 -1
- package/dist/templates/spa/tests/resources.test.ts +2 -2
- package/dist/templates/ssg/package.json +1 -1
- package/dist/templates/ssg/tests/app.test.tsx +1 -1
- package/dist/templates/ssg/tests/components/counter.test.tsx +1 -1
- package/dist/templates/ssg/tests/resources.test.ts +1 -1
- package/dist/templates/ssg/tests/ssg-config.test.ts +1 -1
- package/dist/templates/ssr/package.json +1 -1
- package/dist/templates/ssr/tests/app.test.tsx +1 -1
- package/dist/templates/ssr/tests/components/counter.test.tsx +1 -1
- package/dist/templates/ssr/tests/resources.test.ts +1 -1
- package/dist/templates/startkit/package.json +1 -1
- package/dist/templates/startkit/tests/app.test.tsx +3 -3
- package/dist/update.js +1 -1
- package/package.json +15 -14
package/dist/analyze.js
CHANGED
|
@@ -71,7 +71,7 @@ async function runAnalyzeCli(args = process.argv.slice(2), io = console, runtime
|
|
|
71
71
|
io.log(helpText.trimEnd());
|
|
72
72
|
return 0;
|
|
73
73
|
}
|
|
74
|
-
const report = await (runtime.analyze ?? (await import("./runner-
|
|
74
|
+
const report = await (runtime.analyze ?? (await import("./runner-D2iNzz9g.js")).runAnalysis)({
|
|
75
75
|
cwd: parsed.cwd,
|
|
76
76
|
workspacePatterns: parsed.workspacePatterns,
|
|
77
77
|
check: parsed.check
|
package/dist/cli.js
CHANGED
|
@@ -96,7 +96,7 @@ async function runCli(args = process.argv.slice(2), io = console) {
|
|
|
96
96
|
return runAnalyzeCli(args.slice(1), io);
|
|
97
97
|
}
|
|
98
98
|
if (command === "check" || command === "doctor" || command === "repair") {
|
|
99
|
-
const { runGuardrailCli } = await import("./guardrails-
|
|
99
|
+
const { runGuardrailCli } = await import("./guardrails-BVxzrcBX.js");
|
|
100
100
|
return runGuardrailCli(command, args.slice(1), io);
|
|
101
101
|
}
|
|
102
102
|
if (command === "generate") {
|
package/dist/create.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as isDirectExecution } from "./is-direct-execution-Cdlr-ZUl.js";
|
|
3
|
-
import { n as installBundledSkills } from "./skills-
|
|
3
|
+
import { n as installBundledSkills } from "./skills-C72Z3-Cr.js";
|
|
4
4
|
import { n as publishStagedDirectory, t as createSiblingStage } from "./directory-swap-DWoHtx7C.js";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
package/dist/generate.js
CHANGED
|
@@ -419,7 +419,7 @@ async function loadOpenApi(input, options = {}) {
|
|
|
419
419
|
return parsed.origin;
|
|
420
420
|
})),
|
|
421
421
|
allowPrivateHosts: options.allowPrivateHosts ?? false,
|
|
422
|
-
maxBytes: positiveOption("maxBytes", options.maxBytes ??
|
|
422
|
+
maxBytes: positiveOption("maxBytes", options.maxBytes ?? 5242880),
|
|
423
423
|
maxDepth: positiveOption("maxDepth", options.maxDepth ?? 32),
|
|
424
424
|
maxRedirects: positiveOption("maxRedirects", options.maxRedirects ?? 5),
|
|
425
425
|
timeoutMs: positiveOption("timeoutMs", options.timeoutMs ?? 1e4),
|
|
@@ -604,7 +604,8 @@ async function writeGenerated(directory, files, check) {
|
|
|
604
604
|
async function generate(input, output, check = false, loadOptions = {}) {
|
|
605
605
|
if (!/^https?:\/\//.test(input)) {
|
|
606
606
|
const resolvedInput = resolve(input);
|
|
607
|
-
const
|
|
607
|
+
const resolvedOutput = resolve(output);
|
|
608
|
+
const inputWithinOutput = relative(resolvedOutput, resolvedInput);
|
|
608
609
|
if (!inputWithinOutput.startsWith("..") && !isAbsolute(inputWithinOutput)) throw new GenerationError("Generated output must not contain its OpenAPI input document");
|
|
609
610
|
}
|
|
610
611
|
const files = generateFiles(await loadOpenApi(input, loadOptions));
|
|
@@ -98,7 +98,7 @@ async function runGuardrailCli(command, args = process.argv.slice(2), io = conso
|
|
|
98
98
|
cwd: parsed.cwd,
|
|
99
99
|
workspacePatterns: parsed.workspacePatterns
|
|
100
100
|
};
|
|
101
|
-
const { runCheck, runDoctor, runRepair } = await import("./runner-
|
|
101
|
+
const { runCheck, runDoctor, runRepair } = await import("./runner-N2qHCBar.js");
|
|
102
102
|
const report = command === "doctor" ? await runDoctor(options, runtime) : command === "repair" ? await runRepair(options) : await runCheck(options, runtime);
|
|
103
103
|
if (parsed.json) io.log(JSON.stringify(report));
|
|
104
104
|
else if (report.command === "doctor") printDoctor(report, io);
|
package/dist/openapi.js
CHANGED
|
@@ -61,7 +61,8 @@ function exporterFrom(moduleValue) {
|
|
|
61
61
|
function serializeOpenApi(document) {
|
|
62
62
|
return `${dump(document, {
|
|
63
63
|
lineWidth: -1,
|
|
64
|
-
noRefs: true
|
|
64
|
+
noRefs: true,
|
|
65
|
+
quoteStyle: "double"
|
|
65
66
|
}).replace(/\n+$/, "")}\n`;
|
|
66
67
|
}
|
|
67
68
|
function validateOpenApiDocument(document) {
|
|
@@ -16,8 +16,9 @@ function executionEnvironment(env) {
|
|
|
16
16
|
async function loadNpmConfiguration(root, env = process.env) {
|
|
17
17
|
const effectiveEnvironment = executionEnvironment(env);
|
|
18
18
|
const { definitions, flatten, shorthands } = npmDefinitions;
|
|
19
|
+
const npmPath = path.dirname(fileURLToPath(import.meta.resolve("@npmcli/config/package.json")));
|
|
19
20
|
const configuration = new Config({
|
|
20
|
-
npmPath
|
|
21
|
+
npmPath,
|
|
21
22
|
definitions,
|
|
22
23
|
flatten,
|
|
23
24
|
shorthands,
|
|
@@ -413,15 +413,47 @@ function collectStateBindings(sourceFile, bindings) {
|
|
|
413
413
|
owners
|
|
414
414
|
};
|
|
415
415
|
}
|
|
416
|
-
function
|
|
416
|
+
function isCallableJsxProp(node, checker) {
|
|
417
|
+
const expression = node.parent;
|
|
418
|
+
if (!ts.isJsxExpression(expression) || !expression.expression) return false;
|
|
419
|
+
const attribute = expression.parent;
|
|
420
|
+
if (!ts.isJsxAttribute(attribute)) return false;
|
|
421
|
+
const opening = attribute.parent.parent;
|
|
422
|
+
if (!ts.isJsxOpeningLikeElement(opening)) return false;
|
|
423
|
+
const attributeName = ts.isIdentifier(attribute.name) ? attribute.name.text : ts.isJsxNamespacedName(attribute.name) && ts.isIdentifier(attribute.name.name) ? attribute.name.name.text : null;
|
|
424
|
+
if (!attributeName) return false;
|
|
425
|
+
let expected = checker.getContextualType(expression.expression);
|
|
426
|
+
if (!expected || expected.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
|
|
427
|
+
const tagType = checker.getTypeAtLocation(opening.tagName);
|
|
428
|
+
for (const signature of tagType.getCallSignatures()) {
|
|
429
|
+
const propsParameter = signature.parameters[0];
|
|
430
|
+
if (!propsParameter) continue;
|
|
431
|
+
const prop = checker.getTypeOfSymbolAtLocation(propsParameter, opening.tagName).getProperty(attributeName);
|
|
432
|
+
if (!prop) continue;
|
|
433
|
+
expected = checker.getTypeOfSymbolAtLocation(prop, attribute);
|
|
434
|
+
if (expected.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) continue;
|
|
435
|
+
if (expected.getCallSignatures().length > 0 || expected.isUnion()) break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (!expected || expected.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return false;
|
|
439
|
+
const hasCallableSignature = (type) => type.isUnion() ? type.types.some((member) => hasCallableSignature(member)) : type.getCallSignatures().length > 0;
|
|
440
|
+
return hasCallableSignature(expected);
|
|
441
|
+
}
|
|
442
|
+
function identifierIsReadAsValue(node, checker) {
|
|
417
443
|
const parent = node.parent;
|
|
418
444
|
if (ts.isCallExpression(parent) && parent.expression === node) return false;
|
|
419
|
-
if (ts.isPropertyAccessExpression(parent)
|
|
445
|
+
if (ts.isPropertyAccessExpression(parent)) {
|
|
446
|
+
if (parent.expression === node || parent.name === node) return false;
|
|
447
|
+
}
|
|
448
|
+
if (ts.isElementAccessExpression(parent) && parent.expression === node) return false;
|
|
420
449
|
if (ts.isVariableDeclaration(parent) && parent.name === node) return false;
|
|
421
450
|
if (ts.isBindingElement(parent) && parent.name === node) return false;
|
|
422
|
-
if (ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent)) return false;
|
|
423
|
-
if (ts.
|
|
424
|
-
|
|
451
|
+
if (ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent) || ts.isParameter(parent) && parent.name === node || ts.isPropertyDeclaration(parent) && parent.name === node || ts.isPropertySignature(parent) && parent.name === node || ts.isMethodDeclaration(parent) && parent.name === node || ts.isMethodSignature(parent) && parent.name === node || ts.isExportSpecifier(parent) && (parent.name === node || parent.propertyName === node) || ts.isTypeQueryNode(parent) && parent.exprName === node) return false;
|
|
452
|
+
if (ts.isJsxAttribute(parent) && parent.name === node) return false;
|
|
453
|
+
if (isCallableJsxProp(node, checker)) return false;
|
|
454
|
+
if (ts.isPropertyAssignment(parent) && parent.name === node) return false;
|
|
455
|
+
if (ts.isShorthandPropertyAssignment(parent)) return true;
|
|
456
|
+
return true;
|
|
425
457
|
}
|
|
426
458
|
const stateAccessRule = {
|
|
427
459
|
id: "askr/state-access",
|
|
@@ -434,7 +466,7 @@ const stateAccessRule = {
|
|
|
434
466
|
const state = collectStateBindings(sourceFile, sourceBindings(sourceFile));
|
|
435
467
|
visit(sourceFile, (node) => {
|
|
436
468
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && state.setters.has(node.expression.text) && node.arguments.length === 0) diagnostics.push(diagnostic(context, node.expression, this, `State setter '${node.expression.text}' is called without a value or updater.`, "Pass the next value or a functional updater."));
|
|
437
|
-
else if (ts.isIdentifier(node) && state.getters.has(node.text) && identifierIsReadAsValue(node)) diagnostics.push(diagnostic(context, node, this, `State getter '${node.text}' is used as a value instead of being called.`, `Read it with ${node.text}().`));
|
|
469
|
+
else if (ts.isIdentifier(node) && state.getters.has(node.text) && identifierIsReadAsValue(node, context.checker)) diagnostics.push(diagnostic(context, node, this, `State getter '${node.text}' is used as a value instead of being called.`, `Read it with ${node.text}().`));
|
|
438
470
|
});
|
|
439
471
|
}
|
|
440
472
|
return diagnostics;
|
|
@@ -525,6 +557,32 @@ const stableDependenciesRule = {
|
|
|
525
557
|
function jsxAttributes(node) {
|
|
526
558
|
return new Map(node.attributes.properties.flatMap((attribute) => ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) ? [[attribute.name.text, attribute]] : []));
|
|
527
559
|
}
|
|
560
|
+
function staticallyNonFunction(expression, checker) {
|
|
561
|
+
if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression) || ts.isClassExpression(expression)) return false;
|
|
562
|
+
if (ts.isStringLiteral(expression) || ts.isNumericLiteral(expression) || expression.kind === ts.SyntaxKind.TrueKeyword || expression.kind === ts.SyntaxKind.FalseKeyword || expression.kind === ts.SyntaxKind.NullKeyword || ts.isObjectLiteralExpression(expression) || ts.isArrayLiteralExpression(expression)) return true;
|
|
563
|
+
const type = checker.getTypeAtLocation(expression);
|
|
564
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) return false;
|
|
565
|
+
if (type.isUnion()) return type.types.every((member) => staticallyNonFunctionType(member));
|
|
566
|
+
return staticallyNonFunctionType(type);
|
|
567
|
+
}
|
|
568
|
+
function staticallyNonFunctionType(type) {
|
|
569
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) return false;
|
|
570
|
+
if (type.isUnion()) return type.types.some((member) => !(member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike)) && staticallyNonFunctionType(member));
|
|
571
|
+
return type.getCallSignatures().length === 0;
|
|
572
|
+
}
|
|
573
|
+
function staticallyNotTrue(expression, checker) {
|
|
574
|
+
if (expression.kind === ts.SyntaxKind.TrueKeyword) return false;
|
|
575
|
+
if (ts.isStringLiteral(expression) || ts.isNumericLiteral(expression) || expression.kind === ts.SyntaxKind.FalseKeyword || expression.kind === ts.SyntaxKind.NullKeyword || ts.isObjectLiteralExpression(expression) || ts.isArrayLiteralExpression(expression)) return true;
|
|
576
|
+
const type = checker.getTypeAtLocation(expression);
|
|
577
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) return false;
|
|
578
|
+
return staticallyNotTrueType(type, checker);
|
|
579
|
+
}
|
|
580
|
+
function staticallyNotTrueType(type, checker) {
|
|
581
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) return false;
|
|
582
|
+
if (type.isUnion()) return type.types.every((member) => staticallyNotTrueType(member, checker));
|
|
583
|
+
if (type.flags & ts.TypeFlags.BooleanLike) return (type.flags & ts.TypeFlags.BooleanLiteral) !== 0 && type === checker.getFalseType();
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
528
586
|
const forContractRule = {
|
|
529
587
|
id: "askr/for-contract",
|
|
530
588
|
category: "correctness",
|
|
@@ -543,6 +601,14 @@ const forContractRule = {
|
|
|
543
601
|
const byIndex = attributes.get("byIndex");
|
|
544
602
|
if (!by && !byIndex) diagnostics.push(diagnostic(context, node.tagName, this, "<For> requires a stable by function or explicit byIndex.", "Prefer by={(item) => item.id}; use byIndex only for positional lists."));
|
|
545
603
|
else if (by && byIndex) diagnostics.push(diagnostic(context, byIndex, this, "<For> accepts either by or byIndex, not both.", "Remove one key strategy."));
|
|
604
|
+
if (by) {
|
|
605
|
+
const expression = by.initializer && ts.isJsxExpression(by.initializer) ? by.initializer.expression : void 0;
|
|
606
|
+
if (!expression || staticallyNonFunction(expression, context.checker)) diagnostics.push(diagnostic(context, by, this, "<For> by must be a function.", "Pass by={(item) => item.id} or use byIndex for positional lists."));
|
|
607
|
+
}
|
|
608
|
+
if (byIndex?.initializer) {
|
|
609
|
+
const expression = ts.isJsxExpression(byIndex.initializer) ? byIndex.initializer.expression : void 0;
|
|
610
|
+
if (!expression || staticallyNotTrue(expression, context.checker)) diagnostics.push(diagnostic(context, byIndex, this, "<For> byIndex must be true.", "Use bare byIndex or byIndex={true} for positional lists."));
|
|
611
|
+
}
|
|
546
612
|
const element = ts.isJsxOpeningElement(node) && ts.isJsxElement(node.parent) ? node.parent : null;
|
|
547
613
|
if (ts.isJsxSelfClosingElement(node) || element && element.children.every((child) => ts.isJsxText(child) && child.text.trim().length === 0)) diagnostics.push(diagnostic(context, node.tagName, this, "<For> is missing its item renderer child.", "Provide a function child such as {(item) => <Row item={item} />}."));
|
|
548
614
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as inspectBundledSkills } from "./skills-
|
|
1
|
+
import { t as inspectBundledSkills } from "./skills-C72Z3-Cr.js";
|
|
2
2
|
import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
|
|
3
|
-
import { analysisHasBlockingFindings, runAnalysis } from "./runner-
|
|
3
|
+
import { analysisHasBlockingFindings, runAnalysis } from "./runner-D2iNzz9g.js";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -316,8 +316,8 @@ async function pathExists$1(filePath) {
|
|
|
316
316
|
return Boolean(await fs.stat(filePath).catch(() => null));
|
|
317
317
|
}
|
|
318
318
|
const MAX_REVIEW_FILES = 1e4;
|
|
319
|
-
const MAX_REVIEW_FILE_BYTES =
|
|
320
|
-
const MAX_REVIEW_TOTAL_BYTES =
|
|
319
|
+
const MAX_REVIEW_FILE_BYTES = 2097152;
|
|
320
|
+
const MAX_REVIEW_TOTAL_BYTES = 20971520;
|
|
321
321
|
function shouldReadFile(filePath) {
|
|
322
322
|
return TEXT_FILE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
323
323
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as syncBundledSkills, n as installBundledSkills, r as runSkillsCli, t as inspectBundledSkills } from "./skills-
|
|
2
|
+
import { i as syncBundledSkills, n as installBundledSkills, r as runSkillsCli, t as inspectBundledSkills } from "./skills-C72Z3-Cr.js";
|
|
3
3
|
export { inspectBundledSkills, installBundledSkills, runSkillsCli, syncBundledSkills };
|
package/dist/ssg-config.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ interface SsgOutputBudgets {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
interface SsgOutputReportConfig {
|
|
67
|
+
/** Deployment pathname prefix stripped from root-absolute asset references. */
|
|
68
|
+
basePath?: string;
|
|
67
69
|
budgets?: SsgOutputBudgets;
|
|
68
70
|
/** Number of largest pages retained in the summary. Defaults to 20. */
|
|
69
71
|
largestPages?: number;
|
package/dist/ssg.js
CHANGED
|
@@ -23,7 +23,7 @@ const changeFrequencies = /* @__PURE__ */ new Set([
|
|
|
23
23
|
"never"
|
|
24
24
|
]);
|
|
25
25
|
const MAX_URLS_PER_SITEMAP = 5e4;
|
|
26
|
-
const MAX_SITEMAP_BYTES =
|
|
26
|
+
const MAX_SITEMAP_BYTES = 52428800;
|
|
27
27
|
const MANIFEST_PATH = ".askr/sitemap-manifest.json";
|
|
28
28
|
function escapeXml(value) {
|
|
29
29
|
return value.replace(/[&<>"']/g, (character) => {
|
|
@@ -404,7 +404,7 @@ function isReportableAsset(filePath) {
|
|
|
404
404
|
async function removeSsgOutputReport(outputDir) {
|
|
405
405
|
await fs.rm(path.join(outputDir, REPORT_PATH), { force: true });
|
|
406
406
|
}
|
|
407
|
-
function localReference(reference, documentPath) {
|
|
407
|
+
function localReference(reference, documentPath, basePath) {
|
|
408
408
|
let resolved;
|
|
409
409
|
try {
|
|
410
410
|
const portableDocumentPath = documentPath.replaceAll("\\", "/");
|
|
@@ -416,11 +416,25 @@ function localReference(reference, documentPath) {
|
|
|
416
416
|
}
|
|
417
417
|
if (resolved.origin !== "https://askr.invalid") return void 0;
|
|
418
418
|
try {
|
|
419
|
-
|
|
419
|
+
let pathname = decodeURIComponent(resolved.pathname);
|
|
420
|
+
if (basePath && reference.startsWith("/") && (pathname === basePath || pathname.startsWith(`${basePath}/`))) pathname = pathname.slice(basePath.length);
|
|
421
|
+
return pathname.replace(/^\/+/, "");
|
|
420
422
|
} catch {
|
|
421
423
|
return;
|
|
422
424
|
}
|
|
423
425
|
}
|
|
426
|
+
function deploymentBasePath(value) {
|
|
427
|
+
if (value === void 0 || value === "" || value === "/") return "";
|
|
428
|
+
if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || value.includes("?") || value.includes("#") || value.includes("\\")) throw new Error("outputReport.basePath must be an absolute URL pathname");
|
|
429
|
+
let decoded;
|
|
430
|
+
try {
|
|
431
|
+
decoded = decodeURIComponent(value);
|
|
432
|
+
} catch {
|
|
433
|
+
throw new Error("outputReport.basePath must use valid URL encoding");
|
|
434
|
+
}
|
|
435
|
+
if (decoded.split("/").some((segment) => segment === "." || segment === "..")) throw new Error("outputReport.basePath must not contain dot-segment traversal");
|
|
436
|
+
return decoded.replace(/\/+$/, "");
|
|
437
|
+
}
|
|
424
438
|
function positiveCount(value, fallback, label) {
|
|
425
439
|
const resolved = value ?? fallback;
|
|
426
440
|
if (!Number.isSafeInteger(resolved) || resolved < 1) throw new Error(`${label} must be a positive integer`);
|
|
@@ -433,6 +447,7 @@ function validateByteBudget(budget, label) {
|
|
|
433
447
|
}
|
|
434
448
|
}
|
|
435
449
|
function validateConfig(config) {
|
|
450
|
+
const basePath = deploymentBasePath(config.basePath);
|
|
436
451
|
const budgets = config.budgets;
|
|
437
452
|
validateByteBudget(budgets?.routes, "outputReport.budgets.routes");
|
|
438
453
|
for (const [route, budget] of Object.entries(budgets?.routeOverrides ?? {})) if (budget !== false) validateByteBudget(budget, `route override ${route}`);
|
|
@@ -443,6 +458,7 @@ function validateConfig(config) {
|
|
|
443
458
|
for (const [route, share] of hydrationShares) if (share !== void 0 && share !== false && (!Number.isFinite(share) || share < 0 || share > 1)) throw new Error(`hydration share for ${route} must be from 0 through 1`);
|
|
444
459
|
positiveCount(config.largestPages, 20, "outputReport.largestPages");
|
|
445
460
|
positiveCount(config.largestAssets, 20, "outputReport.largestAssets");
|
|
461
|
+
return basePath;
|
|
446
462
|
}
|
|
447
463
|
function budgetViolations(report, budgets = {}) {
|
|
448
464
|
const violations = [];
|
|
@@ -474,7 +490,7 @@ function budgetViolations(report, budgets = {}) {
|
|
|
474
490
|
return violations;
|
|
475
491
|
}
|
|
476
492
|
async function writeSsgOutputReport(outputDir, routes, inspections, config = {}) {
|
|
477
|
-
validateConfig(config);
|
|
493
|
+
const basePath = validateConfig(config);
|
|
478
494
|
const assets = [];
|
|
479
495
|
for (const filePath of (await emittedFiles(outputDir)).filter(isReportableAsset)) {
|
|
480
496
|
const measured = size(await fs.readFile(path.join(outputDir, filePath)));
|
|
@@ -486,12 +502,27 @@ async function writeSsgOutputReport(outputDir, routes, inspections, config = {})
|
|
|
486
502
|
}
|
|
487
503
|
assets.sort((left, right) => left.path.localeCompare(right.path));
|
|
488
504
|
const assetMap = new Map(assets.map((asset) => [asset.path, asset]));
|
|
505
|
+
const missingReferences = /* @__PURE__ */ new Set();
|
|
489
506
|
const outputRoutes = [];
|
|
490
507
|
for (const route of [...routes].sort((left, right) => left.path.localeCompare(right.path))) {
|
|
491
508
|
const inspection = inspections.get(route.path);
|
|
492
509
|
if (!inspection) continue;
|
|
493
510
|
const html = inspection.html;
|
|
494
|
-
const referenced = (references, type) =>
|
|
511
|
+
const referenced = (references, type) => {
|
|
512
|
+
const resolved = [];
|
|
513
|
+
for (const reference of references) {
|
|
514
|
+
const localPath = localReference(reference, inspection.filePath, basePath);
|
|
515
|
+
if (!localPath) continue;
|
|
516
|
+
const asset = assetMap.get(localPath);
|
|
517
|
+
if (!asset) {
|
|
518
|
+
missingReferences.add(`route ${route.path} references missing ${type} asset ${localPath}`);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (asset.type !== type) continue;
|
|
522
|
+
resolved.push(asset);
|
|
523
|
+
}
|
|
524
|
+
return resolved.sort((left, right) => left.path.localeCompare(right.path));
|
|
525
|
+
};
|
|
495
526
|
outputRoutes.push({
|
|
496
527
|
route: route.path,
|
|
497
528
|
filePath: inspection.filePath,
|
|
@@ -506,6 +537,7 @@ async function writeSsgOutputReport(outputDir, routes, inspections, config = {})
|
|
|
506
537
|
}
|
|
507
538
|
});
|
|
508
539
|
}
|
|
540
|
+
if (missingReferences.size > 0) throw new Error(`SSG output contains missing local assets:\n${[...missingReferences].sort().map((item) => `- ${item}`).join("\n")}`);
|
|
509
541
|
const total = (type) => assets.filter((asset) => asset.type === type).reduce((sum, asset) => ({
|
|
510
542
|
raw: sum.raw + asset.raw,
|
|
511
543
|
gzip: sum.gzip + asset.gzip
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vite-plus/test';
|
|
2
2
|
|
|
3
3
|
describe('SPA template structure', () => {
|
|
4
|
-
it('
|
|
4
|
+
it('should document the route-first shell and theme layers the app should keep', () => {
|
|
5
5
|
const structure = [
|
|
6
6
|
'src/main.tsx',
|
|
7
7
|
'src/pages/_routes.tsx',
|
|
@@ -3,7 +3,7 @@ import MetricCard from '../../src/components/shared/metric-card';
|
|
|
3
3
|
import StatusBadge from '../../src/components/shared/status-badge';
|
|
4
4
|
|
|
5
5
|
describe('shared app components', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('should export small app-local wrappers around theme primitives', () => {
|
|
7
7
|
expect(MetricCard).toBeDefined();
|
|
8
8
|
expect(StatusBadge).toBeDefined();
|
|
9
9
|
expect(typeof MetricCard).toBe('function');
|
|
@@ -3,7 +3,7 @@ import { getOperationsSnapshot } from '../src/adapters/operations-client';
|
|
|
3
3
|
import { loadOperations } from '../src/features/operations/operations.query';
|
|
4
4
|
|
|
5
5
|
describe('operations data flow', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('should load the dashboard snapshot through the feature query boundary', async () => {
|
|
7
7
|
const snapshot = await loadOperations({});
|
|
8
8
|
|
|
9
9
|
expect(snapshot.metrics.length).toBeGreaterThan(0);
|
|
@@ -11,7 +11,7 @@ describe('operations data flow', () => {
|
|
|
11
11
|
expect(snapshot.lastEventId).toMatch(/^evt_/);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
14
|
+
it('should keep cancellation owned by the adapter', async () => {
|
|
15
15
|
const controller = new AbortController();
|
|
16
16
|
const request = getOperationsSnapshot({ signal: controller.signal });
|
|
17
17
|
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vite-plus/test';
|
|
|
2
2
|
import { navItems } from '../src/app';
|
|
3
3
|
|
|
4
4
|
describe('App shell', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('should expose the workflow-focused navigation order', () => {
|
|
6
6
|
expect(navItems.map((item) => item.label)).toEqual([
|
|
7
7
|
'Home',
|
|
8
8
|
'Workflow',
|
|
@@ -3,7 +3,7 @@ import { renderToString } from '@askrjs/askr/ssr';
|
|
|
3
3
|
import Counter from '../../src/components/counter';
|
|
4
4
|
|
|
5
5
|
describe('Counter', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('should render the initial hydration check view', () => {
|
|
7
7
|
const html = renderToString(() => <Counter />);
|
|
8
8
|
|
|
9
9
|
expect(html).toContain('Hydration check');
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vite-plus/test';
|
|
|
2
2
|
import { fetchUser } from '../src/resources/user';
|
|
3
3
|
|
|
4
4
|
describe('Resources', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('should create a resource for fetching user data', () => {
|
|
6
6
|
// Resources are called during component render
|
|
7
7
|
// Here we just verify the function is defined
|
|
8
8
|
expect(fetchUser).toBeDefined();
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vite-plus/test';
|
|
|
2
2
|
import { staticConfig } from '../ssg.config';
|
|
3
3
|
|
|
4
4
|
describe('SSG config', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('should keep the sample route tree explicit', () => {
|
|
6
6
|
expect(
|
|
7
7
|
staticConfig.registry.manifest.records.map((route) => route.path).sort()
|
|
8
8
|
).toEqual(['/', '/content', '/preview', '/workflow'].sort());
|
|
@@ -3,7 +3,7 @@ import { renderToString } from '@askrjs/askr/ssr';
|
|
|
3
3
|
import Counter from '../../src/components/counter';
|
|
4
4
|
|
|
5
5
|
describe('Counter', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('should render its initial state during SSR', () => {
|
|
7
7
|
const html = renderToString(() => <Counter />);
|
|
8
8
|
|
|
9
9
|
expect(html).toContain('counter-value');
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vite-plus/test';
|
|
|
2
2
|
import { fetchUser } from '../src/resources/user';
|
|
3
3
|
|
|
4
4
|
describe('SSR Resources', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('should create a resource for fetching user data', () => {
|
|
6
6
|
// Resources are called during component render
|
|
7
7
|
// Here we just verify the function is defined
|
|
8
8
|
expect(fetchUser).toBeDefined();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vite-plus/test';
|
|
2
2
|
|
|
3
3
|
describe('App Component', () => {
|
|
4
|
-
it('
|
|
4
|
+
it('should expose the current starter route set', () => {
|
|
5
5
|
const routes = [
|
|
6
6
|
'/',
|
|
7
7
|
'/login',
|
|
@@ -16,14 +16,14 @@ describe('App Component', () => {
|
|
|
16
16
|
expect(routes).toContain('/login');
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it('
|
|
19
|
+
it('should use the updated primary nav labels', () => {
|
|
20
20
|
const navLabels = ['Dashboard', 'Accounts', 'Settings'];
|
|
21
21
|
|
|
22
22
|
expect(navLabels).toContain('Accounts');
|
|
23
23
|
expect(navLabels).toContain('Settings');
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('
|
|
26
|
+
it('should document the starter appearance presets', () => {
|
|
27
27
|
const appearanceModes = ['Default', 'Harbor', 'Ink'];
|
|
28
28
|
|
|
29
29
|
expect(appearanceModes).toContain('Harbor');
|
package/dist/update.js
CHANGED
|
@@ -193,7 +193,7 @@ function collectEdits(decisions) {
|
|
|
193
193
|
}] : []));
|
|
194
194
|
}
|
|
195
195
|
async function defaultRegistry(root, packageNames, requirements) {
|
|
196
|
-
const { fetchPackuments, loadNpmConfiguration } = await import("./registry-
|
|
196
|
+
const { fetchPackuments, loadNpmConfiguration } = await import("./registry-BmGIkoO0.js");
|
|
197
197
|
return fetchPackuments(packageNames, await loadNpmConfiguration(root), { requirements });
|
|
198
198
|
}
|
|
199
199
|
async function runDependencyCli(command, args, io = console, runtime = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askrjs/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "Unified CLI for the Askr platform",
|
|
5
5
|
"homepage": "https://github.com/askrjs/askr-cli#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -60,27 +60,28 @@
|
|
|
60
60
|
"npm-registry-fetch": "^20.0.1",
|
|
61
61
|
"parse5": "^8.0.1",
|
|
62
62
|
"semver": "^7.8.5",
|
|
63
|
-
"tsx": "^4.23.
|
|
64
|
-
"typescript": "
|
|
63
|
+
"tsx": "^4.23.11",
|
|
64
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@askrjs/askr": ">=0.0.
|
|
68
|
-
"@askrjs/charts": ">=0.1.
|
|
69
|
-
"@askrjs/logos": ">=0.0.
|
|
70
|
-
"@askrjs/lucide": ">=0.0.
|
|
71
|
-
"@askrjs/themes": ">=0.0.
|
|
72
|
-
"@askrjs/ui": ">=0.0.
|
|
73
|
-
"@askrjs/vite": ">=0.0.
|
|
67
|
+
"@askrjs/askr": ">=0.0.89 <0.1.0",
|
|
68
|
+
"@askrjs/charts": ">=0.1.6 <0.2.0",
|
|
69
|
+
"@askrjs/logos": ">=0.0.7 <0.1.0",
|
|
70
|
+
"@askrjs/lucide": ">=0.0.9 <0.1.0",
|
|
71
|
+
"@askrjs/themes": ">=0.0.24 <0.1.0",
|
|
72
|
+
"@askrjs/ui": ">=0.0.27 <0.1.0",
|
|
73
|
+
"@askrjs/vite": ">=0.0.13 <0.1.0",
|
|
74
74
|
"@types/js-yaml": "^4.0.9",
|
|
75
|
-
"@types/node": "^26.
|
|
75
|
+
"@types/node": "^26.2.0",
|
|
76
76
|
"@types/semver": "^7.8.0",
|
|
77
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
77
78
|
"@vitest/coverage-v8": "^4.1.10",
|
|
78
|
-
"publint": "^0.3.
|
|
79
|
-
"vite-plus": "0.2.
|
|
79
|
+
"publint": "^0.3.23",
|
|
80
|
+
"vite-plus": "0.2.8",
|
|
80
81
|
"vitest": "^4.1.10"
|
|
81
82
|
},
|
|
82
83
|
"peerDependencies": {
|
|
83
|
-
"@askrjs/askr": ">=0.0.
|
|
84
|
+
"@askrjs/askr": ">=0.0.89 <0.1.0"
|
|
84
85
|
},
|
|
85
86
|
"peerDependenciesMeta": {
|
|
86
87
|
"@askrjs/askr": {
|