@jay-framework/jay-stack-cli 0.24.5 → 0.24.7
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/INSTRUCTIONS.md +24 -1
- package/agent-kit-template/contracts/GUIDE.md +1 -1
- package/agent-kit-template/contracts/component-contracts.md +42 -12
- package/agent-kit-template/designer/INSTRUCTIONS.md +23 -20
- package/agent-kit-template/designer/cli-commands.md +46 -0
- package/agent-kit-template/designer/contracts-and-plugins.md +30 -1
- package/agent-kit-template/designer/design-system-guide.md +535 -0
- package/agent-kit-template/designer/jay-html-components.md +101 -31
- package/agent-kit-template/designer/jay-html-styling.md +69 -0
- package/agent-kit-template/designer/jay-html-syntax.md +22 -13
- package/agent-kit-template/designer/jay-html-template-syntax.md +3 -3
- package/agent-kit-template/designer/project-structure.md +1 -1
- package/agent-kit-template/designer/routing.md +2 -2
- package/agent-kit-template/designer/validation-guide.md +31 -5
- package/agent-kit-template/developer/INSTRUCTIONS.md +24 -19
- package/agent-kit-template/developer/page-components.md +23 -2
- package/agent-kit-template/developer/project-structure.md +1 -1
- package/agent-kit-template/developer/routing.md +1 -1
- package/agent-kit-template/devops/production-build.md +1 -1
- package/agent-kit-template/plugin/INSTRUCTIONS.md +3 -1
- package/agent-kit-template/plugin/component-structure.md +1 -1
- package/agent-kit-template/plugin/contracts-guide.md +18 -0
- package/agent-kit-template/plugin/design-system-guide.md +173 -0
- package/dist/{index-B9vCxAZ1.js → index-l6QVpvnY.js} +19 -14
- package/dist/index.js +1903 -199
- package/package.json +13 -12
package/dist/index.js
CHANGED
|
@@ -7,22 +7,24 @@ import path$1 from "path";
|
|
|
7
7
|
import fs$1, { promises } from "fs";
|
|
8
8
|
import YAML from "yaml";
|
|
9
9
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
10
|
-
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile,
|
|
10
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, compileContract, parseAction, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
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";
|
|
11
12
|
import fs$2 from "node:fs/promises";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import path__default from "node:path";
|
|
15
|
+
import { scanPlugins, sortPluginsByDependencies, getServiceRegistry, actionRegistry, resolveServices, ActionRegistry, discoverPluginsWithInit, executePluginServerInits, runInitCallbacks, registerService } from "@jay-framework/stack-server-runtime";
|
|
14
16
|
import { createRequire } from "module";
|
|
15
17
|
import jsBeautify from "js-beautify";
|
|
16
|
-
import { scanPlugins, sortPluginsByDependencies, getServiceRegistry, actionRegistry, resolveServices, ActionRegistry, discoverPluginsWithInit, executePluginServerInits, runInitCallbacks, registerService } from "@jay-framework/stack-server-runtime";
|
|
17
18
|
import "node:crypto";
|
|
18
19
|
import * as fs from "node:fs";
|
|
19
20
|
import fs__default from "node:fs";
|
|
20
21
|
import { createRequire as createRequire$1 } from "node:module";
|
|
21
22
|
import { isJayAction, isJayStreamAction, isJayCliCommand, CONSOLE_CONTEXT } from "@jay-framework/fullstack-component";
|
|
23
|
+
import { parse, NodeType, HTMLElement } from "node-html-parser";
|
|
22
24
|
import { Command } from "commander";
|
|
23
25
|
import chalk from "chalk";
|
|
24
|
-
import { loadPluginManifest as loadPluginManifest$1, normalizeActionEntry as normalizeActionEntry$1, JAY_EXTENSION, RuntimeMode as RuntimeMode$1, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
25
26
|
import { glob } from "glob";
|
|
27
|
+
import postcss from "postcss";
|
|
26
28
|
import { fileURLToPath } from "node:url";
|
|
27
29
|
import { select, confirm, input } from "@inquirer/prompts";
|
|
28
30
|
const DEFAULT_CONFIG = {
|
|
@@ -88,12 +90,13 @@ function updateConfig(updates) {
|
|
|
88
90
|
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
async function generatePageDefinitionFiles(
|
|
92
|
-
for (const
|
|
93
|
-
const jayHtmlPath = route.fsRoute.jayHtmlPath;
|
|
93
|
+
async function generatePageDefinitionFiles(jayHtmlPaths, tsConfigPath, projectRoot) {
|
|
94
|
+
for (const jayHtmlPath of jayHtmlPaths) {
|
|
94
95
|
if (!fs$1.existsSync(jayHtmlPath)) {
|
|
95
96
|
continue;
|
|
96
97
|
}
|
|
98
|
+
const dirname = path$1.dirname(jayHtmlPath);
|
|
99
|
+
const filename = path$1.basename(jayHtmlPath);
|
|
97
100
|
const definitionFilePath = jayHtmlPath + ".d.ts";
|
|
98
101
|
try {
|
|
99
102
|
const [sourceStats, defStats] = await Promise.all([
|
|
@@ -107,8 +110,6 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
107
110
|
}
|
|
108
111
|
try {
|
|
109
112
|
const jayHtml = await fs$1.promises.readFile(jayHtmlPath, "utf-8");
|
|
110
|
-
const filename = path$1.basename(jayHtmlPath);
|
|
111
|
-
const dirname = path$1.dirname(jayHtmlPath);
|
|
112
113
|
const parsedJayHtml = await parseJayFile(
|
|
113
114
|
jayHtml,
|
|
114
115
|
filename,
|
|
@@ -132,6 +133,71 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
}
|
|
136
|
+
const generatePageDefinitionFiles$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
137
|
+
__proto__: null,
|
|
138
|
+
generatePageDefinitionFiles
|
|
139
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
140
|
+
const IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "build", "dist", ".git", ".cache"]);
|
|
141
|
+
async function generateContractDefinitionFiles(root) {
|
|
142
|
+
const contractFiles = await collectContractFiles(root);
|
|
143
|
+
for (const contractPath of contractFiles) {
|
|
144
|
+
const definitionFilePath = contractPath + ".d.ts";
|
|
145
|
+
try {
|
|
146
|
+
const [sourceStats, defStats] = await Promise.all([
|
|
147
|
+
fs$1.promises.stat(contractPath),
|
|
148
|
+
fs$1.promises.stat(definitionFilePath).catch(() => null)
|
|
149
|
+
]);
|
|
150
|
+
if (defStats && defStats.mtime >= sourceStats.mtime) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const code = await fs$1.promises.readFile(contractPath, "utf-8");
|
|
157
|
+
const filename = path$1.basename(contractPath);
|
|
158
|
+
const parsed = parseContract(code, filename);
|
|
159
|
+
const tsCode = await compileContract(parsed, contractPath, JAY_IMPORT_RESOLVER);
|
|
160
|
+
if (tsCode.validations.length > 0) {
|
|
161
|
+
getLogger().warn(
|
|
162
|
+
`failed to generate .d.ts for ${contractPath} with validation errors: ${tsCode.validations.join("\n")}`
|
|
163
|
+
);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
await fs$1.promises.writeFile(definitionFilePath, checkValidationErrors(tsCode), "utf-8");
|
|
167
|
+
getLogger().info(`📦 Generated contract definition file: ${definitionFilePath}`);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
getLogger().error(`Failed to generate definition file for ${contractPath}: ${error}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function collectContractFiles(root) {
|
|
174
|
+
const results = [];
|
|
175
|
+
async function walk(dir) {
|
|
176
|
+
let entries;
|
|
177
|
+
try {
|
|
178
|
+
entries = await fs$1.promises.readdir(dir, { withFileTypes: true });
|
|
179
|
+
} catch {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const fullPath = path$1.join(dir, entry.name);
|
|
184
|
+
if (entry.isDirectory()) {
|
|
185
|
+
if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
await walk(fullPath);
|
|
189
|
+
} else if (entry.isFile() && entry.name.endsWith(JAY_CONTRACT_EXTENSION$1)) {
|
|
190
|
+
results.push(fullPath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await walk(root);
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
197
|
+
const generateContractDefinitionFiles$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
198
|
+
__proto__: null,
|
|
199
|
+
generateContractDefinitionFiles
|
|
200
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
135
201
|
async function startDevServer(options = {}) {
|
|
136
202
|
const projectPath = options.projectPath || process.cwd();
|
|
137
203
|
if (projectPath !== process.cwd()) {
|
|
@@ -177,7 +243,14 @@ async function startDevServer(options = {}) {
|
|
|
177
243
|
app.get(route.path, route.handler);
|
|
178
244
|
});
|
|
179
245
|
});
|
|
180
|
-
|
|
246
|
+
void (async () => {
|
|
247
|
+
await generatePageDefinitionFiles(
|
|
248
|
+
routes.map((route) => route.fsRoute.jayHtmlPath),
|
|
249
|
+
jayOptions.tsConfigFilePath,
|
|
250
|
+
process.cwd()
|
|
251
|
+
);
|
|
252
|
+
await generateContractDefinitionFiles(path$1.resolve("src"));
|
|
253
|
+
})();
|
|
181
254
|
httpServer.listen(devServerPort, () => {
|
|
182
255
|
log.important(`🚀 Jay Stack dev server started successfully!`);
|
|
183
256
|
log.important(`📱 Dev Server: http://localhost:${devServerPort}`);
|
|
@@ -231,6 +304,8 @@ createRequire(import.meta.url);
|
|
|
231
304
|
var __defProp2 = Object.defineProperty;
|
|
232
305
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
233
306
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
|
|
307
|
+
const JAY_EXTENSION = ".jay-html";
|
|
308
|
+
const JAY_CONTRACT_EXTENSION = ".jay-contract";
|
|
234
309
|
class JayAtomicType {
|
|
235
310
|
constructor(name) {
|
|
236
311
|
__publicField2(this, "kind", 0);
|
|
@@ -803,6 +878,172 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
|
|
|
803
878
|
}
|
|
804
879
|
return registeredActions;
|
|
805
880
|
}
|
|
881
|
+
function readTemplateMeta(jayHtmlPath) {
|
|
882
|
+
let content;
|
|
883
|
+
try {
|
|
884
|
+
content = fs.readFileSync(jayHtmlPath, "utf-8");
|
|
885
|
+
} catch {
|
|
886
|
+
return {};
|
|
887
|
+
}
|
|
888
|
+
const root = parse(content);
|
|
889
|
+
const ref = root.querySelector('script[type="application/jay-data"]')?.getAttribute("contract");
|
|
890
|
+
const contractRef = ref ? path.resolve(path.dirname(jayHtmlPath), ref) : void 0;
|
|
891
|
+
const rawTitle = root.querySelector("title")?.text?.trim();
|
|
892
|
+
const staticTitle = rawTitle ? rawTitle.replace(/\{[^}]*\}/g, "").trim() : "";
|
|
893
|
+
return { contractRef, title: staticTitle.length > 0 ? staticTitle : void 0 };
|
|
894
|
+
}
|
|
895
|
+
function listTemplatesForContractFile(contractFile) {
|
|
896
|
+
if (!contractFile) return [];
|
|
897
|
+
const dir = path.dirname(contractFile);
|
|
898
|
+
const target = path.resolve(contractFile);
|
|
899
|
+
const contractBasename = path.basename(contractFile, JAY_CONTRACT_EXTENSION);
|
|
900
|
+
let names;
|
|
901
|
+
try {
|
|
902
|
+
names = fs.readdirSync(dir);
|
|
903
|
+
} catch {
|
|
904
|
+
return [];
|
|
905
|
+
}
|
|
906
|
+
const variants = [];
|
|
907
|
+
for (const f of names) {
|
|
908
|
+
if (!f.endsWith(JAY_EXTENSION)) continue;
|
|
909
|
+
const jayHtmlPath = path.join(dir, f);
|
|
910
|
+
const meta = readTemplateMeta(jayHtmlPath);
|
|
911
|
+
if (meta.contractRef !== target) continue;
|
|
912
|
+
const stem = path.basename(f, JAY_EXTENSION);
|
|
913
|
+
variants.push({
|
|
914
|
+
path: jayHtmlPath,
|
|
915
|
+
variant: stem === contractBasename ? "" : stem,
|
|
916
|
+
...meta.title && { title: meta.title }
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
return variants.sort((a, b) => a.variant.localeCompare(b.variant));
|
|
920
|
+
}
|
|
921
|
+
function hasTemplateForContractFile(contractFile) {
|
|
922
|
+
return listTemplatesForContractFile(contractFile).length > 0;
|
|
923
|
+
}
|
|
924
|
+
function readContractField(contractFile, field) {
|
|
925
|
+
try {
|
|
926
|
+
const parsed = YAML.parse(fs.readFileSync(contractFile, "utf-8"));
|
|
927
|
+
const value = parsed?.[field];
|
|
928
|
+
return typeof value === "string" ? value : void 0;
|
|
929
|
+
} catch {
|
|
930
|
+
return void 0;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function findContractFilesSync(dir) {
|
|
934
|
+
const out = [];
|
|
935
|
+
let entries;
|
|
936
|
+
try {
|
|
937
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
938
|
+
} catch {
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
for (const entry of entries) {
|
|
942
|
+
const full = path.join(dir, entry.name);
|
|
943
|
+
if (entry.isDirectory()) out.push(...findContractFilesSync(full));
|
|
944
|
+
else if (entry.name.endsWith(JAY_CONTRACT_EXTENSION)) out.push(full);
|
|
945
|
+
}
|
|
946
|
+
return out.sort();
|
|
947
|
+
}
|
|
948
|
+
function buildDesignSystemIndex(options) {
|
|
949
|
+
const { projectRoot, plugins, resolvePluginContractPath } = options;
|
|
950
|
+
const componentsBase = options.componentsBase ? path.resolve(projectRoot, options.componentsBase) : path.join(projectRoot, "src", "components");
|
|
951
|
+
const rel = (p) => "./" + path.relative(projectRoot, p).replace(/\\/g, "/");
|
|
952
|
+
const makeEntry = (contractFile, source, pluginName, nameOverride) => {
|
|
953
|
+
const name = nameOverride ?? readContractField(contractFile, "name") ?? path.basename(contractFile, JAY_CONTRACT_EXTENSION);
|
|
954
|
+
const description = readContractField(contractFile, "description");
|
|
955
|
+
const templates = listTemplatesForContractFile(contractFile).map((t) => ({
|
|
956
|
+
...t,
|
|
957
|
+
path: rel(t.path)
|
|
958
|
+
}));
|
|
959
|
+
return {
|
|
960
|
+
name,
|
|
961
|
+
contractPath: rel(contractFile),
|
|
962
|
+
...description && { description },
|
|
963
|
+
source,
|
|
964
|
+
...pluginName && { plugin: pluginName },
|
|
965
|
+
templates
|
|
966
|
+
};
|
|
967
|
+
};
|
|
968
|
+
const components = [];
|
|
969
|
+
for (const contractFile of findContractFilesSync(componentsBase)) {
|
|
970
|
+
components.push(makeEntry(contractFile, "local", void 0));
|
|
971
|
+
}
|
|
972
|
+
for (const [, plugin] of plugins) {
|
|
973
|
+
const contracts = plugin.manifest.contracts;
|
|
974
|
+
if (!Array.isArray(contracts)) continue;
|
|
975
|
+
for (const contract of contracts) {
|
|
976
|
+
const spec = contract.contract;
|
|
977
|
+
if (!spec) continue;
|
|
978
|
+
const contractFile = resolvePluginContractPath(plugin, spec);
|
|
979
|
+
components.push(makeEntry(contractFile, "plugin", plugin.name, contract.name));
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return { components };
|
|
983
|
+
}
|
|
984
|
+
function toKebabCase$1(str) {
|
|
985
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[\s_]+/g, "-").replace(/^-/, "");
|
|
986
|
+
}
|
|
987
|
+
function renderAddMenu(index) {
|
|
988
|
+
const lines = [];
|
|
989
|
+
lines.push("# Design-system elements you can add");
|
|
990
|
+
lines.push("");
|
|
991
|
+
lines.push(
|
|
992
|
+
"Generated by `jay-stack agent-kit`. Each component ships a contract and one or more"
|
|
993
|
+
);
|
|
994
|
+
lines.push(
|
|
995
|
+
"`.jay-html` templates you can flatten into a page as a `<jay:X>` region. Pick a variant,"
|
|
996
|
+
);
|
|
997
|
+
lines.push("paste its import + region, then run `jay-stack sync` to flatten the markup. See");
|
|
998
|
+
lines.push("`designer/design-system-guide.md`.");
|
|
999
|
+
lines.push("");
|
|
1000
|
+
if (index.components.length === 0) {
|
|
1001
|
+
lines.push("_No design-system components found._");
|
|
1002
|
+
lines.push("");
|
|
1003
|
+
return lines.join("\n");
|
|
1004
|
+
}
|
|
1005
|
+
for (const component of index.components) {
|
|
1006
|
+
lines.push(
|
|
1007
|
+
component.description ? `## ${component.name} — ${component.description}` : `## ${component.name}`
|
|
1008
|
+
);
|
|
1009
|
+
lines.push("");
|
|
1010
|
+
if (component.templates.length === 0) {
|
|
1011
|
+
lines.push(
|
|
1012
|
+
"_No template yet (COMPONENT-NO-TEMPLATE) — this component can't be flattened until it ships a `.jay-html`._"
|
|
1013
|
+
);
|
|
1014
|
+
lines.push("");
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
lines.push("| Variant | Title | Template |");
|
|
1018
|
+
lines.push("| --- | --- | --- |");
|
|
1019
|
+
for (const template of component.templates) {
|
|
1020
|
+
const variant = template.variant === "" ? "(default)" : template.variant;
|
|
1021
|
+
lines.push(`| ${variant} | ${template.title ?? ""} | ${template.path} |`);
|
|
1022
|
+
}
|
|
1023
|
+
lines.push("");
|
|
1024
|
+
const first = component.templates[0];
|
|
1025
|
+
const regionTag = toKebabCase$1(component.name);
|
|
1026
|
+
const contractAttr = component.source === "plugin" ? component.name : component.contractPath;
|
|
1027
|
+
lines.push("```html");
|
|
1028
|
+
lines.push("<script");
|
|
1029
|
+
lines.push(' type="application/jay-headless"');
|
|
1030
|
+
if (component.source === "plugin" && component.plugin) {
|
|
1031
|
+
lines.push(` plugin="${component.plugin}"`);
|
|
1032
|
+
}
|
|
1033
|
+
lines.push(` contract="${contractAttr}"`);
|
|
1034
|
+
lines.push(` template="${first.path}"`);
|
|
1035
|
+
lines.push("><\/script>");
|
|
1036
|
+
lines.push("");
|
|
1037
|
+
lines.push(
|
|
1038
|
+
`<jay:${regionTag} ref="${regionTag}"><!-- flattened copy; edit freely --></jay:${regionTag}>`
|
|
1039
|
+
);
|
|
1040
|
+
lines.push("```");
|
|
1041
|
+
lines.push("");
|
|
1042
|
+
lines.push("Then run `jay-stack sync` to flatten it.");
|
|
1043
|
+
lines.push("");
|
|
1044
|
+
}
|
|
1045
|
+
return lines.join("\n");
|
|
1046
|
+
}
|
|
806
1047
|
const require$1 = createRequire(import.meta.url);
|
|
807
1048
|
async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
|
|
808
1049
|
const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
|
|
@@ -932,6 +1173,15 @@ function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, pro
|
|
|
932
1173
|
function toKebabCase(str) {
|
|
933
1174
|
return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
934
1175
|
}
|
|
1176
|
+
function readComponentsBase(projectRoot) {
|
|
1177
|
+
try {
|
|
1178
|
+
const parsed = YAML.parse(fs.readFileSync(path.join(projectRoot, ".jay"), "utf-8"));
|
|
1179
|
+
const base = parsed?.devServer?.componentsBase;
|
|
1180
|
+
if (typeof base === "string" && base.length > 0) return base;
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
return "./src/components";
|
|
1184
|
+
}
|
|
935
1185
|
async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
|
|
936
1186
|
const {
|
|
937
1187
|
projectRoot,
|
|
@@ -1151,13 +1401,26 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
|
|
|
1151
1401
|
...data.commands?.length && { commands: data.commands }
|
|
1152
1402
|
}))
|
|
1153
1403
|
};
|
|
1404
|
+
const designSystem = buildDesignSystemIndex({
|
|
1405
|
+
projectRoot,
|
|
1406
|
+
componentsBase: readComponentsBase(projectRoot),
|
|
1407
|
+
plugins,
|
|
1408
|
+
resolvePluginContractPath: (plugin, spec) => resolveStaticContractPath(plugin, spec, projectRoot)
|
|
1409
|
+
});
|
|
1410
|
+
pluginsIndex.designSystemIndex = "./design-system-index.yaml";
|
|
1154
1411
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
1155
1412
|
const agentKitDir = path.dirname(outputDir);
|
|
1156
1413
|
const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
|
|
1157
1414
|
fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
|
|
1415
|
+
const designSystemIndexPath = path.join(agentKitDir, "design-system-index.yaml");
|
|
1416
|
+
fs.writeFileSync(designSystemIndexPath, YAML.stringify(designSystem), "utf-8");
|
|
1417
|
+
const addMenuPath = path.join(agentKitDir, "design-system.md");
|
|
1418
|
+
fs.writeFileSync(addMenuPath, renderAddMenu(designSystem), "utf-8");
|
|
1158
1419
|
if (verbose) {
|
|
1159
1420
|
getLogger().info(`
|
|
1160
1421
|
✅ Plugins index written to: ${pluginsIndexPath}`);
|
|
1422
|
+
getLogger().info(`✅ Design-system index written to: ${designSystemIndexPath}`);
|
|
1423
|
+
getLogger().info(`✅ Design-system add-menu written to: ${addMenuPath}`);
|
|
1161
1424
|
}
|
|
1162
1425
|
return {
|
|
1163
1426
|
pluginsIndex,
|
|
@@ -1581,132 +1844,6 @@ async function initializeServicesForCli(projectRoot, viteServer, quiet = false)
|
|
|
1581
1844
|
}
|
|
1582
1845
|
return { services: getServiceRegistry2(), initErrors };
|
|
1583
1846
|
}
|
|
1584
|
-
async function runDev(projectPath, options) {
|
|
1585
|
-
const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
|
|
1586
|
-
setDevLogger(createDevLogger(logLevel));
|
|
1587
|
-
const testMode = options.testMode || options.timeout !== void 0;
|
|
1588
|
-
await startDevServer({
|
|
1589
|
-
projectPath: projectPath || process.cwd(),
|
|
1590
|
-
testMode,
|
|
1591
|
-
timeout: options.timeout,
|
|
1592
|
-
logLevel
|
|
1593
|
-
});
|
|
1594
|
-
}
|
|
1595
|
-
async function resolveProductionContext(projectPath, versionOverride) {
|
|
1596
|
-
const resolvedPath = path__default.resolve(projectPath || process.cwd());
|
|
1597
|
-
const jayConfigPath = path__default.join(resolvedPath, ".jay");
|
|
1598
|
-
let pagesBase = "./src/pages";
|
|
1599
|
-
let siteBaseUrl;
|
|
1600
|
-
try {
|
|
1601
|
-
const jayConfig = YAML.parse(await fs$2.readFile(jayConfigPath, "utf-8"));
|
|
1602
|
-
pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
|
|
1603
|
-
siteBaseUrl = jayConfig?.site?.baseUrl;
|
|
1604
|
-
} catch {
|
|
1605
|
-
}
|
|
1606
|
-
const version = versionOverride || await resolveVersionFromPackageJson(resolvedPath);
|
|
1607
|
-
return {
|
|
1608
|
-
resolvedPath,
|
|
1609
|
-
pagesRoot: path__default.resolve(resolvedPath, pagesBase),
|
|
1610
|
-
buildRoot: path__default.join(resolvedPath, "build"),
|
|
1611
|
-
version,
|
|
1612
|
-
tsConfigFilePath: path__default.join(resolvedPath, "tsconfig.json"),
|
|
1613
|
-
siteBaseUrl
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
async function resolveVersionFromPackageJson(projectRoot) {
|
|
1617
|
-
try {
|
|
1618
|
-
const pkgJson = JSON.parse(
|
|
1619
|
-
await fs$2.readFile(path__default.join(projectRoot, "package.json"), "utf-8")
|
|
1620
|
-
);
|
|
1621
|
-
if (pkgJson.version) {
|
|
1622
|
-
return pkgJson.version;
|
|
1623
|
-
}
|
|
1624
|
-
} catch {
|
|
1625
|
-
}
|
|
1626
|
-
return "1";
|
|
1627
|
-
}
|
|
1628
|
-
function initLogger(verbose) {
|
|
1629
|
-
const logLevel = verbose ? "verbose" : "info";
|
|
1630
|
-
setDevLogger(createDevLogger(logLevel));
|
|
1631
|
-
}
|
|
1632
|
-
async function runBuild(projectPath, options) {
|
|
1633
|
-
initLogger(options.verbose);
|
|
1634
|
-
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
1635
|
-
const { buildVersion } = await import("./index-B9vCxAZ1.js");
|
|
1636
|
-
await buildVersion({
|
|
1637
|
-
version: ctx.version,
|
|
1638
|
-
projectRoot: ctx.resolvedPath,
|
|
1639
|
-
pagesRoot: ctx.pagesRoot,
|
|
1640
|
-
buildRoot: ctx.buildRoot,
|
|
1641
|
-
concurrency: 4,
|
|
1642
|
-
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
1643
|
-
minify: options.minify,
|
|
1644
|
-
siteBaseUrl: ctx.siteBaseUrl
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
async function runServe(projectPath, options) {
|
|
1648
|
-
initLogger(options.verbose);
|
|
1649
|
-
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
1650
|
-
if (options.role === "renderer") {
|
|
1651
|
-
const { startRendererServer } = await import("@jay-framework/production-server");
|
|
1652
|
-
await startRendererServer({
|
|
1653
|
-
buildRoot: ctx.buildRoot,
|
|
1654
|
-
version: ctx.version,
|
|
1655
|
-
port: parseInt(options.port, 10),
|
|
1656
|
-
projectRoot: ctx.resolvedPath,
|
|
1657
|
-
pagesRoot: ctx.pagesRoot,
|
|
1658
|
-
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
1659
|
-
siteBaseUrl: ctx.siteBaseUrl
|
|
1660
|
-
});
|
|
1661
|
-
} else {
|
|
1662
|
-
const { startMainServer } = await import("@jay-framework/production-server");
|
|
1663
|
-
await startMainServer({
|
|
1664
|
-
buildRoot: ctx.buildRoot,
|
|
1665
|
-
version: ctx.version,
|
|
1666
|
-
port: parseInt(options.port, 10),
|
|
1667
|
-
testMode: options.testMode,
|
|
1668
|
-
publicBasePath: options.staticBaseUrl,
|
|
1669
|
-
serveStatic: options.serveStatic
|
|
1670
|
-
});
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
async function runRebuild(projectPath, options) {
|
|
1674
|
-
initLogger(options.verbose);
|
|
1675
|
-
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
1676
|
-
let params;
|
|
1677
|
-
if (options.params) {
|
|
1678
|
-
params = JSON.parse(options.params);
|
|
1679
|
-
}
|
|
1680
|
-
const { rebuild } = await import("@jay-framework/production-server");
|
|
1681
|
-
let target;
|
|
1682
|
-
if (options.contract) {
|
|
1683
|
-
target = { mode: "contract", contractName: options.contract, params };
|
|
1684
|
-
} else if (options.route) {
|
|
1685
|
-
target = { mode: "route", routePattern: options.route, params };
|
|
1686
|
-
} else if (options.url) {
|
|
1687
|
-
target = { mode: "url", url: options.url };
|
|
1688
|
-
} else {
|
|
1689
|
-
getLogger().error(chalk.red("One of --contract, --route, or --url is required"));
|
|
1690
|
-
process.exit(1);
|
|
1691
|
-
}
|
|
1692
|
-
const result = await rebuild({
|
|
1693
|
-
projectRoot: ctx.resolvedPath,
|
|
1694
|
-
pagesRoot: ctx.pagesRoot,
|
|
1695
|
-
buildRoot: ctx.buildRoot,
|
|
1696
|
-
version: ctx.version,
|
|
1697
|
-
target,
|
|
1698
|
-
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
1699
|
-
siteBaseUrl: ctx.siteBaseUrl
|
|
1700
|
-
});
|
|
1701
|
-
if (result.errors.length > 0) {
|
|
1702
|
-
for (const err of result.errors) {
|
|
1703
|
-
getLogger().error(
|
|
1704
|
-
chalk.red(` Error: ${err.route} ${JSON.stringify(err.params)}: ${err.error}`)
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
process.exit(1);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
1847
|
const s = createRequire(import.meta.url), e = s("typescript"), u = e;
|
|
1711
1848
|
new Proxy(e, {
|
|
1712
1849
|
get(t, r) {
|
|
@@ -1714,7 +1851,7 @@ new Proxy(e, {
|
|
|
1714
1851
|
}
|
|
1715
1852
|
});
|
|
1716
1853
|
const FRAMEWORK_PROP_TYPES = /* @__PURE__ */ new Set(["PageProps", "RequestQuery"]);
|
|
1717
|
-
function checkComponentPropsAndParams(sourceCode, contract,
|
|
1854
|
+
function checkComponentPropsAndParams(sourceCode, contract, contractName2, contractPath, sourcePath) {
|
|
1718
1855
|
const errors = [];
|
|
1719
1856
|
const warnings = [];
|
|
1720
1857
|
const sourceFile = u.createSourceFile(
|
|
@@ -1733,7 +1870,7 @@ function checkComponentPropsAndParams(sourceCode, contract, contractName, contra
|
|
|
1733
1870
|
localInterfaces,
|
|
1734
1871
|
contractImportedTypes,
|
|
1735
1872
|
contract,
|
|
1736
|
-
|
|
1873
|
+
contractName2,
|
|
1737
1874
|
contractPath,
|
|
1738
1875
|
sourcePath,
|
|
1739
1876
|
errors,
|
|
@@ -1746,7 +1883,7 @@ function checkComponentPropsAndParams(sourceCode, contract, contractName, contra
|
|
|
1746
1883
|
localInterfaces,
|
|
1747
1884
|
contractImportedTypes,
|
|
1748
1885
|
contract,
|
|
1749
|
-
|
|
1886
|
+
contractName2,
|
|
1750
1887
|
contractPath,
|
|
1751
1888
|
sourcePath,
|
|
1752
1889
|
errors,
|
|
@@ -1865,10 +2002,10 @@ function extractTypeNames(typeNode) {
|
|
|
1865
2002
|
}
|
|
1866
2003
|
return [];
|
|
1867
2004
|
}
|
|
1868
|
-
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract,
|
|
2005
|
+
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName2, contractPath, sourcePath, errors, warnings) {
|
|
1869
2006
|
if (FRAMEWORK_PROP_TYPES.has(propsTypeName)) return;
|
|
1870
2007
|
if (contractImportedTypes.has(propsTypeName)) return;
|
|
1871
|
-
const prefix = `[${
|
|
2008
|
+
const prefix = `[${contractName2}]`;
|
|
1872
2009
|
const iface = localInterfaces.get(propsTypeName);
|
|
1873
2010
|
if (!iface) {
|
|
1874
2011
|
if (!contract.props || contract.props.length === 0) {
|
|
@@ -1916,8 +2053,8 @@ function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedT
|
|
|
1916
2053
|
}
|
|
1917
2054
|
}
|
|
1918
2055
|
}
|
|
1919
|
-
function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImportedTypes, contract,
|
|
1920
|
-
const prefix = `[${
|
|
2056
|
+
function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImportedTypes, contract, contractName2, contractPath, sourcePath, errors, warnings) {
|
|
2057
|
+
const prefix = `[${contractName2}]`;
|
|
1921
2058
|
if (!contract.params || contract.params.length === 0) {
|
|
1922
2059
|
errors.push({
|
|
1923
2060
|
type: "contract-invalid",
|
|
@@ -3679,14 +3816,14 @@ async function checkComponentContractConsistency(contract, context, result) {
|
|
|
3679
3816
|
} catch {
|
|
3680
3817
|
return;
|
|
3681
3818
|
}
|
|
3682
|
-
const
|
|
3819
|
+
const contractName2 = contract.contract.replace(/\.jay-contract$/, "");
|
|
3683
3820
|
const checkResult = checkComponentPropsAndParams(
|
|
3684
3821
|
sourceCode,
|
|
3685
3822
|
{
|
|
3686
3823
|
props: parsed.val?.props,
|
|
3687
3824
|
params: parsed.val?.params
|
|
3688
3825
|
},
|
|
3689
|
-
|
|
3826
|
+
contractName2,
|
|
3690
3827
|
contractPath,
|
|
3691
3828
|
sourcePath
|
|
3692
3829
|
);
|
|
@@ -3958,36 +4095,731 @@ async function validateDynamicContracts(context, result) {
|
|
|
3958
4095
|
}
|
|
3959
4096
|
}
|
|
3960
4097
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
4098
|
+
function overrideSpecFor(f) {
|
|
4099
|
+
switch (f.kind) {
|
|
4100
|
+
case "attribute":
|
|
4101
|
+
return { target: "markup-attribute", path: f.path, value: f.name };
|
|
4102
|
+
case "style-declaration":
|
|
4103
|
+
return { target: "markup-attribute", path: f.path, value: `style.${f.property}` };
|
|
4104
|
+
case "children":
|
|
4105
|
+
return { target: "markup-attribute", path: f.path, value: "children" };
|
|
4106
|
+
case "css-rule":
|
|
4107
|
+
return { target: "css-comment", value: "" };
|
|
4108
|
+
case "css-declaration":
|
|
4109
|
+
return { target: "css-comment", value: f.property };
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
function facetLabel(f) {
|
|
4113
|
+
switch (f.kind) {
|
|
4114
|
+
case "attribute":
|
|
4115
|
+
return `<${f.element.toLowerCase()}> attribute "${f.name}"`;
|
|
4116
|
+
case "style-declaration":
|
|
4117
|
+
return `<${f.element.toLowerCase()}> style "${f.property}"`;
|
|
4118
|
+
case "children":
|
|
4119
|
+
return `<${f.element.toLowerCase()}> children`;
|
|
4120
|
+
case "css-rule":
|
|
4121
|
+
return `css rule "${f.selector}"`;
|
|
4122
|
+
case "css-declaration":
|
|
4123
|
+
return `css "${f.selector}" declaration "${f.property}"`;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
const CONTENT_MARKER = "jay-content";
|
|
4127
|
+
const META_ATTRS = /* @__PURE__ */ new Set(["jc", "override", "page-scope", CONTENT_MARKER]);
|
|
4128
|
+
function isMetaAttr(name) {
|
|
4129
|
+
return META_ATTRS.has(name.toLowerCase());
|
|
4130
|
+
}
|
|
4131
|
+
function isRegionTag(el) {
|
|
4132
|
+
return (el.tagName ?? "").toLowerCase().startsWith("jay:");
|
|
4133
|
+
}
|
|
4134
|
+
function readAttr(el, name) {
|
|
4135
|
+
const v = el.getAttribute(name);
|
|
4136
|
+
if (v !== void 0 && v !== null) return v;
|
|
4137
|
+
const raw = el.rawAttributes ?? {};
|
|
4138
|
+
return Object.prototype.hasOwnProperty.call(raw, name) ? raw[name] ?? "" : void 0;
|
|
4139
|
+
}
|
|
4140
|
+
function isPageScope(el) {
|
|
4141
|
+
return readAttr(el, "page-scope") !== void 0;
|
|
4142
|
+
}
|
|
4143
|
+
const NO_SUPPRESSION = Object.freeze({
|
|
4144
|
+
all: false,
|
|
4145
|
+
attributes: /* @__PURE__ */ new Set(),
|
|
4146
|
+
styleProps: /* @__PURE__ */ new Set(),
|
|
4147
|
+
children: false
|
|
4148
|
+
});
|
|
4149
|
+
function emptySuppression() {
|
|
4150
|
+
return { all: false, attributes: /* @__PURE__ */ new Set(), styleProps: /* @__PURE__ */ new Set(), children: false };
|
|
4151
|
+
}
|
|
4152
|
+
function parseFacetTokens(spec, sup) {
|
|
4153
|
+
for (const token of spec.split(/[\s,]+/)) {
|
|
4154
|
+
const t = token.trim();
|
|
4155
|
+
if (!t) continue;
|
|
4156
|
+
if (t.toLowerCase() === "children") sup.children = true;
|
|
4157
|
+
else if (t.toLowerCase().startsWith("style."))
|
|
4158
|
+
sup.styleProps.add(t.slice("style.".length).toLowerCase());
|
|
4159
|
+
else sup.attributes.add(t.toLowerCase());
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
function parseOverride(el) {
|
|
4163
|
+
if (isPageScope(el)) {
|
|
4164
|
+
return { all: true, attributes: /* @__PURE__ */ new Set(), styleProps: /* @__PURE__ */ new Set(), children: false };
|
|
4165
|
+
}
|
|
4166
|
+
const raw = readAttr(el, "override");
|
|
4167
|
+
const sup = emptySuppression();
|
|
4168
|
+
if (raw === void 0) return sup;
|
|
4169
|
+
const spec = raw.trim();
|
|
4170
|
+
if (spec === "" || spec === "*") {
|
|
4171
|
+
sup.all = true;
|
|
4172
|
+
return sup;
|
|
4173
|
+
}
|
|
4174
|
+
parseFacetTokens(spec, sup);
|
|
4175
|
+
return sup;
|
|
4176
|
+
}
|
|
4177
|
+
function parseContent(el) {
|
|
4178
|
+
const raw = readAttr(el, CONTENT_MARKER);
|
|
4179
|
+
const sup = emptySuppression();
|
|
4180
|
+
if (raw === void 0) return sup;
|
|
4181
|
+
const spec = raw.trim();
|
|
4182
|
+
if (spec === "*") {
|
|
4183
|
+
sup.all = true;
|
|
4184
|
+
return sup;
|
|
4185
|
+
}
|
|
4186
|
+
if (spec === "") {
|
|
4187
|
+
sup.children = true;
|
|
4188
|
+
return sup;
|
|
4189
|
+
}
|
|
4190
|
+
parseFacetTokens(spec, sup);
|
|
4191
|
+
return sup;
|
|
4192
|
+
}
|
|
4193
|
+
function unionSuppression(a, b) {
|
|
4194
|
+
return {
|
|
4195
|
+
all: a.all || b.all,
|
|
4196
|
+
attributes: /* @__PURE__ */ new Set([...a.attributes, ...b.attributes]),
|
|
4197
|
+
styleProps: /* @__PURE__ */ new Set([...a.styleProps, ...b.styleProps]),
|
|
4198
|
+
children: a.children || b.children
|
|
4199
|
+
};
|
|
3963
4200
|
}
|
|
3964
|
-
|
|
3965
|
-
|
|
4201
|
+
const BINDING = /\{[^}]*\}/g;
|
|
4202
|
+
function normalizeExpr(value) {
|
|
4203
|
+
const bindingsCollapsed = value.replace(BINDING, (m) => m.replace(/\s+/g, ""));
|
|
4204
|
+
return bindingsCollapsed.replace(/\s+/g, " ").trim();
|
|
4205
|
+
}
|
|
4206
|
+
function parseInlineStyle(style) {
|
|
4207
|
+
const out = /* @__PURE__ */ new Map();
|
|
4208
|
+
if (!style) return out;
|
|
4209
|
+
for (const decl of splitDeclarations(style)) {
|
|
4210
|
+
const idx = firstColon(decl);
|
|
4211
|
+
if (idx < 0) continue;
|
|
4212
|
+
const property = decl.slice(0, idx).trim().toLowerCase();
|
|
4213
|
+
const value = decl.slice(idx + 1).trim();
|
|
4214
|
+
if (property) out.set(property, value);
|
|
4215
|
+
}
|
|
4216
|
+
return out;
|
|
4217
|
+
}
|
|
4218
|
+
function serializeInlineStyle(decls) {
|
|
4219
|
+
return [...decls].map(([property, value]) => `${property}: ${value}`).join("; ");
|
|
4220
|
+
}
|
|
4221
|
+
function splitDeclarations(style) {
|
|
4222
|
+
const parts = [];
|
|
4223
|
+
let depthParen = 0;
|
|
4224
|
+
let depthBrace = 0;
|
|
4225
|
+
let current = "";
|
|
4226
|
+
for (const ch of style) {
|
|
4227
|
+
if (ch === "(") depthParen++;
|
|
4228
|
+
else if (ch === ")") depthParen = Math.max(0, depthParen - 1);
|
|
4229
|
+
else if (ch === "{") depthBrace++;
|
|
4230
|
+
else if (ch === "}") depthBrace = Math.max(0, depthBrace - 1);
|
|
4231
|
+
if (ch === ";" && depthParen === 0 && depthBrace === 0) {
|
|
4232
|
+
parts.push(current);
|
|
4233
|
+
current = "";
|
|
4234
|
+
} else {
|
|
4235
|
+
current += ch;
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
if (current.trim()) parts.push(current);
|
|
4239
|
+
return parts.filter((p) => p.trim());
|
|
4240
|
+
}
|
|
4241
|
+
function firstColon(decl) {
|
|
4242
|
+
let depthParen = 0;
|
|
4243
|
+
let depthBrace = 0;
|
|
4244
|
+
for (let i = 0; i < decl.length; i++) {
|
|
4245
|
+
const ch = decl[i];
|
|
4246
|
+
if (ch === "(") depthParen++;
|
|
4247
|
+
else if (ch === ")") depthParen = Math.max(0, depthParen - 1);
|
|
4248
|
+
else if (ch === "{") depthBrace++;
|
|
4249
|
+
else if (ch === "}") depthBrace = Math.max(0, depthBrace - 1);
|
|
4250
|
+
else if (ch === ":" && depthParen === 0 && depthBrace === 0) return i;
|
|
4251
|
+
}
|
|
4252
|
+
return -1;
|
|
4253
|
+
}
|
|
4254
|
+
function diffBodies(sourceParent, regionParent) {
|
|
4255
|
+
const out = [];
|
|
4256
|
+
const ignoreClass = isRegionTag(regionParent) ? readAttr(regionParent, "ref") : void 0;
|
|
4257
|
+
diffChildren([], sourceParent, regionParent, ignoreClass, out);
|
|
4258
|
+
return out;
|
|
4259
|
+
}
|
|
4260
|
+
function diffChildren(parentPath, sParent, rParent, ignoreClass, out) {
|
|
4261
|
+
const rSup = isElement(rParent) ? parseOverride(rParent) : NO_SUPPRESSION;
|
|
4262
|
+
const sSup = isElement(sParent) ? parseContent(sParent) : NO_SUPPRESSION;
|
|
4263
|
+
const sup = unionSuppression(rSup, sSup);
|
|
4264
|
+
if (sup.all || sup.children) return;
|
|
4265
|
+
const s2 = contentNodes(sParent);
|
|
4266
|
+
const r = contentNodes(rParent);
|
|
4267
|
+
if (!childrenAligned(s2, r)) {
|
|
4268
|
+
const facet = { kind: "children", path: parentPath, element: tagOf(rParent) };
|
|
4269
|
+
out.push({
|
|
4270
|
+
facet,
|
|
4271
|
+
change: "changed",
|
|
4272
|
+
sourceValue: summarize(s2),
|
|
4273
|
+
regionValue: summarize(r),
|
|
4274
|
+
addedElementTags: addedElementTags(s2, r)
|
|
4275
|
+
});
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
for (let i = 0; i < r.length; i++) {
|
|
4279
|
+
const rc = r[i];
|
|
4280
|
+
if (rc.nodeType !== NodeType.ELEMENT_NODE) continue;
|
|
4281
|
+
const re = rc;
|
|
4282
|
+
if (isRegionTag(re)) continue;
|
|
4283
|
+
const childIgnoreClass = parentPath.length === 0 ? ignoreClass : void 0;
|
|
4284
|
+
diffElement([...parentPath, i], s2[i], re, childIgnoreClass, out);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
function diffElement(path2, se, re, ignoreClass, out) {
|
|
4288
|
+
const sup = unionSuppression(parseOverride(re), parseContent(se));
|
|
4289
|
+
if (sup.all) return;
|
|
4290
|
+
compareAttributes(path2, se, re, sup, ignoreClass, out);
|
|
4291
|
+
compareStyle(path2, se, re, sup, out);
|
|
4292
|
+
diffChildren(path2, se, re, void 0, out);
|
|
4293
|
+
}
|
|
4294
|
+
function compareAttributes(path2, se, re, sup, ignoreClass, out) {
|
|
4295
|
+
const sm = attrMap(se);
|
|
4296
|
+
const rm = attrMap(re);
|
|
4297
|
+
const names = [.../* @__PURE__ */ new Set([...sm.keys(), ...rm.keys()])].sort();
|
|
4298
|
+
for (const name of names) {
|
|
4299
|
+
const sv = name === "class" ? stripClassToken(sm.get(name), ignoreClass) : sm.get(name);
|
|
4300
|
+
const rv = name === "class" ? stripClassToken(rm.get(name), ignoreClass) : rm.get(name);
|
|
4301
|
+
const change = scalarChange(sv, rv, (a, b) => normalizeExpr(a) === normalizeExpr(b));
|
|
4302
|
+
if (!change) continue;
|
|
4303
|
+
const facet = { kind: "attribute", path: path2, element: tagOf(re), name };
|
|
4304
|
+
if (!sup.all && !sup.attributes.has(name))
|
|
4305
|
+
out.push({ facet, change, sourceValue: sv, regionValue: rv });
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
function compareStyle(path2, se, re, sup, out) {
|
|
4309
|
+
const sm = parseInlineStyle(se.getAttribute("style"));
|
|
4310
|
+
const rm = parseInlineStyle(re.getAttribute("style"));
|
|
4311
|
+
const props = [.../* @__PURE__ */ new Set([...sm.keys(), ...rm.keys()])].sort();
|
|
4312
|
+
for (const property of props) {
|
|
4313
|
+
const sv = sm.get(property);
|
|
4314
|
+
const rv = rm.get(property);
|
|
4315
|
+
const change = scalarChange(sv, rv, (a, b) => normalizeExpr(a) === normalizeExpr(b));
|
|
4316
|
+
if (!change) continue;
|
|
4317
|
+
const facet = { kind: "style-declaration", path: path2, element: tagOf(re), property };
|
|
4318
|
+
if (!sup.all && !sup.styleProps.has(property))
|
|
4319
|
+
out.push({ facet, change, sourceValue: sv, regionValue: rv });
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
function stripClassToken(value, token) {
|
|
4323
|
+
if (value === void 0 || !token) return value;
|
|
4324
|
+
const kept = value.split(/\s+/).filter((t) => t && t !== token);
|
|
4325
|
+
return kept.length ? kept.join(" ") : void 0;
|
|
4326
|
+
}
|
|
4327
|
+
function scalarChange(sv, rv, equal) {
|
|
4328
|
+
if (sv === void 0 && rv === void 0) return void 0;
|
|
4329
|
+
if (sv === void 0) return "added";
|
|
4330
|
+
if (rv === void 0) return "removed";
|
|
4331
|
+
return equal(sv, rv) ? void 0 : "changed";
|
|
4332
|
+
}
|
|
4333
|
+
function attrMap(el) {
|
|
4334
|
+
const map = /* @__PURE__ */ new Map();
|
|
4335
|
+
const attrs = el.attributes;
|
|
4336
|
+
for (const name of Object.keys(attrs)) {
|
|
4337
|
+
const lower = name.toLowerCase();
|
|
4338
|
+
if (isMetaAttr(lower) || lower === "style") continue;
|
|
4339
|
+
map.set(lower, attrs[name] ?? "");
|
|
4340
|
+
}
|
|
4341
|
+
return map;
|
|
4342
|
+
}
|
|
4343
|
+
function contentNodes(parent) {
|
|
4344
|
+
return parent.childNodes.filter((n) => {
|
|
4345
|
+
if (n.nodeType === NodeType.TEXT_NODE) return normalizeExpr(nodeText(n)) !== "";
|
|
4346
|
+
if (n.nodeType === NodeType.ELEMENT_NODE) return !isPageScope(n);
|
|
4347
|
+
return false;
|
|
4348
|
+
});
|
|
3966
4349
|
}
|
|
3967
|
-
function
|
|
3968
|
-
|
|
3969
|
-
for (
|
|
3970
|
-
const
|
|
3971
|
-
|
|
3972
|
-
if (
|
|
3973
|
-
|
|
4350
|
+
function childrenAligned(s2, r) {
|
|
4351
|
+
if (s2.length !== r.length) return false;
|
|
4352
|
+
for (let i = 0; i < s2.length; i++) {
|
|
4353
|
+
const sk = s2[i].nodeType;
|
|
4354
|
+
const rk = r[i].nodeType;
|
|
4355
|
+
if (sk !== rk) return false;
|
|
4356
|
+
if (rk === NodeType.TEXT_NODE) {
|
|
4357
|
+
if (normalizeExpr(nodeText(s2[i])) !== normalizeExpr(nodeText(r[i]))) return false;
|
|
4358
|
+
} else {
|
|
4359
|
+
if (tagOf(s2[i]).toLowerCase() !== tagOf(r[i]).toLowerCase()) return false;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
return true;
|
|
4363
|
+
}
|
|
4364
|
+
function isElement(n) {
|
|
4365
|
+
return n.nodeType === NodeType.ELEMENT_NODE;
|
|
4366
|
+
}
|
|
4367
|
+
function tagOf(n) {
|
|
4368
|
+
return isElement(n) ? n.tagName ?? "(root)" : "(root)";
|
|
4369
|
+
}
|
|
4370
|
+
function nodeText(n) {
|
|
4371
|
+
return n.rawText ?? n.text ?? "";
|
|
4372
|
+
}
|
|
4373
|
+
function addedElementTags(s2, r) {
|
|
4374
|
+
const srcCounts = /* @__PURE__ */ new Map();
|
|
4375
|
+
for (const n of s2)
|
|
4376
|
+
if (n.nodeType === NodeType.ELEMENT_NODE) {
|
|
4377
|
+
const t = tagOf(n).toLowerCase();
|
|
4378
|
+
srcCounts.set(t, (srcCounts.get(t) ?? 0) + 1);
|
|
4379
|
+
}
|
|
4380
|
+
const added = [];
|
|
4381
|
+
for (const n of r)
|
|
4382
|
+
if (n.nodeType === NodeType.ELEMENT_NODE) {
|
|
4383
|
+
const t = tagOf(n).toLowerCase();
|
|
4384
|
+
const remaining = srcCounts.get(t) ?? 0;
|
|
4385
|
+
if (remaining > 0) srcCounts.set(t, remaining - 1);
|
|
4386
|
+
else added.push(t);
|
|
4387
|
+
}
|
|
4388
|
+
return added;
|
|
4389
|
+
}
|
|
4390
|
+
function summarize(nodes) {
|
|
4391
|
+
return nodes.map(
|
|
4392
|
+
(n) => n.nodeType === NodeType.ELEMENT_NODE ? `<${tagOf(n).toLowerCase()}>` : `"${normalizeExpr(nodeText(n))}"`
|
|
4393
|
+
).join("");
|
|
4394
|
+
}
|
|
4395
|
+
function diffCss(source, region) {
|
|
4396
|
+
const s2 = buildUnits(source);
|
|
4397
|
+
const r = buildUnits(region);
|
|
4398
|
+
const out = [];
|
|
4399
|
+
for (const key of [.../* @__PURE__ */ new Set([...s2.keys(), ...r.keys()])].sort()) {
|
|
4400
|
+
const su = s2.get(key);
|
|
4401
|
+
const ru = r.get(key);
|
|
4402
|
+
if (su && !ru) {
|
|
4403
|
+
out.push({
|
|
4404
|
+
facet: { kind: "css-rule", selector: su.facetSelector },
|
|
4405
|
+
change: "removed",
|
|
4406
|
+
sourceValue: serialize(su)
|
|
4407
|
+
});
|
|
4408
|
+
continue;
|
|
4409
|
+
}
|
|
4410
|
+
if (!su && ru) {
|
|
4411
|
+
if (ru.ruleOwned) continue;
|
|
4412
|
+
out.push({
|
|
4413
|
+
facet: { kind: "css-rule", selector: ru.facetSelector },
|
|
4414
|
+
change: "added",
|
|
4415
|
+
regionValue: serialize(ru)
|
|
4416
|
+
});
|
|
4417
|
+
continue;
|
|
4418
|
+
}
|
|
4419
|
+
if (!su || !ru) continue;
|
|
4420
|
+
if (ru.ruleOwned) continue;
|
|
4421
|
+
for (const property of [.../* @__PURE__ */ new Set([...su.decls.keys(), ...ru.decls.keys()])].sort()) {
|
|
4422
|
+
const sv = su.decls.get(property);
|
|
4423
|
+
const rv = ru.decls.get(property);
|
|
4424
|
+
const change = declChange(sv, rv);
|
|
4425
|
+
if (!change) continue;
|
|
4426
|
+
if (ru.ownedProps.has(property)) continue;
|
|
4427
|
+
out.push({
|
|
4428
|
+
facet: { kind: "css-declaration", selector: ru.facetSelector, property },
|
|
4429
|
+
change,
|
|
4430
|
+
sourceValue: sv,
|
|
4431
|
+
regionValue: rv
|
|
4432
|
+
});
|
|
3974
4433
|
}
|
|
3975
4434
|
}
|
|
3976
|
-
return
|
|
4435
|
+
return out;
|
|
4436
|
+
}
|
|
4437
|
+
const OVERRIDE_RE = /^jay:override\s*(?::\s*([\w-]+))?\s*$/i;
|
|
4438
|
+
function buildUnits(css) {
|
|
4439
|
+
const map = /* @__PURE__ */ new Map();
|
|
4440
|
+
const root = postcss.parse(css);
|
|
4441
|
+
root.walkRules((rule) => {
|
|
4442
|
+
const scope = scopeOf(rule);
|
|
4443
|
+
const decls = /* @__PURE__ */ new Map();
|
|
4444
|
+
let ruleOwned = false;
|
|
4445
|
+
const ownedProps = /* @__PURE__ */ new Set();
|
|
4446
|
+
const applyComment = (text) => {
|
|
4447
|
+
const m = text.trim().match(OVERRIDE_RE);
|
|
4448
|
+
if (!m) return;
|
|
4449
|
+
if (m[1]) ownedProps.add(m[1].toLowerCase());
|
|
4450
|
+
else ruleOwned = true;
|
|
4451
|
+
};
|
|
4452
|
+
const prev = rule.prev();
|
|
4453
|
+
if (prev?.type === "comment") applyComment(prev.text);
|
|
4454
|
+
for (const node of rule.nodes ?? []) {
|
|
4455
|
+
if (node.type === "decl") {
|
|
4456
|
+
const decl = node;
|
|
4457
|
+
decls.set(decl.prop.toLowerCase(), decl.value);
|
|
4458
|
+
} else if (node.type === "comment") {
|
|
4459
|
+
applyComment(node.text);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
for (const selector of rule.selectors) {
|
|
4463
|
+
const norm = normalizeExpr(selector);
|
|
4464
|
+
if (!norm) continue;
|
|
4465
|
+
const facetSelector = scope ? `${scope} ${norm}` : norm;
|
|
4466
|
+
const key = `${scope}||${norm}`;
|
|
4467
|
+
const existing = map.get(key);
|
|
4468
|
+
if (existing) {
|
|
4469
|
+
for (const [p, v] of decls) existing.decls.set(p, v);
|
|
4470
|
+
existing.ruleOwned || (existing.ruleOwned = ruleOwned);
|
|
4471
|
+
for (const p of ownedProps) existing.ownedProps.add(p);
|
|
4472
|
+
} else {
|
|
4473
|
+
map.set(key, {
|
|
4474
|
+
scope,
|
|
4475
|
+
selector: norm,
|
|
4476
|
+
facetSelector,
|
|
4477
|
+
decls: new Map(decls),
|
|
4478
|
+
ruleOwned,
|
|
4479
|
+
ownedProps: new Set(ownedProps)
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
});
|
|
4484
|
+
return map;
|
|
3977
4485
|
}
|
|
3978
|
-
function
|
|
3979
|
-
const
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
4486
|
+
function scopeOf(rule) {
|
|
4487
|
+
const parts = [];
|
|
4488
|
+
let node = rule.parent;
|
|
4489
|
+
while (node) {
|
|
4490
|
+
if (node.type === "atrule") {
|
|
4491
|
+
const at = node;
|
|
4492
|
+
if (at.name.toLowerCase() !== "scope") {
|
|
4493
|
+
parts.unshift(`@${at.name} ${at.params}`.trim());
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
node = node.parent;
|
|
3984
4497
|
}
|
|
3985
|
-
return
|
|
4498
|
+
return parts.join(" ");
|
|
3986
4499
|
}
|
|
3987
|
-
function
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
if (
|
|
4500
|
+
function declChange(sv, rv) {
|
|
4501
|
+
if (sv === void 0 && rv === void 0) return void 0;
|
|
4502
|
+
if (sv === void 0) return "added";
|
|
4503
|
+
if (rv === void 0) return "removed";
|
|
4504
|
+
return normalizeExpr(sv) === normalizeExpr(rv) ? void 0 : "changed";
|
|
4505
|
+
}
|
|
4506
|
+
function serialize(u2) {
|
|
4507
|
+
const body = [...u2.decls].map(([p, v]) => `${p}: ${v}`).join("; ");
|
|
4508
|
+
return `${u2.selector} { ${body} }`;
|
|
4509
|
+
}
|
|
4510
|
+
function materialise(pageHtml, opts) {
|
|
4511
|
+
const root = parse(pageHtml);
|
|
4512
|
+
const errors = [];
|
|
4513
|
+
const contributions = [];
|
|
4514
|
+
fillRegions(root, [], opts, errors, contributions);
|
|
4515
|
+
stripContentMarkers(root);
|
|
4516
|
+
const serialized = root.toString();
|
|
4517
|
+
return {
|
|
4518
|
+
html: opts.prettify ? opts.prettify(serialized) : serialized,
|
|
4519
|
+
css: coalesceCss(contributions),
|
|
4520
|
+
errors
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
function coalesceCss(contributions) {
|
|
4524
|
+
const groups = [];
|
|
4525
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
4526
|
+
for (const { key, selector, css, to } of contributions) {
|
|
4527
|
+
if (selector === null) {
|
|
4528
|
+
groups.push({ selectors: [], css });
|
|
4529
|
+
continue;
|
|
4530
|
+
}
|
|
4531
|
+
const existing = byKey.get(key);
|
|
4532
|
+
if (existing) {
|
|
4533
|
+
if (!existing.selectors.includes(selector)) existing.selectors.push(selector);
|
|
4534
|
+
} else {
|
|
4535
|
+
const group = { selectors: [selector], css, to };
|
|
4536
|
+
byKey.set(key, group);
|
|
4537
|
+
groups.push(group);
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
return groups.map(
|
|
4541
|
+
(g) => g.selectors.length > 0 ? scopeWrap(g.css, g.selectors.join(", "), g.to) : g.css
|
|
4542
|
+
).join("\n\n");
|
|
4543
|
+
}
|
|
4544
|
+
function fillRegions(container, stack, opts, errors, cssBlocks, parentScoped = false) {
|
|
4545
|
+
for (const region of directRegions(container)) {
|
|
4546
|
+
const name = contractName(region);
|
|
4547
|
+
const templatePath = opts.resolveTemplate(name);
|
|
4548
|
+
if (!templatePath) continue;
|
|
4549
|
+
if (stack.includes(templatePath)) {
|
|
4550
|
+
errors.push(
|
|
4551
|
+
`template inclusion cycle: <jay:${name}> (${templatePath}) includes itself via ${[...stack, templatePath].join(" → ")}`
|
|
4552
|
+
);
|
|
4553
|
+
continue;
|
|
4554
|
+
}
|
|
4555
|
+
const template = opts.loadTemplate(templatePath);
|
|
4556
|
+
if (!template) {
|
|
4557
|
+
errors.push(
|
|
4558
|
+
`cannot resolve template "${templatePath}" for <jay:${name}> — check the template= provenance`
|
|
4559
|
+
);
|
|
4560
|
+
continue;
|
|
4561
|
+
}
|
|
4562
|
+
const existing = region.innerHTML;
|
|
4563
|
+
const filled = opts.preserveOverrides ? mergeOverrides(template.body, existing) : template.body;
|
|
4564
|
+
region.set_content(filled);
|
|
4565
|
+
const ref = readAttr(region, "ref");
|
|
4566
|
+
let scoped = false;
|
|
4567
|
+
if (template.css) {
|
|
4568
|
+
const selector = (opts.scopeSelector ?? defaultScopeSelector)(region);
|
|
4569
|
+
if (selector) {
|
|
4570
|
+
scoped = true;
|
|
4571
|
+
const to = [
|
|
4572
|
+
...new Set(
|
|
4573
|
+
directRegions(region).filter((c) => opts.resolveTemplate(contractName(c)) !== null).map((c) => readAttr(c, "ref")).filter((r) => !!r).map((r) => `.${r}`)
|
|
4574
|
+
)
|
|
4575
|
+
];
|
|
4576
|
+
cssBlocks.push({
|
|
4577
|
+
key: `${templatePath}\0${[...to].sort().join(",")}`,
|
|
4578
|
+
selector,
|
|
4579
|
+
css: scopeReadyCss(template.body, template.css),
|
|
4580
|
+
to
|
|
4581
|
+
});
|
|
4582
|
+
} else {
|
|
4583
|
+
cssBlocks.push({ key: templatePath, selector: null, css: template.css });
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
if (ref && (scoped || parentScoped)) stampScopeAnchor(region, ref);
|
|
4587
|
+
fillRegions(region, [...stack, templatePath], opts, errors, cssBlocks, scoped);
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
function directRegions(container) {
|
|
4591
|
+
const out = [];
|
|
4592
|
+
const visit = (el) => {
|
|
4593
|
+
for (const child of el.childNodes) {
|
|
4594
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
4595
|
+
if (isRegionTag(child)) out.push(child);
|
|
4596
|
+
else visit(child);
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
visit(container);
|
|
4600
|
+
return out;
|
|
4601
|
+
}
|
|
4602
|
+
function contractName(region) {
|
|
4603
|
+
return (region.tagName ?? "").slice("jay:".length).toLowerCase();
|
|
4604
|
+
}
|
|
4605
|
+
function defaultScopeSelector(region) {
|
|
4606
|
+
const ref = readAttr(region, "ref");
|
|
4607
|
+
return ref ? `.${ref}` : null;
|
|
4608
|
+
}
|
|
4609
|
+
function scopeWrap(css, selector, to) {
|
|
4610
|
+
const limit = to && to.length ? ` to (${to.join(", ")})` : "";
|
|
4611
|
+
return `@scope (${selector})${limit} {
|
|
4612
|
+
${css}
|
|
4613
|
+
}`;
|
|
4614
|
+
}
|
|
4615
|
+
function rootClassesOf(templateBody) {
|
|
4616
|
+
const classes = /* @__PURE__ */ new Set();
|
|
4617
|
+
for (const child of parse(templateBody).childNodes) {
|
|
4618
|
+
if (child instanceof HTMLElement) {
|
|
4619
|
+
for (const c of child.classList.values()) classes.add(c);
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
return classes;
|
|
4623
|
+
}
|
|
4624
|
+
function scopeReadyCss(templateBody, css) {
|
|
4625
|
+
const rootClasses = rootClassesOf(templateBody);
|
|
4626
|
+
if (rootClasses.size === 0) return css;
|
|
4627
|
+
try {
|
|
4628
|
+
const root = postcss.parse(css);
|
|
4629
|
+
root.walkRules((rule) => {
|
|
4630
|
+
rule.selectors = rule.selectors.map((sel) => rewriteRootSelector(sel, rootClasses));
|
|
4631
|
+
});
|
|
4632
|
+
return root.toString();
|
|
4633
|
+
} catch {
|
|
4634
|
+
return css;
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
function rewriteRootSelector(selector, rootClasses) {
|
|
4638
|
+
return selector.replace(
|
|
4639
|
+
/\.([A-Za-z0-9_-]+)/g,
|
|
4640
|
+
(match, name) => rootClasses.has(name) ? ":scope" : match
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
function stampScopeAnchor(region, className) {
|
|
4644
|
+
for (const child of region.childNodes) {
|
|
4645
|
+
if (child instanceof HTMLElement) child.classList.add(className);
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
function mergeOverrides(templateBody, existingBody) {
|
|
4649
|
+
const tRoot = parse(templateBody);
|
|
4650
|
+
const eRoot = parse(existingBody);
|
|
4651
|
+
mergeChildren(tRoot, eRoot);
|
|
4652
|
+
stripContentMarkers(tRoot);
|
|
4653
|
+
return tRoot.toString();
|
|
4654
|
+
}
|
|
4655
|
+
function stripContentMarkers(root) {
|
|
4656
|
+
const visit = (el) => {
|
|
4657
|
+
if (readAttr(el, CONTENT_MARKER) !== void 0) el.removeAttribute(CONTENT_MARKER);
|
|
4658
|
+
for (const child of el.childNodes) if (child instanceof HTMLElement) visit(child);
|
|
4659
|
+
};
|
|
4660
|
+
visit(root);
|
|
4661
|
+
}
|
|
4662
|
+
function mergeChildren(tParent, eParent) {
|
|
4663
|
+
const t = contentElements(tParent);
|
|
4664
|
+
const e2 = contentElements(eParent);
|
|
4665
|
+
if (!aligned(t, e2)) return;
|
|
4666
|
+
for (let i = 0; i < t.length; i++) mergeElement(t[i], e2[i]);
|
|
4667
|
+
}
|
|
4668
|
+
function mergeElement(te, ee) {
|
|
4669
|
+
if (isRegionTag(te)) {
|
|
4670
|
+
te.set_content(ee.innerHTML);
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
const sup = unionSuppression(parseOverride(ee), parseContent(te));
|
|
4674
|
+
if (sup.all) {
|
|
4675
|
+
te.replaceWith(ee.outerHTML);
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
carryOwnedAttributes(te, ee, sup);
|
|
4679
|
+
carryOwnedStyle(te, ee, sup);
|
|
4680
|
+
carryMarkers(te, ee);
|
|
4681
|
+
if (sup.children) {
|
|
4682
|
+
te.set_content(ee.innerHTML);
|
|
4683
|
+
} else {
|
|
4684
|
+
mergeChildren(te, ee);
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
function carryOwnedAttributes(te, ee, sup) {
|
|
4688
|
+
for (const name of sup.attributes) {
|
|
4689
|
+
const v = readAttr(ee, name);
|
|
4690
|
+
if (v === void 0) te.removeAttribute(name);
|
|
4691
|
+
else te.setAttribute(name, v);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
function carryOwnedStyle(te, ee, sup) {
|
|
4695
|
+
if (sup.styleProps.size === 0) return;
|
|
4696
|
+
const merged = parseInlineStyle(te.getAttribute("style"));
|
|
4697
|
+
const pageStyle = parseInlineStyle(ee.getAttribute("style"));
|
|
4698
|
+
for (const prop of sup.styleProps) {
|
|
4699
|
+
const v = pageStyle.get(prop);
|
|
4700
|
+
if (v === void 0) merged.delete(prop);
|
|
4701
|
+
else merged.set(prop, v);
|
|
4702
|
+
}
|
|
4703
|
+
const serialized = serializeInlineStyle(merged);
|
|
4704
|
+
if (serialized) te.setAttribute("style", serialized);
|
|
4705
|
+
else te.removeAttribute("style");
|
|
4706
|
+
}
|
|
4707
|
+
function carryMarkers(te, ee) {
|
|
4708
|
+
const override = readAttr(ee, "override");
|
|
4709
|
+
if (override !== void 0) te.setAttribute("override", override);
|
|
4710
|
+
if (isPageScope(ee)) te.setAttribute("page-scope", readAttr(ee, "page-scope") ?? "");
|
|
4711
|
+
}
|
|
4712
|
+
function contentElements(parent) {
|
|
4713
|
+
return parent.childNodes.filter((n) => n instanceof HTMLElement);
|
|
4714
|
+
}
|
|
4715
|
+
function aligned(t, e2) {
|
|
4716
|
+
if (t.length !== e2.length) return false;
|
|
4717
|
+
for (let i = 0; i < t.length; i++) {
|
|
4718
|
+
if ((t[i].tagName ?? "").toLowerCase() !== (e2[i].tagName ?? "").toLowerCase()) return false;
|
|
4719
|
+
}
|
|
4720
|
+
return true;
|
|
4721
|
+
}
|
|
4722
|
+
function buildMaterialiseOptions(pageDir, jayHtml, readFile, extra = {}) {
|
|
4723
|
+
const nameToPath = /* @__PURE__ */ new Map();
|
|
4724
|
+
for (const imp of jayHtml.headlessImports) {
|
|
4725
|
+
if (imp.template) {
|
|
4726
|
+
nameToPath.set(imp.contractName.toLowerCase(), path$1.resolve(pageDir, imp.template));
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
return {
|
|
4730
|
+
resolveTemplate: (name) => nameToPath.get(name.toLowerCase()) ?? null,
|
|
4731
|
+
loadTemplate: (absPath) => {
|
|
4732
|
+
const content = readFile(absPath);
|
|
4733
|
+
if (content === void 0) return null;
|
|
4734
|
+
const root = parse(content);
|
|
4735
|
+
const body = root.querySelector("body");
|
|
4736
|
+
const styles = root.querySelectorAll("style");
|
|
4737
|
+
const css = styles.map((s2) => s2.textContent).join("\n\n");
|
|
4738
|
+
return {
|
|
4739
|
+
body: body ? body.innerHTML : content,
|
|
4740
|
+
css: css.trim() ? css : void 0
|
|
4741
|
+
};
|
|
4742
|
+
},
|
|
4743
|
+
...extra
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
function extractScopeBlock(css, selector) {
|
|
4747
|
+
for (const { selectors, block } of splitScopeBlocks(css)) {
|
|
4748
|
+
if (selectors.includes(selector)) return block;
|
|
4749
|
+
}
|
|
4750
|
+
return void 0;
|
|
4751
|
+
}
|
|
4752
|
+
function tokenizeCss(css) {
|
|
4753
|
+
const out = [];
|
|
4754
|
+
if (!css) return out;
|
|
4755
|
+
const header = /@scope\s*\(\s*([^)]+?)\s*\)(?:\s*to\s*\(\s*([^)]+?)\s*\))?\s*\{/g;
|
|
4756
|
+
let last = 0;
|
|
4757
|
+
let m;
|
|
4758
|
+
while ((m = header.exec(css)) !== null) {
|
|
4759
|
+
let depth = 0;
|
|
4760
|
+
let end = -1;
|
|
4761
|
+
for (let i = m.index + m[0].length - 1; i < css.length; i++) {
|
|
4762
|
+
const ch = css[i];
|
|
4763
|
+
if (ch === "{") depth++;
|
|
4764
|
+
else if (ch === "}") {
|
|
4765
|
+
depth--;
|
|
4766
|
+
if (depth === 0) {
|
|
4767
|
+
end = i + 1;
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
if (end === -1) break;
|
|
4773
|
+
if (m.index > last) out.push({ type: "raw", text: css.slice(last, m.index) });
|
|
4774
|
+
const selector = m[1];
|
|
4775
|
+
const selectors = selector.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
4776
|
+
const to = (m[2] ?? "").split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
4777
|
+
out.push({ type: "scope", selector, selectors, to, block: css.slice(m.index, end) });
|
|
4778
|
+
last = end;
|
|
4779
|
+
header.lastIndex = end;
|
|
4780
|
+
}
|
|
4781
|
+
if (last < css.length) out.push({ type: "raw", text: css.slice(last) });
|
|
4782
|
+
return out;
|
|
4783
|
+
}
|
|
4784
|
+
function splitScopeBlocks(css) {
|
|
4785
|
+
return tokenizeCss(css).filter((s2) => s2.type === "scope").map(({ selector, selectors, to, block }) => ({ selector, selectors, to, block }));
|
|
4786
|
+
}
|
|
4787
|
+
function scopeInnerBody(block) {
|
|
4788
|
+
return block.replace(/^@scope\s*\([^)]*\)(?:\s*to\s*\([^)]*\))?\s*\{/, "").replace(/\}\s*$/, "");
|
|
4789
|
+
}
|
|
4790
|
+
function normalizeCssBody(body) {
|
|
4791
|
+
return body.replace(/\s+/g, " ").replace(/\s*([{}:;,])\s*/g, "$1").replace(/;}/g, "}").trim();
|
|
4792
|
+
}
|
|
4793
|
+
async function findJayFiles(dir) {
|
|
4794
|
+
return await glob(`${dir}/**/*${JAY_EXTENSION$1}`);
|
|
4795
|
+
}
|
|
4796
|
+
async function findContractFiles(dir) {
|
|
4797
|
+
return await glob(`${dir}/**/*${JAY_CONTRACT_EXTENSION$1}`);
|
|
4798
|
+
}
|
|
4799
|
+
function flattenContractTags(tags, prefix) {
|
|
4800
|
+
const result = [];
|
|
4801
|
+
for (const tag of tags) {
|
|
4802
|
+
const tagPath = prefix ? `${prefix}.${tag.tag}` : tag.tag;
|
|
4803
|
+
result.push({ path: tagPath, required: tag.required === true });
|
|
4804
|
+
if (tag.tags) {
|
|
4805
|
+
result.push(...flattenContractTags(tag.tags, tagPath));
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
return result;
|
|
4809
|
+
}
|
|
4810
|
+
function extractExpressions(text) {
|
|
4811
|
+
const results = [];
|
|
4812
|
+
const regex = /\{([^}]+)\}/g;
|
|
4813
|
+
let match;
|
|
4814
|
+
while ((match = regex.exec(text)) !== null) {
|
|
4815
|
+
results.push(match[1].trim());
|
|
4816
|
+
}
|
|
4817
|
+
return results;
|
|
4818
|
+
}
|
|
4819
|
+
function extractTagPath(expr) {
|
|
4820
|
+
let cleaned = expr.replace(/^!/, "").trim();
|
|
4821
|
+
cleaned = cleaned.split(/\s*[!=]==?\s*/)[0].trim();
|
|
4822
|
+
if (cleaned === "." || cleaned === "") return null;
|
|
3991
4823
|
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(cleaned)) {
|
|
3992
4824
|
return cleaned;
|
|
3993
4825
|
}
|
|
@@ -4038,9 +4870,9 @@ function collectUsedTags(jayHtml) {
|
|
|
4038
4870
|
const tagName = element.rawTagName?.toLowerCase();
|
|
4039
4871
|
let childScopes = scopes;
|
|
4040
4872
|
if (tagName?.startsWith("jay:")) {
|
|
4041
|
-
const
|
|
4873
|
+
const contractName2 = tagName.substring(4);
|
|
4042
4874
|
const idx = imports.findIndex(
|
|
4043
|
-
(imp) => imp.contractName ===
|
|
4875
|
+
(imp) => imp.contractName === contractName2 && imp.contract
|
|
4044
4876
|
);
|
|
4045
4877
|
if (idx !== -1) {
|
|
4046
4878
|
childScopes = [...scopes, { importIndex: idx, prefix: "" }];
|
|
@@ -4204,9 +5036,9 @@ function checkRefElementTypes(jayHtml, file) {
|
|
|
4204
5036
|
const tagName = element.rawTagName?.toLowerCase();
|
|
4205
5037
|
let childScopes = scopes;
|
|
4206
5038
|
if (tagName?.startsWith("jay:")) {
|
|
4207
|
-
const
|
|
5039
|
+
const contractName2 = tagName.substring(4);
|
|
4208
5040
|
const idx = imports.findIndex(
|
|
4209
|
-
(imp) => imp.contractName ===
|
|
5041
|
+
(imp) => imp.contractName === contractName2 && imp.contract
|
|
4210
5042
|
);
|
|
4211
5043
|
if (idx !== -1) {
|
|
4212
5044
|
childScopes = [...scopes, { importIndex: idx, prefix: "" }];
|
|
@@ -4292,7 +5124,7 @@ const DOCUMENT_ACCESS_PATTERNS = [
|
|
|
4292
5124
|
const DOM_SUPPRESS_COMMENT = "jay-dom: allow";
|
|
4293
5125
|
function checkDirectDocumentAccess(jayHtmlPath) {
|
|
4294
5126
|
const dirname = path$1.dirname(jayHtmlPath);
|
|
4295
|
-
const basename = path$1.basename(jayHtmlPath, JAY_EXTENSION);
|
|
5127
|
+
const basename = path$1.basename(jayHtmlPath, JAY_EXTENSION$1);
|
|
4296
5128
|
const candidates = [path$1.join(dirname, `${basename}.ts`), path$1.join(dirname, "page.ts")];
|
|
4297
5129
|
const compPath = candidates.find((p) => fs$1.existsSync(p));
|
|
4298
5130
|
if (!compPath) return [];
|
|
@@ -4418,39 +5250,410 @@ const HEADLESS_SKIP_ATTRS = /* @__PURE__ */ new Set([
|
|
|
4418
5250
|
"props",
|
|
4419
5251
|
"key",
|
|
4420
5252
|
"jay-coordinate-base",
|
|
4421
|
-
"jay-scope"
|
|
5253
|
+
"jay-scope",
|
|
5254
|
+
"jc"
|
|
5255
|
+
// component-provenance marker stamped on a flattened <jay:X> region (DL#196)
|
|
4422
5256
|
]);
|
|
4423
5257
|
const PHASE_ORDER = {
|
|
4424
5258
|
slow: 0,
|
|
4425
5259
|
fast: 1,
|
|
4426
5260
|
"fast+interactive": 2
|
|
4427
5261
|
};
|
|
4428
|
-
function
|
|
5262
|
+
function resolveBindingTag(bindingPath, jayHtml) {
|
|
4429
5263
|
const segments = bindingPath.split(".");
|
|
4430
5264
|
const root = segments[0];
|
|
4431
5265
|
const keyedImport = jayHtml.headlessImports.find((i) => i.key === root && i.contract);
|
|
4432
5266
|
if (keyedImport?.contract) {
|
|
4433
5267
|
const tagPath = segments.slice(1).join(".");
|
|
4434
5268
|
if (!tagPath) return void 0;
|
|
4435
|
-
|
|
4436
|
-
if (!tag) return void 0;
|
|
4437
|
-
return tag.phase || "slow";
|
|
5269
|
+
return resolveContractTag(keyedImport.contract, tagPath);
|
|
4438
5270
|
}
|
|
4439
5271
|
if (jayHtml.contract) {
|
|
4440
|
-
|
|
4441
|
-
if (!tag) return void 0;
|
|
4442
|
-
return tag.phase || "slow";
|
|
5272
|
+
return resolveContractTag(jayHtml.contract, bindingPath);
|
|
4443
5273
|
}
|
|
4444
5274
|
return void 0;
|
|
4445
5275
|
}
|
|
5276
|
+
function resolveBindingPhase(bindingPath, jayHtml) {
|
|
5277
|
+
const tag = resolveBindingTag(bindingPath, jayHtml);
|
|
5278
|
+
return tag ? tag.phase || "slow" : void 0;
|
|
5279
|
+
}
|
|
5280
|
+
function resolveBindingDataType(bindingPath, jayHtml) {
|
|
5281
|
+
return resolveBindingTag(bindingPath, jayHtml)?.dataType;
|
|
5282
|
+
}
|
|
5283
|
+
function typeLabel(t) {
|
|
5284
|
+
return isEnumType$1(t) ? `enum(${t.values.join(" | ")})` : t.name;
|
|
5285
|
+
}
|
|
5286
|
+
function collectRegionElements(root) {
|
|
5287
|
+
const out = [];
|
|
5288
|
+
const visit = (el) => {
|
|
5289
|
+
for (const child of el.childNodes) {
|
|
5290
|
+
if (child.nodeType !== NodeType.ELEMENT_NODE) continue;
|
|
5291
|
+
const childEl = child;
|
|
5292
|
+
if (isRegionTag(childEl)) out.push(childEl);
|
|
5293
|
+
visit(childEl);
|
|
5294
|
+
}
|
|
5295
|
+
};
|
|
5296
|
+
visit(root);
|
|
5297
|
+
return out;
|
|
5298
|
+
}
|
|
5299
|
+
function directChildRegions(region) {
|
|
5300
|
+
const out = [];
|
|
5301
|
+
const visit = (el) => {
|
|
5302
|
+
for (const child of el.childNodes) {
|
|
5303
|
+
if (child.nodeType !== NodeType.ELEMENT_NODE) continue;
|
|
5304
|
+
const childEl = child;
|
|
5305
|
+
if (isRegionTag(childEl)) out.push(childEl);
|
|
5306
|
+
else visit(childEl);
|
|
5307
|
+
}
|
|
5308
|
+
};
|
|
5309
|
+
visit(region);
|
|
5310
|
+
return out;
|
|
5311
|
+
}
|
|
5312
|
+
function formatRegionDrift(contractName2, template, entry) {
|
|
5313
|
+
const label = facetLabel(entry.facet);
|
|
5314
|
+
const values = entry.change === "added" ? ` (now ${entry.regionValue ?? ""})` : entry.change === "removed" ? ` (was ${entry.sourceValue ?? ""})` : ` (${entry.sourceValue ?? ""} → ${entry.regionValue ?? ""})`;
|
|
5315
|
+
const spec = overrideSpecFor(entry.facet);
|
|
5316
|
+
const mark = spec.target === "markup-attribute" ? `mark the node override="${spec.value}"` : spec.value ? `mark the CSS rule /* jay:override: ${spec.value} */` : "mark the CSS rule /* jay:override */";
|
|
5317
|
+
const contentSlotHint = entry.facet.kind === "children" ? ` Or, if these children are a content slot, mark the node jay-content in the template "${template}" so consumer edits are expected.` : "";
|
|
5318
|
+
return {
|
|
5319
|
+
message: `<jay:${contractName2}> region differs from source template "${template}": ${label} ${entry.change}${values}.`,
|
|
5320
|
+
suggestion: `To keep the page's version, ${mark}; to discard it and re-flatten from source, run \`jay-stack sync\`.${contentSlotHint}`
|
|
5321
|
+
};
|
|
5322
|
+
}
|
|
5323
|
+
function checkRegionDrift(jayHtml, loadTemplate) {
|
|
5324
|
+
const importsWithTemplate = jayHtml.headlessImports.filter((imp) => imp.template);
|
|
5325
|
+
if (importsWithTemplate.length === 0) return [];
|
|
5326
|
+
const warnings = [];
|
|
5327
|
+
const templateBodyCache = /* @__PURE__ */ new Map();
|
|
5328
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5329
|
+
const contractName2 = (region.rawTagName ?? "").toLowerCase().substring(4);
|
|
5330
|
+
const imp = importsWithTemplate.find((i) => i.contractName === contractName2);
|
|
5331
|
+
if (!imp?.template) continue;
|
|
5332
|
+
let templateBody = templateBodyCache.get(imp.template);
|
|
5333
|
+
if (templateBody === void 0) {
|
|
5334
|
+
const content = loadTemplate(imp.template);
|
|
5335
|
+
templateBody = (content ? parse(content).querySelector("body") : null) ?? null;
|
|
5336
|
+
templateBodyCache.set(imp.template, templateBody);
|
|
5337
|
+
if (!templateBody) {
|
|
5338
|
+
warnings.push({
|
|
5339
|
+
message: `<jay:${contractName2}> declares template="${imp.template}" but its source template could not be read.`,
|
|
5340
|
+
suggestion: "Fix the path or remove the attribute."
|
|
5341
|
+
});
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
if (!templateBody) continue;
|
|
5345
|
+
for (const entry of diffBodies(templateBody, region)) {
|
|
5346
|
+
warnings.push(formatRegionDrift(contractName2, imp.template, entry));
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
return warnings;
|
|
5350
|
+
}
|
|
5351
|
+
function extractStyleCss(templateHtml) {
|
|
5352
|
+
return parse(templateHtml).querySelectorAll("style").map((s2) => s2.textContent).join("\n\n");
|
|
5353
|
+
}
|
|
5354
|
+
function checkRegionCssDrift(jayHtml, loadTemplate) {
|
|
5355
|
+
const importsWithTemplate = jayHtml.headlessImports.filter((imp) => imp.template);
|
|
5356
|
+
if (importsWithTemplate.length === 0) return [];
|
|
5357
|
+
const pageCss = jayHtml.css ?? "";
|
|
5358
|
+
const warnings = [];
|
|
5359
|
+
const templateCssCache = /* @__PURE__ */ new Map();
|
|
5360
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5361
|
+
const contractName2 = (region.rawTagName ?? "").toLowerCase().substring(4);
|
|
5362
|
+
const imp = importsWithTemplate.find((i) => i.contractName === contractName2);
|
|
5363
|
+
if (!imp?.template) continue;
|
|
5364
|
+
const ref = region.getAttribute("ref");
|
|
5365
|
+
if (!ref) continue;
|
|
5366
|
+
let templateCss = templateCssCache.get(imp.template);
|
|
5367
|
+
if (templateCss === void 0) {
|
|
5368
|
+
const content = loadTemplate(imp.template);
|
|
5369
|
+
if (content === void 0) {
|
|
5370
|
+
templateCss = null;
|
|
5371
|
+
} else {
|
|
5372
|
+
const body = parse(content).querySelector("body");
|
|
5373
|
+
templateCss = scopeReadyCss(body ? body.innerHTML : "", extractStyleCss(content));
|
|
5374
|
+
}
|
|
5375
|
+
templateCssCache.set(imp.template, templateCss);
|
|
5376
|
+
}
|
|
5377
|
+
if (templateCss === null) continue;
|
|
5378
|
+
const pageBlock = extractScopeBlock(pageCss, `.${ref}`) ?? "";
|
|
5379
|
+
if (!templateCss.trim() && !pageBlock.trim()) continue;
|
|
5380
|
+
if (templateCss.trim() && !pageBlock.trim()) {
|
|
5381
|
+
warnings.push({
|
|
5382
|
+
message: `<jay:${contractName2}> (template="${imp.template}") has no @scope (.${ref}) CSS block on the page, but its source template ships CSS.`,
|
|
5383
|
+
suggestion: "Run jay-stack sync to re-flatten the region CSS."
|
|
5384
|
+
});
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
for (const entry of diffCss(templateCss, pageBlock)) {
|
|
5388
|
+
warnings.push(formatRegionDrift(contractName2, imp.template, entry));
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
return warnings;
|
|
5392
|
+
}
|
|
5393
|
+
function checkRegionCssScoping(jayHtml) {
|
|
5394
|
+
const importsWithTemplate = jayHtml.headlessImports.filter((imp) => imp.template);
|
|
5395
|
+
if (importsWithTemplate.length === 0) return [];
|
|
5396
|
+
const pageCss = jayHtml.css ?? "";
|
|
5397
|
+
if (!pageCss.trim()) return [];
|
|
5398
|
+
const templateOf = /* @__PURE__ */ new Map();
|
|
5399
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5400
|
+
const contractName2 = (region.rawTagName ?? "").toLowerCase().substring(4);
|
|
5401
|
+
const imp = importsWithTemplate.find((i) => i.contractName === contractName2);
|
|
5402
|
+
const ref = region.getAttribute("ref");
|
|
5403
|
+
if (imp?.template && ref) templateOf.set(`.${ref}`, imp.template);
|
|
5404
|
+
}
|
|
5405
|
+
const warnings = [];
|
|
5406
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5407
|
+
for (const { selectors, to, block } of splitScopeBlocks(pageCss)) {
|
|
5408
|
+
const known = selectors.filter((s2) => templateOf.has(s2));
|
|
5409
|
+
if (known.length === 0) continue;
|
|
5410
|
+
const templates = [...new Set(known.map((s2) => templateOf.get(s2)))];
|
|
5411
|
+
if (templates.length > 1) {
|
|
5412
|
+
warnings.push({
|
|
5413
|
+
message: `A single @scope (${selectors.join(", ")}) block mixes refs from different source templates (${templates.join(", ")}); each region's CSS must be scoped to its own template.`,
|
|
5414
|
+
suggestion: "Run jay-stack sync to re-flatten the region CSS."
|
|
5415
|
+
});
|
|
5416
|
+
continue;
|
|
5417
|
+
}
|
|
5418
|
+
const key = `${templates[0]}\0${scopeBody(block)}\0${[...to].sort().join(",")}`;
|
|
5419
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(selectors.join(", "));
|
|
5420
|
+
}
|
|
5421
|
+
for (const [key, blockSelectors] of groups) {
|
|
5422
|
+
if (blockSelectors.length < 2) continue;
|
|
5423
|
+
const template = key.split("\0")[0];
|
|
5424
|
+
warnings.push({
|
|
5425
|
+
message: `${blockSelectors.length} @scope blocks (${blockSelectors.join(" ; ")}) share template "${template}" and identical CSS but are not coalesced into one selector-list block.`,
|
|
5426
|
+
suggestion: "Run jay-stack sync to coalesce the region CSS."
|
|
5427
|
+
});
|
|
5428
|
+
}
|
|
5429
|
+
return warnings;
|
|
5430
|
+
}
|
|
5431
|
+
function checkNestedRegionRefs(jayHtml, loadTemplate) {
|
|
5432
|
+
const importsWithTemplate = jayHtml.headlessImports.filter((imp) => imp.template);
|
|
5433
|
+
if (importsWithTemplate.length === 0) return [];
|
|
5434
|
+
const warnings = [];
|
|
5435
|
+
const shipsCssCache = /* @__PURE__ */ new Map();
|
|
5436
|
+
const templateShipsCss = (rel) => {
|
|
5437
|
+
let v = shipsCssCache.get(rel);
|
|
5438
|
+
if (v === void 0) {
|
|
5439
|
+
const content = loadTemplate(rel);
|
|
5440
|
+
v = content !== void 0 && extractStyleCss(content).trim() !== "";
|
|
5441
|
+
shipsCssCache.set(rel, v);
|
|
5442
|
+
}
|
|
5443
|
+
return v;
|
|
5444
|
+
};
|
|
5445
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5446
|
+
const contractName2 = (region.rawTagName ?? "").toLowerCase().substring(4);
|
|
5447
|
+
const imp = importsWithTemplate.find((i) => i.contractName === contractName2);
|
|
5448
|
+
if (!imp?.template) continue;
|
|
5449
|
+
const parentRef = region.getAttribute("ref");
|
|
5450
|
+
if (!parentRef) continue;
|
|
5451
|
+
if (!templateShipsCss(imp.template)) continue;
|
|
5452
|
+
for (const child of directChildRegions(region)) {
|
|
5453
|
+
const childName = (child.rawTagName ?? "").toLowerCase().substring(4);
|
|
5454
|
+
const childImp = importsWithTemplate.find((i) => i.contractName === childName);
|
|
5455
|
+
if (!childImp?.template) continue;
|
|
5456
|
+
if (child.getAttribute("ref")) continue;
|
|
5457
|
+
warnings.push({
|
|
5458
|
+
message: `<jay:${childName}> is nested inside <jay:${contractName2}> (template="${imp.template}"), whose CSS is scoped as an @scope (.${parentRef}) donut, but <jay:${childName}> has no ref= — so its region cannot be isolated from the parent's styles.`,
|
|
5459
|
+
suggestion: `Add a ref= to the <jay:${childName}> in template "${imp.template}" so sync can stamp its scope-anchor class and emit the \`to (.<ref>)\` boundary.`
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
return warnings;
|
|
5464
|
+
}
|
|
5465
|
+
const CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
5466
|
+
// inline text semantics
|
|
5467
|
+
"span",
|
|
5468
|
+
"strong",
|
|
5469
|
+
"b",
|
|
5470
|
+
"em",
|
|
5471
|
+
"i",
|
|
5472
|
+
"u",
|
|
5473
|
+
"s",
|
|
5474
|
+
"small",
|
|
5475
|
+
"mark",
|
|
5476
|
+
"sub",
|
|
5477
|
+
"sup",
|
|
5478
|
+
"abbr",
|
|
5479
|
+
"cite",
|
|
5480
|
+
"q",
|
|
5481
|
+
"code",
|
|
5482
|
+
"kbd",
|
|
5483
|
+
"samp",
|
|
5484
|
+
"var",
|
|
5485
|
+
"del",
|
|
5486
|
+
"ins",
|
|
5487
|
+
"time",
|
|
5488
|
+
"data",
|
|
5489
|
+
"bdi",
|
|
5490
|
+
"bdo",
|
|
5491
|
+
"ruby",
|
|
5492
|
+
"rt",
|
|
5493
|
+
"rp",
|
|
5494
|
+
// links & line breaks
|
|
5495
|
+
"a",
|
|
5496
|
+
"br",
|
|
5497
|
+
"wbr",
|
|
5498
|
+
// media (content, not layout)
|
|
5499
|
+
"img",
|
|
5500
|
+
"picture",
|
|
5501
|
+
"source",
|
|
5502
|
+
"svg",
|
|
5503
|
+
"use",
|
|
5504
|
+
"path",
|
|
5505
|
+
"icon",
|
|
5506
|
+
"figure",
|
|
5507
|
+
"figcaption",
|
|
5508
|
+
"audio",
|
|
5509
|
+
"video",
|
|
5510
|
+
// lists
|
|
5511
|
+
"ul",
|
|
5512
|
+
"ol",
|
|
5513
|
+
"li",
|
|
5514
|
+
"dl",
|
|
5515
|
+
"dt",
|
|
5516
|
+
"dd",
|
|
5517
|
+
// tables
|
|
5518
|
+
"table",
|
|
5519
|
+
"thead",
|
|
5520
|
+
"tbody",
|
|
5521
|
+
"tfoot",
|
|
5522
|
+
"tr",
|
|
5523
|
+
"td",
|
|
5524
|
+
"th",
|
|
5525
|
+
"caption",
|
|
5526
|
+
"colgroup",
|
|
5527
|
+
"col",
|
|
5528
|
+
// text blocks & headings
|
|
5529
|
+
"p",
|
|
5530
|
+
"h1",
|
|
5531
|
+
"h2",
|
|
5532
|
+
"h3",
|
|
5533
|
+
"h4",
|
|
5534
|
+
"h5",
|
|
5535
|
+
"h6",
|
|
5536
|
+
"blockquote",
|
|
5537
|
+
"pre",
|
|
5538
|
+
"hr",
|
|
5539
|
+
// form labels/copy (not controls, which imply structure)
|
|
5540
|
+
"label"
|
|
5541
|
+
]);
|
|
5542
|
+
function isNonContentOverride(entry, contentTags) {
|
|
5543
|
+
switch (entry.facet.kind) {
|
|
5544
|
+
case "style-declaration":
|
|
5545
|
+
case "css-rule":
|
|
5546
|
+
case "css-declaration":
|
|
5547
|
+
return true;
|
|
5548
|
+
case "attribute":
|
|
5549
|
+
return entry.facet.name.toLowerCase() === "class";
|
|
5550
|
+
case "children":
|
|
5551
|
+
return (entry.addedElementTags ?? []).some((tag) => !contentTags.has(tag));
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
function regionContractName(region) {
|
|
5555
|
+
return (region.rawTagName ?? "").toLowerCase().substring(4);
|
|
5556
|
+
}
|
|
5557
|
+
function isRegionRuleSuppressed(imp, jayHtml, ruleId) {
|
|
5558
|
+
if (imp?.suppressedValidations?.includes(ruleId)) return true;
|
|
5559
|
+
if (ruleId === "REGION-NOT-LINKED" && imp) {
|
|
5560
|
+
const allow = jayHtml.validationOverrides?.["jay-stack"]?.["allow-inline-region"];
|
|
5561
|
+
const list = Array.isArray(allow) ? allow.map((v) => String(v).toLowerCase()) : [];
|
|
5562
|
+
if (list.includes(imp.contractName.toLowerCase())) return true;
|
|
5563
|
+
}
|
|
5564
|
+
return false;
|
|
5565
|
+
}
|
|
5566
|
+
function checkRegionNotLinked(jayHtml, templatesFor) {
|
|
5567
|
+
const findings = [];
|
|
5568
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5569
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5570
|
+
const contractName2 = regionContractName(region);
|
|
5571
|
+
if (seen.has(contractName2)) continue;
|
|
5572
|
+
const imp = jayHtml.headlessImports.find((i) => i.contractName === contractName2);
|
|
5573
|
+
if (!imp) continue;
|
|
5574
|
+
if (imp.key || imp.template) continue;
|
|
5575
|
+
const templates = templatesFor(imp);
|
|
5576
|
+
if (templates.length === 0) continue;
|
|
5577
|
+
if (isRegionRuleSuppressed(imp, jayHtml, "REGION-NOT-LINKED")) continue;
|
|
5578
|
+
seen.add(contractName2);
|
|
5579
|
+
const templateHint = templates.length === 1 ? `template="${templates[0].path}"` : `template= one of: ${templates.map((t) => `"${t.path}"`).join(", ")}`;
|
|
5580
|
+
findings.push({
|
|
5581
|
+
message: `<jay:${contractName2}> is hand-authored, but a design-system template exists for contract "${imp.contractName}". Prefer linking it as a design-system element.`,
|
|
5582
|
+
suggestion: `Add ${templateHint} to the <script type="application/jay-headless"> import and run \`jay-stack sync\`. For a deliberate one-off, suppress on the import with jay-validations="REGION-NOT-LINKED" (or list the contract under allow-inline-region in <script type="application/jay-validations">). See agent-kit/designer/design-system-guide.md.`
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
return findings;
|
|
5586
|
+
}
|
|
5587
|
+
function checkRegionOverrideNonContent(jayHtml, loadTemplate, contentTags = CONTENT_TAGS) {
|
|
5588
|
+
const importsWithTemplate = jayHtml.headlessImports.filter((imp) => imp.template);
|
|
5589
|
+
if (importsWithTemplate.length === 0) return [];
|
|
5590
|
+
const findings = [];
|
|
5591
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5592
|
+
const templateBodyCache = /* @__PURE__ */ new Map();
|
|
5593
|
+
for (const region of collectRegionElements(jayHtml.body)) {
|
|
5594
|
+
const contractName2 = regionContractName(region);
|
|
5595
|
+
if (seen.has(contractName2)) continue;
|
|
5596
|
+
const imp = importsWithTemplate.find((i) => i.contractName === contractName2);
|
|
5597
|
+
if (!imp?.template) continue;
|
|
5598
|
+
if (isRegionRuleSuppressed(imp, jayHtml, "REGION-OVERRIDE-NON-CONTENT")) continue;
|
|
5599
|
+
let templateBody = templateBodyCache.get(imp.template);
|
|
5600
|
+
if (templateBody === void 0) {
|
|
5601
|
+
const content = loadTemplate(imp.template);
|
|
5602
|
+
templateBody = (content ? parse(content).querySelector("body") : null) ?? null;
|
|
5603
|
+
templateBodyCache.set(imp.template, templateBody);
|
|
5604
|
+
}
|
|
5605
|
+
if (!templateBody) continue;
|
|
5606
|
+
const hasNonContent = diffBodies(templateBody, region).some(
|
|
5607
|
+
(entry) => isNonContentOverride(entry, contentTags)
|
|
5608
|
+
);
|
|
5609
|
+
if (!hasNonContent) continue;
|
|
5610
|
+
seen.add(contractName2);
|
|
5611
|
+
findings.push({
|
|
5612
|
+
message: `<jay:${contractName2}> changes its design-system template's look or structure (style, class, or net-new layout DOM) — that is a different design, not a content tweak.`,
|
|
5613
|
+
suggestion: `Prefer a second design-system template (a new variant) for contract "${imp.contractName}" and link this region to it. Editing text/images, or enriching text with inline markup, is fine; use conditionals only for runtime state changes, not for a different design. To accept this override, suppress on the import with jay-validations="REGION-OVERRIDE-NON-CONTENT". See agent-kit/designer/design-system-guide.md.`
|
|
5614
|
+
});
|
|
5615
|
+
}
|
|
5616
|
+
return findings;
|
|
5617
|
+
}
|
|
5618
|
+
function checkComponentNoTemplate(contractFile, contractName2, hasTemplate, allowNoTemplate) {
|
|
5619
|
+
if (hasTemplate(contractFile)) return void 0;
|
|
5620
|
+
const base = path$1.basename(contractFile, JAY_CONTRACT_EXTENSION$1).toLowerCase();
|
|
5621
|
+
if (allowNoTemplate.has(contractName2.toLowerCase()) || allowNoTemplate.has(base))
|
|
5622
|
+
return void 0;
|
|
5623
|
+
return {
|
|
5624
|
+
message: `Component "${contractName2}" ships no .jay-html template. If it renders UI, create a reusable design-system template so consumers flatten it (template= + jay-stack sync) instead of hand-authoring each usage.`,
|
|
5625
|
+
suggestion: `Author a ${base}.jay-html next to the contract. If this component is intentionally UI-less (data/logic only), suppress project-wide with allow-no-template: ["${contractName2}"] in <script type="application/jay-validations">. See agent-kit/designer/design-system-guide.md.`
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
function checkNoDesignSystem(regionsSeen, templateImportsSeen, suppressed) {
|
|
5629
|
+
if (suppressed || templateImportsSeen > 0) return void 0;
|
|
5630
|
+
if (regionsSeen > 0) {
|
|
5631
|
+
return {
|
|
5632
|
+
message: "This project composes components but none are design-system elements (no template= imports). Ship a .jay-html template with a reused component and flatten it so pages share consistent, upgradable UI.",
|
|
5633
|
+
suggestion: 'Add template= to a headless import and run `jay-stack sync`. Suppress project-wide with allow-no-design-system: true in <script type="application/jay-validations">. See agent-kit/designer/design-system-guide.md.'
|
|
5634
|
+
};
|
|
5635
|
+
}
|
|
5636
|
+
return {
|
|
5637
|
+
message: "This project shares no UI through design-system elements. Consider composing reusable sections as components with .jay-html templates — regions can be used without code, purely to flatten and share a design system.",
|
|
5638
|
+
suggestion: 'See agent-kit/designer/design-system-guide.md. Suppress project-wide with allow-no-design-system: true in <script type="application/jay-validations">.'
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
function scopeBody(block) {
|
|
5642
|
+
return block.replace(/^@scope\s*\([^)]*\)(?:\s*to\s*\([^)]*\))?\s*\{/, "").replace(/\}\s*$/, "").replace(/\s+/g, " ").trim();
|
|
5643
|
+
}
|
|
5644
|
+
function checkRegionRecursion(pageHtml, pageDir, jayHtml, readFile) {
|
|
5645
|
+
if (!jayHtml.headlessImports.some((imp) => imp.template)) return [];
|
|
5646
|
+
const { errors } = materialise(pageHtml, buildMaterialiseOptions(pageDir, jayHtml, readFile));
|
|
5647
|
+
return errors.filter((e2) => e2.startsWith("template inclusion cycle"));
|
|
5648
|
+
}
|
|
4446
5649
|
function checkHeadlessInstanceProps(jayHtml, file) {
|
|
4447
5650
|
const imports = jayHtml.headlessImports;
|
|
4448
5651
|
const warnings = [];
|
|
4449
5652
|
function walkElement(element) {
|
|
4450
5653
|
const tagName = element.rawTagName?.toLowerCase();
|
|
4451
5654
|
if (tagName?.startsWith("jay:")) {
|
|
4452
|
-
const
|
|
4453
|
-
const imp = imports.find((i) => i.contractName ===
|
|
5655
|
+
const contractName2 = tagName.substring(4);
|
|
5656
|
+
const imp = imports.find((i) => i.contractName === contractName2 && i.contract);
|
|
4454
5657
|
if (imp?.contract) {
|
|
4455
5658
|
const contract = imp.contract;
|
|
4456
5659
|
const attrs = element.attributes ?? {};
|
|
@@ -4467,7 +5670,7 @@ function checkHeadlessInstanceProps(jayHtml, file) {
|
|
|
4467
5670
|
for (const prop of passedProps) {
|
|
4468
5671
|
if (!contractPropNamesLower.has(prop.toLowerCase())) {
|
|
4469
5672
|
warnings.push(
|
|
4470
|
-
`<jay:${
|
|
5673
|
+
`<jay:${contractName2}> passes attribute "${prop}" but the "${contract.name}" contract does not declare it as a prop. Add to ${contractName2}.jay-contract: props: [{ name: ${prop}, type: string }]`
|
|
4471
5674
|
);
|
|
4472
5675
|
}
|
|
4473
5676
|
}
|
|
@@ -4477,7 +5680,7 @@ function checkHeadlessInstanceProps(jayHtml, file) {
|
|
|
4477
5680
|
for (const contractProp of contract.props) {
|
|
4478
5681
|
if (contractProp.required && !passedPropsLower.has(contractProp.name.toLowerCase())) {
|
|
4479
5682
|
warnings.push(
|
|
4480
|
-
`<jay:${
|
|
5683
|
+
`<jay:${contractName2}> is missing required prop "${contractProp.name}" declared in the "${contract.name}" contract.`
|
|
4481
5684
|
);
|
|
4482
5685
|
}
|
|
4483
5686
|
}
|
|
@@ -4489,18 +5692,35 @@ function checkHeadlessInstanceProps(jayHtml, file) {
|
|
|
4489
5692
|
}
|
|
4490
5693
|
for (const contractProp of contract.props) {
|
|
4491
5694
|
const attrValue = lowerAttrs[contractProp.name.toLowerCase()];
|
|
4492
|
-
if (
|
|
5695
|
+
if (attrValue === void 0) continue;
|
|
5696
|
+
const propType = contractProp.dataType;
|
|
4493
5697
|
const bindingMatch = attrValue.match(/^\{(.+)\}$/);
|
|
4494
|
-
if (!bindingMatch)
|
|
5698
|
+
if (!bindingMatch) {
|
|
5699
|
+
if (attrValue.includes("{")) continue;
|
|
5700
|
+
if (propType && isEnumType$1(propType)) {
|
|
5701
|
+
if (!propType.values.includes(attrValue)) {
|
|
5702
|
+
warnings.push(
|
|
5703
|
+
`<jay:${contractName2}> prop "${contractProp.name}" = "${attrValue}" is not a declared value of enum(${propType.values.join(" | ")}). Use one of: ${propType.values.join(", ")}.`
|
|
5704
|
+
);
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
continue;
|
|
5708
|
+
}
|
|
4495
5709
|
const bindingPath = bindingMatch[1];
|
|
5710
|
+
const sourceType = resolveBindingDataType(bindingPath, jayHtml);
|
|
5711
|
+
if (propType && sourceType && !equalJayTypes(propType, sourceType)) {
|
|
5712
|
+
warnings.push(
|
|
5713
|
+
`<jay:${contractName2}> prop "${contractProp.name}" (${typeLabel(propType)}) is bound to {${bindingPath}} (${typeLabel(sourceType)}). The binding source type must match the prop type.`
|
|
5714
|
+
);
|
|
5715
|
+
}
|
|
4496
5716
|
const sourcePhase = resolveBindingPhase(bindingPath, jayHtml);
|
|
4497
5717
|
if (!sourcePhase) continue;
|
|
4498
|
-
const propPhase = contractProp.phase ?? "slow";
|
|
5718
|
+
const propPhase = imp.structural ? resolveContractTag(contract, contractProp.name)?.phase ?? contractProp.phase ?? "slow" : contractProp.phase ?? "slow";
|
|
4499
5719
|
const sourceOrder = PHASE_ORDER[sourcePhase] ?? 0;
|
|
4500
5720
|
const propOrder = PHASE_ORDER[propPhase] ?? 0;
|
|
4501
5721
|
if (sourceOrder > propOrder) {
|
|
4502
5722
|
warnings.push(
|
|
4503
|
-
`<jay:${
|
|
5723
|
+
`<jay:${contractName2}> prop "${contractProp.name}" (phase: ${propPhase}) is bound to {${bindingPath}} which is phase: ${sourcePhase}. The binding source phase must be ≤ the prop phase. Use a ${propPhase}-phase binding, a route param, or a literal value.`
|
|
4504
5724
|
);
|
|
4505
5725
|
}
|
|
4506
5726
|
}
|
|
@@ -4666,13 +5886,17 @@ async function validateJayFiles(options = {}) {
|
|
|
4666
5886
|
const warnings = [];
|
|
4667
5887
|
const coverage = [];
|
|
4668
5888
|
const parsedFiles = [];
|
|
5889
|
+
let regionsSeen = 0;
|
|
5890
|
+
let templateImportsSeen = 0;
|
|
4669
5891
|
const pageJayHtmlFiles = await findJayFiles(scanDir);
|
|
4670
5892
|
const componentJayHtmlFiles = await findJayFiles(componentsDir).catch(() => []);
|
|
4671
5893
|
const jayHtmlFiles = [...pageJayHtmlFiles, ...componentJayHtmlFiles];
|
|
4672
|
-
const
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
5894
|
+
const componentJayHtmlFileSet = new Set(componentJayHtmlFiles);
|
|
5895
|
+
const componentContractFiles = await findContractFiles(componentsDir).catch(
|
|
5896
|
+
() => []
|
|
5897
|
+
);
|
|
5898
|
+
const contractFiles = [...await findContractFiles(scanDir), ...componentContractFiles];
|
|
5899
|
+
const contractNameByFile = /* @__PURE__ */ new Map();
|
|
4676
5900
|
if (options.verbose) {
|
|
4677
5901
|
getLogger().info(chalk.gray(`Scanning directory: ${scanDir}`));
|
|
4678
5902
|
getLogger().info(chalk.gray(`Found ${jayHtmlFiles.length} .jay-html files`));
|
|
@@ -4684,6 +5908,7 @@ async function validateJayFiles(options = {}) {
|
|
|
4684
5908
|
try {
|
|
4685
5909
|
const content = await promises.readFile(contractFile, "utf-8");
|
|
4686
5910
|
const result = parseContract(content, path$1.basename(contractFile));
|
|
5911
|
+
if (result.val?.name) contractNameByFile.set(contractFile, result.val.name);
|
|
4687
5912
|
if (result.validations.length > 0) {
|
|
4688
5913
|
for (const validation of result.validations) {
|
|
4689
5914
|
errors.push({
|
|
@@ -4711,7 +5936,7 @@ async function validateJayFiles(options = {}) {
|
|
|
4711
5936
|
}
|
|
4712
5937
|
for (const jayFile of jayHtmlFiles) {
|
|
4713
5938
|
const relativePath = path$1.relative(projectRoot, jayFile);
|
|
4714
|
-
const filename = path$1.basename(jayFile.replace(JAY_EXTENSION, ""));
|
|
5939
|
+
const filename = path$1.basename(jayFile.replace(JAY_EXTENSION$1, ""));
|
|
4715
5940
|
const dirname = path$1.dirname(jayFile);
|
|
4716
5941
|
try {
|
|
4717
5942
|
const content = await promises.readFile(jayFile, "utf-8");
|
|
@@ -4774,12 +5999,95 @@ async function validateJayFiles(options = {}) {
|
|
|
4774
5999
|
}
|
|
4775
6000
|
const headlessPropResults = checkHeadlessInstanceProps(parsedFile.val, relativePath);
|
|
4776
6001
|
for (const msg of headlessPropResults) {
|
|
4777
|
-
if (msg.includes("is missing required prop") || msg.includes("source phase must be")) {
|
|
6002
|
+
if (msg.includes("is missing required prop") || msg.includes("source phase must be") || msg.includes("is not a declared value of enum") || msg.includes("binding source type must match")) {
|
|
4778
6003
|
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
4779
6004
|
} else {
|
|
4780
6005
|
warnings.push({ file: relativePath, message: msg });
|
|
4781
6006
|
}
|
|
4782
6007
|
}
|
|
6008
|
+
const readTemplateRel = (templatePath) => {
|
|
6009
|
+
try {
|
|
6010
|
+
return fs$1.readFileSync(path$1.resolve(dirname, templatePath), "utf-8");
|
|
6011
|
+
} catch {
|
|
6012
|
+
return void 0;
|
|
6013
|
+
}
|
|
6014
|
+
};
|
|
6015
|
+
const driftWarnings = checkRegionDrift(parsedFile.val, readTemplateRel);
|
|
6016
|
+
for (const finding of driftWarnings) {
|
|
6017
|
+
warnings.push({
|
|
6018
|
+
file: relativePath,
|
|
6019
|
+
message: finding.message,
|
|
6020
|
+
suggestion: finding.suggestion
|
|
6021
|
+
});
|
|
6022
|
+
}
|
|
6023
|
+
const cssDriftWarnings = checkRegionCssDrift(parsedFile.val, readTemplateRel);
|
|
6024
|
+
for (const finding of cssDriftWarnings) {
|
|
6025
|
+
warnings.push({
|
|
6026
|
+
file: relativePath,
|
|
6027
|
+
message: finding.message,
|
|
6028
|
+
suggestion: finding.suggestion
|
|
6029
|
+
});
|
|
6030
|
+
}
|
|
6031
|
+
const cssScopingWarnings = checkRegionCssScoping(parsedFile.val);
|
|
6032
|
+
for (const finding of cssScopingWarnings) {
|
|
6033
|
+
warnings.push({
|
|
6034
|
+
file: relativePath,
|
|
6035
|
+
message: finding.message,
|
|
6036
|
+
suggestion: finding.suggestion
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
const nestedRefWarnings = checkNestedRegionRefs(parsedFile.val, readTemplateRel);
|
|
6040
|
+
for (const finding of nestedRefWarnings) {
|
|
6041
|
+
warnings.push({
|
|
6042
|
+
file: relativePath,
|
|
6043
|
+
message: finding.message,
|
|
6044
|
+
suggestion: finding.suggestion
|
|
6045
|
+
});
|
|
6046
|
+
}
|
|
6047
|
+
regionsSeen += collectRegionElements(parsedFile.val.body).length;
|
|
6048
|
+
templateImportsSeen += parsedFile.val.headlessImports.filter(
|
|
6049
|
+
(imp) => imp.template
|
|
6050
|
+
).length;
|
|
6051
|
+
const notLinkedWarnings = componentJayHtmlFileSet.has(jayFile) ? [] : checkRegionNotLinked(
|
|
6052
|
+
parsedFile.val,
|
|
6053
|
+
(imp) => listTemplatesForContractFile(imp.contractPath).map((t) => ({
|
|
6054
|
+
...t,
|
|
6055
|
+
path: "./" + path$1.relative(projectRoot, t.path).replace(/\\/g, "/")
|
|
6056
|
+
}))
|
|
6057
|
+
);
|
|
6058
|
+
for (const finding of notLinkedWarnings) {
|
|
6059
|
+
warnings.push({
|
|
6060
|
+
file: relativePath,
|
|
6061
|
+
message: finding.message,
|
|
6062
|
+
suggestion: finding.suggestion
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
const overrideWarnings = checkRegionOverrideNonContent(
|
|
6066
|
+
parsedFile.val,
|
|
6067
|
+
readTemplateRel
|
|
6068
|
+
);
|
|
6069
|
+
for (const finding of overrideWarnings) {
|
|
6070
|
+
warnings.push({
|
|
6071
|
+
file: relativePath,
|
|
6072
|
+
message: finding.message,
|
|
6073
|
+
suggestion: finding.suggestion
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
6076
|
+
const recursionErrors = checkRegionRecursion(
|
|
6077
|
+
content,
|
|
6078
|
+
dirname,
|
|
6079
|
+
parsedFile.val,
|
|
6080
|
+
(absPath) => {
|
|
6081
|
+
try {
|
|
6082
|
+
return fs$1.readFileSync(absPath, "utf-8");
|
|
6083
|
+
} catch {
|
|
6084
|
+
return void 0;
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
);
|
|
6088
|
+
for (const msg of recursionErrors) {
|
|
6089
|
+
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
6090
|
+
}
|
|
4783
6091
|
const fileCoverage = analyzeTagCoverage(parsedFile.val, relativePath);
|
|
4784
6092
|
if (fileCoverage) {
|
|
4785
6093
|
coverage.push(fileCoverage);
|
|
@@ -4852,6 +6160,44 @@ async function validateJayFiles(options = {}) {
|
|
|
4852
6160
|
suggestion: "Add to .jay config:\n site:\n baseUrl: https://your-domain.com"
|
|
4853
6161
|
});
|
|
4854
6162
|
}
|
|
6163
|
+
const allowNoTemplate = /* @__PURE__ */ new Set();
|
|
6164
|
+
let allowNoDesignSystem = false;
|
|
6165
|
+
for (const { parsed } of parsedFiles) {
|
|
6166
|
+
const overrides = parsed.validationOverrides?.["jay-stack"];
|
|
6167
|
+
if (!overrides) continue;
|
|
6168
|
+
const list = overrides["allow-no-template"];
|
|
6169
|
+
if (Array.isArray(list))
|
|
6170
|
+
for (const name of list) allowNoTemplate.add(String(name).toLowerCase());
|
|
6171
|
+
if (overrides["allow-no-design-system"] === true) allowNoDesignSystem = true;
|
|
6172
|
+
}
|
|
6173
|
+
for (const contractFile of componentContractFiles) {
|
|
6174
|
+
const contractName2 = contractNameByFile.get(contractFile) ?? path$1.basename(contractFile, JAY_CONTRACT_EXTENSION$1);
|
|
6175
|
+
const finding = checkComponentNoTemplate(
|
|
6176
|
+
contractFile,
|
|
6177
|
+
contractName2,
|
|
6178
|
+
hasTemplateForContractFile,
|
|
6179
|
+
allowNoTemplate
|
|
6180
|
+
);
|
|
6181
|
+
if (finding) {
|
|
6182
|
+
warnings.push({
|
|
6183
|
+
file: path$1.relative(projectRoot, contractFile),
|
|
6184
|
+
message: finding.message,
|
|
6185
|
+
suggestion: finding.suggestion
|
|
6186
|
+
});
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
const noDesignSystem = checkNoDesignSystem(
|
|
6190
|
+
regionsSeen,
|
|
6191
|
+
templateImportsSeen,
|
|
6192
|
+
allowNoDesignSystem
|
|
6193
|
+
);
|
|
6194
|
+
if (noDesignSystem) {
|
|
6195
|
+
warnings.push({
|
|
6196
|
+
file: ".jay",
|
|
6197
|
+
message: noDesignSystem.message,
|
|
6198
|
+
suggestion: noDesignSystem.suggestion
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
4855
6201
|
const pluginValidators = await runPluginValidators(projectRoot, parsedFiles, errors, warnings);
|
|
4856
6202
|
return {
|
|
4857
6203
|
valid: errors.length === 0,
|
|
@@ -4988,6 +6334,20 @@ async function runValidate(scanPath, options) {
|
|
|
4988
6334
|
process.exit(1);
|
|
4989
6335
|
}
|
|
4990
6336
|
}
|
|
6337
|
+
async function surfaceValidationIssues(projectPath, options) {
|
|
6338
|
+
let result;
|
|
6339
|
+
try {
|
|
6340
|
+
result = await validateJayFiles({ path: projectPath, verbose: options.verbose });
|
|
6341
|
+
} catch (error) {
|
|
6342
|
+
getLogger().warn(chalk.yellow(`Skipped jay-html validation: ${error?.message ?? error}`));
|
|
6343
|
+
return true;
|
|
6344
|
+
}
|
|
6345
|
+
if (!result.valid) {
|
|
6346
|
+
printJayValidationResult(result, { verbose: options.verbose });
|
|
6347
|
+
return false;
|
|
6348
|
+
}
|
|
6349
|
+
return true;
|
|
6350
|
+
}
|
|
4991
6351
|
async function runValidatePlugin(pluginPath, options) {
|
|
4992
6352
|
const result = await validatePlugin({
|
|
4993
6353
|
pluginPath: pluginPath || process.cwd(),
|
|
@@ -5048,6 +6408,334 @@ function printPluginValidationResult(result, verbose) {
|
|
|
5048
6408
|
logger.important(chalk.red(`${result.errors.length} errors found.`));
|
|
5049
6409
|
}
|
|
5050
6410
|
}
|
|
6411
|
+
const runValidate$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
6412
|
+
__proto__: null,
|
|
6413
|
+
runValidate,
|
|
6414
|
+
runValidatePlugin,
|
|
6415
|
+
surfaceValidationIssues
|
|
6416
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
6417
|
+
async function runDev(projectPath, options) {
|
|
6418
|
+
const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
|
|
6419
|
+
setDevLogger(createDevLogger(logLevel));
|
|
6420
|
+
if (!options.quiet) {
|
|
6421
|
+
await surfaceValidationIssues(projectPath, { verbose: options.verbose });
|
|
6422
|
+
}
|
|
6423
|
+
const testMode = options.testMode || options.timeout !== void 0;
|
|
6424
|
+
await startDevServer({
|
|
6425
|
+
projectPath: projectPath || process.cwd(),
|
|
6426
|
+
testMode,
|
|
6427
|
+
timeout: options.timeout,
|
|
6428
|
+
logLevel
|
|
6429
|
+
});
|
|
6430
|
+
}
|
|
6431
|
+
async function resolveProductionContext(projectPath, versionOverride) {
|
|
6432
|
+
const resolvedPath = path__default.resolve(projectPath || process.cwd());
|
|
6433
|
+
const jayConfigPath = path__default.join(resolvedPath, ".jay");
|
|
6434
|
+
let pagesBase = "./src/pages";
|
|
6435
|
+
let siteBaseUrl;
|
|
6436
|
+
try {
|
|
6437
|
+
const jayConfig = YAML.parse(await fs$2.readFile(jayConfigPath, "utf-8"));
|
|
6438
|
+
pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
|
|
6439
|
+
siteBaseUrl = jayConfig?.site?.baseUrl;
|
|
6440
|
+
} catch {
|
|
6441
|
+
}
|
|
6442
|
+
const version = versionOverride || await resolveVersionFromPackageJson(resolvedPath);
|
|
6443
|
+
return {
|
|
6444
|
+
resolvedPath,
|
|
6445
|
+
pagesRoot: path__default.resolve(resolvedPath, pagesBase),
|
|
6446
|
+
buildRoot: path__default.join(resolvedPath, "build"),
|
|
6447
|
+
version,
|
|
6448
|
+
tsConfigFilePath: path__default.join(resolvedPath, "tsconfig.json"),
|
|
6449
|
+
siteBaseUrl
|
|
6450
|
+
};
|
|
6451
|
+
}
|
|
6452
|
+
async function resolveVersionFromPackageJson(projectRoot) {
|
|
6453
|
+
try {
|
|
6454
|
+
const pkgJson = JSON.parse(
|
|
6455
|
+
await fs$2.readFile(path__default.join(projectRoot, "package.json"), "utf-8")
|
|
6456
|
+
);
|
|
6457
|
+
if (pkgJson.version) {
|
|
6458
|
+
return pkgJson.version;
|
|
6459
|
+
}
|
|
6460
|
+
} catch {
|
|
6461
|
+
}
|
|
6462
|
+
return "1";
|
|
6463
|
+
}
|
|
6464
|
+
function initLogger(verbose) {
|
|
6465
|
+
const logLevel = verbose ? "verbose" : "info";
|
|
6466
|
+
setDevLogger(createDevLogger(logLevel));
|
|
6467
|
+
}
|
|
6468
|
+
async function runBuild(projectPath, options) {
|
|
6469
|
+
initLogger(options.verbose);
|
|
6470
|
+
const { surfaceValidationIssues: surfaceValidationIssues2 } = await Promise.resolve().then(() => runValidate$1);
|
|
6471
|
+
const valid = await surfaceValidationIssues2(projectPath, { verbose: options.verbose });
|
|
6472
|
+
if (!valid) {
|
|
6473
|
+
process.exit(1);
|
|
6474
|
+
}
|
|
6475
|
+
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
6476
|
+
await generateDefinitionFiles(ctx);
|
|
6477
|
+
const { buildVersion } = await import("./index-l6QVpvnY.js");
|
|
6478
|
+
await buildVersion({
|
|
6479
|
+
version: ctx.version,
|
|
6480
|
+
projectRoot: ctx.resolvedPath,
|
|
6481
|
+
pagesRoot: ctx.pagesRoot,
|
|
6482
|
+
buildRoot: ctx.buildRoot,
|
|
6483
|
+
concurrency: 4,
|
|
6484
|
+
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
6485
|
+
minify: options.minify,
|
|
6486
|
+
siteBaseUrl: ctx.siteBaseUrl
|
|
6487
|
+
});
|
|
6488
|
+
}
|
|
6489
|
+
async function generateDefinitionFiles(ctx) {
|
|
6490
|
+
const { glob: glob2 } = await import("glob");
|
|
6491
|
+
const { generatePageDefinitionFiles: generatePageDefinitionFiles2 } = await Promise.resolve().then(() => generatePageDefinitionFiles$1);
|
|
6492
|
+
const { generateContractDefinitionFiles: generateContractDefinitionFiles2 } = await Promise.resolve().then(() => generateContractDefinitionFiles$1);
|
|
6493
|
+
const jayHtmlPaths = await glob2("**/page.jay-html", {
|
|
6494
|
+
cwd: ctx.pagesRoot,
|
|
6495
|
+
absolute: true,
|
|
6496
|
+
ignore: ["**/node_modules/**", "**/build/**", "**/dist/**"]
|
|
6497
|
+
});
|
|
6498
|
+
await generatePageDefinitionFiles2(jayHtmlPaths, ctx.tsConfigFilePath, ctx.resolvedPath);
|
|
6499
|
+
await generateContractDefinitionFiles2(path__default.resolve(ctx.resolvedPath, "src"));
|
|
6500
|
+
}
|
|
6501
|
+
async function runServe(projectPath, options) {
|
|
6502
|
+
initLogger(options.verbose);
|
|
6503
|
+
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
6504
|
+
if (options.role === "renderer") {
|
|
6505
|
+
const { startRendererServer } = await import("@jay-framework/production-server");
|
|
6506
|
+
await startRendererServer({
|
|
6507
|
+
buildRoot: ctx.buildRoot,
|
|
6508
|
+
version: ctx.version,
|
|
6509
|
+
port: parseInt(options.port, 10),
|
|
6510
|
+
projectRoot: ctx.resolvedPath,
|
|
6511
|
+
pagesRoot: ctx.pagesRoot,
|
|
6512
|
+
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
6513
|
+
siteBaseUrl: ctx.siteBaseUrl
|
|
6514
|
+
});
|
|
6515
|
+
} else {
|
|
6516
|
+
const { startMainServer } = await import("@jay-framework/production-server");
|
|
6517
|
+
await startMainServer({
|
|
6518
|
+
buildRoot: ctx.buildRoot,
|
|
6519
|
+
version: ctx.version,
|
|
6520
|
+
port: parseInt(options.port, 10),
|
|
6521
|
+
testMode: options.testMode,
|
|
6522
|
+
publicBasePath: options.staticBaseUrl,
|
|
6523
|
+
serveStatic: options.serveStatic
|
|
6524
|
+
});
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
async function runRebuild(projectPath, options) {
|
|
6528
|
+
initLogger(options.verbose);
|
|
6529
|
+
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
6530
|
+
let params;
|
|
6531
|
+
if (options.params) {
|
|
6532
|
+
params = JSON.parse(options.params);
|
|
6533
|
+
}
|
|
6534
|
+
const { rebuild } = await import("@jay-framework/production-server");
|
|
6535
|
+
let target;
|
|
6536
|
+
if (options.contract) {
|
|
6537
|
+
target = { mode: "contract", contractName: options.contract, params };
|
|
6538
|
+
} else if (options.route) {
|
|
6539
|
+
target = { mode: "route", routePattern: options.route, params };
|
|
6540
|
+
} else if (options.url) {
|
|
6541
|
+
target = { mode: "url", url: options.url };
|
|
6542
|
+
} else {
|
|
6543
|
+
getLogger().error(chalk.red("One of --contract, --route, or --url is required"));
|
|
6544
|
+
process.exit(1);
|
|
6545
|
+
}
|
|
6546
|
+
const result = await rebuild({
|
|
6547
|
+
projectRoot: ctx.resolvedPath,
|
|
6548
|
+
pagesRoot: ctx.pagesRoot,
|
|
6549
|
+
buildRoot: ctx.buildRoot,
|
|
6550
|
+
version: ctx.version,
|
|
6551
|
+
target,
|
|
6552
|
+
tsConfigFilePath: ctx.tsConfigFilePath,
|
|
6553
|
+
siteBaseUrl: ctx.siteBaseUrl
|
|
6554
|
+
});
|
|
6555
|
+
if (result.errors.length > 0) {
|
|
6556
|
+
for (const err of result.errors) {
|
|
6557
|
+
getLogger().error(
|
|
6558
|
+
chalk.red(` Error: ${err.route} ${JSON.stringify(err.params)}: ${err.error}`)
|
|
6559
|
+
);
|
|
6560
|
+
}
|
|
6561
|
+
process.exit(1);
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
function syncPageContent(rawPage, pageDir, jayHtml, readFile) {
|
|
6565
|
+
const regions = countDesignSystemRegions(jayHtml);
|
|
6566
|
+
if (regions === 0) {
|
|
6567
|
+
return { content: rawPage, changed: false, regions: 0, errors: [] };
|
|
6568
|
+
}
|
|
6569
|
+
const opts = buildMaterialiseOptions(pageDir, jayHtml, readFile, { preserveOverrides: true });
|
|
6570
|
+
const { html, css, errors } = materialise(rawPage, opts);
|
|
6571
|
+
const merged = mergeScopeCss(html, css);
|
|
6572
|
+
const content = prettifyHtml(merged);
|
|
6573
|
+
const changed = content !== prettifyHtml(rawPage);
|
|
6574
|
+
return { content, changed, regions, errors };
|
|
6575
|
+
}
|
|
6576
|
+
function countDesignSystemRegions(jayHtml) {
|
|
6577
|
+
const named = new Set(
|
|
6578
|
+
jayHtml.headlessImports.filter((i) => i.template).map((i) => i.contractName.toLowerCase())
|
|
6579
|
+
);
|
|
6580
|
+
if (named.size === 0) return 0;
|
|
6581
|
+
let count = 0;
|
|
6582
|
+
const visit = (el) => {
|
|
6583
|
+
for (const child of el.childNodes ?? []) {
|
|
6584
|
+
if (child.rawTagName === void 0) continue;
|
|
6585
|
+
const tag = (child.rawTagName ?? "").toLowerCase();
|
|
6586
|
+
if (tag.startsWith("jay:") && named.has(tag.substring(4))) count++;
|
|
6587
|
+
visit(child);
|
|
6588
|
+
}
|
|
6589
|
+
};
|
|
6590
|
+
visit(jayHtml.body);
|
|
6591
|
+
return count;
|
|
6592
|
+
}
|
|
6593
|
+
function mergeScopeCss(html, aggregatedCss) {
|
|
6594
|
+
if (!aggregatedCss.trim()) return html;
|
|
6595
|
+
const root = parse(html);
|
|
6596
|
+
const head = root.querySelector("head");
|
|
6597
|
+
if (!head) return html;
|
|
6598
|
+
const styleEl = head.querySelector("style");
|
|
6599
|
+
const existingCss = styleEl ? styleEl.textContent : "";
|
|
6600
|
+
const groups = splitScopeBlocks(aggregatedCss);
|
|
6601
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
6602
|
+
const canonicalBody = /* @__PURE__ */ new Map();
|
|
6603
|
+
for (const g of groups) {
|
|
6604
|
+
const nb = normalizeCssBody(scopeInnerBody(g.block));
|
|
6605
|
+
for (const s2 of g.selectors) {
|
|
6606
|
+
groupOf.set(s2, g);
|
|
6607
|
+
canonicalBody.set(s2, nb);
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
const overridden = /* @__PURE__ */ new Set();
|
|
6611
|
+
const kept = [];
|
|
6612
|
+
for (const seg of tokenizeCss(existingCss)) {
|
|
6613
|
+
if (seg.type === "raw") {
|
|
6614
|
+
if (seg.text.trim()) kept.push(seg.text.trim());
|
|
6615
|
+
continue;
|
|
6616
|
+
}
|
|
6617
|
+
const marked = hasOverrideMarker(seg.block);
|
|
6618
|
+
const body = normalizeCssBody(scopeInnerBody(seg.block));
|
|
6619
|
+
const keepMembers = seg.selectors.filter((s2) => {
|
|
6620
|
+
if (!groupOf.has(s2)) return true;
|
|
6621
|
+
const diverges = marked && body !== canonicalBody.get(s2);
|
|
6622
|
+
if (diverges) overridden.add(s2);
|
|
6623
|
+
return diverges;
|
|
6624
|
+
});
|
|
6625
|
+
if (keepMembers.length === seg.selectors.length) kept.push(seg.block);
|
|
6626
|
+
else if (keepMembers.length > 0) kept.push(narrowScopeBlock(seg.block, keepMembers));
|
|
6627
|
+
}
|
|
6628
|
+
const appended = [];
|
|
6629
|
+
for (const g of groups) {
|
|
6630
|
+
const plain = g.selectors.filter((s2) => !overridden.has(s2));
|
|
6631
|
+
if (plain.length === 0) continue;
|
|
6632
|
+
appended.push(
|
|
6633
|
+
plain.length === g.selectors.length ? g.block : narrowScopeBlock(g.block, plain)
|
|
6634
|
+
);
|
|
6635
|
+
}
|
|
6636
|
+
const newCss = [...kept, ...appended].filter(Boolean).join("\n\n");
|
|
6637
|
+
if (newCss === existingCss.trim()) return html;
|
|
6638
|
+
if (styleEl) styleEl.set_content(newCss);
|
|
6639
|
+
else head.insertAdjacentHTML("beforeend", `<style>${newCss}</style>`);
|
|
6640
|
+
return root.toString();
|
|
6641
|
+
}
|
|
6642
|
+
function hasOverrideMarker(block) {
|
|
6643
|
+
return /\/\*\s*jay:override\b/.test(block);
|
|
6644
|
+
}
|
|
6645
|
+
function narrowScopeBlock(block, selectors) {
|
|
6646
|
+
return block.replace(
|
|
6647
|
+
/^(@scope\s*\()\s*[^)]*?\s*(\)(?:\s*to\s*\([^)]*\))?\s*\{)/,
|
|
6648
|
+
`$1${selectors.join(", ")}$2`
|
|
6649
|
+
);
|
|
6650
|
+
}
|
|
6651
|
+
async function resolveTargets(projectRoot, scanDirs, target, all) {
|
|
6652
|
+
if (target && !all) {
|
|
6653
|
+
const file = target.split("#")[0];
|
|
6654
|
+
const abs = path$1.isAbsolute(file) ? file : path$1.resolve(projectRoot, file);
|
|
6655
|
+
return [abs];
|
|
6656
|
+
}
|
|
6657
|
+
const found = await Promise.all(
|
|
6658
|
+
scanDirs.map((dir) => glob(`${dir}/**/*${JAY_EXTENSION$1}`).catch(() => []))
|
|
6659
|
+
);
|
|
6660
|
+
return [...new Set(found.flat())];
|
|
6661
|
+
}
|
|
6662
|
+
async function runSync(target, options = {}) {
|
|
6663
|
+
const logger = getLogger();
|
|
6664
|
+
const projectRoot = options.path ? path$1.resolve(options.path) : process.cwd();
|
|
6665
|
+
if (target && target.includes("#")) {
|
|
6666
|
+
logger.important(
|
|
6667
|
+
chalk.yellow(
|
|
6668
|
+
`Per-region targeting (#ref) is not yet supported — syncing the whole page "${target.split("#")[0]}".`
|
|
6669
|
+
)
|
|
6670
|
+
);
|
|
6671
|
+
}
|
|
6672
|
+
const resolvedConfig = getConfigWithDefaults(loadConfig(projectRoot));
|
|
6673
|
+
const scanDirs = [
|
|
6674
|
+
path$1.resolve(projectRoot, resolvedConfig.devServer.pagesBase),
|
|
6675
|
+
path$1.resolve(projectRoot, resolvedConfig.devServer.componentsBase)
|
|
6676
|
+
];
|
|
6677
|
+
const files = await resolveTargets(projectRoot, scanDirs, target, options.all ?? false);
|
|
6678
|
+
let filesChanged = 0;
|
|
6679
|
+
let regionsSynced = 0;
|
|
6680
|
+
let hadError = false;
|
|
6681
|
+
for (const file of files) {
|
|
6682
|
+
const relativePath = path$1.relative(projectRoot, file);
|
|
6683
|
+
let content;
|
|
6684
|
+
try {
|
|
6685
|
+
content = await promises.readFile(file, "utf-8");
|
|
6686
|
+
} catch {
|
|
6687
|
+
logger.error(chalk.red(`✗ ${relativePath}: could not read file`));
|
|
6688
|
+
hadError = true;
|
|
6689
|
+
continue;
|
|
6690
|
+
}
|
|
6691
|
+
const dirname = path$1.dirname(file);
|
|
6692
|
+
const filename = path$1.basename(file.replace(JAY_EXTENSION$1, ""));
|
|
6693
|
+
const parsed = await parseJayFile(
|
|
6694
|
+
content,
|
|
6695
|
+
filename,
|
|
6696
|
+
dirname,
|
|
6697
|
+
{},
|
|
6698
|
+
JAY_IMPORT_RESOLVER,
|
|
6699
|
+
projectRoot
|
|
6700
|
+
);
|
|
6701
|
+
if (parsed.validations.length > 0 || !parsed.val) {
|
|
6702
|
+
logger.error(chalk.red(`✗ ${relativePath}: ${parsed.validations.join("; ")}`));
|
|
6703
|
+
hadError = true;
|
|
6704
|
+
continue;
|
|
6705
|
+
}
|
|
6706
|
+
const result = syncPageContent(content, dirname, parsed.val, (absPath) => {
|
|
6707
|
+
try {
|
|
6708
|
+
return fs$1.readFileSync(absPath, "utf-8");
|
|
6709
|
+
} catch {
|
|
6710
|
+
return void 0;
|
|
6711
|
+
}
|
|
6712
|
+
});
|
|
6713
|
+
if (result.errors.length > 0) {
|
|
6714
|
+
for (const err of result.errors) logger.error(chalk.red(`✗ ${relativePath}: ${err}`));
|
|
6715
|
+
hadError = true;
|
|
6716
|
+
continue;
|
|
6717
|
+
}
|
|
6718
|
+
if (result.regions === 0) continue;
|
|
6719
|
+
if (result.changed) {
|
|
6720
|
+
await promises.writeFile(file, result.content, "utf-8");
|
|
6721
|
+
filesChanged++;
|
|
6722
|
+
regionsSynced += result.regions;
|
|
6723
|
+
logger.important(
|
|
6724
|
+
chalk.green(`✓ synced ${result.regions} region(s) in ${relativePath}`)
|
|
6725
|
+
);
|
|
6726
|
+
} else if (options.verbose) {
|
|
6727
|
+
logger.important(chalk.gray(`• ${relativePath} — already in sync`));
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
if (filesChanged === 0) {
|
|
6731
|
+
logger.important("Nothing to sync.");
|
|
6732
|
+
} else {
|
|
6733
|
+
logger.important(
|
|
6734
|
+
chalk.green(`Synced ${regionsSynced} region(s) across ${filesChanged} file(s).`)
|
|
6735
|
+
);
|
|
6736
|
+
}
|
|
6737
|
+
if (hadError) process.exit(1);
|
|
6738
|
+
}
|
|
5051
6739
|
const ALL_ROLES = ["designer", "developer", "plugin", "devops"];
|
|
5052
6740
|
async function runAgentKit(options) {
|
|
5053
6741
|
const projectRoot = process.cwd();
|
|
@@ -5122,6 +6810,11 @@ Materialized ${totalContracts} contracts`));
|
|
|
5122
6810
|
getLogger().important(` Static: ${result.staticCount}`);
|
|
5123
6811
|
getLogger().important(` Dynamic: ${result.dynamicCount}`);
|
|
5124
6812
|
getLogger().important(` Output: ${result.outputDir}`);
|
|
6813
|
+
if (result.pluginsIndex.designSystemIndex) {
|
|
6814
|
+
getLogger().important(
|
|
6815
|
+
` Design-system catalog: agent-kit/design-system-index.yaml + design-system.md`
|
|
6816
|
+
);
|
|
6817
|
+
}
|
|
5125
6818
|
}
|
|
5126
6819
|
return { initErrors, viteServer: keepViteAlive ? viteServer : void 0 };
|
|
5127
6820
|
} catch (error) {
|
|
@@ -5384,7 +7077,7 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
5384
7077
|
let viteServer;
|
|
5385
7078
|
try {
|
|
5386
7079
|
let pluginName;
|
|
5387
|
-
let
|
|
7080
|
+
let contractName2;
|
|
5388
7081
|
if (contractRef.startsWith("@")) {
|
|
5389
7082
|
const secondSlash = contractRef.indexOf("/", contractRef.indexOf("/") + 1);
|
|
5390
7083
|
if (secondSlash === -1) {
|
|
@@ -5394,7 +7087,7 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
5394
7087
|
process.exit(1);
|
|
5395
7088
|
}
|
|
5396
7089
|
pluginName = contractRef.substring(0, secondSlash);
|
|
5397
|
-
|
|
7090
|
+
contractName2 = contractRef.substring(secondSlash + 1);
|
|
5398
7091
|
} else {
|
|
5399
7092
|
const slashIndex = contractRef.indexOf("/");
|
|
5400
7093
|
if (slashIndex === -1) {
|
|
@@ -5404,7 +7097,7 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
5404
7097
|
process.exit(1);
|
|
5405
7098
|
}
|
|
5406
7099
|
pluginName = contractRef.substring(0, slashIndex);
|
|
5407
|
-
|
|
7100
|
+
contractName2 = contractRef.substring(slashIndex + 1);
|
|
5408
7101
|
}
|
|
5409
7102
|
if (options.verbose) {
|
|
5410
7103
|
getLogger().info("Starting Vite for TypeScript support...");
|
|
@@ -5412,10 +7105,10 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
5412
7105
|
viteServer = await createViteForCli({ projectRoot });
|
|
5413
7106
|
await initializeServices(projectRoot, viteServer);
|
|
5414
7107
|
const { resolvePluginComponent } = await import("@jay-framework/compiler-shared");
|
|
5415
|
-
const resolution = resolvePluginComponent(projectRoot, pluginName,
|
|
7108
|
+
const resolution = resolvePluginComponent(projectRoot, pluginName, contractName2);
|
|
5416
7109
|
if (resolution.validations.length > 0 || !resolution.val) {
|
|
5417
7110
|
getLogger().error(
|
|
5418
|
-
chalk.red(`❌ Could not resolve plugin "${pluginName}" contract "${
|
|
7111
|
+
chalk.red(`❌ Could not resolve plugin "${pluginName}" contract "${contractName2}"`)
|
|
5419
7112
|
);
|
|
5420
7113
|
for (const msg of resolution.validations) {
|
|
5421
7114
|
getLogger().error(` ${msg}`);
|
|
@@ -5915,6 +7608,17 @@ program.command("validate").description("Validate all .jay-html and .jay-contrac
|
|
|
5915
7608
|
process.exit(1);
|
|
5916
7609
|
}
|
|
5917
7610
|
});
|
|
7611
|
+
program.command("sync [target]").description(
|
|
7612
|
+
"Re-flatten design-system regions from their source templates (keeps override facets)"
|
|
7613
|
+
).option("-p, --path <path>", "Project root (default: cwd)").option("--all", "Sync every page in the project (default when no target is given)").option("-v, --verbose", "Show pages that are already in sync").action(async (target, options) => {
|
|
7614
|
+
try {
|
|
7615
|
+
await runSync(target, options);
|
|
7616
|
+
} catch (error) {
|
|
7617
|
+
getLogger().error(chalk.red("Sync failed:") + " " + error.message);
|
|
7618
|
+
if (error.stack) getLogger().error(error.stack);
|
|
7619
|
+
process.exit(1);
|
|
7620
|
+
}
|
|
7621
|
+
});
|
|
5918
7622
|
program.command("validate-plugin").description("Validate a Jay Stack plugin package").option("-p, --path <path>", "Plugin root (default: cwd)").option("--local", "Validate local plugins in src/plugins/").option("-v, --verbose", "Show detailed validation output").option("--strict", "Treat warnings as errors (for CI)").option("--generate-types", "Generate .d.ts files for contracts").action(async (options) => {
|
|
5919
7623
|
try {
|
|
5920
7624
|
await runValidatePlugin(options.path, options);
|