@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.
Files changed (31) hide show
  1. package/dist/analyze.js +1 -1
  2. package/dist/cli.js +1 -1
  3. package/dist/create.js +1 -1
  4. package/dist/generate.js +3 -2
  5. package/dist/{guardrails-CVle3CCm.js → guardrails-BVxzrcBX.js} +1 -1
  6. package/dist/openapi.js +2 -1
  7. package/dist/{registry-BVliEbUv.js → registry-BmGIkoO0.js} +2 -1
  8. package/dist/{runner-CYvonJ0C.js → runner-D2iNzz9g.js} +72 -6
  9. package/dist/{runner-u214Evez.js → runner-N2qHCBar.js} +2 -2
  10. package/dist/{skills-CSGdAZHN.js → skills-C72Z3-Cr.js} +2 -2
  11. package/dist/skills.js +1 -1
  12. package/dist/ssg-config.d.ts +2 -0
  13. package/dist/ssg.js +37 -5
  14. package/dist/templates/full-stack/package.json +1 -1
  15. package/dist/templates/spa/package.json +1 -1
  16. package/dist/templates/spa/tests/app.test.tsx +1 -1
  17. package/dist/templates/spa/tests/components/shared.test.tsx +1 -1
  18. package/dist/templates/spa/tests/resources.test.ts +2 -2
  19. package/dist/templates/ssg/package.json +1 -1
  20. package/dist/templates/ssg/tests/app.test.tsx +1 -1
  21. package/dist/templates/ssg/tests/components/counter.test.tsx +1 -1
  22. package/dist/templates/ssg/tests/resources.test.ts +1 -1
  23. package/dist/templates/ssg/tests/ssg-config.test.ts +1 -1
  24. package/dist/templates/ssr/package.json +1 -1
  25. package/dist/templates/ssr/tests/app.test.tsx +1 -1
  26. package/dist/templates/ssr/tests/components/counter.test.tsx +1 -1
  27. package/dist/templates/ssr/tests/resources.test.ts +1 -1
  28. package/dist/templates/startkit/package.json +1 -1
  29. package/dist/templates/startkit/tests/app.test.tsx +3 -3
  30. package/dist/update.js +1 -1
  31. 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-CYvonJ0C.js")).runAnalysis)({
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-CVle3CCm.js");
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-CSGdAZHN.js";
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 ?? 5 * 1024 * 1024),
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 inputWithinOutput = relative(resolve(output), resolvedInput);
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-u214Evez.js");
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: path.dirname(fileURLToPath(import.meta.resolve("@npmcli/config/package.json"))),
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 identifierIsReadAsValue(node) {
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) && parent.expression === node) return false;
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.isJsxExpression(parent)) return !ts.isJsxAttribute(parent.parent);
424
- return ts.isReturnStatement(parent);
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-CSGdAZHN.js";
1
+ import { t as inspectBundledSkills } from "./skills-C72Z3-Cr.js";
2
2
  import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
3
- import { analysisHasBlockingFindings, runAnalysis } from "./runner-CYvonJ0C.js";
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 = 2 * 1024 * 1024;
320
- const MAX_REVIEW_TOTAL_BYTES = 20 * 1024 * 1024;
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-CSGdAZHN.js";
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 };
@@ -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 = 50 * 1024 * 1024;
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
- return decodeURIComponent(resolved.pathname).replace(/^\/+/, "");
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) => references.map((reference) => localReference(reference, inspection.filePath)).filter((reference) => Boolean(reference)).map((reference) => assetMap.get(reference)).filter((asset) => asset?.type === type).sort((left, right) => left.path.localeCompare(right.path));
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
@@ -30,7 +30,7 @@
30
30
  "@askrjs/vite": ">=0.0.6 <0.1.0",
31
31
  "@types/node": "^20.19.0",
32
32
  "jsdom": "^29.1.1",
33
- "typescript": "^6.0.3",
33
+ "typescript": "^7.0.2",
34
34
  "vite-plus": "^0.2.4",
