@jay-framework/jay-stack-cli 0.25.5 → 0.26.0
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/agent-kit-template/designer/contracts-and-plugins.md +14 -0
- package/agent-kit-template/designer/validation-guide.md +26 -10
- package/agent-kit-template/developer/INSTRUCTIONS.md +19 -18
- package/agent-kit-template/developer/configuration.md +4 -0
- package/agent-kit-template/developer/page-components.md +9 -6
- package/agent-kit-template/developer/query-param-pages.md +426 -0
- package/agent-kit-template/developer/render-results.md +28 -1
- package/agent-kit-template/devops/INSTRUCTIONS.md +17 -6
- package/agent-kit-template/devops/fetch-handler.md +21 -0
- package/agent-kit-template/devops/invalidation.md +2 -7
- package/agent-kit-template/devops/production-build.md +8 -3
- package/agent-kit-template/devops/serving-modes.md +25 -0
- package/agent-kit-template/devops/source-editing.md +89 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +25 -24
- package/agent-kit-template/plugin/actions-guide.md +57 -0
- package/agent-kit-template/plugin/contracts-guide.md +42 -0
- package/agent-kit-template/plugin/render-results.md +19 -0
- package/agent-kit-template/plugin/source-editing.md +70 -0
- package/agent-kit-template/plugin/validation.md +36 -0
- package/dist/index.js +332 -247
- package/package.json +16 -16
- package/dist/index-_Gyf630q.js +0 -1812
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import YAML from "yaml";
|
|
|
9
9
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
10
10
|
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, compileContract, parseAction, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
11
11
|
import { checkValidationErrors, JAY_CONTRACT_EXTENSION as JAY_CONTRACT_EXTENSION$1, loadPluginManifest as loadPluginManifest$1, normalizeActionEntry as normalizeActionEntry$1, JAY_EXTENSION as JAY_EXTENSION$1, RuntimeMode as RuntimeMode$1, GenerateTarget, findDynamicContract, isEnumType as isEnumType$1, equalJayTypes, prettifyHtml } from "@jay-framework/compiler-shared";
|
|
12
|
-
import fs$
|
|
12
|
+
import fs$1$1 from "node:fs/promises";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import path__default from "node:path";
|
|
15
15
|
import { scanPlugins, sortPluginsByDependencies, getServiceRegistry, actionRegistry, resolveServices, headMetaToHeadTags, ActionRegistry, discoverPluginsWithInit, executePluginServerInits, runInitCallbacks, registerService } from "@jay-framework/stack-server-runtime";
|
|
@@ -26,6 +26,7 @@ import chalk from "chalk";
|
|
|
26
26
|
import { glob } from "glob";
|
|
27
27
|
import postcss from "postcss";
|
|
28
28
|
import { scanRoutes, JayRouteParamType } from "@jay-framework/stack-route-scanner";
|
|
29
|
+
import { FilesystemArtifactStore } from "@jay-framework/production-server";
|
|
29
30
|
import { fileURLToPath } from "node:url";
|
|
30
31
|
import { select, confirm, input } from "@inquirer/prompts";
|
|
31
32
|
const DEFAULT_CONFIG = {
|
|
@@ -3312,7 +3313,7 @@ async function validateSchema(context, result) {
|
|
|
3312
3313
|
}
|
|
3313
3314
|
}
|
|
3314
3315
|
const hasCapability = Boolean(
|
|
3315
|
-
manifest.contracts || manifest.dynamic_contracts || manifest.actions || manifest.validators || manifest.routes || manifest.services || manifest.contexts || manifest.init || manifest.setup || manifest.agentkit || manifest.commands
|
|
3316
|
+
manifest.contracts || manifest.dynamic_contracts || manifest.actions || manifest.validators || manifest.projectValidators || manifest.routes || manifest.services || manifest.contexts || manifest.init || manifest.setup || manifest.agentkit || manifest.commands
|
|
3316
3317
|
);
|
|
3317
3318
|
if (!hasCapability) {
|
|
3318
3319
|
if (manifest.global === true) {
|
|
@@ -3328,7 +3329,7 @@ async function validateSchema(context, result) {
|
|
|
3328
3329
|
} else {
|
|
3329
3330
|
result.warnings.push({
|
|
3330
3331
|
type: "schema",
|
|
3331
|
-
message: "Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, routes, services, contexts, init, setup, agentkit, commands)",
|
|
3332
|
+
message: "Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, projectValidators, routes, services, contexts, init, setup, agentkit, commands)",
|
|
3332
3333
|
location: "plugin.yaml",
|
|
3333
3334
|
suggestion: "Declare at least one capability. See agent-kit/plugin/plugin-structure.md"
|
|
3334
3335
|
});
|
|
@@ -3518,6 +3519,43 @@ async function validateSchema(context, result) {
|
|
|
3518
3519
|
});
|
|
3519
3520
|
}
|
|
3520
3521
|
}
|
|
3522
|
+
if (manifest.projectValidators) {
|
|
3523
|
+
if (!Array.isArray(manifest.projectValidators)) {
|
|
3524
|
+
result.errors.push({
|
|
3525
|
+
type: "schema",
|
|
3526
|
+
message: 'Field "projectValidators" must be an array',
|
|
3527
|
+
location: "plugin.yaml"
|
|
3528
|
+
});
|
|
3529
|
+
} else {
|
|
3530
|
+
manifest.projectValidators.forEach((validator, index) => {
|
|
3531
|
+
if (!validator.name) {
|
|
3532
|
+
result.errors.push({
|
|
3533
|
+
type: "schema",
|
|
3534
|
+
message: `Project validator at index ${index} is missing "name" field`,
|
|
3535
|
+
location: "plugin.yaml"
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
if (!validator.handler) {
|
|
3539
|
+
result.errors.push({
|
|
3540
|
+
type: "schema",
|
|
3541
|
+
message: `Project validator "${validator.name || index}" is missing "handler" field`,
|
|
3542
|
+
location: "plugin.yaml",
|
|
3543
|
+
suggestion: "Specify the relative path to the project validator handler module"
|
|
3544
|
+
});
|
|
3545
|
+
}
|
|
3546
|
+
if (validator.handler) {
|
|
3547
|
+
validateHandlerRef(
|
|
3548
|
+
validator.handler,
|
|
3549
|
+
`Project validator "${validator.name}" handler`,
|
|
3550
|
+
"plugin.yaml projectValidators",
|
|
3551
|
+
context,
|
|
3552
|
+
result,
|
|
3553
|
+
"./tools"
|
|
3554
|
+
);
|
|
3555
|
+
}
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3521
3559
|
if (manifest.setup) {
|
|
3522
3560
|
if (typeof manifest.setup !== "string") {
|
|
3523
3561
|
result.errors.push({
|
|
@@ -3599,7 +3637,7 @@ function hasComponentCapability(context) {
|
|
|
3599
3637
|
return Boolean(m.contracts || m.dynamic_contracts || m.routes);
|
|
3600
3638
|
}
|
|
3601
3639
|
function needsToolsEntry(manifest) {
|
|
3602
|
-
if (manifest.validators || manifest.commands || manifest.agentkit || manifest.setup) {
|
|
3640
|
+
if (manifest.validators || manifest.projectValidators || manifest.commands || manifest.agentkit || manifest.setup) {
|
|
3603
3641
|
return true;
|
|
3604
3642
|
}
|
|
3605
3643
|
if (manifest.actions) {
|
|
@@ -3701,6 +3739,25 @@ function resolveContractFile(contractSpec, context) {
|
|
|
3701
3739
|
}
|
|
3702
3740
|
async function validateContract(contract, index, context, generateTypes, result) {
|
|
3703
3741
|
result.contractsChecked = (result.contractsChecked || 0) + 1;
|
|
3742
|
+
const hasEditingGuide = typeof contract.editingGuide === "string" && contract.editingGuide.length > 0;
|
|
3743
|
+
const isReadonly = contract.readonly === true;
|
|
3744
|
+
if (!hasEditingGuide && !isReadonly) {
|
|
3745
|
+
result.warnings.push({
|
|
3746
|
+
type: "source-editing-coverage",
|
|
3747
|
+
message: `Contract "${contract.name}" declares no source-editing story. Add "editingGuide: <path.md>" (how to edit its bound data) or "readonly: true" (not editable in place).`,
|
|
3748
|
+
location: `plugin.yaml contracts[${index}]`,
|
|
3749
|
+
suggestion: "See the plugin source-editing guide (DL#219)."
|
|
3750
|
+
});
|
|
3751
|
+
} else if (hasEditingGuide) {
|
|
3752
|
+
const guideFullPath = path$1.resolve(context.pluginPath, contract.editingGuide);
|
|
3753
|
+
if (!fs$1.existsSync(guideFullPath)) {
|
|
3754
|
+
result.warnings.push({
|
|
3755
|
+
type: "source-editing-coverage",
|
|
3756
|
+
message: `Contract "${contract.name}" editingGuide file not found: ${contract.editingGuide}`,
|
|
3757
|
+
location: `plugin.yaml contracts[${index}]`
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3704
3761
|
const contractPath = resolveContractFile(contract.contract, context);
|
|
3705
3762
|
if (!contractPath) {
|
|
3706
3763
|
result.errors.push({
|
|
@@ -4204,7 +4261,7 @@ function facetLabel(f) {
|
|
|
4204
4261
|
}
|
|
4205
4262
|
}
|
|
4206
4263
|
const CONTENT_MARKER = "jay-content";
|
|
4207
|
-
const META_ATTRS = /* @__PURE__ */ new Set(["jc", "override", "page-scope", CONTENT_MARKER]);
|
|
4264
|
+
const META_ATTRS = /* @__PURE__ */ new Set(["jc", "override", "page-scope", CONTENT_MARKER, "jay-policy"]);
|
|
4208
4265
|
function isMetaAttr(name) {
|
|
4209
4266
|
return META_ATTRS.has(name.toLowerCase());
|
|
4210
4267
|
}
|
|
@@ -4827,6 +4884,8 @@ function normalizeCssBody(body) {
|
|
|
4827
4884
|
}
|
|
4828
4885
|
const SUPPRESS_KEY = "allow-broken-links";
|
|
4829
4886
|
const SUPPRESS_HINT = 'suppress with jay-stack:\n allow-broken-links: true\nin a <script type="application/jay-validations"> block';
|
|
4887
|
+
const SELF_LINK_ALLOW_ATTR = "jay-allow-self-link";
|
|
4888
|
+
const TODO_SCHEME = /^todo:/i;
|
|
4830
4889
|
function normalizePath(p) {
|
|
4831
4890
|
let s2 = p.split("#")[0].split("?")[0];
|
|
4832
4891
|
s2 = s2.replace(/\/{2,}/g, "/");
|
|
@@ -4922,64 +4981,137 @@ function originOf(baseUrl) {
|
|
|
4922
4981
|
return void 0;
|
|
4923
4982
|
}
|
|
4924
4983
|
}
|
|
4984
|
+
function fragmentOf(raw) {
|
|
4985
|
+
const hash = raw.indexOf("#");
|
|
4986
|
+
return hash === -1 ? "" : raw.slice(hash + 1);
|
|
4987
|
+
}
|
|
4925
4988
|
function classifyHref(raw, baseUrl) {
|
|
4926
4989
|
if (raw.includes("{")) return { kind: "skip" };
|
|
4927
4990
|
const t = raw.trim();
|
|
4928
4991
|
if (t === "" || t === "#") return { kind: "degenerate" };
|
|
4929
|
-
if (
|
|
4992
|
+
if (TODO_SCHEME.test(t)) return { kind: "todo", note: t.replace(TODO_SCHEME, "").trim() };
|
|
4993
|
+
if (t.startsWith("#")) return { kind: "localFragment", fragment: t.slice(1) };
|
|
4930
4994
|
if (/^(mailto:|tel:|javascript:|data:|sms:|ftp:)/i.test(t)) return { kind: "skip" };
|
|
4931
4995
|
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t) || t.startsWith("//")) {
|
|
4932
4996
|
const sameOrigin = baseUrl ? originOf(baseUrl) : void 0;
|
|
4933
4997
|
if (sameOrigin) {
|
|
4934
4998
|
try {
|
|
4935
4999
|
const u2 = new URL(t.startsWith("//") ? "https:" + t : t);
|
|
4936
|
-
if (u2.origin === sameOrigin)
|
|
4937
|
-
|
|
5000
|
+
if (u2.origin === sameOrigin) {
|
|
5001
|
+
const frag = u2.hash.slice(1);
|
|
5002
|
+
return frag ? { kind: "check", targetPath: normalizePath(u2.pathname), fragment: frag } : { kind: "check", targetPath: normalizePath(u2.pathname) };
|
|
5003
|
+
}
|
|
4938
5004
|
} catch {
|
|
4939
5005
|
}
|
|
4940
5006
|
}
|
|
4941
5007
|
return { kind: "skip" };
|
|
4942
5008
|
}
|
|
4943
|
-
if (t.startsWith("/"))
|
|
5009
|
+
if (t.startsWith("/")) {
|
|
5010
|
+
const frag = fragmentOf(t);
|
|
5011
|
+
return frag ? { kind: "check", targetPath: normalizePath(t), fragment: frag } : { kind: "check", targetPath: normalizePath(t) };
|
|
5012
|
+
}
|
|
4944
5013
|
return { kind: "skip" };
|
|
4945
5014
|
}
|
|
5015
|
+
function fragmentNeedsAnchor(fragment) {
|
|
5016
|
+
const f = fragment.trim();
|
|
5017
|
+
return f !== "" && f.toLowerCase() !== "top";
|
|
5018
|
+
}
|
|
5019
|
+
function collectAnchorIds(body) {
|
|
5020
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5021
|
+
for (const el of body.querySelectorAll("[id]")) {
|
|
5022
|
+
const id = el.getAttribute("id");
|
|
5023
|
+
if (id) ids.add(id);
|
|
5024
|
+
}
|
|
5025
|
+
for (const a of body.querySelectorAll("a[name]")) {
|
|
5026
|
+
const name = a.getAttribute("name");
|
|
5027
|
+
if (name) ids.add(name);
|
|
5028
|
+
}
|
|
5029
|
+
return ids;
|
|
5030
|
+
}
|
|
4946
5031
|
function checkInternalLinks(options) {
|
|
4947
5032
|
const { parsedFiles, oracle, assetUrls, baseUrl, projectRoot } = options;
|
|
4948
5033
|
const findings = [];
|
|
5034
|
+
const idsByUrl = /* @__PURE__ */ new Map();
|
|
4949
5035
|
for (const { relativePath, parsed } of parsedFiles) {
|
|
4950
|
-
|
|
5036
|
+
const url = oracle.selfUrlByPath.get(path$1.resolve(projectRoot ?? ".", relativePath));
|
|
5037
|
+
if (url !== void 0) idsByUrl.set(url, collectAnchorIds(parsed.body));
|
|
5038
|
+
}
|
|
5039
|
+
for (const { relativePath, parsed } of parsedFiles) {
|
|
5040
|
+
const allowBroken = parsed.validationOverrides?.["jay-stack"]?.[SUPPRESS_KEY] === true;
|
|
4951
5041
|
const ownUrl = oracle.selfUrlByPath.get(path$1.resolve(projectRoot ?? ".", relativePath));
|
|
5042
|
+
const ownIds = ownUrl !== void 0 ? idsByUrl.get(ownUrl) : void 0;
|
|
4952
5043
|
for (const anchor of parsed.body.querySelectorAll("a[href]")) {
|
|
4953
5044
|
const raw = anchor.getAttribute("href");
|
|
4954
5045
|
if (raw === void 0 || raw === null) continue;
|
|
4955
5046
|
const verdict = classifyHref(raw, baseUrl);
|
|
4956
5047
|
if (verdict.kind === "skip") continue;
|
|
4957
5048
|
const tag = openingTag(anchor);
|
|
5049
|
+
if (verdict.kind === "todo") {
|
|
5050
|
+
const note = verdict.note ? ` — intended target "${verdict.note}" is not wired up yet` : "";
|
|
5051
|
+
findings.push({
|
|
5052
|
+
file: relativePath,
|
|
5053
|
+
severity: "warning",
|
|
5054
|
+
message: `TODO link ${tag}${note}. This reminder cannot be suppressed — resolve it before release.`,
|
|
5055
|
+
suggestion: `Replace the "todo:" href with the real destination once the page/route exists.`
|
|
5056
|
+
});
|
|
5057
|
+
continue;
|
|
5058
|
+
}
|
|
5059
|
+
if (verdict.kind === "localFragment") {
|
|
5060
|
+
if (!fragmentNeedsAnchor(verdict.fragment)) continue;
|
|
5061
|
+
if (ownIds && !ownIds.has(verdict.fragment)) {
|
|
5062
|
+
findings.push({
|
|
5063
|
+
file: relativePath,
|
|
5064
|
+
severity: "error",
|
|
5065
|
+
message: `Broken fragment link ${tag} — this page has no element with id "${verdict.fragment}".`,
|
|
5066
|
+
suggestion: `Add id="${verdict.fragment}" to the target element, or fix the fragment.`
|
|
5067
|
+
});
|
|
5068
|
+
}
|
|
5069
|
+
continue;
|
|
5070
|
+
}
|
|
4958
5071
|
if (verdict.kind === "degenerate") {
|
|
5072
|
+
if (allowBroken) continue;
|
|
4959
5073
|
findings.push({
|
|
4960
5074
|
file: relativePath,
|
|
5075
|
+
severity: "error",
|
|
4961
5076
|
message: `Anchor ${tag} has a placeholder href "${raw}" — it links nowhere and ships as a dead link.`,
|
|
4962
5077
|
suggestion: `Point the link at a real page, use <button> for a JS-driven action, or ${SUPPRESS_HINT}.`
|
|
4963
5078
|
});
|
|
4964
5079
|
continue;
|
|
4965
5080
|
}
|
|
4966
5081
|
const target = verdict.targetPath;
|
|
4967
|
-
|
|
5082
|
+
const fragment = verdict.fragment ?? "";
|
|
5083
|
+
if (ownUrl !== void 0 && target === ownUrl && !fragmentNeedsAnchor(fragment)) {
|
|
5084
|
+
if (anchor.hasAttribute(SELF_LINK_ALLOW_ATTR)) continue;
|
|
4968
5085
|
findings.push({
|
|
4969
5086
|
file: relativePath,
|
|
5087
|
+
severity: "warning",
|
|
4970
5088
|
message: `Anchor ${tag} points to the page's own route "${target}" — a self-link goes nowhere.`,
|
|
4971
|
-
suggestion: `Remove the link, use "#"
|
|
5089
|
+
suggestion: `Remove the link, use an in-page "#" fragment, or add ${SELF_LINK_ALLOW_ATTR} to the anchor if intentional.`
|
|
4972
5090
|
});
|
|
4973
5091
|
continue;
|
|
4974
5092
|
}
|
|
4975
|
-
|
|
4976
|
-
if (
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
5093
|
+
const resolves = oracle.staticUrls.has(target) || assetUrls.has(target) || oracle.dynamicMatchers.some((re) => re.test(target));
|
|
5094
|
+
if (!resolves) {
|
|
5095
|
+
if (allowBroken) continue;
|
|
5096
|
+
findings.push({
|
|
5097
|
+
file: relativePath,
|
|
5098
|
+
severity: "error",
|
|
5099
|
+
message: `Broken internal link in anchor ${tag} — no page or public asset produces the URL "${target}".`,
|
|
5100
|
+
suggestion: `Create a placeholder page/route for "${target}", or remove the link. Otherwise ${SUPPRESS_HINT}.`
|
|
5101
|
+
});
|
|
5102
|
+
continue;
|
|
5103
|
+
}
|
|
5104
|
+
if (fragmentNeedsAnchor(fragment)) {
|
|
5105
|
+
const targetIds = target === ownUrl ? ownIds : idsByUrl.get(target);
|
|
5106
|
+
if (targetIds && !targetIds.has(fragment)) {
|
|
5107
|
+
findings.push({
|
|
5108
|
+
file: relativePath,
|
|
5109
|
+
severity: "error",
|
|
5110
|
+
message: `Broken fragment link ${tag} — page "${target}" has no element with id "${fragment}".`,
|
|
5111
|
+
suggestion: `Add id="${fragment}" to the target element on "${target}", or fix the fragment.`
|
|
5112
|
+
});
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
4983
5115
|
}
|
|
4984
5116
|
}
|
|
4985
5117
|
return findings;
|
|
@@ -4989,6 +5121,47 @@ const LINK_SUPPRESS_HINT = 'suppress with jay-stack:\n allow-broken-links: true
|
|
|
4989
5121
|
const UNRESOLVED_BINDING = /\{[A-Za-z_$][\w$]*(?:\.[\w$]+)*\}/;
|
|
4990
5122
|
const TITLE_MAX = 60;
|
|
4991
5123
|
const DESCRIPTION_MAX = 160;
|
|
5124
|
+
const PAGE_CACHE_SOFT_BYTES = 300 * 1024;
|
|
5125
|
+
const PAGE_CACHE_HARD_BYTES = 512 * 1024;
|
|
5126
|
+
async function checkPageCacheFileSizes(args) {
|
|
5127
|
+
const { cacheAbsPath, routePattern } = args;
|
|
5128
|
+
const findings = [];
|
|
5129
|
+
const rel = path$1.basename(path$1.dirname(cacheAbsPath)) + "/" + path$1.basename(cacheAbsPath);
|
|
5130
|
+
let bytes;
|
|
5131
|
+
try {
|
|
5132
|
+
bytes = (await promises.stat(cacheAbsPath)).size;
|
|
5133
|
+
} catch (err) {
|
|
5134
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5135
|
+
findings.push({
|
|
5136
|
+
file: routePattern,
|
|
5137
|
+
message: `${rel} is listed in the build manifest but could not be read (${detail}).`,
|
|
5138
|
+
suggestion: "Rebuild the project or fix the missing pre-rendered cache file.",
|
|
5139
|
+
severity: "error",
|
|
5140
|
+
source: "build-output"
|
|
5141
|
+
});
|
|
5142
|
+
return findings;
|
|
5143
|
+
}
|
|
5144
|
+
if (bytes >= PAGE_CACHE_HARD_BYTES) {
|
|
5145
|
+
const hardKiB = PAGE_CACHE_HARD_BYTES / 1024;
|
|
5146
|
+
findings.push({
|
|
5147
|
+
file: routePattern,
|
|
5148
|
+
message: `${rel} is ${bytes} bytes (≥ ${PAGE_CACHE_HARD_BYTES} bytes / ${hardKiB} KiB hard limit). Shrink slow-phase carry before deploy.`,
|
|
5149
|
+
suggestion: "Reduce slow-phase carryForward in this cache file before deploy (smaller payloads, defer non-first-paint data). See agent-kit developer/query-param-pages.md — cache size thresholds.",
|
|
5150
|
+
severity: "error",
|
|
5151
|
+
source: "build-output"
|
|
5152
|
+
});
|
|
5153
|
+
} else if (bytes >= PAGE_CACHE_SOFT_BYTES) {
|
|
5154
|
+
const softKiB = PAGE_CACHE_SOFT_BYTES / 1024;
|
|
5155
|
+
findings.push({
|
|
5156
|
+
file: routePattern,
|
|
5157
|
+
message: `${rel} is ${bytes} bytes (≥ ${PAGE_CACHE_SOFT_BYTES} bytes / ${softKiB} KiB soft budget). Consider deferring non-critical carry data.`,
|
|
5158
|
+
suggestion: "Review carryForward size: keep only what the default view needs for first paint; defer the rest (per-request fetch or post-hydration load). Oversize caches often need product decisions about what loads when — see agent-kit developer/query-param-pages.md.",
|
|
5159
|
+
severity: "warning",
|
|
5160
|
+
source: "build-output"
|
|
5161
|
+
});
|
|
5162
|
+
}
|
|
5163
|
+
return findings;
|
|
5164
|
+
}
|
|
4992
5165
|
function buildUrlFromManifest(pattern, params) {
|
|
4993
5166
|
return pattern.replace(/\[\[(\w+)\]\]/g, (_, name) => params[name] || "").replace(/\[\.\.\.(\w+)\]/g, (_, name) => params[name] || "").replace(/\[(\w+)\]/g, (_, name) => params[name] || "").replace(/\/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
4994
5167
|
}
|
|
@@ -5059,16 +5232,15 @@ async function loadBuildManifest(backendDir) {
|
|
|
5059
5232
|
}
|
|
5060
5233
|
return { manifest, metadata };
|
|
5061
5234
|
}
|
|
5062
|
-
function buildBuildOracle(manifest, backendDir) {
|
|
5235
|
+
async function buildBuildOracle(manifest, backendDir) {
|
|
5236
|
+
const store = new FilesystemArtifactStore(backendDir);
|
|
5063
5237
|
const urls = /* @__PURE__ */ new Set();
|
|
5064
5238
|
const instances = [];
|
|
5065
5239
|
for (const route of manifest.routes) {
|
|
5066
5240
|
const hasDynamic = route.segments.some((s2) => s2.type !== "static");
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
}
|
|
5071
|
-
for (const instance of route.instances) {
|
|
5241
|
+
let count = 0;
|
|
5242
|
+
for await (const instance of store.listInstances(route, { fresh: true })) {
|
|
5243
|
+
count++;
|
|
5072
5244
|
const url = routeInstanceUrl(route, instance);
|
|
5073
5245
|
urls.add(url);
|
|
5074
5246
|
instances.push({
|
|
@@ -5078,6 +5250,7 @@ function buildBuildOracle(manifest, backendDir) {
|
|
|
5078
5250
|
headMeta: route.headMeta
|
|
5079
5251
|
});
|
|
5080
5252
|
}
|
|
5253
|
+
if (count === 0 && !hasDynamic) urls.add(normalizePath(route.pattern));
|
|
5081
5254
|
}
|
|
5082
5255
|
return { urls, instances };
|
|
5083
5256
|
}
|
|
@@ -5335,122 +5508,6 @@ async function newestSourceMtime(dirs) {
|
|
|
5335
5508
|
for (const dir of dirs) await walk(dir);
|
|
5336
5509
|
return newest;
|
|
5337
5510
|
}
|
|
5338
|
-
const URN_PATTERN = /^urn:air:[^:\s]+:[^:\s]+:[^:\s]+$/;
|
|
5339
|
-
const MEDIA_TYPE_PATTERN = /^[a-z]+\/[A-Za-z0-9.+-]+$/;
|
|
5340
|
-
const ARD_SUGGEST = "See developer/seo-guide.md (ARD v0.91) for the schema and a worked example.";
|
|
5341
|
-
function validateArdDocument(doc) {
|
|
5342
|
-
const findings = [];
|
|
5343
|
-
if (!doc || typeof doc !== "object") {
|
|
5344
|
-
return [
|
|
5345
|
-
{
|
|
5346
|
-
message: "ard.json must be a JSON object with an `entries` array.",
|
|
5347
|
-
severity: "error",
|
|
5348
|
-
suggestion: ARD_SUGGEST
|
|
5349
|
-
}
|
|
5350
|
-
];
|
|
5351
|
-
}
|
|
5352
|
-
const entries = doc.entries;
|
|
5353
|
-
if (!Array.isArray(entries)) {
|
|
5354
|
-
return [
|
|
5355
|
-
{
|
|
5356
|
-
message: "ard.json is missing the required `entries` array.",
|
|
5357
|
-
severity: "error",
|
|
5358
|
-
suggestion: ARD_SUGGEST
|
|
5359
|
-
}
|
|
5360
|
-
];
|
|
5361
|
-
}
|
|
5362
|
-
entries.forEach((entry, i) => {
|
|
5363
|
-
const at = `ard.json entries[${i}]`;
|
|
5364
|
-
if (!entry || typeof entry !== "object") {
|
|
5365
|
-
findings.push({ message: `${at} must be an object.`, severity: "error" });
|
|
5366
|
-
return;
|
|
5367
|
-
}
|
|
5368
|
-
if (typeof entry.identifier !== "string" || !URN_PATTERN.test(entry.identifier)) {
|
|
5369
|
-
findings.push({
|
|
5370
|
-
message: `${at}.identifier must match urn:air:<publisher>:<namespace>:<name>.`,
|
|
5371
|
-
severity: "error",
|
|
5372
|
-
suggestion: ARD_SUGGEST
|
|
5373
|
-
});
|
|
5374
|
-
}
|
|
5375
|
-
if (typeof entry.displayName !== "string" || entry.displayName.trim() === "") {
|
|
5376
|
-
findings.push({
|
|
5377
|
-
message: `${at}.displayName is required.`,
|
|
5378
|
-
severity: "error"
|
|
5379
|
-
});
|
|
5380
|
-
}
|
|
5381
|
-
if (typeof entry.type !== "string" || !MEDIA_TYPE_PATTERN.test(entry.type)) {
|
|
5382
|
-
findings.push({
|
|
5383
|
-
message: `${at}.type must be an IANA media type (e.g. application/mcp-server-card+json).`,
|
|
5384
|
-
severity: "error"
|
|
5385
|
-
});
|
|
5386
|
-
}
|
|
5387
|
-
const hasUrl = entry.url !== void 0;
|
|
5388
|
-
const hasData = entry.data !== void 0;
|
|
5389
|
-
if (hasUrl === hasData) {
|
|
5390
|
-
findings.push({
|
|
5391
|
-
message: `${at} must have exactly one of \`url\` or \`data\`.`,
|
|
5392
|
-
severity: "error"
|
|
5393
|
-
});
|
|
5394
|
-
}
|
|
5395
|
-
const rq = entry.representativeQueries;
|
|
5396
|
-
if (rq === void 0) {
|
|
5397
|
-
findings.push({
|
|
5398
|
-
message: `${at} is missing representativeQueries (2–5 recommended) — agents may not surface this resource.`,
|
|
5399
|
-
severity: "warning",
|
|
5400
|
-
suggestion: ARD_SUGGEST
|
|
5401
|
-
});
|
|
5402
|
-
} else if (!Array.isArray(rq) || rq.length < 2 || rq.length > 5) {
|
|
5403
|
-
findings.push({
|
|
5404
|
-
message: `${at}.representativeQueries should list 2–5 example queries.`,
|
|
5405
|
-
severity: "warning"
|
|
5406
|
-
});
|
|
5407
|
-
}
|
|
5408
|
-
});
|
|
5409
|
-
return findings;
|
|
5410
|
-
}
|
|
5411
|
-
async function checkArdFiles(projectRoot) {
|
|
5412
|
-
const wellKnown = path$1.resolve(projectRoot, "public/.well-known");
|
|
5413
|
-
const ardPath = path$1.join(wellKnown, "ard.json");
|
|
5414
|
-
const aliasPath = path$1.join(wellKnown, "ai-catalog.json");
|
|
5415
|
-
const ardRaw = await readIfExists(ardPath);
|
|
5416
|
-
const aliasRaw = await readIfExists(aliasPath);
|
|
5417
|
-
if (ardRaw === null && aliasRaw === null) {
|
|
5418
|
-
return { findings: [], absent: true };
|
|
5419
|
-
}
|
|
5420
|
-
const findings = [];
|
|
5421
|
-
if (ardRaw === null && aliasRaw !== null) {
|
|
5422
|
-
findings.push({
|
|
5423
|
-
message: "Only public/.well-known/ai-catalog.json found — ARD renamed this to ard.json. Add ard.json (keep ai-catalog.json as a copy for older clients).",
|
|
5424
|
-
severity: "warning",
|
|
5425
|
-
suggestion: ARD_SUGGEST
|
|
5426
|
-
});
|
|
5427
|
-
} else if (ardRaw !== null && aliasRaw !== null && ardRaw.trim() !== aliasRaw.trim()) {
|
|
5428
|
-
findings.push({
|
|
5429
|
-
message: "public/.well-known/ard.json and ai-catalog.json differ — the alias should be a copy of ard.json.",
|
|
5430
|
-
severity: "warning"
|
|
5431
|
-
});
|
|
5432
|
-
}
|
|
5433
|
-
const canonicalRaw = ardRaw ?? aliasRaw;
|
|
5434
|
-
let parsed;
|
|
5435
|
-
try {
|
|
5436
|
-
parsed = JSON.parse(canonicalRaw);
|
|
5437
|
-
} catch (e2) {
|
|
5438
|
-
findings.push({
|
|
5439
|
-
message: `${ardRaw !== null ? "ard.json" : "ai-catalog.json"} is not valid JSON: ${e2.message}`,
|
|
5440
|
-
severity: "error"
|
|
5441
|
-
});
|
|
5442
|
-
return { findings, absent: false };
|
|
5443
|
-
}
|
|
5444
|
-
findings.push(...validateArdDocument(parsed));
|
|
5445
|
-
return { findings, absent: false };
|
|
5446
|
-
}
|
|
5447
|
-
async function readIfExists(p) {
|
|
5448
|
-
try {
|
|
5449
|
-
return await promises.readFile(p, "utf8");
|
|
5450
|
-
} catch {
|
|
5451
|
-
return null;
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
5511
|
async function findJayFiles(dir) {
|
|
5455
5512
|
return await glob(`${dir}/**/*${JAY_EXTENSION$1}`);
|
|
5456
5513
|
}
|
|
@@ -6454,6 +6511,37 @@ function resolveContractLinks(contract, contractPath) {
|
|
|
6454
6511
|
const contractDir = path$1.dirname(contractPath);
|
|
6455
6512
|
return { ...contract, tags: resolveLinkedTags(contract.tags, contractDir) };
|
|
6456
6513
|
}
|
|
6514
|
+
async function loadPluginToolsHandler(plugin, handlerRef, capabilityLabel, name, source, errors) {
|
|
6515
|
+
try {
|
|
6516
|
+
let handlerModule;
|
|
6517
|
+
if (plugin.isLocal) {
|
|
6518
|
+
const handlerPath = path$1.resolve(plugin.pluginPath, handlerRef);
|
|
6519
|
+
handlerModule = await import(handlerPath);
|
|
6520
|
+
} else {
|
|
6521
|
+
handlerModule = await import(`${plugin.packageName}/tools`);
|
|
6522
|
+
}
|
|
6523
|
+
const fn = plugin.isLocal ? handlerModule.validate ?? handlerModule.default : handlerModule[handlerRef];
|
|
6524
|
+
if (typeof fn !== "function") {
|
|
6525
|
+
const expected = plugin.isLocal ? "validate" : handlerRef;
|
|
6526
|
+
errors.push({
|
|
6527
|
+
file: `plugin:${plugin.name}`,
|
|
6528
|
+
message: `${capabilityLabel} "${name}" handler does not export a "${expected}" function`,
|
|
6529
|
+
stage: "plugin",
|
|
6530
|
+
source
|
|
6531
|
+
});
|
|
6532
|
+
return void 0;
|
|
6533
|
+
}
|
|
6534
|
+
return fn;
|
|
6535
|
+
} catch (loadErr) {
|
|
6536
|
+
errors.push({
|
|
6537
|
+
file: `plugin:${plugin.name}`,
|
|
6538
|
+
message: `Failed to load ${capabilityLabel.toLowerCase()} "${name}": ${loadErr.message}`,
|
|
6539
|
+
stage: "plugin",
|
|
6540
|
+
source
|
|
6541
|
+
});
|
|
6542
|
+
return void 0;
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
6457
6545
|
async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
6458
6546
|
const scannedPlugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
6459
6547
|
const loadedValidators = [];
|
|
@@ -6461,37 +6549,16 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
6461
6549
|
if (!plugin.manifest.validators) continue;
|
|
6462
6550
|
for (const validatorDef of plugin.manifest.validators) {
|
|
6463
6551
|
const source = `${plugin.name}/${validatorDef.name}`;
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
}
|
|
6473
|
-
validatorFn = plugin.isLocal ? handlerModule.validate ?? handlerModule.default : handlerModule[validatorDef.handler];
|
|
6474
|
-
if (typeof validatorFn !== "function") {
|
|
6475
|
-
errors.push({
|
|
6476
|
-
file: `plugin:${plugin.name}`,
|
|
6477
|
-
message: `Validator "${validatorDef.name}" handler does not export a "validate" function`,
|
|
6478
|
-
stage: "plugin",
|
|
6479
|
-
source
|
|
6480
|
-
});
|
|
6481
|
-
loadedValidators.push(source);
|
|
6482
|
-
continue;
|
|
6483
|
-
}
|
|
6484
|
-
} catch (loadErr) {
|
|
6485
|
-
errors.push({
|
|
6486
|
-
file: `plugin:${plugin.name}`,
|
|
6487
|
-
message: `Failed to load validator "${validatorDef.name}": ${loadErr.message}`,
|
|
6488
|
-
stage: "plugin",
|
|
6489
|
-
source
|
|
6490
|
-
});
|
|
6491
|
-
loadedValidators.push(source);
|
|
6492
|
-
continue;
|
|
6493
|
-
}
|
|
6552
|
+
const validatorFn = await loadPluginToolsHandler(
|
|
6553
|
+
plugin,
|
|
6554
|
+
validatorDef.handler,
|
|
6555
|
+
"Validator",
|
|
6556
|
+
validatorDef.name,
|
|
6557
|
+
source,
|
|
6558
|
+
errors
|
|
6559
|
+
);
|
|
6494
6560
|
loadedValidators.push(source);
|
|
6561
|
+
if (!validatorFn) continue;
|
|
6495
6562
|
for (const { relativePath, parsed } of parsedFiles) {
|
|
6496
6563
|
const pageContractPath = parsed.contractRef ? path$1.resolve(
|
|
6497
6564
|
path$1.dirname(path$1.resolve(projectRoot, relativePath)),
|
|
@@ -6574,6 +6641,56 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
6574
6641
|
}
|
|
6575
6642
|
return loadedValidators;
|
|
6576
6643
|
}
|
|
6644
|
+
async function runProjectValidators(projectRoot, errors, warnings) {
|
|
6645
|
+
const scannedPlugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
6646
|
+
const loadedValidators = [];
|
|
6647
|
+
for (const [, plugin] of scannedPlugins) {
|
|
6648
|
+
if (!plugin.manifest.projectValidators) continue;
|
|
6649
|
+
for (const validatorDef of plugin.manifest.projectValidators) {
|
|
6650
|
+
const source = `${plugin.name}/${validatorDef.name}`;
|
|
6651
|
+
const validatorFn = await loadPluginToolsHandler(
|
|
6652
|
+
plugin,
|
|
6653
|
+
validatorDef.handler,
|
|
6654
|
+
"Project validator",
|
|
6655
|
+
validatorDef.name,
|
|
6656
|
+
source,
|
|
6657
|
+
errors
|
|
6658
|
+
);
|
|
6659
|
+
loadedValidators.push(source);
|
|
6660
|
+
if (!validatorFn) continue;
|
|
6661
|
+
const ctx = { projectRoot };
|
|
6662
|
+
try {
|
|
6663
|
+
const findings = await validatorFn(ctx);
|
|
6664
|
+
for (const finding of findings) {
|
|
6665
|
+
if (finding.severity === "error") {
|
|
6666
|
+
errors.push({
|
|
6667
|
+
file: finding.file,
|
|
6668
|
+
message: finding.message,
|
|
6669
|
+
stage: "plugin",
|
|
6670
|
+
source,
|
|
6671
|
+
suggestion: finding.suggestion
|
|
6672
|
+
});
|
|
6673
|
+
} else {
|
|
6674
|
+
warnings.push({
|
|
6675
|
+
file: finding.file,
|
|
6676
|
+
message: finding.message,
|
|
6677
|
+
source,
|
|
6678
|
+
suggestion: finding.suggestion
|
|
6679
|
+
});
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
} catch (runErr) {
|
|
6683
|
+
errors.push({
|
|
6684
|
+
file: ".jay",
|
|
6685
|
+
message: `Project validator "${source}" threw: ${runErr.message}`,
|
|
6686
|
+
stage: "plugin",
|
|
6687
|
+
source
|
|
6688
|
+
});
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
return loadedValidators;
|
|
6693
|
+
}
|
|
6577
6694
|
async function validateJayFiles(options = {}) {
|
|
6578
6695
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
6579
6696
|
const config = loadConfig(projectRoot);
|
|
@@ -6930,56 +7047,6 @@ async function validateJayFiles(options = {}) {
|
|
|
6930
7047
|
}
|
|
6931
7048
|
};
|
|
6932
7049
|
}
|
|
6933
|
-
const robotsTxtPath = path$1.resolve(projectRoot, "public/robots.txt");
|
|
6934
|
-
if (!fs$1.existsSync(robotsTxtPath)) {
|
|
6935
|
-
warnings.push({
|
|
6936
|
-
file: "public/robots.txt",
|
|
6937
|
-
message: "public/robots.txt not found — search engines may crawl pages you don't intend to expose.",
|
|
6938
|
-
suggestion: "Create public/robots.txt with: User-agent: *\nAllow: /\nSitemap: https://your-domain.com/sitemap.xml"
|
|
6939
|
-
});
|
|
6940
|
-
}
|
|
6941
|
-
if (!config.site?.baseUrl) {
|
|
6942
|
-
warnings.push({
|
|
6943
|
-
file: ".jay",
|
|
6944
|
-
message: "site.baseUrl not configured — sitemap.xml will not be generated in production.",
|
|
6945
|
-
suggestion: "Add to .jay config:\n site:\n baseUrl: https://your-domain.com"
|
|
6946
|
-
});
|
|
6947
|
-
}
|
|
6948
|
-
if (!config.site?.name) {
|
|
6949
|
-
warnings.push({
|
|
6950
|
-
file: ".jay",
|
|
6951
|
-
message: "site.name not configured — Organization/WebSite JSON-LD and llms.txt will not be emitted, reducing how well search engines and agents understand the site.",
|
|
6952
|
-
suggestion: "Add to .jay config:\n site:\n name: Your Site Name\n logo: /logo.png\n description: What your site is about"
|
|
6953
|
-
});
|
|
6954
|
-
}
|
|
6955
|
-
const allowNoArd = config.validations?.["allow-no-ard"] === true;
|
|
6956
|
-
const ardResult = await checkArdFiles(projectRoot);
|
|
6957
|
-
if (ardResult.absent) {
|
|
6958
|
-
if (!allowNoArd) {
|
|
6959
|
-
warnings.push({
|
|
6960
|
-
file: "public/.well-known/ard.json",
|
|
6961
|
-
message: "public/.well-known/ard.json not found — agents cannot discover this site’s resources/APIs via Agentic Resource Discovery.",
|
|
6962
|
-
suggestion: "Author public/.well-known/ard.json (ARD v0.91), or suppress with `validations: { allow-no-ard: true }` in .jay if the site has no agent resources to advertise. See developer/seo-guide.md."
|
|
6963
|
-
});
|
|
6964
|
-
}
|
|
6965
|
-
} else {
|
|
6966
|
-
for (const finding of ardResult.findings) {
|
|
6967
|
-
if (finding.severity === "error") {
|
|
6968
|
-
errors.push({
|
|
6969
|
-
file: "public/.well-known/ard.json",
|
|
6970
|
-
message: finding.message,
|
|
6971
|
-
stage: "generate",
|
|
6972
|
-
suggestion: finding.suggestion
|
|
6973
|
-
});
|
|
6974
|
-
} else {
|
|
6975
|
-
warnings.push({
|
|
6976
|
-
file: "public/.well-known/ard.json",
|
|
6977
|
-
message: finding.message,
|
|
6978
|
-
suggestion: finding.suggestion
|
|
6979
|
-
});
|
|
6980
|
-
}
|
|
6981
|
-
}
|
|
6982
|
-
}
|
|
6983
7050
|
const allowNoErrorPages = config.validations?.["allow-no-error-pages"] === true;
|
|
6984
7051
|
if (!allowNoErrorPages) {
|
|
6985
7052
|
const scanResult = await scanRoutes(scanDir, {
|
|
@@ -7066,13 +7133,22 @@ async function validateJayFiles(options = {}) {
|
|
|
7066
7133
|
projectRoot
|
|
7067
7134
|
});
|
|
7068
7135
|
for (const finding of linkFindings) {
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7136
|
+
if (finding.severity === "warning") {
|
|
7137
|
+
warnings.push({
|
|
7138
|
+
file: finding.file,
|
|
7139
|
+
message: finding.message,
|
|
7140
|
+
source: "internal-links",
|
|
7141
|
+
suggestion: finding.suggestion
|
|
7142
|
+
});
|
|
7143
|
+
} else {
|
|
7144
|
+
errors.push({
|
|
7145
|
+
file: finding.file,
|
|
7146
|
+
message: finding.message,
|
|
7147
|
+
stage: "generate",
|
|
7148
|
+
source: "internal-links",
|
|
7149
|
+
suggestion: finding.suggestion
|
|
7150
|
+
});
|
|
7151
|
+
}
|
|
7076
7152
|
}
|
|
7077
7153
|
if (options.tier2) {
|
|
7078
7154
|
await runBuildOutputValidation({
|
|
@@ -7087,6 +7163,7 @@ async function validateJayFiles(options = {}) {
|
|
|
7087
7163
|
});
|
|
7088
7164
|
}
|
|
7089
7165
|
const pluginValidators = await runPluginValidators(projectRoot, parsedFiles, errors, warnings);
|
|
7166
|
+
const projectValidators = await runProjectValidators(projectRoot, errors, warnings);
|
|
7090
7167
|
return {
|
|
7091
7168
|
valid: errors.length === 0,
|
|
7092
7169
|
jayHtmlFilesScanned: jayHtmlFiles.length,
|
|
@@ -7094,7 +7171,7 @@ async function validateJayFiles(options = {}) {
|
|
|
7094
7171
|
errors,
|
|
7095
7172
|
warnings,
|
|
7096
7173
|
coverage,
|
|
7097
|
-
pluginValidators,
|
|
7174
|
+
pluginValidators: [...pluginValidators, ...projectValidators],
|
|
7098
7175
|
designSystemCoverage,
|
|
7099
7176
|
designSystemReuse
|
|
7100
7177
|
};
|
|
@@ -7133,7 +7210,7 @@ async function runBuildOutputValidation(args) {
|
|
|
7133
7210
|
suggestion: "Re-run `jay-stack build` for accurate results."
|
|
7134
7211
|
});
|
|
7135
7212
|
}
|
|
7136
|
-
const oracle = buildBuildOracle(loaded.manifest, backendDir);
|
|
7213
|
+
const oracle = await buildBuildOracle(loaded.manifest, backendDir);
|
|
7137
7214
|
const findings = [];
|
|
7138
7215
|
findings.push(
|
|
7139
7216
|
...checkTemplateLinksAgainstBuild({
|
|
@@ -7145,6 +7222,14 @@ async function runBuildOutputValidation(args) {
|
|
|
7145
7222
|
})
|
|
7146
7223
|
);
|
|
7147
7224
|
findings.push(...checkBuildJsonLd(loaded.manifest));
|
|
7225
|
+
for (const inst of oracle.instances) {
|
|
7226
|
+
findings.push(
|
|
7227
|
+
...await checkPageCacheFileSizes({
|
|
7228
|
+
cacheAbsPath: inst.cacheAbsPath,
|
|
7229
|
+
routePattern: inst.pattern
|
|
7230
|
+
})
|
|
7231
|
+
);
|
|
7232
|
+
}
|
|
7148
7233
|
for (const instance of oracle.instances) {
|
|
7149
7234
|
const slowViewState = await loadSlowViewState(instance.cacheAbsPath);
|
|
7150
7235
|
findings.push(
|
|
@@ -7513,7 +7598,7 @@ async function resolveProductionContext(projectPath, versionOverride) {
|
|
|
7513
7598
|
let siteBaseUrl;
|
|
7514
7599
|
let site;
|
|
7515
7600
|
try {
|
|
7516
|
-
const jayConfig = YAML.parse(await fs$
|
|
7601
|
+
const jayConfig = YAML.parse(await fs$1$1.readFile(jayConfigPath, "utf-8"));
|
|
7517
7602
|
pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
|
|
7518
7603
|
siteBaseUrl = jayConfig?.site?.baseUrl;
|
|
7519
7604
|
site = jayConfig?.site;
|
|
@@ -7533,7 +7618,7 @@ async function resolveProductionContext(projectPath, versionOverride) {
|
|
|
7533
7618
|
async function resolveVersionFromPackageJson(projectRoot) {
|
|
7534
7619
|
try {
|
|
7535
7620
|
const pkgJson = JSON.parse(
|
|
7536
|
-
await fs$
|
|
7621
|
+
await fs$1$1.readFile(path__default.join(projectRoot, "package.json"), "utf-8")
|
|
7537
7622
|
);
|
|
7538
7623
|
if (pkgJson.version) {
|
|
7539
7624
|
return pkgJson.version;
|
|
@@ -7554,7 +7639,7 @@ async function runBuild(projectPath, options) {
|
|
|
7554
7639
|
}
|
|
7555
7640
|
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
7556
7641
|
await generateDefinitionFiles(ctx);
|
|
7557
|
-
const { buildVersion } = await import("
|
|
7642
|
+
const { buildVersion } = await import("@jay-framework/production-build");
|
|
7558
7643
|
await buildVersion({
|
|
7559
7644
|
version: ctx.version,
|
|
7560
7645
|
projectRoot: ctx.resolvedPath,
|
|
@@ -7920,22 +8005,22 @@ async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
|
7920
8005
|
const roleOutputDir = path__default.join(agentKitDir, role);
|
|
7921
8006
|
let files;
|
|
7922
8007
|
try {
|
|
7923
|
-
files = (await fs$
|
|
8008
|
+
files = (await fs$1$1.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
|
|
7924
8009
|
} catch {
|
|
7925
8010
|
continue;
|
|
7926
8011
|
}
|
|
7927
|
-
await fs$
|
|
8012
|
+
await fs$1$1.mkdir(roleOutputDir, { recursive: true });
|
|
7928
8013
|
for (const filename of files) {
|
|
7929
|
-
await fs$
|
|
8014
|
+
await fs$1$1.copyFile(
|
|
7930
8015
|
path__default.join(roleTemplateDir, filename),
|
|
7931
8016
|
path__default.join(roleOutputDir, filename)
|
|
7932
8017
|
);
|
|
7933
8018
|
getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
|
|
7934
8019
|
}
|
|
7935
8020
|
}
|
|
7936
|
-
const topLevelFiles = (await fs$
|
|
8021
|
+
const topLevelFiles = (await fs$1$1.readdir(templateDir)).filter((f) => f.endsWith(".md"));
|
|
7937
8022
|
for (const filename of topLevelFiles) {
|
|
7938
|
-
await fs$
|
|
8023
|
+
await fs$1$1.copyFile(path__default.join(templateDir, filename), path__default.join(agentKitDir, filename));
|
|
7939
8024
|
getLogger().info(chalk.gray(` Created agent-kit/${filename}`));
|
|
7940
8025
|
}
|
|
7941
8026
|
const sharedDirs = ["contracts"];
|
|
@@ -7947,15 +8032,15 @@ async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
|
7947
8032
|
}
|
|
7948
8033
|
}
|
|
7949
8034
|
async function copyDirRecursive(src, dest) {
|
|
7950
|
-
await fs$
|
|
7951
|
-
const entries = await fs$
|
|
8035
|
+
await fs$1$1.mkdir(dest, { recursive: true });
|
|
8036
|
+
const entries = await fs$1$1.readdir(src, { withFileTypes: true });
|
|
7952
8037
|
for (const entry of entries) {
|
|
7953
8038
|
const srcPath = path__default.join(src, entry.name);
|
|
7954
8039
|
const destPath = path__default.join(dest, entry.name);
|
|
7955
8040
|
if (entry.isDirectory()) {
|
|
7956
8041
|
await copyDirRecursive(srcPath, destPath);
|
|
7957
8042
|
} else if (entry.name.endsWith(".md")) {
|
|
7958
|
-
await fs$
|
|
8043
|
+
await fs$1$1.copyFile(srcPath, destPath);
|
|
7959
8044
|
}
|
|
7960
8045
|
}
|
|
7961
8046
|
}
|
|
@@ -7971,7 +8056,7 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
7971
8056
|
const roleSourceDir = path__default.join(pluginAgentKitDir, role);
|
|
7972
8057
|
let files;
|
|
7973
8058
|
try {
|
|
7974
|
-
files = (await fs$
|
|
8059
|
+
files = (await fs$1$1.readdir(roleSourceDir)).filter(
|
|
7975
8060
|
(f) => f.endsWith(".md") && f !== "INSTRUCTIONS.md"
|
|
7976
8061
|
);
|
|
7977
8062
|
} catch {
|
|
@@ -7979,13 +8064,13 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
7979
8064
|
}
|
|
7980
8065
|
if (files.length === 0) continue;
|
|
7981
8066
|
const roleOutputDir = path__default.join(agentKitDir, role);
|
|
7982
|
-
await fs$
|
|
8067
|
+
await fs$1$1.mkdir(roleOutputDir, { recursive: true });
|
|
7983
8068
|
for (const filename of files) {
|
|
7984
8069
|
const sourcePath = path__default.join(roleSourceDir, filename);
|
|
7985
|
-
await fs$
|
|
8070
|
+
await fs$1$1.copyFile(sourcePath, path__default.join(roleOutputDir, filename));
|
|
7986
8071
|
let description = "";
|
|
7987
8072
|
try {
|
|
7988
|
-
const content = await fs$
|
|
8073
|
+
const content = await fs$1$1.readFile(sourcePath, "utf-8");
|
|
7989
8074
|
const lines = content.split("\n");
|
|
7990
8075
|
let pastHeading = false;
|
|
7991
8076
|
for (const line of lines) {
|
|
@@ -8024,7 +8109,7 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
8024
8109
|
lines.push(`| [${filename}](${filename}) | ${pluginName} | ${description} |`);
|
|
8025
8110
|
}
|
|
8026
8111
|
lines.push("");
|
|
8027
|
-
await fs$
|
|
8112
|
+
await fs$1$1.appendFile(instructionsPath, lines.join("\n"));
|
|
8028
8113
|
}
|
|
8029
8114
|
}
|
|
8030
8115
|
async function generatePluginAgentKit(projectRoot, options, initErrors, viteServer) {
|