35
35
  "vitest": "^4.1.10"
36
36
  },
@@ -26,7 +26,7 @@
26
26
  "@askrjs/vite": ">=0.0.6 <0.1.0",
27
27
  "@types/node": "^20.19.0",
28
28
  "jsdom": "^29.1.1",
29
- "typescript": "^6.0.3",
29
+ "typescript": "^7.0.2",
30
30
  "vite-plus": "^0.2.4"
31
31
  },
32
32
  "engines": {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vite-plus/test';
2
2
 
3
3
  describe('SPA template structure', () => {
4
- it('documents the route-first shell and theme layers the app should keep', () => {
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('exports small app-local wrappers around theme primitives', () => {
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('loads the dashboard snapshot through the feature query boundary', async () => {
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('keeps cancellation owned by the adapter', async () => {
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
 
@@ -27,7 +27,7 @@
27
27
  "@askrjs/vite": ">=0.0.6 <0.1.0",
28
28
  "@types/node": "^20.19.0",
29
29
  "jsdom": "^29.1.1",
30
- "typescript": "^6.0.3",
30
+ "typescript": "^7.0.2",
31
31
  "vite-plus": "^0.2.4"
32
32
  },
33
33
  "engines": {
@@ -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('exposes the workflow-focused navigation order', () => {
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('renders the initial hydration check view', () => {
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('creates a resource for fetching user data', () => {
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('keeps the sample route tree explicit', () => {
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());
@@ -26,7 +26,7 @@
26
26
  "@types/node": "^20.19.0",
27
27
  "jsdom": "^29.1.1",
28
28
  "tsx": "^4.23.1",
29
- "typescript": "^6.0.3",
29
+ "typescript": "^7.0.2",
30
30
  "vite-plus": "^0.2.4"
31
31
  },
32
32
  "engines": {
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from 'vite-plus/test';
2
2
 
3
3
  describe('App Component', () => {
4
- it('renders layout with navigation', () => {
4
+ it('should render layout with navigation', () => {
5
5
  const app = (
6
6
  <div>
7
7
  <header>
@@ -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('renders its initial state during SSR', () => {
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('creates a resource for fetching user data', () => {
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();
@@ -27,7 +27,7 @@
27
27
  "@askrjs/vite": ">=0.0.6 <0.1.0",
28
28
  "@types/node": "^20.19.0",
29
29
  "jsdom": "^29.1.1",
30
- "typescript": "^6.0.3",
30
+ "typescript": "^7.0.2",
31
31
  "vite-plus": "^0.2.4"
32
32
  },
33
33
  "engines": {
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from 'vite-plus/test';
2
2
 
3
3
  describe('App Component', () => {
4
- it('exposes the current starter route set', () => {
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('uses the updated primary nav labels', () => {
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('documents the starter appearance presets', () => {
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-BVliEbUv.js");
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.21",
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.5",
64
- "typescript": "^6.0.3"
63
+ "tsx": "^4.23.11",
64
+ "typescript": "npm:@typescript/typescript6@^6.0.2"
65
65
  },
66
66
  "devDependencies": {
67
- "@askrjs/askr": ">=0.0.85 <0.1.0",
68
- "@askrjs/charts": ">=0.1.4 <0.2.0",
69
- "@askrjs/logos": ">=0.0.5 <0.1.0",
70
- "@askrjs/lucide": ">=0.0.6 <0.1.0",
71
- "@askrjs/themes": ">=0.0.21 <0.1.0",
72
- "@askrjs/ui": ">=0.0.24 <0.1.0",
73
- "@askrjs/vite": ">=0.0.11 <0.1.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.1.2",
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.22",
79
- "vite-plus": "0.2.7",
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.85 <0.1.0"
84
+ "@askrjs/askr": ">=0.0.89 <0.1.0"
84
85
  },
85
86
  "peerDependenciesMeta": {
86
87
  "@askrjs/askr": {