@jay-framework/jay-stack-cli 0.22.2 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-kit-template/designer/INSTRUCTIONS.md +4 -2
- package/agent-kit-template/designer/contracts-and-plugins.md +20 -0
- package/agent-kit-template/designer/jay-html-components.md +44 -6
- package/agent-kit-template/designer/jay-html-syntax.md +2 -0
- package/agent-kit-template/designer/jay-html-template-syntax.md +105 -1
- package/agent-kit-template/designer/navigation-patterns.md +73 -0
- package/agent-kit-template/designer/routing.md +56 -1
- package/agent-kit-template/designer/validation-guide.md +88 -0
- package/agent-kit-template/developer/routing.md +12 -1
- package/agent-kit-template/plugin/INSTRUCTIONS.md +25 -23
- package/agent-kit-template/plugin/add-menu-guide.md +114 -0
- package/agent-kit-template/plugin/aiditor-settings-guide.md +296 -0
- package/agent-kit-template/plugin/dev-server-service.md +2 -0
- package/agent-kit-template/plugin/plugin-routes.md +49 -0
- package/agent-kit-template/plugin/setup-guide.md +4 -16
- package/dist/index.d.ts +1 -88
- package/dist/index.js +388 -2662
- package/package.json +10 -11
- package/lib/vendors/README.md +0 -510
- package/lib/vendors/figma/README.md +0 -396
- package/test/vendors/figma/fixtures/README.md +0 -164
package/dist/index.js
CHANGED
|
@@ -2,25 +2,24 @@
|
|
|
2
2
|
import express from "express";
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
import { mkDevServer, createViteForCli } from "@jay-framework/dev-server";
|
|
5
|
-
import { createEditorServer } from "@jay-framework/editor-server";
|
|
6
5
|
import getPort from "get-port";
|
|
7
6
|
import path from "path";
|
|
8
7
|
import fs, { promises } from "fs";
|
|
9
8
|
import YAML from "yaml";
|
|
10
9
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
11
|
-
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile,
|
|
12
|
-
import {
|
|
13
|
-
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts, SetupNeedsAnswerError } from "@jay-framework/stack-server-runtime";
|
|
10
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
11
|
+
import { scanPlugins, listContracts, materializeContracts, SetupNeedsAnswerError, discoverPluginsWithSetup, sortPluginsByDependencies, discoverPluginsWithInit, executePluginSetup, executePluginServerInits, runInitCallbacks } from "@jay-framework/stack-server-runtime";
|
|
14
12
|
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
15
13
|
import { Command } from "commander";
|
|
16
14
|
import chalk from "chalk";
|
|
17
15
|
import path$1 from "node:path";
|
|
18
16
|
import fs$1 from "node:fs/promises";
|
|
17
|
+
import { loadPluginManifest, JAY_EXTENSION, RuntimeMode, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
19
18
|
import { createRequire } from "module";
|
|
20
19
|
import { glob } from "glob";
|
|
21
20
|
import fsSync from "node:fs";
|
|
22
21
|
import { fileURLToPath } from "node:url";
|
|
23
|
-
import {
|
|
22
|
+
import { select, confirm, input } from "@inquirer/prompts";
|
|
24
23
|
const DEFAULT_CONFIG = {
|
|
25
24
|
devServer: {
|
|
26
25
|
portRange: [3e3, 3100],
|
|
@@ -28,9 +27,6 @@ const DEFAULT_CONFIG = {
|
|
|
28
27
|
componentsBase: "./src/components",
|
|
29
28
|
publicFolder: "./public",
|
|
30
29
|
configBase: "./config"
|
|
31
|
-
},
|
|
32
|
-
editorServer: {
|
|
33
|
-
portRange: [3101, 3200]
|
|
34
30
|
}
|
|
35
31
|
};
|
|
36
32
|
function loadConfig() {
|
|
@@ -45,2316 +41,41 @@ function loadConfig() {
|
|
|
45
41
|
devServer: {
|
|
46
42
|
...DEFAULT_CONFIG.devServer,
|
|
47
43
|
...userConfig.devServer
|
|
48
|
-
},
|
|
49
|
-
editorServer: {
|
|
50
|
-
...DEFAULT_CONFIG.editorServer,
|
|
51
|
-
...userConfig.editorServer
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
} catch (error) {
|
|
55
|
-
getLogger().warn(`Failed to parse .jay YAML config file, using defaults: ${error}`);
|
|
56
|
-
return DEFAULT_CONFIG;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function getConfigWithDefaults(config) {
|
|
60
|
-
return {
|
|
61
|
-
devServer: {
|
|
62
|
-
portRange: config.devServer?.portRange || DEFAULT_CONFIG.devServer.portRange,
|
|
63
|
-
pagesBase: config.devServer?.pagesBase || DEFAULT_CONFIG.devServer.pagesBase,
|
|
64
|
-
componentsBase: config.devServer?.componentsBase || DEFAULT_CONFIG.devServer.componentsBase,
|
|
65
|
-
publicFolder: config.devServer?.publicFolder || DEFAULT_CONFIG.devServer.publicFolder,
|
|
66
|
-
configBase: config.devServer?.configBase || DEFAULT_CONFIG.devServer.configBase
|
|
67
|
-
},
|
|
68
|
-
editorServer: {
|
|
69
|
-
portRange: config.editorServer?.portRange || DEFAULT_CONFIG.editorServer.portRange,
|
|
70
|
-
editorId: config.editorServer?.editorId
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function updateConfig(updates) {
|
|
75
|
-
const configPath = path.resolve(".jay");
|
|
76
|
-
try {
|
|
77
|
-
const existingConfig = loadConfig();
|
|
78
|
-
const updatedConfig = {
|
|
79
|
-
...existingConfig,
|
|
80
|
-
...updates,
|
|
81
|
-
devServer: {
|
|
82
|
-
...existingConfig.devServer,
|
|
83
|
-
...updates.devServer
|
|
84
|
-
},
|
|
85
|
-
editorServer: {
|
|
86
|
-
...existingConfig.editorServer,
|
|
87
|
-
...updates.editorServer
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
91
|
-
fs.writeFileSync(configPath, yamlContent);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function rgbToHex(color, opacity) {
|
|
97
|
-
const r = Math.round(color.r * 255);
|
|
98
|
-
const g = Math.round(color.g * 255);
|
|
99
|
-
const b = Math.round(color.b * 255);
|
|
100
|
-
if (opacity !== void 0 && opacity < 1) {
|
|
101
|
-
const alphaHex = Math.round(opacity * 255).toString(16).padStart(2, "0");
|
|
102
|
-
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}${alphaHex}`;
|
|
103
|
-
} else {
|
|
104
|
-
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function getPositionType(node) {
|
|
108
|
-
if (node.layoutPositioning === "ABSOLUTE") {
|
|
109
|
-
return "absolute";
|
|
110
|
-
}
|
|
111
|
-
if (node.parentOverflowDirection && node.parentOverflowDirection !== "NONE") {
|
|
112
|
-
if (node.parentNumberOfFixedChildren && node.parentChildIndex !== void 0) {
|
|
113
|
-
if (node.parentChildIndex >= 0 && node.parentChildIndex < node.parentNumberOfFixedChildren) {
|
|
114
|
-
return "sticky";
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (node.scrollBehavior === "FIXED" && node.parentLayoutMode === "NONE") {
|
|
119
|
-
return "fixed";
|
|
120
|
-
}
|
|
121
|
-
if (node.parentType === "SECTION") {
|
|
122
|
-
return "absolute";
|
|
123
|
-
}
|
|
124
|
-
if (node.parentLayoutMode === "NONE") {
|
|
125
|
-
return "absolute";
|
|
126
|
-
}
|
|
127
|
-
if (node.parentLayoutMode && (node.parentLayoutMode === "HORIZONTAL" || node.parentLayoutMode === "VERTICAL")) {
|
|
128
|
-
return "relative";
|
|
129
|
-
}
|
|
130
|
-
return "static";
|
|
131
|
-
}
|
|
132
|
-
function getPositionStyle(node) {
|
|
133
|
-
if (node.type === "COMPONENT") {
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
const positionType = getPositionType(node);
|
|
137
|
-
if (positionType === "static") {
|
|
138
|
-
return "";
|
|
139
|
-
}
|
|
140
|
-
if (positionType === "absolute" || positionType === "fixed") {
|
|
141
|
-
const top = node.y !== void 0 ? node.y : 0;
|
|
142
|
-
const left = node.x !== void 0 ? node.x : 0;
|
|
143
|
-
return `position: ${positionType};top: ${top}px;left: ${left}px;`;
|
|
144
|
-
}
|
|
145
|
-
if (positionType === "sticky") {
|
|
146
|
-
return `position: ${positionType};top: 0;z-index: 10;`;
|
|
147
|
-
}
|
|
148
|
-
return `position: ${positionType};`;
|
|
149
|
-
}
|
|
150
|
-
function getAutoLayoutChildSizeStyles(node) {
|
|
151
|
-
if (!node.parentLayoutMode || node.parentLayoutMode === "NONE") {
|
|
152
|
-
const width2 = node.width !== void 0 ? node.width : 0;
|
|
153
|
-
const height2 = node.height !== void 0 ? node.height : 0;
|
|
154
|
-
return `width: ${width2}px;height: ${height2}px;`;
|
|
155
|
-
}
|
|
156
|
-
let styles = "";
|
|
157
|
-
if (!node.layoutGrow && !node.layoutAlign) {
|
|
158
|
-
const width2 = node.width !== void 0 ? node.width : 0;
|
|
159
|
-
const height2 = node.height !== void 0 ? node.height : 0;
|
|
160
|
-
return `width: ${width2}px;height: ${height2}px;`;
|
|
161
|
-
}
|
|
162
|
-
const isHorizontalLayout = node.parentLayoutMode === "HORIZONTAL";
|
|
163
|
-
const width = node.width !== void 0 ? node.width : 0;
|
|
164
|
-
const height = node.height !== void 0 ? node.height : 0;
|
|
165
|
-
if (node.layoutSizingHorizontal) {
|
|
166
|
-
switch (node.layoutSizingHorizontal) {
|
|
167
|
-
case "FIXED":
|
|
168
|
-
styles += `width: ${width}px;`;
|
|
169
|
-
break;
|
|
170
|
-
case "HUG":
|
|
171
|
-
styles += "width: fit-content;";
|
|
172
|
-
break;
|
|
173
|
-
case "FILL":
|
|
174
|
-
if (node.type === "TEXT") {
|
|
175
|
-
styles += "width: auto;";
|
|
176
|
-
} else if (isHorizontalLayout) {
|
|
177
|
-
styles += "flex-grow: 1;";
|
|
178
|
-
} else {
|
|
179
|
-
styles += "width: 100%;";
|
|
180
|
-
}
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
if (isHorizontalLayout && node.layoutGrow && node.layoutGrow > 0) {
|
|
185
|
-
styles += `flex-grow: ${node.layoutGrow};width: 0;`;
|
|
186
|
-
} else if (!isHorizontalLayout && node.layoutAlign === "STRETCH") {
|
|
187
|
-
styles += "width: 100%;";
|
|
188
|
-
} else {
|
|
189
|
-
styles += `width: ${width}px;`;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (node.layoutSizingVertical) {
|
|
193
|
-
switch (node.layoutSizingVertical) {
|
|
194
|
-
case "FIXED":
|
|
195
|
-
styles += `height: ${height}px;`;
|
|
196
|
-
break;
|
|
197
|
-
case "HUG":
|
|
198
|
-
styles += "height: fit-content;";
|
|
199
|
-
break;
|
|
200
|
-
case "FILL":
|
|
201
|
-
if (!isHorizontalLayout) {
|
|
202
|
-
styles += "flex-grow: 1;";
|
|
203
|
-
} else {
|
|
204
|
-
styles += "height: 100%;";
|
|
205
|
-
}
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
if (!isHorizontalLayout && node.layoutGrow && node.layoutGrow > 0) {
|
|
210
|
-
styles += `flex-grow: ${node.layoutGrow};height: 0;`;
|
|
211
|
-
} else if (isHorizontalLayout && node.layoutAlign === "STRETCH") {
|
|
212
|
-
styles += "height: 100%;";
|
|
213
|
-
} else {
|
|
214
|
-
styles += `height: ${height}px;`;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (node.layoutAlign) {
|
|
218
|
-
switch (node.layoutAlign) {
|
|
219
|
-
case "MIN":
|
|
220
|
-
styles += "align-self: flex-start;";
|
|
221
|
-
break;
|
|
222
|
-
case "CENTER":
|
|
223
|
-
styles += "align-self: center;";
|
|
224
|
-
break;
|
|
225
|
-
case "MAX":
|
|
226
|
-
styles += "align-self: flex-end;";
|
|
227
|
-
break;
|
|
228
|
-
case "STRETCH":
|
|
229
|
-
styles += "align-self: stretch;";
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return styles;
|
|
234
|
-
}
|
|
235
|
-
function getNodeSizeStyles(node) {
|
|
236
|
-
if (node.parentType === "SECTION") {
|
|
237
|
-
const height = node.height !== void 0 ? node.height : 0;
|
|
238
|
-
return `width: 100%;height: ${height}px;`;
|
|
239
|
-
}
|
|
240
|
-
return getAutoLayoutChildSizeStyles(node);
|
|
241
|
-
}
|
|
242
|
-
function getCommonStyles(node) {
|
|
243
|
-
let styles = "";
|
|
244
|
-
const transformStyles = [];
|
|
245
|
-
if (node.opacity !== void 0 && node.opacity < 1) {
|
|
246
|
-
styles += `opacity: ${node.opacity};`;
|
|
247
|
-
}
|
|
248
|
-
if (node.rotation !== void 0 && node.rotation !== 0) {
|
|
249
|
-
transformStyles.push(`rotate(${node.rotation}deg)`);
|
|
250
|
-
}
|
|
251
|
-
if (node.effects && Array.isArray(node.effects) && node.effects.length > 0) {
|
|
252
|
-
const visibleEffects = node.effects.filter((e2) => e2.visible !== false).reverse();
|
|
253
|
-
const filterFunctions = [];
|
|
254
|
-
const boxShadows = [];
|
|
255
|
-
for (const effect of visibleEffects) {
|
|
256
|
-
switch (effect.type) {
|
|
257
|
-
case "DROP_SHADOW":
|
|
258
|
-
case "INNER_SHADOW": {
|
|
259
|
-
if (effect.color && effect.offset && effect.radius !== void 0) {
|
|
260
|
-
const { offset, radius, color, spread } = effect;
|
|
261
|
-
const shadowColor = `rgba(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}, ${color.a ?? 1})`;
|
|
262
|
-
const inset = effect.type === "INNER_SHADOW" ? "inset " : "";
|
|
263
|
-
boxShadows.push(
|
|
264
|
-
`${inset}${offset.x}px ${offset.y}px ${radius}px ${spread ?? 0}px ${shadowColor}`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
case "LAYER_BLUR":
|
|
270
|
-
if (effect.radius !== void 0) {
|
|
271
|
-
filterFunctions.push(`blur(${effect.radius}px)`);
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
case "BACKGROUND_BLUR":
|
|
275
|
-
if (effect.radius !== void 0) {
|
|
276
|
-
styles += `backdrop-filter: blur(${effect.radius}px);`;
|
|
277
|
-
styles += `-webkit-backdrop-filter: blur(${effect.radius}px);`;
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (boxShadows.length > 0) {
|
|
283
|
-
styles += `box-shadow: ${boxShadows.join(", ")};`;
|
|
284
|
-
}
|
|
285
|
-
if (filterFunctions.length > 0) {
|
|
286
|
-
styles += `filter: ${filterFunctions.join(" ")};`;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (transformStyles.length > 0) {
|
|
290
|
-
styles += `transform: ${transformStyles.join(" ")};`;
|
|
291
|
-
const width = node.width !== void 0 ? node.width : 0;
|
|
292
|
-
const height = node.height !== void 0 ? node.height : 0;
|
|
293
|
-
styles += `transform-origin: ${width / 2}px ${height / 2}px;`;
|
|
294
|
-
}
|
|
295
|
-
return styles;
|
|
296
|
-
}
|
|
297
|
-
function getBorderRadius(node) {
|
|
298
|
-
if (typeof node.cornerRadius === "number") {
|
|
299
|
-
return `border-radius: ${node.cornerRadius}px;`;
|
|
300
|
-
} else if (node.cornerRadius === "MIXED" && node.topLeftRadius !== void 0) {
|
|
301
|
-
return `border-radius: ${node.topLeftRadius}px ${node.topRightRadius}px ${node.bottomRightRadius}px ${node.bottomLeftRadius}px;`;
|
|
302
|
-
}
|
|
303
|
-
return "border-radius: 0px;";
|
|
304
|
-
}
|
|
305
|
-
function getAutoLayoutStyles(node) {
|
|
306
|
-
if (node.layoutMode === "NONE" || !node.layoutMode) {
|
|
307
|
-
return "";
|
|
308
|
-
}
|
|
309
|
-
let flexStyles = "display: flex;";
|
|
310
|
-
if (node.layoutMode === "HORIZONTAL") {
|
|
311
|
-
flexStyles += "flex-direction: row;";
|
|
312
|
-
} else if (node.layoutMode === "VERTICAL") {
|
|
313
|
-
flexStyles += "flex-direction: column;";
|
|
314
|
-
}
|
|
315
|
-
if (node.primaryAxisAlignItems) {
|
|
316
|
-
switch (node.primaryAxisAlignItems) {
|
|
317
|
-
case "MIN":
|
|
318
|
-
flexStyles += "justify-content: flex-start;";
|
|
319
|
-
break;
|
|
320
|
-
case "CENTER":
|
|
321
|
-
flexStyles += "justify-content: center;";
|
|
322
|
-
break;
|
|
323
|
-
case "MAX":
|
|
324
|
-
flexStyles += "justify-content: flex-end;";
|
|
325
|
-
break;
|
|
326
|
-
case "SPACE_BETWEEN":
|
|
327
|
-
flexStyles += "justify-content: space-between;";
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (node.counterAxisAlignItems) {
|
|
332
|
-
switch (node.counterAxisAlignItems) {
|
|
333
|
-
case "MIN":
|
|
334
|
-
flexStyles += "align-items: flex-start;";
|
|
335
|
-
break;
|
|
336
|
-
case "CENTER":
|
|
337
|
-
flexStyles += "align-items: center;";
|
|
338
|
-
break;
|
|
339
|
-
case "MAX":
|
|
340
|
-
flexStyles += "align-items: flex-end;";
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (typeof node.itemSpacing === "number") {
|
|
345
|
-
flexStyles += `gap: ${node.itemSpacing}px;`;
|
|
346
|
-
}
|
|
347
|
-
if (typeof node.paddingLeft === "number")
|
|
348
|
-
flexStyles += `padding-left: ${node.paddingLeft}px;`;
|
|
349
|
-
if (typeof node.paddingRight === "number")
|
|
350
|
-
flexStyles += `padding-right: ${node.paddingRight}px;`;
|
|
351
|
-
if (typeof node.paddingTop === "number")
|
|
352
|
-
flexStyles += `padding-top: ${node.paddingTop}px;`;
|
|
353
|
-
if (typeof node.paddingBottom === "number")
|
|
354
|
-
flexStyles += `padding-bottom: ${node.paddingBottom}px;`;
|
|
355
|
-
return flexStyles;
|
|
356
|
-
}
|
|
357
|
-
function getOverflowStyles(node) {
|
|
358
|
-
let overflowStyles = "";
|
|
359
|
-
const shouldClip = node.clipsContent;
|
|
360
|
-
const overflowDirection = node.overflowDirection || "NONE";
|
|
361
|
-
switch (overflowDirection) {
|
|
362
|
-
case "HORIZONTAL":
|
|
363
|
-
overflowStyles += shouldClip ? "overflow-x: auto; overflow-y: hidden;" : "overflow-x: auto; overflow-y: visible;";
|
|
364
|
-
break;
|
|
365
|
-
case "VERTICAL":
|
|
366
|
-
overflowStyles += shouldClip ? "overflow-x: hidden; overflow-y: auto;" : "overflow-x: visible; overflow-y: auto;";
|
|
367
|
-
break;
|
|
368
|
-
case "BOTH":
|
|
369
|
-
overflowStyles += "overflow: auto;";
|
|
370
|
-
break;
|
|
371
|
-
case "NONE":
|
|
372
|
-
default:
|
|
373
|
-
overflowStyles += shouldClip ? "overflow: hidden;" : "overflow: visible;";
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
if (overflowDirection !== "NONE") {
|
|
377
|
-
overflowStyles += "scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, 0.3) transparent;";
|
|
378
|
-
}
|
|
379
|
-
return overflowStyles;
|
|
380
|
-
}
|
|
381
|
-
function getBackgroundFillsStyle(node) {
|
|
382
|
-
if (!node.fills || !Array.isArray(node.fills) || node.fills.length === 0) {
|
|
383
|
-
return "background: transparent;";
|
|
384
|
-
}
|
|
385
|
-
const backgrounds = [];
|
|
386
|
-
const backgroundSizes = [];
|
|
387
|
-
const backgroundPositions = [];
|
|
388
|
-
const backgroundRepeats = [];
|
|
389
|
-
for (const fill of [...node.fills].reverse()) {
|
|
390
|
-
if (fill.visible === false)
|
|
391
|
-
continue;
|
|
392
|
-
if (fill.type === "SOLID" && fill.color) {
|
|
393
|
-
const { r, g, b } = fill.color;
|
|
394
|
-
const opacity = fill.opacity !== void 0 ? fill.opacity : 1;
|
|
395
|
-
backgrounds.push(
|
|
396
|
-
`linear-gradient(rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${opacity}), rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${opacity}))`
|
|
397
|
-
);
|
|
398
|
-
backgroundSizes.push("100% 100%");
|
|
399
|
-
backgroundPositions.push("center");
|
|
400
|
-
backgroundRepeats.push("no-repeat");
|
|
401
|
-
} else if (fill.type === "IMAGE") {
|
|
402
|
-
console.warn("Image fills are not yet supported in vendor conversion");
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (backgrounds.length === 0) {
|
|
406
|
-
return "background: transparent;";
|
|
407
|
-
}
|
|
408
|
-
let style = `background-image: ${backgrounds.join(", ")};`;
|
|
409
|
-
style += `background-size: ${backgroundSizes.join(", ")};`;
|
|
410
|
-
style += `background-position: ${backgroundPositions.join(", ")};`;
|
|
411
|
-
style += `background-repeat: ${backgroundRepeats.join(", ")};`;
|
|
412
|
-
return style;
|
|
413
|
-
}
|
|
414
|
-
function getStrokeStyles(node) {
|
|
415
|
-
if (!node.strokes || node.strokes.length === 0) {
|
|
416
|
-
return "";
|
|
417
|
-
}
|
|
418
|
-
const visibleStrokes = node.strokes.filter((s2) => s2.visible !== false);
|
|
419
|
-
if (visibleStrokes.length === 0) {
|
|
420
|
-
return "";
|
|
421
|
-
}
|
|
422
|
-
const stroke = visibleStrokes[0];
|
|
423
|
-
let cssProps = [];
|
|
424
|
-
if (stroke.type === "SOLID" && stroke.color) {
|
|
425
|
-
const r = Math.round(stroke.color.r * 255);
|
|
426
|
-
const g = Math.round(stroke.color.g * 255);
|
|
427
|
-
const b = Math.round(stroke.color.b * 255);
|
|
428
|
-
const a = stroke.opacity !== void 0 ? stroke.opacity : 1;
|
|
429
|
-
if (a < 1) {
|
|
430
|
-
cssProps.push(`border-color: rgba(${r}, ${g}, ${b}, ${a.toFixed(2)});`);
|
|
431
|
-
} else {
|
|
432
|
-
cssProps.push(`border-color: rgb(${r}, ${g}, ${b});`);
|
|
433
|
-
}
|
|
434
|
-
} else {
|
|
435
|
-
return "";
|
|
436
|
-
}
|
|
437
|
-
const top = typeof node.strokeTopWeight === "number" ? node.strokeTopWeight : typeof node.strokeWeight === "number" ? node.strokeWeight : 0;
|
|
438
|
-
const right = typeof node.strokeRightWeight === "number" ? node.strokeRightWeight : typeof node.strokeWeight === "number" ? node.strokeWeight : 0;
|
|
439
|
-
const bottom = typeof node.strokeBottomWeight === "number" ? node.strokeBottomWeight : typeof node.strokeWeight === "number" ? node.strokeWeight : 0;
|
|
440
|
-
const left = typeof node.strokeLeftWeight === "number" ? node.strokeLeftWeight : typeof node.strokeWeight === "number" ? node.strokeWeight : 0;
|
|
441
|
-
if (top !== 0 || right !== 0 || bottom !== 0 || left !== 0) {
|
|
442
|
-
if (top === right && right === bottom && bottom === left) {
|
|
443
|
-
cssProps.push(`border-width: ${top}px;`);
|
|
444
|
-
} else {
|
|
445
|
-
cssProps.push(`border-width: ${top}px ${right}px ${bottom}px ${left}px;`);
|
|
446
|
-
}
|
|
447
|
-
if (node.dashPattern && node.dashPattern.length > 0) {
|
|
448
|
-
cssProps.push("border-style: dashed;");
|
|
449
|
-
} else {
|
|
450
|
-
cssProps.push("border-style: solid;");
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return cssProps.join(" ");
|
|
454
|
-
}
|
|
455
|
-
function getFrameSizeStyles(node) {
|
|
456
|
-
const isTopLevel = node.parentType === "SECTION";
|
|
457
|
-
if (isTopLevel) {
|
|
458
|
-
const height2 = node.height !== void 0 ? node.height : 0;
|
|
459
|
-
return `width: 100%;height: ${height2}px;`;
|
|
460
|
-
}
|
|
461
|
-
if (node.layoutMode === "NONE" || !node.layoutMode) {
|
|
462
|
-
const width2 = node.width !== void 0 ? node.width : 0;
|
|
463
|
-
const height2 = node.height !== void 0 ? node.height : 0;
|
|
464
|
-
return `width: ${width2}px;height: ${height2}px;`;
|
|
465
|
-
}
|
|
466
|
-
let sizeStyles = "";
|
|
467
|
-
const width = node.width !== void 0 ? node.width : 0;
|
|
468
|
-
const height = node.height !== void 0 ? node.height : 0;
|
|
469
|
-
if (node.layoutSizingHorizontal) {
|
|
470
|
-
switch (node.layoutSizingHorizontal) {
|
|
471
|
-
case "FIXED":
|
|
472
|
-
sizeStyles += `width: ${width}px;`;
|
|
473
|
-
break;
|
|
474
|
-
case "HUG":
|
|
475
|
-
sizeStyles += "width: fit-content;";
|
|
476
|
-
break;
|
|
477
|
-
case "FILL":
|
|
478
|
-
sizeStyles += "width: 100%;";
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
sizeStyles += `width: ${width}px;`;
|
|
483
|
-
}
|
|
484
|
-
if (node.layoutSizingVertical) {
|
|
485
|
-
switch (node.layoutSizingVertical) {
|
|
486
|
-
case "FIXED":
|
|
487
|
-
sizeStyles += `height: ${height}px;`;
|
|
488
|
-
break;
|
|
489
|
-
case "HUG":
|
|
490
|
-
sizeStyles += "height: fit-content;";
|
|
491
|
-
break;
|
|
492
|
-
case "FILL":
|
|
493
|
-
sizeStyles += "height: 100%;";
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
} else {
|
|
497
|
-
sizeStyles += `height: ${height}px;`;
|
|
498
|
-
}
|
|
499
|
-
if (node.layoutWrap === "WRAP") {
|
|
500
|
-
sizeStyles += `max-width: ${width}px;`;
|
|
501
|
-
}
|
|
502
|
-
return sizeStyles;
|
|
503
|
-
}
|
|
504
|
-
function escapeHtmlContent(text) {
|
|
505
|
-
const map = {
|
|
506
|
-
"&": "&",
|
|
507
|
-
"<": "<",
|
|
508
|
-
">": ">",
|
|
509
|
-
'"': """,
|
|
510
|
-
"'": "'"
|
|
511
|
-
};
|
|
512
|
-
return text.replace(/[&<>"']/g, (char) => map[char]);
|
|
513
|
-
}
|
|
514
|
-
function convertTextNodeToHtml(node, indent, dynamicContent, refAttr, attributesHtml) {
|
|
515
|
-
const {
|
|
516
|
-
name,
|
|
517
|
-
id,
|
|
518
|
-
characters,
|
|
519
|
-
fontName,
|
|
520
|
-
fontSize,
|
|
521
|
-
fontWeight,
|
|
522
|
-
fills,
|
|
523
|
-
textAlignHorizontal,
|
|
524
|
-
textAlignVertical,
|
|
525
|
-
letterSpacing,
|
|
526
|
-
lineHeight,
|
|
527
|
-
textDecoration,
|
|
528
|
-
textCase,
|
|
529
|
-
textTruncation,
|
|
530
|
-
maxLines,
|
|
531
|
-
maxWidth,
|
|
532
|
-
textAutoResize,
|
|
533
|
-
hasMissingFont,
|
|
534
|
-
hyperlinks
|
|
535
|
-
} = node;
|
|
536
|
-
if (hasMissingFont || !characters) {
|
|
537
|
-
if (hasMissingFont) {
|
|
538
|
-
return `${indent}<!-- Text node "${name}" has missing fonts -->
|
|
539
|
-
`;
|
|
540
|
-
}
|
|
541
|
-
return "";
|
|
542
|
-
}
|
|
543
|
-
let fontFamilyStyle = "font-family: sans-serif;";
|
|
544
|
-
if (fontName && typeof fontName === "object" && fontName.family) {
|
|
545
|
-
fontFamilyStyle = `font-family: '${fontName.family}', sans-serif;`;
|
|
546
|
-
}
|
|
547
|
-
const fontSizeValue = typeof fontSize === "number" ? fontSize : 16;
|
|
548
|
-
const fontSizeStyle = `font-size: ${fontSizeValue}px;`;
|
|
549
|
-
const fontWeightValue = typeof fontWeight === "number" ? fontWeight : 400;
|
|
550
|
-
const fontWeightStyle = `font-weight: ${fontWeightValue};`;
|
|
551
|
-
let textColor = "#000000";
|
|
552
|
-
if (fills && Array.isArray(fills) && fills.length > 0 && fills[0].type === "SOLID" && fills[0].color) {
|
|
553
|
-
textColor = rgbToHex(fills[0].color);
|
|
554
|
-
}
|
|
555
|
-
const colorStyle = `color: ${textColor};`;
|
|
556
|
-
const textAlign = textAlignHorizontal ? textAlignHorizontal.toLowerCase() : "left";
|
|
557
|
-
const textAlignStyle = `text-align: ${textAlign};`;
|
|
558
|
-
let verticalAlignWrapperStyle = "";
|
|
559
|
-
if (textAlignVertical) {
|
|
560
|
-
verticalAlignWrapperStyle = "display: flex; flex-direction: column;";
|
|
561
|
-
switch (textAlignVertical) {
|
|
562
|
-
case "TOP":
|
|
563
|
-
verticalAlignWrapperStyle += "justify-content: flex-start;";
|
|
564
|
-
break;
|
|
565
|
-
case "CENTER":
|
|
566
|
-
verticalAlignWrapperStyle += "justify-content: center;";
|
|
567
|
-
break;
|
|
568
|
-
case "BOTTOM":
|
|
569
|
-
verticalAlignWrapperStyle += "justify-content: flex-end;";
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
let letterSpacingStyle = "";
|
|
574
|
-
if (letterSpacing && letterSpacing.value !== 0) {
|
|
575
|
-
const unit = letterSpacing.unit === "PIXELS" ? "px" : "%";
|
|
576
|
-
letterSpacingStyle = `letter-spacing: ${letterSpacing.value}${unit};`;
|
|
577
|
-
}
|
|
578
|
-
let lineHeightStyle = "";
|
|
579
|
-
if (lineHeight) {
|
|
580
|
-
if (lineHeight.unit === "AUTO") {
|
|
581
|
-
lineHeightStyle = "line-height: normal;";
|
|
582
|
-
} else {
|
|
583
|
-
const unit = lineHeight.unit === "PIXELS" ? "px" : "%";
|
|
584
|
-
lineHeightStyle = `line-height: ${lineHeight.value}${unit};`;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
let textDecorationStyle = "";
|
|
588
|
-
if (textDecoration === "UNDERLINE") {
|
|
589
|
-
textDecorationStyle = "text-decoration: underline;";
|
|
590
|
-
} else if (textDecoration === "STRIKETHROUGH") {
|
|
591
|
-
textDecorationStyle = "text-decoration: line-through;";
|
|
592
|
-
}
|
|
593
|
-
let textTransformStyle = "";
|
|
594
|
-
if (textCase && textCase !== "ORIGINAL") {
|
|
595
|
-
switch (textCase) {
|
|
596
|
-
case "UPPER":
|
|
597
|
-
textTransformStyle = "text-transform: uppercase;";
|
|
598
|
-
break;
|
|
599
|
-
case "LOWER":
|
|
600
|
-
textTransformStyle = "text-transform: lowercase;";
|
|
601
|
-
break;
|
|
602
|
-
case "TITLE":
|
|
603
|
-
textTransformStyle = "text-transform: capitalize;";
|
|
604
|
-
break;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
let truncationStyle = "";
|
|
608
|
-
if (textTruncation === "ENDING") {
|
|
609
|
-
if (maxLines && maxLines > 1) {
|
|
610
|
-
truncationStyle = `display: -webkit-box; -webkit-line-clamp: ${maxLines}; -webkit-box-orient: vertical; overflow: hidden;`;
|
|
611
|
-
} else {
|
|
612
|
-
truncationStyle = "white-space: nowrap; overflow: hidden; text-overflow: ellipsis;";
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
if (maxWidth && maxWidth > 0) {
|
|
616
|
-
truncationStyle += `max-width: ${maxWidth}px;`;
|
|
617
|
-
}
|
|
618
|
-
const positionStyle = getPositionStyle(node);
|
|
619
|
-
let sizeStyles = getNodeSizeStyles(node);
|
|
620
|
-
const commonStyles = getCommonStyles(node);
|
|
621
|
-
if (textAutoResize === "HEIGHT") {
|
|
622
|
-
sizeStyles = sizeStyles.replace(/height: [^;]+;/, "height: auto;");
|
|
623
|
-
}
|
|
624
|
-
const textStyles = `${fontFamilyStyle}${fontSizeStyle}${fontWeightStyle}${colorStyle}${textAlignStyle}${letterSpacingStyle}${lineHeightStyle}${textDecorationStyle}${textTransformStyle}${truncationStyle}`;
|
|
625
|
-
let htmlContent = "";
|
|
626
|
-
if (dynamicContent) {
|
|
627
|
-
htmlContent = dynamicContent;
|
|
628
|
-
} else if (hyperlinks && hyperlinks.length > 0) {
|
|
629
|
-
let lastEnd = 0;
|
|
630
|
-
for (const link of hyperlinks) {
|
|
631
|
-
if (link.start > lastEnd) {
|
|
632
|
-
const beforeText = characters.substring(lastEnd, link.start);
|
|
633
|
-
htmlContent += escapeHtmlContent(beforeText).replace(/\n/g, "<br>");
|
|
634
|
-
}
|
|
635
|
-
const linkText = characters.substring(link.start, link.end + 1);
|
|
636
|
-
htmlContent += `<a href="${escapeHtmlContent(link.url)}" style="color: inherit;">${escapeHtmlContent(linkText).replace(/\n/g, "<br>")}</a>`;
|
|
637
|
-
lastEnd = link.end + 1;
|
|
638
|
-
}
|
|
639
|
-
if (lastEnd < characters.length) {
|
|
640
|
-
const afterText = characters.substring(lastEnd);
|
|
641
|
-
htmlContent += escapeHtmlContent(afterText).replace(/\n/g, "<br>");
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
htmlContent = escapeHtmlContent(characters).replace(/\n/g, "<br>");
|
|
645
|
-
}
|
|
646
|
-
const childIndent = indent + " ";
|
|
647
|
-
const innerIndent = indent + " ";
|
|
648
|
-
const styleAttr = `${positionStyle}${sizeStyles}${commonStyles}${textStyles}`;
|
|
649
|
-
const refString = refAttr || "";
|
|
650
|
-
const attrsString = attributesHtml || "";
|
|
651
|
-
if (verticalAlignWrapperStyle) {
|
|
652
|
-
return `${indent}<div data-figma-id="${id}"${refString}${attrsString} style="${styleAttr}${verticalAlignWrapperStyle}">
|
|
653
|
-
${childIndent}<div style="${textStyles}">
|
|
654
|
-
${innerIndent}${htmlContent}
|
|
655
|
-
${childIndent}</div>
|
|
656
|
-
${indent}</div>
|
|
657
|
-
`;
|
|
658
|
-
} else {
|
|
659
|
-
return `${indent}<div data-figma-id="${id}"${refString}${attrsString} style="${styleAttr}">${htmlContent}</div>
|
|
660
|
-
`;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
function convertImageNodeToHtml(node, indent, srcBinding, altBinding, refAttr, staticImageUrl) {
|
|
664
|
-
const { name, id } = node;
|
|
665
|
-
const positionStyle = getPositionStyle(node);
|
|
666
|
-
const commonStyles = getCommonStyles(node);
|
|
667
|
-
const borderRadius = getBorderRadius(node);
|
|
668
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
669
|
-
const styles = `${positionStyle}${sizeStyles}${borderRadius}${commonStyles}`.trim();
|
|
670
|
-
let src = "";
|
|
671
|
-
if (srcBinding) {
|
|
672
|
-
src = srcBinding;
|
|
673
|
-
} else if (staticImageUrl) {
|
|
674
|
-
src = staticImageUrl;
|
|
675
|
-
} else {
|
|
676
|
-
src = "/placeholder-image.png";
|
|
677
|
-
console.warn(`Image node "${name}" (${id}) has no src binding or static image`);
|
|
678
|
-
}
|
|
679
|
-
const alt = altBinding || name;
|
|
680
|
-
const refAttribute = refAttr || "";
|
|
681
|
-
const styleAttribute = styles ? ` style="${styles}"` : "";
|
|
682
|
-
const dataAttribute = ` data-figma-id="${id}"`;
|
|
683
|
-
return `${indent}<img${dataAttribute}${refAttribute} src="${src}" alt="${alt}"${styleAttribute} />
|
|
684
|
-
`;
|
|
685
|
-
}
|
|
686
|
-
function extractStaticImageUrl(node) {
|
|
687
|
-
if (!node.fills || !Array.isArray(node.fills)) {
|
|
688
|
-
return void 0;
|
|
689
|
-
}
|
|
690
|
-
for (const fill of node.fills) {
|
|
691
|
-
if (fill.visible !== false && fill.type === "IMAGE") {
|
|
692
|
-
if (fill.imageUrl) {
|
|
693
|
-
return fill.imageUrl;
|
|
694
|
-
}
|
|
695
|
-
if (fill.imageHash) {
|
|
696
|
-
console.warn(
|
|
697
|
-
`Image fill with hash "${fill.imageHash}" found on node "${node.name}" (${node.id}) but no imageUrl in serialized data. Update plugin serialization to export and save images.`
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
return void 0;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
return void 0;
|
|
704
|
-
}
|
|
705
|
-
function convertRectangleToHtml(node, indent) {
|
|
706
|
-
const { id } = node;
|
|
707
|
-
const positionStyle = getPositionStyle(node);
|
|
708
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
709
|
-
const commonStyles = getCommonStyles(node);
|
|
710
|
-
const backgroundStyle = getBackgroundFillsStyle(node);
|
|
711
|
-
const borderRadius = getBorderRadius(node);
|
|
712
|
-
const strokeStyles = getStrokeStyles(node);
|
|
713
|
-
const allStyles = `${positionStyle}${sizeStyles}${backgroundStyle}${strokeStyles}${borderRadius}${commonStyles}box-sizing: border-box;`;
|
|
714
|
-
return `${indent}<div data-figma-id="${id}" style="${allStyles}"></div>
|
|
715
|
-
`;
|
|
716
|
-
}
|
|
717
|
-
function convertEllipseToHtml(node, indent) {
|
|
718
|
-
const { id } = node;
|
|
719
|
-
const positionStyle = getPositionStyle(node);
|
|
720
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
721
|
-
const commonStyles = getCommonStyles(node);
|
|
722
|
-
const backgroundStyle = getBackgroundFillsStyle(node);
|
|
723
|
-
const strokeStyles = getStrokeStyles(node);
|
|
724
|
-
const borderRadius = "border-radius: 50%;";
|
|
725
|
-
const allStyles = `${positionStyle}${sizeStyles}${backgroundStyle}${strokeStyles}${borderRadius}${commonStyles}box-sizing: border-box;`;
|
|
726
|
-
return `${indent}<div data-figma-id="${id}" style="${allStyles}"></div>
|
|
727
|
-
`;
|
|
728
|
-
}
|
|
729
|
-
function convertVectorToHtml(node, indent) {
|
|
730
|
-
const { id, name, svgContent, svgExportFailed, width, height } = node;
|
|
731
|
-
const positionStyle = getPositionStyle(node);
|
|
732
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
733
|
-
const commonStyles = getCommonStyles(node);
|
|
734
|
-
let finalSvgContent;
|
|
735
|
-
if (svgExportFailed || !svgContent) {
|
|
736
|
-
finalSvgContent = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg"><rect width="${width}" height="${height}" fill="none" stroke="#ccc" stroke-width="1" stroke-dasharray="5,5"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="10" fill="#999">Vector: ${name}</text></svg>`;
|
|
737
|
-
} else {
|
|
738
|
-
finalSvgContent = svgContent;
|
|
739
|
-
}
|
|
740
|
-
const allStyles = `${positionStyle}${sizeStyles}${commonStyles}box-sizing: border-box;`;
|
|
741
|
-
const childIndent = indent + " ";
|
|
742
|
-
return `${indent}<div data-figma-id="${id}" data-figma-type="vector" style="${allStyles}">
|
|
743
|
-
${childIndent}${finalSvgContent}
|
|
744
|
-
${indent}</div>
|
|
745
|
-
`;
|
|
746
|
-
}
|
|
747
|
-
function getComponentVariantValues(node, propertyBindings) {
|
|
748
|
-
const values = /* @__PURE__ */ new Map();
|
|
749
|
-
const filterPseudoVariants = (variantValues) => {
|
|
750
|
-
return variantValues.filter((value) => !value.includes(":"));
|
|
751
|
-
};
|
|
752
|
-
if (node.componentPropertyDefinitions) {
|
|
753
|
-
for (const binding of propertyBindings) {
|
|
754
|
-
const propDef = node.componentPropertyDefinitions[binding.property];
|
|
755
|
-
if (propDef && propDef.type === "VARIANT" && propDef.variantOptions) {
|
|
756
|
-
const filtered = filterPseudoVariants(propDef.variantOptions);
|
|
757
|
-
if (filtered.length > 0) {
|
|
758
|
-
values.set(binding.property, filtered);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
if (values.size === 0 && node.variants && node.variants.length > 0) {
|
|
764
|
-
const propertyValuesMap = /* @__PURE__ */ new Map();
|
|
765
|
-
for (const variant of node.variants) {
|
|
766
|
-
if (variant.variantProperties) {
|
|
767
|
-
for (const binding of propertyBindings) {
|
|
768
|
-
const propValue = variant.variantProperties[binding.property];
|
|
769
|
-
if (propValue && !propValue.includes(":")) {
|
|
770
|
-
if (!propertyValuesMap.has(binding.property)) {
|
|
771
|
-
propertyValuesMap.set(binding.property, /* @__PURE__ */ new Set());
|
|
772
|
-
}
|
|
773
|
-
propertyValuesMap.get(binding.property).add(propValue);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
for (const [prop, valueSet] of propertyValuesMap) {
|
|
779
|
-
if (valueSet.size > 0) {
|
|
780
|
-
values.set(prop, Array.from(valueSet));
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return values;
|
|
785
|
-
}
|
|
786
|
-
function isBooleanVariant(values, contractTag) {
|
|
787
|
-
if (contractTag.dataType !== "boolean") {
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
if (values.length !== 2) {
|
|
791
|
-
return false;
|
|
792
|
-
}
|
|
793
|
-
const sortedValues = [...values].sort();
|
|
794
|
-
return sortedValues[0] === "false" && sortedValues[1] === "true";
|
|
795
|
-
}
|
|
796
|
-
function generatePermutations(propertyValues, bindings) {
|
|
797
|
-
const properties = Array.from(propertyValues.entries());
|
|
798
|
-
if (properties.length === 0) {
|
|
799
|
-
return [];
|
|
800
|
-
}
|
|
801
|
-
const permutations = [];
|
|
802
|
-
function generate(index, current) {
|
|
803
|
-
if (index === properties.length) {
|
|
804
|
-
permutations.push([...current]);
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
const [propName, propValues] = properties[index];
|
|
808
|
-
const binding = bindings.find((b) => b.property === propName);
|
|
809
|
-
if (!binding)
|
|
810
|
-
return;
|
|
811
|
-
const isBoolean = isBooleanVariant(propValues, binding.contractTag);
|
|
812
|
-
for (const value of propValues) {
|
|
813
|
-
current.push({ property: propName, tagPath: binding.tagPath, value, isBoolean });
|
|
814
|
-
generate(index + 1, current);
|
|
815
|
-
current.pop();
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
generate(0, []);
|
|
819
|
-
return permutations;
|
|
820
|
-
}
|
|
821
|
-
function findComponentVariant(node, permutation) {
|
|
822
|
-
if (!node.variants || node.variants.length === 0) {
|
|
823
|
-
throw new Error(
|
|
824
|
-
`Node "${node.name}" has no variants array - cannot find variant component`
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
const targetProps = /* @__PURE__ */ new Map();
|
|
828
|
-
for (const { property, value } of permutation) {
|
|
829
|
-
targetProps.set(property, value);
|
|
830
|
-
}
|
|
831
|
-
const matchingVariant = node.variants.find((variant) => {
|
|
832
|
-
if (!variant.variantProperties) {
|
|
833
|
-
return false;
|
|
834
|
-
}
|
|
835
|
-
for (const [prop, value] of targetProps) {
|
|
836
|
-
if (variant.variantProperties[prop] !== value) {
|
|
837
|
-
return false;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
for (const [prop, value] of Object.entries(variant.variantProperties)) {
|
|
841
|
-
if (targetProps.has(prop) && targetProps.get(prop) !== value) {
|
|
842
|
-
return false;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
return true;
|
|
846
|
-
});
|
|
847
|
-
if (!matchingVariant) {
|
|
848
|
-
console.log(
|
|
849
|
-
`No matching variant found for "${node.name}" with properties:`,
|
|
850
|
-
Object.fromEntries(targetProps),
|
|
851
|
-
"\nAvailable variants:",
|
|
852
|
-
node.variants.map((v) => v.variantProperties),
|
|
853
|
-
"\nUsing first variant as fallback"
|
|
854
|
-
);
|
|
855
|
-
return node.variants[0] || node;
|
|
856
|
-
}
|
|
857
|
-
return matchingVariant;
|
|
858
|
-
}
|
|
859
|
-
function buildVariantCondition(permutation) {
|
|
860
|
-
const conditions = permutation.map(({ tagPath, value, isBoolean }) => {
|
|
861
|
-
if (isBoolean) {
|
|
862
|
-
if (value === "true") {
|
|
863
|
-
return tagPath;
|
|
864
|
-
} else {
|
|
865
|
-
return `!${tagPath}`;
|
|
866
|
-
}
|
|
867
|
-
} else {
|
|
868
|
-
return `${tagPath} == ${value}`;
|
|
869
|
-
}
|
|
870
|
-
});
|
|
871
|
-
return conditions.join(" && ");
|
|
872
|
-
}
|
|
873
|
-
function convertVariantNode(node, analysis, context, convertNodeToJayHtml2) {
|
|
874
|
-
const indent = " ".repeat(context.indentLevel);
|
|
875
|
-
const innerIndent = " ".repeat(context.indentLevel + 1);
|
|
876
|
-
const propertyValues = getComponentVariantValues(node, analysis.propertyBindings);
|
|
877
|
-
const permutations = generatePermutations(propertyValues, analysis.propertyBindings);
|
|
878
|
-
if (permutations.length === 0) {
|
|
879
|
-
throw new Error(
|
|
880
|
-
`No permutations generated for variant node "${node.name}" - check property definitions`
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
let variantHtml = "";
|
|
884
|
-
for (const permutation of permutations) {
|
|
885
|
-
const conditions = buildVariantCondition(permutation);
|
|
886
|
-
const variantNode = findComponentVariant(node, permutation);
|
|
887
|
-
variantHtml += `${innerIndent}<div if="${conditions}">
|
|
888
|
-
`;
|
|
889
|
-
const variantContext = {
|
|
890
|
-
...context,
|
|
891
|
-
indentLevel: context.indentLevel + 2
|
|
892
|
-
// +2 because we're inside wrapper and if div
|
|
893
|
-
};
|
|
894
|
-
if (variantNode.children && variantNode.children.length > 0) {
|
|
895
|
-
for (const child of variantNode.children) {
|
|
896
|
-
variantHtml += convertNodeToJayHtml2(child, variantContext);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
variantHtml += `${innerIndent}</div>
|
|
900
|
-
`;
|
|
901
|
-
}
|
|
902
|
-
const positionStyle = getPositionStyle(node);
|
|
903
|
-
const frameSizeStyles = getFrameSizeStyles(node);
|
|
904
|
-
const backgroundStyle = getBackgroundFillsStyle(node);
|
|
905
|
-
const borderRadius = getBorderRadius(node);
|
|
906
|
-
const strokeStyles = getStrokeStyles(node);
|
|
907
|
-
const flexStyles = getAutoLayoutStyles(node);
|
|
908
|
-
const overflowStyles = getOverflowStyles(node);
|
|
909
|
-
const commonStyles = getCommonStyles(node);
|
|
910
|
-
const wrapperStyleAttr = `${positionStyle}${frameSizeStyles}${backgroundStyle}${strokeStyles}${borderRadius}${overflowStyles}${commonStyles}${flexStyles}box-sizing: border-box;`;
|
|
911
|
-
let refAttr = "";
|
|
912
|
-
if (analysis.refPath) {
|
|
913
|
-
refAttr = ` ref="${analysis.refPath}"`;
|
|
914
|
-
} else if (analysis.dualPath) {
|
|
915
|
-
refAttr = ` ref="${analysis.dualPath}"`;
|
|
916
|
-
} else if (analysis.interactiveVariantPath) {
|
|
917
|
-
refAttr = ` ref="${analysis.interactiveVariantPath}"`;
|
|
918
|
-
}
|
|
919
|
-
return `${indent}<div id="${node.id}" data-figma-id="${node.id}" data-figma-type="variant-container"${refAttr} style="${wrapperStyleAttr}">
|
|
920
|
-
` + variantHtml + `${indent}</div>
|
|
921
|
-
`;
|
|
922
|
-
}
|
|
923
|
-
function convertRepeaterNode(node, analysis, context, convertNodeToJayHtml2) {
|
|
924
|
-
const { repeaterPath, trackByKey } = analysis;
|
|
925
|
-
const indent = " ".repeat(context.indentLevel);
|
|
926
|
-
const innerIndent = " ".repeat(context.indentLevel + 1);
|
|
927
|
-
if (node.type !== "FRAME") {
|
|
928
|
-
throw new Error(`Repeater node "${node.name}" must be a FRAME (got: ${node.type})`);
|
|
929
|
-
}
|
|
930
|
-
if (!node.layoutMode || node.layoutMode === "NONE") {
|
|
931
|
-
throw new Error(
|
|
932
|
-
`Repeater node "${node.name}" must have auto-layout (HORIZONTAL or VERTICAL)`
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
const positionStyle = getPositionStyle(node);
|
|
936
|
-
const frameSizeStyles = getFrameSizeStyles(node);
|
|
937
|
-
const backgroundStyle = getBackgroundFillsStyle(node);
|
|
938
|
-
const borderRadius = getBorderRadius(node);
|
|
939
|
-
const strokeStyles = getStrokeStyles(node);
|
|
940
|
-
const flexStyles = getAutoLayoutStyles(node);
|
|
941
|
-
const overflowStyles = getOverflowStyles(node);
|
|
942
|
-
const commonStyles = getCommonStyles(node);
|
|
943
|
-
const outerStyleAttr = `${positionStyle}${frameSizeStyles}${backgroundStyle}${strokeStyles}${borderRadius}${overflowStyles}${commonStyles}${flexStyles}box-sizing: border-box;`;
|
|
944
|
-
let innerDivSizeStyles = "";
|
|
945
|
-
if (node.layoutWrap === "WRAP") {
|
|
946
|
-
innerDivSizeStyles = "width: fit-content; height: fit-content;";
|
|
947
|
-
} else if (node.layoutMode === "HORIZONTAL") {
|
|
948
|
-
innerDivSizeStyles = "height: 100%;";
|
|
949
|
-
} else if (node.layoutMode === "VERTICAL") {
|
|
950
|
-
innerDivSizeStyles = "width: 100%;";
|
|
951
|
-
}
|
|
952
|
-
let html = `${indent}<div id="${node.id}" data-figma-id="${node.id}" data-figma-type="frame-repeater" style="${outerStyleAttr}">
|
|
953
|
-
`;
|
|
954
|
-
html += `${innerIndent}<div style="position: relative; ${innerDivSizeStyles}" forEach="${repeaterPath}" trackBy="${trackByKey}">
|
|
955
|
-
`;
|
|
956
|
-
const newContext = {
|
|
957
|
-
...context,
|
|
958
|
-
repeaterPathStack: [...context.repeaterPathStack, repeaterPath.split(".")],
|
|
959
|
-
indentLevel: context.indentLevel + 2
|
|
960
|
-
// +2 because we're inside both divs
|
|
961
|
-
};
|
|
962
|
-
if (node.children && node.children.length > 0) {
|
|
963
|
-
html += convertNodeToJayHtml2(node.children[0], newContext);
|
|
964
|
-
} else {
|
|
965
|
-
throw new Error(
|
|
966
|
-
`Repeater node "${node.name}" has no children - repeater template is required`
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
html += `${innerIndent}</div>
|
|
970
|
-
`;
|
|
971
|
-
html += `${indent}</div>
|
|
972
|
-
`;
|
|
973
|
-
return html;
|
|
974
|
-
}
|
|
975
|
-
function convertGroupNode(node, analysis, context, convertNodeToJayHtml2) {
|
|
976
|
-
const indent = " ".repeat(context.indentLevel);
|
|
977
|
-
const positionStyle = getPositionStyle(node);
|
|
978
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
979
|
-
const commonStyles = getCommonStyles(node);
|
|
980
|
-
const styleAttr = `${positionStyle}${sizeStyles}${commonStyles}box-sizing: border-box;`;
|
|
981
|
-
let refAttr = "";
|
|
982
|
-
if (analysis.refPath) {
|
|
983
|
-
refAttr = ` ref="${analysis.refPath}"`;
|
|
984
|
-
} else if (analysis.dualPath) {
|
|
985
|
-
refAttr = ` ref="${analysis.dualPath}"`;
|
|
986
|
-
}
|
|
987
|
-
let htmlAttrs = `id="${node.id}" data-figma-id="${node.id}" data-figma-type="group"${refAttr} style="${styleAttr}"`;
|
|
988
|
-
for (const [attr, tagPath] of analysis.attributes) {
|
|
989
|
-
htmlAttrs += ` ${attr}="{${tagPath}}"`;
|
|
990
|
-
}
|
|
991
|
-
let html = `${indent}<div ${htmlAttrs}>
|
|
992
|
-
`;
|
|
993
|
-
const childContext = {
|
|
994
|
-
...context,
|
|
995
|
-
indentLevel: context.indentLevel + 1
|
|
996
|
-
};
|
|
997
|
-
if (node.children && node.children.length > 0) {
|
|
998
|
-
for (const child of node.children) {
|
|
999
|
-
html += convertNodeToJayHtml2(child, childContext);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
html += `${indent}</div>
|
|
1003
|
-
`;
|
|
1004
|
-
return html;
|
|
1005
|
-
}
|
|
1006
|
-
function findContractTag(tags, tagPath) {
|
|
1007
|
-
if (tagPath.length === 0) {
|
|
1008
|
-
return void 0;
|
|
1009
|
-
}
|
|
1010
|
-
const tag = tags.find((t) => t.tag === tagPath[0]);
|
|
1011
|
-
if (!tag) {
|
|
1012
|
-
return void 0;
|
|
1013
|
-
}
|
|
1014
|
-
if (tagPath.length === 1) {
|
|
1015
|
-
return tag;
|
|
1016
|
-
}
|
|
1017
|
-
if (!tag.tags || tag.tags.length === 0) {
|
|
1018
|
-
return void 0;
|
|
1019
|
-
}
|
|
1020
|
-
return findContractTag(tag.tags, tagPath.slice(1));
|
|
1021
|
-
}
|
|
1022
|
-
function findPlugin(plugins, pluginName) {
|
|
1023
|
-
return plugins.find((p) => p.name === pluginName);
|
|
1024
|
-
}
|
|
1025
|
-
function findPluginContract(plugin, componentName) {
|
|
1026
|
-
const contract = plugin.contracts.find((c) => c.name === componentName);
|
|
1027
|
-
return contract ? { tags: contract.tags } : void 0;
|
|
1028
|
-
}
|
|
1029
|
-
function findPageContract(projectPage) {
|
|
1030
|
-
return projectPage.contract ? { tags: projectPage.contract.tags } : void 0;
|
|
1031
|
-
}
|
|
1032
|
-
function isDataTag(contractTag) {
|
|
1033
|
-
if (Array.isArray(contractTag.type)) {
|
|
1034
|
-
return contractTag.type.includes("data");
|
|
1035
|
-
}
|
|
1036
|
-
return contractTag.type === "data";
|
|
1037
|
-
}
|
|
1038
|
-
function isInteractiveTag(contractTag) {
|
|
1039
|
-
if (Array.isArray(contractTag.type)) {
|
|
1040
|
-
return contractTag.type.includes("interactive");
|
|
1041
|
-
}
|
|
1042
|
-
return contractTag.type === "interactive";
|
|
1043
|
-
}
|
|
1044
|
-
function isDualTag(contractTag) {
|
|
1045
|
-
if (Array.isArray(contractTag.type)) {
|
|
1046
|
-
return contractTag.type.includes("data") && contractTag.type.includes("interactive");
|
|
1047
|
-
}
|
|
1048
|
-
return false;
|
|
1049
|
-
}
|
|
1050
|
-
function isRepeaterTag(contractTag) {
|
|
1051
|
-
return contractTag.type === "subContract" && contractTag.repeated === true;
|
|
1052
|
-
}
|
|
1053
|
-
function applyRepeaterContext(path2, repeaterStack) {
|
|
1054
|
-
for (const repeaterPath of repeaterStack) {
|
|
1055
|
-
const prefix = repeaterPath.join(".") + ".";
|
|
1056
|
-
if (path2.startsWith(prefix)) {
|
|
1057
|
-
path2 = path2.substring(prefix.length);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
return path2;
|
|
1061
|
-
}
|
|
1062
|
-
function resolveBinding(binding, context) {
|
|
1063
|
-
let contract;
|
|
1064
|
-
let key;
|
|
1065
|
-
let tagPathWithoutKey;
|
|
1066
|
-
if (binding.pageContractPath.pluginName && binding.pageContractPath.componentName) {
|
|
1067
|
-
const plugin = findPlugin(context.plugins, binding.pageContractPath.pluginName);
|
|
1068
|
-
if (!plugin) {
|
|
1069
|
-
throw new Error(`Plugin not found: ${binding.pageContractPath.pluginName}`);
|
|
1070
|
-
}
|
|
1071
|
-
contract = findPluginContract(plugin, binding.pageContractPath.componentName);
|
|
1072
|
-
if (!contract) {
|
|
1073
|
-
throw new Error(
|
|
1074
|
-
`Contract not found in plugin ${binding.pageContractPath.pluginName}: ${binding.pageContractPath.componentName}`
|
|
1075
|
-
);
|
|
1076
|
-
}
|
|
1077
|
-
const usedComponent = context.projectPage.usedComponents?.find(
|
|
1078
|
-
(c) => c.componentName === binding.pageContractPath.componentName
|
|
1079
|
-
);
|
|
1080
|
-
if (!usedComponent) {
|
|
1081
|
-
throw new Error(
|
|
1082
|
-
`Used component not found in page: ${binding.pageContractPath.componentName}`
|
|
1083
|
-
);
|
|
1084
|
-
}
|
|
1085
|
-
key = usedComponent.key;
|
|
1086
|
-
tagPathWithoutKey = binding.tagPath.slice(1);
|
|
1087
|
-
} else {
|
|
1088
|
-
contract = findPageContract(context.projectPage);
|
|
1089
|
-
if (!contract) {
|
|
1090
|
-
throw new Error(`Page contract not found for page ${context.projectPage.url}`);
|
|
1091
|
-
}
|
|
1092
|
-
tagPathWithoutKey = binding.tagPath;
|
|
1093
|
-
}
|
|
1094
|
-
const contractTag = findContractTag(contract.tags, tagPathWithoutKey);
|
|
1095
|
-
if (!contractTag) {
|
|
1096
|
-
throw new Error(`Contract tag not found: ${tagPathWithoutKey.join(".")} in contract`);
|
|
1097
|
-
}
|
|
1098
|
-
let fullPath;
|
|
1099
|
-
if (key) {
|
|
1100
|
-
fullPath = [key, ...tagPathWithoutKey].join(".");
|
|
1101
|
-
} else {
|
|
1102
|
-
fullPath = binding.tagPath.join(".");
|
|
1103
|
-
}
|
|
1104
|
-
fullPath = applyRepeaterContext(fullPath, context.repeaterPathStack);
|
|
1105
|
-
return { fullPath, contractTag };
|
|
1106
|
-
}
|
|
1107
|
-
function getBindingsData(node) {
|
|
1108
|
-
const bindingsDataRaw = node.pluginData?.["jay-layer-bindings"];
|
|
1109
|
-
if (bindingsDataRaw) {
|
|
1110
|
-
try {
|
|
1111
|
-
return JSON.parse(bindingsDataRaw);
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
console.warn(`Failed to parse bindings data for node ${node.name}:`, error);
|
|
1114
|
-
return [];
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
return [];
|
|
1118
|
-
}
|
|
1119
|
-
function analyzeBindings(bindings, context) {
|
|
1120
|
-
const analysis = {
|
|
1121
|
-
type: "none",
|
|
1122
|
-
attributes: /* @__PURE__ */ new Map(),
|
|
1123
|
-
propertyBindings: [],
|
|
1124
|
-
isRepeater: false
|
|
1125
|
-
};
|
|
1126
|
-
if (bindings.length === 0) {
|
|
1127
|
-
return analysis;
|
|
1128
|
-
}
|
|
1129
|
-
const resolved = bindings.map((b) => ({
|
|
1130
|
-
binding: b,
|
|
1131
|
-
...resolveBinding(b, context)
|
|
1132
|
-
})).filter((r) => r.fullPath && r.contractTag);
|
|
1133
|
-
if (resolved.length === 0) {
|
|
1134
|
-
return analysis;
|
|
1135
|
-
}
|
|
1136
|
-
const repeaterBinding = resolved.find((r) => isRepeaterTag(r.contractTag));
|
|
1137
|
-
if (repeaterBinding) {
|
|
1138
|
-
analysis.type = "repeater";
|
|
1139
|
-
analysis.isRepeater = true;
|
|
1140
|
-
analysis.repeaterPath = repeaterBinding.fullPath;
|
|
1141
|
-
analysis.repeaterTag = repeaterBinding.contractTag;
|
|
1142
|
-
analysis.trackByKey = repeaterBinding.contractTag.trackBy || "id";
|
|
1143
|
-
return analysis;
|
|
1144
|
-
}
|
|
1145
|
-
const propertyBindings = resolved.filter((r) => r.binding.property);
|
|
1146
|
-
if (propertyBindings.length > 0) {
|
|
1147
|
-
if (propertyBindings.length !== resolved.length) {
|
|
1148
|
-
throw new Error(`Node has mixed property and non-property bindings - this is invalid`);
|
|
1149
|
-
}
|
|
1150
|
-
analysis.type = "property-variant";
|
|
1151
|
-
analysis.propertyBindings = propertyBindings.map((r) => ({
|
|
1152
|
-
property: r.binding.property,
|
|
1153
|
-
tagPath: r.fullPath,
|
|
1154
|
-
contractTag: r.contractTag
|
|
1155
|
-
}));
|
|
1156
|
-
for (const r of propertyBindings) {
|
|
1157
|
-
if (isInteractiveTag(r.contractTag)) {
|
|
1158
|
-
analysis.interactiveVariantPath = r.fullPath;
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
return analysis;
|
|
1163
|
-
}
|
|
1164
|
-
const attributeBindings = resolved.filter((r) => r.binding.attribute);
|
|
1165
|
-
if (attributeBindings.length > 0) {
|
|
1166
|
-
analysis.type = "attribute";
|
|
1167
|
-
for (const r of attributeBindings) {
|
|
1168
|
-
analysis.attributes.set(r.binding.attribute, r.fullPath);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
const contentBindings = resolved.filter((r) => !r.binding.attribute && !r.binding.property);
|
|
1172
|
-
if (contentBindings.length > 0) {
|
|
1173
|
-
const binding = contentBindings[0];
|
|
1174
|
-
if (isDualTag(binding.contractTag)) {
|
|
1175
|
-
analysis.type = "dual";
|
|
1176
|
-
analysis.dualPath = binding.fullPath;
|
|
1177
|
-
} else if (isInteractiveTag(binding.contractTag)) {
|
|
1178
|
-
analysis.type = "interactive";
|
|
1179
|
-
analysis.refPath = binding.fullPath;
|
|
1180
|
-
} else if (isDataTag(binding.contractTag)) {
|
|
1181
|
-
if (analysis.type === "attribute") {
|
|
1182
|
-
analysis.dynamicContentPath = binding.fullPath;
|
|
1183
|
-
analysis.dynamicContentTag = binding.contractTag;
|
|
1184
|
-
} else {
|
|
1185
|
-
analysis.type = "dynamic-content";
|
|
1186
|
-
analysis.dynamicContentPath = binding.fullPath;
|
|
1187
|
-
analysis.dynamicContentTag = binding.contractTag;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
return analysis;
|
|
1192
|
-
}
|
|
1193
|
-
function validateBindings(analysis, node) {
|
|
1194
|
-
if (analysis.type === "property-variant" && analysis.attributes.size > 0) {
|
|
1195
|
-
throw new Error(
|
|
1196
|
-
`Node "${node.name}" has both property and attribute bindings - this is invalid`
|
|
1197
|
-
);
|
|
1198
|
-
}
|
|
1199
|
-
if (analysis.type === "interactive" && analysis.attributes.size > 0) {
|
|
1200
|
-
throw new Error(
|
|
1201
|
-
`Node "${node.name}" has interactive binding with attributes - this is invalid`
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
function convertRegularNode(node, analysis, context) {
|
|
1206
|
-
const indent = " ".repeat(context.indentLevel);
|
|
1207
|
-
const { type, children, pluginData } = node;
|
|
1208
|
-
const semanticHtml = pluginData?.["semanticHtml"];
|
|
1209
|
-
if (type === "TEXT") {
|
|
1210
|
-
const dynamicContent = analysis.dynamicContentPath ? `{${analysis.dynamicContentPath}}` : "";
|
|
1211
|
-
const refAttr = analysis.refPath ? ` ref="${analysis.refPath}"` : "";
|
|
1212
|
-
const dualContent = analysis.dualPath ? `{${analysis.dualPath}}` : "";
|
|
1213
|
-
const dualRef = analysis.dualPath ? ` ref="${analysis.dualPath}"` : "";
|
|
1214
|
-
let attributesHtml = "";
|
|
1215
|
-
for (const [attr, tagPath] of analysis.attributes) {
|
|
1216
|
-
attributesHtml += ` ${attr}="{${tagPath}}"`;
|
|
1217
|
-
}
|
|
1218
|
-
return convertTextNodeToHtml(
|
|
1219
|
-
node,
|
|
1220
|
-
indent,
|
|
1221
|
-
dynamicContent || dualContent,
|
|
1222
|
-
refAttr || dualRef,
|
|
1223
|
-
attributesHtml
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
if (semanticHtml === "img") {
|
|
1227
|
-
let srcBinding;
|
|
1228
|
-
let altBinding;
|
|
1229
|
-
for (const [attr, tagPath] of analysis.attributes) {
|
|
1230
|
-
if (attr === "src") {
|
|
1231
|
-
srcBinding = `{${tagPath}}`;
|
|
1232
|
-
} else if (attr === "alt") {
|
|
1233
|
-
altBinding = `{${tagPath}}`;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
let staticImageUrl;
|
|
1237
|
-
if (!srcBinding) {
|
|
1238
|
-
staticImageUrl = extractStaticImageUrl(node);
|
|
1239
|
-
}
|
|
1240
|
-
const refAttr = analysis.refPath ? ` ref="${analysis.refPath}"` : analysis.dualPath ? ` ref="${analysis.dualPath}"` : "";
|
|
1241
|
-
return convertImageNodeToHtml(
|
|
1242
|
-
node,
|
|
1243
|
-
indent,
|
|
1244
|
-
srcBinding,
|
|
1245
|
-
altBinding,
|
|
1246
|
-
refAttr,
|
|
1247
|
-
staticImageUrl
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
const positionStyle = getPositionStyle(node);
|
|
1251
|
-
const sizeStyles = getNodeSizeStyles(node);
|
|
1252
|
-
const commonStyles = getCommonStyles(node);
|
|
1253
|
-
let styleAttr = "";
|
|
1254
|
-
if (type === "FRAME") {
|
|
1255
|
-
const backgroundStyle = getBackgroundFillsStyle(node);
|
|
1256
|
-
const borderRadius = getBorderRadius(node);
|
|
1257
|
-
const strokeStyles = getStrokeStyles(node);
|
|
1258
|
-
const flexStyles = getAutoLayoutStyles(node);
|
|
1259
|
-
const overflowStyles = getOverflowStyles(node);
|
|
1260
|
-
const frameSizeStyles = getFrameSizeStyles(node);
|
|
1261
|
-
styleAttr = `${positionStyle}${frameSizeStyles}${backgroundStyle}${strokeStyles}${borderRadius}${overflowStyles}${commonStyles}${flexStyles}box-sizing: border-box;`;
|
|
1262
|
-
} else {
|
|
1263
|
-
styleAttr = `${positionStyle}${sizeStyles}${commonStyles}`;
|
|
1264
|
-
}
|
|
1265
|
-
const tag = semanticHtml || "div";
|
|
1266
|
-
let htmlAttrs = `data-figma-id="${node.id}" data-figma-type="${type.toLowerCase()}" style="${styleAttr}"`;
|
|
1267
|
-
if (analysis.refPath) {
|
|
1268
|
-
htmlAttrs += ` ref="${analysis.refPath}"`;
|
|
1269
|
-
} else if (analysis.dualPath) {
|
|
1270
|
-
htmlAttrs += ` ref="${analysis.dualPath}"`;
|
|
1271
|
-
}
|
|
1272
|
-
for (const [attr, tagPath] of analysis.attributes) {
|
|
1273
|
-
htmlAttrs += ` ${attr}="{${tagPath}}"`;
|
|
1274
|
-
}
|
|
1275
|
-
if (type === "RECTANGLE") {
|
|
1276
|
-
return convertRectangleToHtml(node, indent);
|
|
1277
|
-
} else if (type === "ELLIPSE") {
|
|
1278
|
-
return convertEllipseToHtml(node, indent);
|
|
1279
|
-
} else if (type === "GROUP") {
|
|
1280
|
-
return convertGroupNode(node, analysis, context, convertNodeToJayHtml);
|
|
1281
|
-
} else if (type === "VECTOR" || type === "STAR" || type === "POLYGON" || type === "LINE" || type === "BOOLEAN_OPERATION") {
|
|
1282
|
-
return convertVectorToHtml(node, indent);
|
|
1283
|
-
} else if (children && children.length > 0) {
|
|
1284
|
-
let html = `${indent}<${tag} ${htmlAttrs}>
|
|
1285
|
-
`;
|
|
1286
|
-
html += `${indent} <!-- ${node.name} -->
|
|
1287
|
-
`;
|
|
1288
|
-
const childContext = {
|
|
1289
|
-
...context,
|
|
1290
|
-
indentLevel: context.indentLevel + 1
|
|
1291
|
-
};
|
|
1292
|
-
for (const child of children) {
|
|
1293
|
-
html += convertNodeToJayHtml(child, childContext);
|
|
1294
|
-
}
|
|
1295
|
-
html += `${indent}</${tag}>
|
|
1296
|
-
`;
|
|
1297
|
-
return html;
|
|
1298
|
-
} else {
|
|
1299
|
-
return `${indent}<!-- ${node.name} (${type}) -->
|
|
1300
|
-
`;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
function convertNodeToJayHtml(node, context) {
|
|
1304
|
-
const { name, type, children, pluginData } = node;
|
|
1305
|
-
const isJPage = pluginData?.["jpage"] === "true";
|
|
1306
|
-
const urlRoute = pluginData?.["urlRoute"];
|
|
1307
|
-
if (type === "TEXT" && node.fontName) {
|
|
1308
|
-
if (typeof node.fontName === "object" && node.fontName.family) {
|
|
1309
|
-
context.fontFamilies.add(node.fontName.family);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
const indent = " ".repeat(context.indentLevel);
|
|
1313
|
-
if (type === "SECTION" && isJPage) {
|
|
1314
|
-
let html = `${indent}<section data-figma-id="${node.id}" data-page-url="${urlRoute || ""}">
|
|
1315
|
-
`;
|
|
1316
|
-
html += `${indent} <!-- Jay Page: ${name} -->
|
|
1317
|
-
`;
|
|
1318
|
-
if (children && children.length > 0) {
|
|
1319
|
-
const childContext = {
|
|
1320
|
-
...context,
|
|
1321
|
-
indentLevel: context.indentLevel + 1
|
|
1322
|
-
};
|
|
1323
|
-
for (const child of children) {
|
|
1324
|
-
html += convertNodeToJayHtml(child, childContext);
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
html += `${indent}</section>
|
|
1328
|
-
`;
|
|
1329
|
-
return html;
|
|
1330
|
-
}
|
|
1331
|
-
const bindings = getBindingsData(node);
|
|
1332
|
-
const analysis = analyzeBindings(bindings, context);
|
|
1333
|
-
validateBindings(analysis, node);
|
|
1334
|
-
if (analysis.isRepeater) {
|
|
1335
|
-
return convertRepeaterNode(node, analysis, context, convertNodeToJayHtml);
|
|
1336
|
-
}
|
|
1337
|
-
if (analysis.type === "property-variant") {
|
|
1338
|
-
return convertVariantNode(node, analysis, context, convertNodeToJayHtml);
|
|
1339
|
-
}
|
|
1340
|
-
return convertRegularNode(node, analysis, context);
|
|
1341
|
-
}
|
|
1342
|
-
function findContentFrame(section) {
|
|
1343
|
-
if (!section.children || section.children.length === 0) {
|
|
1344
|
-
return {
|
|
1345
|
-
frame: null,
|
|
1346
|
-
error: `Jay Page section "${section.name}" has no children`
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
const frameNodes = section.children.filter((child) => child.type === "FRAME");
|
|
1350
|
-
if (frameNodes.length === 0) {
|
|
1351
|
-
return {
|
|
1352
|
-
frame: null,
|
|
1353
|
-
error: `Jay Page section "${section.name}" has no FrameNode children. Found: ${section.children.map((c) => c.type).join(", ")}`
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
if (frameNodes.length > 1) {
|
|
1357
|
-
return {
|
|
1358
|
-
frame: frameNodes[0],
|
|
1359
|
-
warning: `Jay Page section "${section.name}" has ${frameNodes.length} FrameNodes, using the first one`
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
return { frame: frameNodes[0] };
|
|
1363
|
-
}
|
|
1364
|
-
const figmaVendor = {
|
|
1365
|
-
vendorId: "figma",
|
|
1366
|
-
async convertToBodyHtml(vendorDoc, pageUrl, projectPage, plugins) {
|
|
1367
|
-
console.log(`🎨 Converting Figma document for page: ${pageUrl}`);
|
|
1368
|
-
console.log(` Document type: ${vendorDoc.type}, name: ${vendorDoc.name}`);
|
|
1369
|
-
const isJPage = vendorDoc.pluginData?.["jpage"] === "true";
|
|
1370
|
-
if (!isJPage) {
|
|
1371
|
-
throw new Error(
|
|
1372
|
-
`Document "${vendorDoc.name}" is not marked as a Jay Page (missing jpage='true' in pluginData)`
|
|
1373
|
-
);
|
|
1374
|
-
}
|
|
1375
|
-
const { frame, error, warning } = findContentFrame(vendorDoc);
|
|
1376
|
-
if (error) {
|
|
1377
|
-
throw new Error(`Cannot convert to Jay HTML: ${error}`);
|
|
1378
|
-
}
|
|
1379
|
-
if (warning) {
|
|
1380
|
-
console.warn(`⚠️ ${warning}`);
|
|
1381
|
-
}
|
|
1382
|
-
if (!frame) {
|
|
1383
|
-
throw new Error(`Cannot convert to Jay HTML: No content frame found`);
|
|
1384
|
-
}
|
|
1385
|
-
console.log(` Converting content frame: ${frame.name} (${frame.type})`);
|
|
1386
|
-
const fontFamilies = /* @__PURE__ */ new Set();
|
|
1387
|
-
const context = {
|
|
1388
|
-
repeaterPathStack: [],
|
|
1389
|
-
indentLevel: 1,
|
|
1390
|
-
// Start at 1 for body content
|
|
1391
|
-
fontFamilies,
|
|
1392
|
-
projectPage,
|
|
1393
|
-
plugins
|
|
1394
|
-
};
|
|
1395
|
-
const bodyHtml = convertNodeToJayHtml(frame, context);
|
|
1396
|
-
if (fontFamilies.size > 0) {
|
|
1397
|
-
console.log(
|
|
1398
|
-
` Found ${fontFamilies.size} font families: ${Array.from(fontFamilies).join(", ")}`
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1401
|
-
return {
|
|
1402
|
-
bodyHtml,
|
|
1403
|
-
fontFamilies,
|
|
1404
|
-
// No contract data for now - Figma vendor doesn't generate contracts yet
|
|
1405
|
-
contractData: void 0
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
};
|
|
1409
|
-
const vendorRegistry = /* @__PURE__ */ new Map([
|
|
1410
|
-
[figmaVendor.vendorId, figmaVendor]
|
|
1411
|
-
// Add more vendors here as they are contributed
|
|
1412
|
-
]);
|
|
1413
|
-
function getVendor(vendorId) {
|
|
1414
|
-
return vendorRegistry.get(vendorId);
|
|
1415
|
-
}
|
|
1416
|
-
function hasVendor(vendorId) {
|
|
1417
|
-
return vendorRegistry.has(vendorId);
|
|
1418
|
-
}
|
|
1419
|
-
function getRegisteredVendors() {
|
|
1420
|
-
return Array.from(vendorRegistry.keys());
|
|
1421
|
-
}
|
|
1422
|
-
function escapeHtml(text) {
|
|
1423
|
-
if (!text)
|
|
1424
|
-
return "";
|
|
1425
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1426
|
-
}
|
|
1427
|
-
function generateGoogleFontsLinks(fontFamilies) {
|
|
1428
|
-
if (fontFamilies.size === 0) {
|
|
1429
|
-
return "";
|
|
1430
|
-
}
|
|
1431
|
-
const families = Array.from(fontFamilies);
|
|
1432
|
-
const googleFontsUrl = `https://fonts.googleapis.com/css2?${families.map((family) => {
|
|
1433
|
-
const encodedFamily = encodeURIComponent(family).replace(/%20/g, "+");
|
|
1434
|
-
return `family=${encodedFamily}:wght@100;200;300;400;500;600;700;800;900`;
|
|
1435
|
-
}).join("&")}&display=swap`;
|
|
1436
|
-
return ` <link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1437
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1438
|
-
<link href="${googleFontsUrl}" rel="stylesheet">`;
|
|
1439
|
-
}
|
|
1440
|
-
function generateHeadlessComponentScripts(components) {
|
|
1441
|
-
if (components.length === 0) {
|
|
1442
|
-
return "";
|
|
1443
|
-
}
|
|
1444
|
-
const scriptTags = components.map(
|
|
1445
|
-
(comp) => ` <script
|
|
1446
|
-
type="application/jay-headless"
|
|
1447
|
-
plugin="${comp.plugin}"
|
|
1448
|
-
contract="${comp.contract}"
|
|
1449
|
-
key="${comp.key}"
|
|
1450
|
-
><\/script>`
|
|
1451
|
-
);
|
|
1452
|
-
return "\n" + scriptTags.join("\n");
|
|
1453
|
-
}
|
|
1454
|
-
function generateJayDataScript(contractData) {
|
|
1455
|
-
if (contractData) {
|
|
1456
|
-
return ` <script type="application/jay-data">
|
|
1457
|
-
data:
|
|
1458
|
-
${contractData.tagsYaml}
|
|
1459
|
-
<\/script>`;
|
|
1460
|
-
}
|
|
1461
|
-
return ` <script type="application/jay-data">
|
|
1462
|
-
data:
|
|
1463
|
-
<\/script>`;
|
|
1464
|
-
}
|
|
1465
|
-
function buildJayHtml(options) {
|
|
1466
|
-
const {
|
|
1467
|
-
bodyHtml,
|
|
1468
|
-
fontFamilies,
|
|
1469
|
-
contractData,
|
|
1470
|
-
headlessComponents = [],
|
|
1471
|
-
title = "Page"
|
|
1472
|
-
} = options;
|
|
1473
|
-
const fontLinks = generateGoogleFontsLinks(fontFamilies);
|
|
1474
|
-
const headlessScripts = generateHeadlessComponentScripts(headlessComponents);
|
|
1475
|
-
const jayDataScript = generateJayDataScript(contractData);
|
|
1476
|
-
return `<!DOCTYPE html>
|
|
1477
|
-
<html lang="en">
|
|
1478
|
-
<head>
|
|
1479
|
-
<meta charset="UTF-8">
|
|
1480
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1481
|
-
${fontLinks}${headlessScripts}
|
|
1482
|
-
${jayDataScript}
|
|
1483
|
-
<title>${escapeHtml(title)}</title>
|
|
1484
|
-
<style>
|
|
1485
|
-
/* Basic reset */
|
|
1486
|
-
body { margin: 0; font-family: sans-serif; }
|
|
1487
|
-
a { color: inherit; text-decoration: none; }
|
|
1488
|
-
a:hover { text-decoration: underline; }
|
|
1489
|
-
div { box-sizing: border-box; }
|
|
1490
|
-
|
|
1491
|
-
/* Scrollbar styling for Webkit browsers */
|
|
1492
|
-
::-webkit-scrollbar {
|
|
1493
|
-
width: 8px;
|
|
1494
|
-
height: 8px;
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
::-webkit-scrollbar-track {
|
|
1498
|
-
background: transparent;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
::-webkit-scrollbar-thumb {
|
|
1502
|
-
background: rgba(0, 0, 0, 0.3);
|
|
1503
|
-
border-radius: 4px;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
::-webkit-scrollbar-thumb:hover {
|
|
1507
|
-
background: rgba(0, 0, 0, 0.5);
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
/* Smooth scrolling */
|
|
1511
|
-
* {
|
|
1512
|
-
scroll-behavior: smooth;
|
|
1513
|
-
}
|
|
1514
|
-
</style>
|
|
1515
|
-
</head>
|
|
1516
|
-
<body>
|
|
1517
|
-
${bodyHtml}
|
|
1518
|
-
</body>
|
|
1519
|
-
</html>`;
|
|
1520
|
-
}
|
|
1521
|
-
async function buildJayHtmlFromVendorResult(conversionResult, pageDirectory, pageTitle) {
|
|
1522
|
-
const pageConfigPath = path.join(pageDirectory, "page.conf.yaml");
|
|
1523
|
-
const headlessComponents = [];
|
|
1524
|
-
if (fs.existsSync(pageConfigPath)) {
|
|
1525
|
-
try {
|
|
1526
|
-
const configContent = await fs.promises.readFile(pageConfigPath, "utf-8");
|
|
1527
|
-
const pageConfig = YAML.parse(configContent);
|
|
1528
|
-
if (pageConfig.used_components && Array.isArray(pageConfig.used_components)) {
|
|
1529
|
-
for (const comp of pageConfig.used_components) {
|
|
1530
|
-
if (comp.plugin && comp.contract && comp.key) {
|
|
1531
|
-
headlessComponents.push({
|
|
1532
|
-
plugin: comp.plugin,
|
|
1533
|
-
contract: comp.contract,
|
|
1534
|
-
key: comp.key
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
} catch (configError) {
|
|
1540
|
-
console.warn(`Failed to read page config ${pageConfigPath}:`, configError);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
const title = pageTitle || path.basename(pageDirectory);
|
|
1544
|
-
return buildJayHtml({
|
|
1545
|
-
bodyHtml: conversionResult.bodyHtml,
|
|
1546
|
-
fontFamilies: conversionResult.fontFamilies,
|
|
1547
|
-
contractData: conversionResult.contractData,
|
|
1548
|
-
headlessComponents,
|
|
1549
|
-
title
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
const PAGE_FILENAME = `page${JAY_EXTENSION}`;
|
|
1553
|
-
const PAGE_CONTRACT_FILENAME = `page${JAY_CONTRACT_EXTENSION}`;
|
|
1554
|
-
const PAGE_CONFIG_FILENAME = "page.conf.yaml";
|
|
1555
|
-
function pageUrlToDirectoryPath(pageUrl, pagesBasePath) {
|
|
1556
|
-
const routePath = pageUrl === "/" ? "" : pageUrl;
|
|
1557
|
-
const fsPath = routePath.replace(/:([^/]+)/g, "[$1]");
|
|
1558
|
-
return path.join(pagesBasePath, fsPath);
|
|
1559
|
-
}
|
|
1560
|
-
function jayTypeToString(jayType) {
|
|
1561
|
-
if (!jayType)
|
|
1562
|
-
return void 0;
|
|
1563
|
-
if (jayType instanceof JayAtomicType) {
|
|
1564
|
-
return jayType.name;
|
|
1565
|
-
} else if (jayType instanceof JayEnumType) {
|
|
1566
|
-
return `enum (${jayType.values.join(" | ")})`;
|
|
1567
|
-
} else {
|
|
1568
|
-
return jayType.name || "unknown";
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
function convertContractTagToProtocol(tag) {
|
|
1572
|
-
const typeArray = Array.isArray(tag.type) ? tag.type : [tag.type];
|
|
1573
|
-
const typeStrings = typeArray.map((t) => ContractTagType[t]);
|
|
1574
|
-
return {
|
|
1575
|
-
tag: tag.tag,
|
|
1576
|
-
type: typeStrings.length === 1 ? typeStrings[0] : typeStrings,
|
|
1577
|
-
dataType: tag.dataType ? jayTypeToString(tag.dataType) : void 0,
|
|
1578
|
-
elementType: tag.elementType ? tag.elementType.join(" | ") : void 0,
|
|
1579
|
-
required: tag.required,
|
|
1580
|
-
repeated: tag.repeated,
|
|
1581
|
-
trackBy: tag.trackBy,
|
|
1582
|
-
async: tag.async,
|
|
1583
|
-
phase: tag.phase,
|
|
1584
|
-
link: tag.link,
|
|
1585
|
-
tags: tag.tags ? tag.tags.map(convertContractTagToProtocol) : void 0
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
function convertContractToProtocol(contract) {
|
|
1589
|
-
return {
|
|
1590
|
-
name: contract.name,
|
|
1591
|
-
tags: contract.tags.map(convertContractTagToProtocol)
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
async function isPageDirectory(dirPath) {
|
|
1595
|
-
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1596
|
-
const hasPageHtml = entries.some((e2) => e2.name === PAGE_FILENAME);
|
|
1597
|
-
const hasPageContract = entries.some((e2) => e2.name === PAGE_CONTRACT_FILENAME);
|
|
1598
|
-
const hasPageConfig = entries.some((e2) => e2.name === PAGE_CONFIG_FILENAME);
|
|
1599
|
-
const isPage = hasPageHtml || hasPageContract || hasPageConfig;
|
|
1600
|
-
return { isPage, hasPageHtml, hasPageContract, hasPageConfig };
|
|
1601
|
-
}
|
|
1602
|
-
async function scanPageDirectories(pagesBasePath, onPageFound) {
|
|
1603
|
-
async function scanDirectory(dirPath, urlPath = "") {
|
|
1604
|
-
try {
|
|
1605
|
-
const { isPage, hasPageHtml, hasPageContract, hasPageConfig } = await isPageDirectory(dirPath);
|
|
1606
|
-
if (isPage) {
|
|
1607
|
-
const pageUrl = urlPath || "/";
|
|
1608
|
-
const pageName = dirPath === pagesBasePath ? "Home" : path.basename(dirPath);
|
|
1609
|
-
await onPageFound({
|
|
1610
|
-
dirPath,
|
|
1611
|
-
pageUrl,
|
|
1612
|
-
pageName,
|
|
1613
|
-
hasPageHtml,
|
|
1614
|
-
hasPageContract,
|
|
1615
|
-
hasPageConfig
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1619
|
-
for (const entry of entries) {
|
|
1620
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
1621
|
-
if (entry.isDirectory()) {
|
|
1622
|
-
const isParam = entry.name.startsWith("[") && entry.name.endsWith("]");
|
|
1623
|
-
const segmentUrl = isParam ? `:${entry.name.slice(1, -1)}` : entry.name;
|
|
1624
|
-
const newUrlPath = urlPath + "/" + segmentUrl;
|
|
1625
|
-
await scanDirectory(fullPath, newUrlPath);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
} catch (error) {
|
|
1629
|
-
getLogger().warn(`Failed to scan directory ${dirPath}:`, error);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
await scanDirectory(pagesBasePath);
|
|
1633
|
-
}
|
|
1634
|
-
function expandContractTags(tags, baseDir) {
|
|
1635
|
-
const resolvedTags = [];
|
|
1636
|
-
for (const tag of tags) {
|
|
1637
|
-
if (tag.link) {
|
|
1638
|
-
try {
|
|
1639
|
-
const linkWithExtension = tag.link.endsWith(JAY_CONTRACT_EXTENSION) ? tag.link : tag.link + JAY_CONTRACT_EXTENSION;
|
|
1640
|
-
const linkedPath = JAY_IMPORT_RESOLVER.resolveLink(baseDir, linkWithExtension);
|
|
1641
|
-
const loadResult = JAY_IMPORT_RESOLVER.loadContract(linkedPath);
|
|
1642
|
-
if (loadResult.val) {
|
|
1643
|
-
const expandedSubTags = expandContractTags(
|
|
1644
|
-
loadResult.val.tags,
|
|
1645
|
-
path.dirname(linkedPath)
|
|
1646
|
-
);
|
|
1647
|
-
const resolvedTag = {
|
|
1648
|
-
tag: tag.tag,
|
|
1649
|
-
type: tag.type,
|
|
1650
|
-
// Keep the original enum type
|
|
1651
|
-
tags: expandedSubTags,
|
|
1652
|
-
required: tag.required,
|
|
1653
|
-
repeated: tag.repeated,
|
|
1654
|
-
trackBy: tag.trackBy,
|
|
1655
|
-
async: tag.async,
|
|
1656
|
-
phase: tag.phase,
|
|
1657
|
-
link: tag.link
|
|
1658
|
-
};
|
|
1659
|
-
resolvedTags.push(resolvedTag);
|
|
1660
|
-
} else {
|
|
1661
|
-
getLogger().warn(`Failed to load linked contract: ${tag.link} from ${baseDir}`);
|
|
1662
|
-
resolvedTags.push(tag);
|
|
1663
|
-
}
|
|
1664
|
-
} catch (error) {
|
|
1665
|
-
getLogger().warn(`Error resolving linked contract ${tag.link}:`, error);
|
|
1666
|
-
resolvedTags.push(tag);
|
|
1667
|
-
}
|
|
1668
|
-
} else if (tag.tags) {
|
|
1669
|
-
const resolvedSubTags = expandContractTags(tag.tags, baseDir);
|
|
1670
|
-
const resolvedTag = {
|
|
1671
|
-
...tag,
|
|
1672
|
-
tags: resolvedSubTags
|
|
1673
|
-
};
|
|
1674
|
-
resolvedTags.push(resolvedTag);
|
|
1675
|
-
} else {
|
|
1676
|
-
resolvedTags.push(tag);
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
return resolvedTags;
|
|
1680
|
-
}
|
|
1681
|
-
function loadAndExpandContract(contractFilePath) {
|
|
1682
|
-
try {
|
|
1683
|
-
const loadResult = JAY_IMPORT_RESOLVER.loadContract(contractFilePath);
|
|
1684
|
-
if (loadResult.validations.length > 0) {
|
|
1685
|
-
getLogger().warn(
|
|
1686
|
-
`Contract validation errors in ${contractFilePath}:`,
|
|
1687
|
-
loadResult.validations
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
if (loadResult.val) {
|
|
1691
|
-
const resolvedTags = expandContractTags(
|
|
1692
|
-
loadResult.val.tags,
|
|
1693
|
-
path.dirname(contractFilePath)
|
|
1694
|
-
);
|
|
1695
|
-
return convertContractToProtocol({
|
|
1696
|
-
name: loadResult.val.name,
|
|
1697
|
-
tags: resolvedTags
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
} catch (error) {
|
|
1701
|
-
getLogger().warn(`Failed to parse contract file ${contractFilePath}:`, error);
|
|
1702
|
-
}
|
|
1703
|
-
return null;
|
|
1704
|
-
}
|
|
1705
|
-
async function extractHeadlessComponentsFromJayHtml(jayHtmlContent, pageFilePath, projectRootPath) {
|
|
1706
|
-
try {
|
|
1707
|
-
const parsedJayHtml = await parseJayFile(
|
|
1708
|
-
jayHtmlContent,
|
|
1709
|
-
path.basename(pageFilePath),
|
|
1710
|
-
path.dirname(pageFilePath),
|
|
1711
|
-
{ relativePath: "" },
|
|
1712
|
-
// We don't need TypeScript config for headless extraction
|
|
1713
|
-
JAY_IMPORT_RESOLVER,
|
|
1714
|
-
projectRootPath
|
|
1715
|
-
);
|
|
1716
|
-
if (parsedJayHtml.validations.length > 0) {
|
|
1717
|
-
getLogger().warn(
|
|
1718
|
-
`Jay-HTML parsing warnings for ${pageFilePath}:`,
|
|
1719
|
-
parsedJayHtml.validations
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
if (!parsedJayHtml.val) {
|
|
1723
|
-
getLogger().warn(`Failed to parse jay-html file: ${pageFilePath}`);
|
|
1724
|
-
return [];
|
|
1725
|
-
}
|
|
1726
|
-
const resolvedComponents = [];
|
|
1727
|
-
for (const headlessImport of parsedJayHtml.val.headlessImports) {
|
|
1728
|
-
if (headlessImport.codeLink) {
|
|
1729
|
-
let pluginName = headlessImport.codeLink.module;
|
|
1730
|
-
const nodeModulesMatch = pluginName.match(/node_modules\/([^/]+)/);
|
|
1731
|
-
if (nodeModulesMatch) {
|
|
1732
|
-
pluginName = nodeModulesMatch[1];
|
|
1733
|
-
}
|
|
1734
|
-
const componentName = headlessImport.contract?.name || "unknown";
|
|
1735
|
-
resolvedComponents.push({
|
|
1736
|
-
appName: pluginName,
|
|
1737
|
-
componentName,
|
|
1738
|
-
key: headlessImport.key
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
return resolvedComponents;
|
|
1743
|
-
} catch (error) {
|
|
1744
|
-
getLogger().warn(`Failed to parse jay-html content for ${pageFilePath}:`, error);
|
|
1745
|
-
return [];
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
async function scanProjectComponents(componentsBasePath) {
|
|
1749
|
-
const components = [];
|
|
1750
|
-
try {
|
|
1751
|
-
const entries = await fs.promises.readdir(componentsBasePath, { withFileTypes: true });
|
|
1752
|
-
for (const entry of entries) {
|
|
1753
|
-
if (entry.isFile() && entry.name.endsWith(JAY_EXTENSION)) {
|
|
1754
|
-
const componentName = path.basename(entry.name, JAY_EXTENSION);
|
|
1755
|
-
const componentPath = path.join(componentsBasePath, entry.name);
|
|
1756
|
-
const contractPath = path.join(
|
|
1757
|
-
componentsBasePath,
|
|
1758
|
-
`${componentName}${JAY_CONTRACT_EXTENSION}`
|
|
1759
|
-
);
|
|
1760
|
-
const hasContract = fs.existsSync(contractPath);
|
|
1761
|
-
components.push({
|
|
1762
|
-
name: componentName,
|
|
1763
|
-
filePath: componentPath,
|
|
1764
|
-
contractPath: hasContract ? contractPath : void 0
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
} catch (error) {
|
|
1769
|
-
getLogger().warn(`Failed to scan components directory ${componentsBasePath}:`, error);
|
|
1770
|
-
}
|
|
1771
|
-
return components;
|
|
1772
|
-
}
|
|
1773
|
-
async function getProjectName(configBasePath) {
|
|
1774
|
-
const projectConfigPath = path.join(configBasePath, "project.conf.yaml");
|
|
1775
|
-
try {
|
|
1776
|
-
if (fs.existsSync(projectConfigPath)) {
|
|
1777
|
-
const configContent = await fs.promises.readFile(projectConfigPath, "utf-8");
|
|
1778
|
-
const projectConfig = YAML.parse(configContent);
|
|
1779
|
-
return projectConfig.name || "Unnamed Project";
|
|
1780
|
-
}
|
|
1781
|
-
} catch (error) {
|
|
1782
|
-
getLogger().warn(`Failed to read project config ${projectConfigPath}:`, error);
|
|
1783
|
-
}
|
|
1784
|
-
return "Unnamed Project";
|
|
1785
|
-
}
|
|
1786
|
-
async function scanLocalPluginNames(projectRoot) {
|
|
1787
|
-
const plugins = [];
|
|
1788
|
-
const localPluginsDir = path.join(projectRoot, LOCAL_PLUGIN_PATH);
|
|
1789
|
-
if (!fs.existsSync(localPluginsDir)) {
|
|
1790
|
-
return plugins;
|
|
1791
|
-
}
|
|
1792
|
-
try {
|
|
1793
|
-
const entries = await fs.promises.readdir(localPluginsDir, { withFileTypes: true });
|
|
1794
|
-
for (const entry of entries) {
|
|
1795
|
-
if (entry.isDirectory()) {
|
|
1796
|
-
const pluginDir = path.join(localPluginsDir, entry.name);
|
|
1797
|
-
const pluginYamlPath = path.join(pluginDir, "plugin.yaml");
|
|
1798
|
-
if (fs.existsSync(pluginYamlPath)) {
|
|
1799
|
-
plugins.push(entry.name);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
} catch (error) {
|
|
1804
|
-
getLogger().warn(`Failed to scan local plugins directory ${localPluginsDir}:`, error);
|
|
1805
|
-
}
|
|
1806
|
-
return plugins;
|
|
1807
|
-
}
|
|
1808
|
-
async function findPluginNamesFromPackageJson(projectRootPath) {
|
|
1809
|
-
const pluginNames = [];
|
|
1810
|
-
try {
|
|
1811
|
-
const packageJsonPath = path.join(projectRootPath, "package.json");
|
|
1812
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
1813
|
-
getLogger().warn("package.json not found");
|
|
1814
|
-
return pluginNames;
|
|
1815
|
-
}
|
|
1816
|
-
const packageJsonContent = await fs.promises.readFile(packageJsonPath, "utf-8");
|
|
1817
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
1818
|
-
const workspaceDependencies = /* @__PURE__ */ new Set();
|
|
1819
|
-
const regularDependencies = /* @__PURE__ */ new Set();
|
|
1820
|
-
for (const [depName, version] of Object.entries({
|
|
1821
|
-
...packageJson.dependencies
|
|
1822
|
-
})) {
|
|
1823
|
-
if (typeof version === "string" && version.startsWith("workspace:")) {
|
|
1824
|
-
workspaceDependencies.add(depName);
|
|
1825
|
-
} else {
|
|
1826
|
-
regularDependencies.add(depName);
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
const nodeModulesPath = path.join(projectRootPath, "node_modules");
|
|
1830
|
-
for (const depName of regularDependencies) {
|
|
1831
|
-
if (await checkPackageForPlugin(nodeModulesPath, depName)) {
|
|
1832
|
-
pluginNames.push(depName);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
if (workspaceDependencies.size > 0) {
|
|
1836
|
-
const workspaceNodeModules = await findWorkspaceNodeModulesPath(
|
|
1837
|
-
projectRootPath,
|
|
1838
|
-
Array.from(workspaceDependencies)
|
|
1839
|
-
);
|
|
1840
|
-
if (workspaceNodeModules) {
|
|
1841
|
-
for (const depName of workspaceDependencies) {
|
|
1842
|
-
if (await checkPackageForPlugin(workspaceNodeModules, depName)) {
|
|
1843
|
-
pluginNames.push(depName);
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
} catch (error) {
|
|
1849
|
-
getLogger().error("Error finding plugins from package.json:", error);
|
|
1850
|
-
}
|
|
1851
|
-
return pluginNames;
|
|
1852
|
-
}
|
|
1853
|
-
async function checkPackageForPlugin(nodeModulesDir, packageName) {
|
|
1854
|
-
try {
|
|
1855
|
-
const packageDir = path.join(nodeModulesDir, packageName);
|
|
1856
|
-
const pluginYamlPath = path.join(packageDir, "plugin.yaml");
|
|
1857
|
-
return fs.existsSync(pluginYamlPath);
|
|
1858
|
-
} catch (error) {
|
|
1859
|
-
return false;
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
async function findWorkspaceNodeModulesPath(startPath, workspaceDeps) {
|
|
1863
|
-
let currentPath = startPath;
|
|
1864
|
-
while (currentPath !== path.dirname(currentPath)) {
|
|
1865
|
-
const nodeModulesPath = path.join(currentPath, "node_modules");
|
|
1866
|
-
if (fs.existsSync(nodeModulesPath)) {
|
|
1867
|
-
for (const depName of workspaceDeps) {
|
|
1868
|
-
const depPath = path.join(nodeModulesPath, depName);
|
|
1869
|
-
if (fs.existsSync(depPath)) {
|
|
1870
|
-
return nodeModulesPath;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
currentPath = path.dirname(currentPath);
|
|
1875
|
-
}
|
|
1876
|
-
return null;
|
|
1877
|
-
}
|
|
1878
|
-
async function scanPlugins(projectRootPath) {
|
|
1879
|
-
const plugins = [];
|
|
1880
|
-
try {
|
|
1881
|
-
const [localPluginNames, dependencyPluginNames] = await Promise.all([
|
|
1882
|
-
scanLocalPluginNames(projectRootPath),
|
|
1883
|
-
findPluginNamesFromPackageJson(projectRootPath)
|
|
1884
|
-
]);
|
|
1885
|
-
const allPluginNames = [.../* @__PURE__ */ new Set([...localPluginNames, ...dependencyPluginNames])];
|
|
1886
|
-
getLogger().info(`Found ${allPluginNames.length} plugins: ${allPluginNames.join(", ")}`);
|
|
1887
|
-
for (const pluginName of allPluginNames) {
|
|
1888
|
-
const manifest = resolvePluginManifest(projectRootPath, pluginName);
|
|
1889
|
-
if (manifest.validations.length > 0) {
|
|
1890
|
-
getLogger().warn(
|
|
1891
|
-
`Failed to resolve plugin manifest for ${pluginName}:`,
|
|
1892
|
-
manifest.validations
|
|
1893
|
-
);
|
|
1894
|
-
continue;
|
|
1895
|
-
}
|
|
1896
|
-
if (!manifest.val) {
|
|
1897
|
-
getLogger().warn(
|
|
1898
|
-
`Failed to resolve plugin manifest for ${pluginName}:`,
|
|
1899
|
-
manifest.validations
|
|
1900
|
-
);
|
|
1901
|
-
continue;
|
|
1902
|
-
}
|
|
1903
|
-
const contracts = manifest.val.contracts;
|
|
1904
|
-
plugins.push({
|
|
1905
|
-
name: pluginName,
|
|
1906
|
-
contracts: contracts.map((contract) => {
|
|
1907
|
-
const resolveResult = JAY_IMPORT_RESOLVER.resolvePluginComponent(
|
|
1908
|
-
pluginName,
|
|
1909
|
-
contract.name,
|
|
1910
|
-
projectRootPath
|
|
1911
|
-
);
|
|
1912
|
-
if (resolveResult.validations.length > 0) {
|
|
1913
|
-
getLogger().warn(
|
|
1914
|
-
`Failed to resolve plugin component for ${pluginName}:${contract.name}:`,
|
|
1915
|
-
resolveResult.validations
|
|
1916
|
-
);
|
|
1917
|
-
return null;
|
|
1918
|
-
}
|
|
1919
|
-
if (!resolveResult.val) {
|
|
1920
|
-
getLogger().warn(
|
|
1921
|
-
`Failed to resolve plugin component for ${pluginName}:${contract.name}:`,
|
|
1922
|
-
resolveResult.validations
|
|
1923
|
-
);
|
|
1924
|
-
return null;
|
|
1925
|
-
}
|
|
1926
|
-
const expandedContract = loadAndExpandContract(resolveResult.val.contractPath);
|
|
1927
|
-
if (!expandedContract) {
|
|
1928
|
-
return null;
|
|
1929
|
-
}
|
|
1930
|
-
return expandedContract;
|
|
1931
|
-
})
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
|
-
} catch (error) {
|
|
1935
|
-
getLogger().error("Error scanning plugins:", error);
|
|
1936
|
-
}
|
|
1937
|
-
return plugins;
|
|
1938
|
-
}
|
|
1939
|
-
async function loadProjectPage(pageContext, plugins) {
|
|
1940
|
-
const { dirPath, pageUrl, pageName, hasPageHtml, hasPageContract, hasPageConfig } = pageContext;
|
|
1941
|
-
const pageFilePath = path.join(dirPath, PAGE_FILENAME);
|
|
1942
|
-
const pageConfigPath = path.join(dirPath, PAGE_CONFIG_FILENAME);
|
|
1943
|
-
const contractPath = path.join(dirPath, PAGE_CONTRACT_FILENAME);
|
|
1944
|
-
const projectRootPath = process.cwd();
|
|
1945
|
-
let usedComponents = [];
|
|
1946
|
-
let contract;
|
|
1947
|
-
if (hasPageContract) {
|
|
1948
|
-
const parsedContract = loadAndExpandContract(contractPath);
|
|
1949
|
-
if (parsedContract) {
|
|
1950
|
-
contract = parsedContract;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
if (hasPageHtml) {
|
|
1954
|
-
try {
|
|
1955
|
-
const jayHtmlContent = await fs.promises.readFile(pageFilePath, "utf-8");
|
|
1956
|
-
usedComponents = await extractHeadlessComponentsFromJayHtml(
|
|
1957
|
-
jayHtmlContent,
|
|
1958
|
-
pageFilePath,
|
|
1959
|
-
projectRootPath
|
|
1960
|
-
);
|
|
1961
|
-
} catch (error) {
|
|
1962
|
-
getLogger().warn(`Failed to read page file ${pageFilePath}:`, error);
|
|
1963
|
-
}
|
|
1964
|
-
} else if (hasPageConfig) {
|
|
1965
|
-
try {
|
|
1966
|
-
const configContent = await fs.promises.readFile(pageConfigPath, "utf-8");
|
|
1967
|
-
const pageConfig = YAML.parse(configContent);
|
|
1968
|
-
if (pageConfig.used_components && Array.isArray(pageConfig.used_components)) {
|
|
1969
|
-
for (const comp of pageConfig.used_components) {
|
|
1970
|
-
const key = comp.key || "";
|
|
1971
|
-
if (comp.plugin && comp.contract) {
|
|
1972
|
-
const plugin = plugins.find((p) => p.name === comp.plugin);
|
|
1973
|
-
if (plugin && plugin.contracts) {
|
|
1974
|
-
const contract2 = plugin.contracts.find((c) => c.name === comp.contract);
|
|
1975
|
-
if (contract2) {
|
|
1976
|
-
usedComponents.push({
|
|
1977
|
-
appName: comp.plugin,
|
|
1978
|
-
componentName: comp.contract,
|
|
1979
|
-
key
|
|
1980
|
-
});
|
|
1981
|
-
continue;
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
usedComponents.push({
|
|
1985
|
-
appName: comp.plugin,
|
|
1986
|
-
componentName: comp.contract,
|
|
1987
|
-
key
|
|
1988
|
-
});
|
|
1989
|
-
} else {
|
|
1990
|
-
getLogger().warn(
|
|
1991
|
-
`Invalid component definition in ${pageConfigPath}: Only plugin/contract syntax is supported for headless components. Found:`,
|
|
1992
|
-
comp
|
|
1993
|
-
);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
} catch (error) {
|
|
1998
|
-
getLogger().warn(`Failed to parse page config ${pageConfigPath}:`, error);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
return {
|
|
2002
|
-
name: pageName,
|
|
2003
|
-
url: pageUrl,
|
|
2004
|
-
filePath: pageFilePath,
|
|
2005
|
-
contract,
|
|
2006
|
-
usedComponents
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath, projectRootPath) {
|
|
2010
|
-
const [projectName, components, plugins] = await Promise.all([
|
|
2011
|
-
getProjectName(configBasePath),
|
|
2012
|
-
scanProjectComponents(componentsBasePath),
|
|
2013
|
-
scanPlugins(projectRootPath)
|
|
2014
|
-
]);
|
|
2015
|
-
const pages = [];
|
|
2016
|
-
await scanPageDirectories(pagesBasePath, async (context) => {
|
|
2017
|
-
const page = await loadProjectPage(context, plugins);
|
|
2018
|
-
pages.push(page);
|
|
2019
|
-
});
|
|
2020
|
-
return {
|
|
2021
|
-
name: projectName,
|
|
2022
|
-
localPath: projectRootPath,
|
|
2023
|
-
pages,
|
|
2024
|
-
components,
|
|
2025
|
-
plugins
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
async function handlePagePublish(resolvedConfig, page) {
|
|
2029
|
-
try {
|
|
2030
|
-
const pagesBasePath = path.resolve(resolvedConfig.devServer.pagesBase);
|
|
2031
|
-
const dirname = pageUrlToDirectoryPath(page.route, pagesBasePath);
|
|
2032
|
-
const fullPath = path.join(dirname, PAGE_FILENAME);
|
|
2033
|
-
await fs.promises.mkdir(dirname, { recursive: true });
|
|
2034
|
-
await fs.promises.writeFile(fullPath, page.jayHtml, "utf-8");
|
|
2035
|
-
let contractPath;
|
|
2036
|
-
if (page.contract) {
|
|
2037
|
-
contractPath = path.join(dirname, `page${JAY_CONTRACT_EXTENSION}`);
|
|
2038
|
-
await fs.promises.writeFile(contractPath, page.contract, "utf-8");
|
|
2039
|
-
getLogger().info(`📄 Published page contract: ${contractPath}`);
|
|
2040
|
-
}
|
|
2041
|
-
const createdJayHtml = {
|
|
2042
|
-
jayHtml: page.jayHtml,
|
|
2043
|
-
filename: PAGE_FILENAME,
|
|
2044
|
-
dirname,
|
|
2045
|
-
fullPath
|
|
2046
|
-
};
|
|
2047
|
-
getLogger().info(`📝 Published page: ${fullPath}`);
|
|
2048
|
-
return [
|
|
2049
|
-
{
|
|
2050
|
-
success: true,
|
|
2051
|
-
filePath: fullPath,
|
|
2052
|
-
contractPath
|
|
2053
|
-
},
|
|
2054
|
-
createdJayHtml
|
|
2055
|
-
];
|
|
2056
|
-
} catch (error) {
|
|
2057
|
-
getLogger().error(`Failed to publish page ${page.route}:`, error);
|
|
2058
|
-
return [
|
|
2059
|
-
{
|
|
2060
|
-
success: false,
|
|
2061
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2062
|
-
},
|
|
2063
|
-
void 0
|
|
2064
|
-
];
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
async function handleComponentPublish(resolvedConfig, component) {
|
|
2068
|
-
try {
|
|
2069
|
-
const dirname = path.resolve(resolvedConfig.devServer.componentsBase);
|
|
2070
|
-
const filename = `${component.name}${JAY_EXTENSION}`;
|
|
2071
|
-
const fullPath = path.join(dirname, filename);
|
|
2072
|
-
await fs.promises.mkdir(dirname, { recursive: true });
|
|
2073
|
-
await fs.promises.writeFile(fullPath, component.jayHtml, "utf-8");
|
|
2074
|
-
let contractPath;
|
|
2075
|
-
if (component.contract) {
|
|
2076
|
-
contractPath = path.join(dirname, `${component.name}${JAY_CONTRACT_EXTENSION}`);
|
|
2077
|
-
await fs.promises.writeFile(contractPath, component.contract, "utf-8");
|
|
2078
|
-
}
|
|
2079
|
-
const createdJayHtml = {
|
|
2080
|
-
jayHtml: component.jayHtml,
|
|
2081
|
-
filename,
|
|
2082
|
-
dirname,
|
|
2083
|
-
fullPath
|
|
2084
|
-
};
|
|
2085
|
-
getLogger().info(`🧩 Published component: ${fullPath}`);
|
|
2086
|
-
return [
|
|
2087
|
-
{
|
|
2088
|
-
success: true,
|
|
2089
|
-
filePath: fullPath,
|
|
2090
|
-
contractPath
|
|
2091
|
-
},
|
|
2092
|
-
createdJayHtml
|
|
2093
|
-
];
|
|
2094
|
-
} catch (error) {
|
|
2095
|
-
getLogger().error(`Failed to publish component ${component.name}:`, error);
|
|
2096
|
-
return [
|
|
2097
|
-
{
|
|
2098
|
-
success: false,
|
|
2099
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2100
|
-
},
|
|
2101
|
-
void 0
|
|
2102
|
-
];
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
async function loadPageContracts(dirPath, pageUrl, projectRootPath) {
|
|
2106
|
-
const { hasPageHtml, hasPageContract, hasPageConfig } = await isPageDirectory(dirPath);
|
|
2107
|
-
const plugins = await scanPlugins(projectRootPath);
|
|
2108
|
-
const pageInfo = await loadProjectPage(
|
|
2109
|
-
{
|
|
2110
|
-
dirPath,
|
|
2111
|
-
pageUrl,
|
|
2112
|
-
pageName: path.basename(dirPath),
|
|
2113
|
-
hasPageHtml,
|
|
2114
|
-
hasPageContract,
|
|
2115
|
-
hasPageConfig
|
|
2116
|
-
},
|
|
2117
|
-
plugins
|
|
2118
|
-
);
|
|
2119
|
-
return { projectPage: pageInfo, plugins };
|
|
2120
|
-
}
|
|
2121
|
-
function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
2122
|
-
const onPublish = async (params) => {
|
|
2123
|
-
const status = [];
|
|
2124
|
-
const createdJayHtmls = [];
|
|
2125
|
-
if (params.pages) {
|
|
2126
|
-
for (const page of params.pages) {
|
|
2127
|
-
const [pageStatus, createdJayHtml] = await handlePagePublish(config, page);
|
|
2128
|
-
status.push(pageStatus);
|
|
2129
|
-
if (pageStatus.success)
|
|
2130
|
-
createdJayHtmls.push(createdJayHtml);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
if (params.components) {
|
|
2134
|
-
for (const component of params.components) {
|
|
2135
|
-
const [compStatus, createdJayHtml] = await handleComponentPublish(
|
|
2136
|
-
config,
|
|
2137
|
-
component
|
|
2138
|
-
);
|
|
2139
|
-
status.push(compStatus);
|
|
2140
|
-
if (compStatus.success)
|
|
2141
|
-
createdJayHtmls.push(createdJayHtml);
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
for (const { jayHtml, dirname, filename, fullPath } of createdJayHtmls) {
|
|
2145
|
-
const parsedJayHtml = await parseJayFile(
|
|
2146
|
-
jayHtml,
|
|
2147
|
-
filename,
|
|
2148
|
-
dirname,
|
|
2149
|
-
{ relativePath: tsConfigPath },
|
|
2150
|
-
JAY_IMPORT_RESOLVER,
|
|
2151
|
-
projectRoot
|
|
2152
|
-
);
|
|
2153
|
-
const definitionFile = generateElementDefinitionFile(parsedJayHtml);
|
|
2154
|
-
if (definitionFile.validations.length > 0)
|
|
2155
|
-
getLogger().info(
|
|
2156
|
-
`failed to generate .d.ts for ${fullPath} with validation errors: ${definitionFile.validations.join("\n")}`
|
|
2157
|
-
);
|
|
2158
|
-
else
|
|
2159
|
-
await fs.promises.writeFile(fullPath + ".d.ts", definitionFile.val, "utf-8");
|
|
2160
|
-
}
|
|
2161
|
-
return {
|
|
2162
|
-
type: "publish",
|
|
2163
|
-
success: status.every((s2) => s2.success),
|
|
2164
|
-
status
|
|
2165
|
-
};
|
|
2166
|
-
};
|
|
2167
|
-
const onSaveImage = async (params) => {
|
|
2168
|
-
try {
|
|
2169
|
-
const imagesDir = path.join(path.resolve(config.devServer.publicFolder), "images");
|
|
2170
|
-
await fs.promises.mkdir(imagesDir, { recursive: true });
|
|
2171
|
-
const filename = `${params.imageId}.png`;
|
|
2172
|
-
const imagePath = path.join(imagesDir, filename);
|
|
2173
|
-
await fs.promises.writeFile(imagePath, Buffer.from(params.imageData, "base64"));
|
|
2174
|
-
getLogger().info(`🖼️ Saved image: ${imagePath}`);
|
|
2175
|
-
return {
|
|
2176
|
-
type: "saveImage",
|
|
2177
|
-
success: true,
|
|
2178
|
-
imageUrl: `/images/${filename}`
|
|
2179
|
-
};
|
|
2180
|
-
} catch (error) {
|
|
2181
|
-
getLogger().error("Failed to save image:", error);
|
|
2182
|
-
return {
|
|
2183
|
-
type: "saveImage",
|
|
2184
|
-
success: false,
|
|
2185
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2186
|
-
};
|
|
2187
|
-
}
|
|
2188
|
-
};
|
|
2189
|
-
const onHasImage = async (params) => {
|
|
2190
|
-
try {
|
|
2191
|
-
const filename = `${params.imageId}.png`;
|
|
2192
|
-
const imagePath = path.join(
|
|
2193
|
-
path.resolve(config.devServer.publicFolder),
|
|
2194
|
-
"images",
|
|
2195
|
-
filename
|
|
2196
|
-
);
|
|
2197
|
-
const exists = fs.existsSync(imagePath);
|
|
2198
|
-
return {
|
|
2199
|
-
type: "hasImage",
|
|
2200
|
-
success: true,
|
|
2201
|
-
exists,
|
|
2202
|
-
imageUrl: exists ? `/images/${filename}` : void 0
|
|
2203
|
-
};
|
|
2204
|
-
} catch (error) {
|
|
2205
|
-
getLogger().error("Failed to check image:", error);
|
|
2206
|
-
return {
|
|
2207
|
-
type: "hasImage",
|
|
2208
|
-
success: false,
|
|
2209
|
-
exists: false,
|
|
2210
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
};
|
|
2214
|
-
const onGetProjectInfo = async (params) => {
|
|
2215
|
-
try {
|
|
2216
|
-
const pagesBasePath = path.resolve(config.devServer.pagesBase);
|
|
2217
|
-
const componentsBasePath = path.resolve(config.devServer.componentsBase);
|
|
2218
|
-
const configBasePath = path.resolve(config.devServer.configBase);
|
|
2219
|
-
const info = await scanProjectInfo(
|
|
2220
|
-
pagesBasePath,
|
|
2221
|
-
componentsBasePath,
|
|
2222
|
-
configBasePath,
|
|
2223
|
-
projectRoot
|
|
2224
|
-
);
|
|
2225
|
-
getLogger().info(`📋 Retrieved project info: ${info.name}`);
|
|
2226
|
-
getLogger().info(` Pages: ${info.pages.length}`);
|
|
2227
|
-
getLogger().info(` Components: ${info.components.length}`);
|
|
2228
|
-
getLogger().info(` plugins: ${info.plugins.length}`);
|
|
2229
|
-
return {
|
|
2230
|
-
type: "getProjectInfo",
|
|
2231
|
-
success: true,
|
|
2232
|
-
info
|
|
2233
|
-
};
|
|
2234
|
-
} catch (error) {
|
|
2235
|
-
getLogger().error("Failed to get project info:", error);
|
|
2236
|
-
return {
|
|
2237
|
-
type: "getProjectInfo",
|
|
2238
|
-
success: false,
|
|
2239
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2240
|
-
info: {
|
|
2241
|
-
name: "Error",
|
|
2242
|
-
localPath: process.cwd(),
|
|
2243
|
-
pages: [],
|
|
2244
|
-
components: [],
|
|
2245
|
-
plugins: []
|
|
2246
|
-
}
|
|
2247
|
-
};
|
|
2248
|
-
}
|
|
2249
|
-
};
|
|
2250
|
-
const onExport = async (params) => {
|
|
2251
|
-
try {
|
|
2252
|
-
const pagesBasePath = path.resolve(config.devServer.pagesBase);
|
|
2253
|
-
const { vendorId, pageUrl, vendorDoc } = params;
|
|
2254
|
-
const dirname = pageUrlToDirectoryPath(pageUrl, pagesBasePath);
|
|
2255
|
-
const vendorFilename = `page.${vendorId}.json`;
|
|
2256
|
-
const vendorFilePath = path.join(dirname, vendorFilename);
|
|
2257
|
-
await fs.promises.mkdir(dirname, { recursive: true });
|
|
2258
|
-
await fs.promises.writeFile(
|
|
2259
|
-
vendorFilePath,
|
|
2260
|
-
JSON.stringify(vendorDoc, null, 2),
|
|
2261
|
-
"utf-8"
|
|
2262
|
-
);
|
|
2263
|
-
getLogger().info(`📦 Exported ${vendorId} document to: ${vendorFilePath}`);
|
|
2264
|
-
if (hasVendor(vendorId)) {
|
|
2265
|
-
getLogger().info(`🔄 Converting ${vendorId} document to Jay HTML...`);
|
|
2266
|
-
const vendor = getVendor(vendorId);
|
|
2267
|
-
try {
|
|
2268
|
-
const { projectPage, plugins } = await loadPageContracts(
|
|
2269
|
-
dirname,
|
|
2270
|
-
pageUrl,
|
|
2271
|
-
projectRoot
|
|
2272
|
-
);
|
|
2273
|
-
const conversionResult = await vendor.convertToBodyHtml(
|
|
2274
|
-
vendorDoc,
|
|
2275
|
-
pageUrl,
|
|
2276
|
-
projectPage,
|
|
2277
|
-
plugins
|
|
2278
|
-
);
|
|
2279
|
-
const fullJayHtml = await buildJayHtmlFromVendorResult(
|
|
2280
|
-
conversionResult,
|
|
2281
|
-
dirname,
|
|
2282
|
-
path.basename(dirname)
|
|
2283
|
-
);
|
|
2284
|
-
const jayHtmlPath = path.join(dirname, "page.jay-html");
|
|
2285
|
-
await fs.promises.writeFile(jayHtmlPath, fullJayHtml, "utf-8");
|
|
2286
|
-
getLogger().info(`✅ Successfully converted to Jay HTML: ${jayHtmlPath}`);
|
|
2287
|
-
return {
|
|
2288
|
-
type: "export",
|
|
2289
|
-
success: true,
|
|
2290
|
-
vendorSourcePath: vendorFilePath,
|
|
2291
|
-
jayHtmlPath
|
|
2292
|
-
};
|
|
2293
|
-
} catch (conversionError) {
|
|
2294
|
-
getLogger().error(`❌ Vendor conversion threw an error:`, conversionError);
|
|
2295
|
-
return {
|
|
2296
|
-
type: "export",
|
|
2297
|
-
success: false,
|
|
2298
|
-
vendorSourcePath: vendorFilePath,
|
|
2299
|
-
error: conversionError instanceof Error ? conversionError.message : "Unknown conversion error"
|
|
2300
|
-
};
|
|
2301
|
-
}
|
|
2302
|
-
} else {
|
|
2303
|
-
getLogger().info(`ℹ️ No vendor found for '${vendorId}'. Skipping conversion.`);
|
|
2304
44
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
getLogger().warn(`Failed to parse .jay YAML config file, using defaults: ${error}`);
|
|
48
|
+
return DEFAULT_CONFIG;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getConfigWithDefaults(config) {
|
|
52
|
+
return {
|
|
53
|
+
devServer: {
|
|
54
|
+
portRange: config.devServer?.portRange || DEFAULT_CONFIG.devServer.portRange,
|
|
55
|
+
pagesBase: config.devServer?.pagesBase || DEFAULT_CONFIG.devServer.pagesBase,
|
|
56
|
+
componentsBase: config.devServer?.componentsBase || DEFAULT_CONFIG.devServer.componentsBase,
|
|
57
|
+
publicFolder: config.devServer?.publicFolder || DEFAULT_CONFIG.devServer.publicFolder,
|
|
58
|
+
configBase: config.devServer?.configBase || DEFAULT_CONFIG.devServer.configBase
|
|
2317
59
|
}
|
|
2318
60
|
};
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
error: `No ${vendorId} document found at ${pageUrl}. File not found: ${vendorFilePath}`
|
|
2331
|
-
};
|
|
61
|
+
}
|
|
62
|
+
function updateConfig(updates) {
|
|
63
|
+
const configPath = path.resolve(".jay");
|
|
64
|
+
try {
|
|
65
|
+
const existingConfig = loadConfig();
|
|
66
|
+
const updatedConfig = {
|
|
67
|
+
...existingConfig,
|
|
68
|
+
...updates,
|
|
69
|
+
devServer: {
|
|
70
|
+
...existingConfig.devServer,
|
|
71
|
+
...updates.devServer
|
|
2332
72
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
vendorDoc
|
|
2340
|
-
};
|
|
2341
|
-
} catch (error) {
|
|
2342
|
-
getLogger().error("Failed to import vendor document:", error);
|
|
2343
|
-
return {
|
|
2344
|
-
type: "import",
|
|
2345
|
-
success: false,
|
|
2346
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2347
|
-
};
|
|
2348
|
-
}
|
|
2349
|
-
};
|
|
2350
|
-
return {
|
|
2351
|
-
onPublish,
|
|
2352
|
-
onSaveImage,
|
|
2353
|
-
onHasImage,
|
|
2354
|
-
onGetProjectInfo,
|
|
2355
|
-
onExport,
|
|
2356
|
-
onImport
|
|
2357
|
-
};
|
|
73
|
+
};
|
|
74
|
+
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
75
|
+
fs.writeFileSync(configPath, yamlContent);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
78
|
+
}
|
|
2358
79
|
}
|
|
2359
80
|
async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
2360
81
|
for (const route of routes) {
|
|
@@ -2415,36 +136,6 @@ async function startDevServer(options = {}) {
|
|
|
2415
136
|
const httpServer = http.createServer(app);
|
|
2416
137
|
const devServerPort = await getPort({ port: resolvedConfig.devServer.portRange });
|
|
2417
138
|
const log = getLogger();
|
|
2418
|
-
const editorServer = createEditorServer({
|
|
2419
|
-
portRange: resolvedConfig.editorServer.portRange,
|
|
2420
|
-
editorId: resolvedConfig.editorServer.editorId,
|
|
2421
|
-
onEditorId: (editorId2) => {
|
|
2422
|
-
log.info(`Editor connected with ID: ${editorId2}`);
|
|
2423
|
-
updateConfig({
|
|
2424
|
-
editorServer: {
|
|
2425
|
-
editorId: editorId2
|
|
2426
|
-
}
|
|
2427
|
-
});
|
|
2428
|
-
}
|
|
2429
|
-
});
|
|
2430
|
-
const { port: editorPort, editorId } = await editorServer.start();
|
|
2431
|
-
const registeredVendors = getRegisteredVendors();
|
|
2432
|
-
if (registeredVendors.length > 0) {
|
|
2433
|
-
log.info(
|
|
2434
|
-
`📦 Registered ${registeredVendors.length} vendor(s): ${registeredVendors.join(", ")}`
|
|
2435
|
-
);
|
|
2436
|
-
}
|
|
2437
|
-
const handlers = createEditorHandlers(
|
|
2438
|
-
resolvedConfig,
|
|
2439
|
-
jayOptions.tsConfigFilePath,
|
|
2440
|
-
process.cwd()
|
|
2441
|
-
);
|
|
2442
|
-
editorServer.onPublish(handlers.onPublish);
|
|
2443
|
-
editorServer.onSaveImage(handlers.onSaveImage);
|
|
2444
|
-
editorServer.onHasImage(handlers.onHasImage);
|
|
2445
|
-
editorServer.onGetProjectInfo(handlers.onGetProjectInfo);
|
|
2446
|
-
editorServer.onExport(handlers.onExport);
|
|
2447
|
-
editorServer.onImport(handlers.onImport);
|
|
2448
139
|
const { server, viteServer, routes, service } = await mkDevServer({
|
|
2449
140
|
pagesRootFolder: path.resolve(resolvedConfig.devServer.pagesBase),
|
|
2450
141
|
projectRootFolder: process.cwd(),
|
|
@@ -2454,73 +145,6 @@ async function startDevServer(options = {}) {
|
|
|
2454
145
|
httpServer
|
|
2455
146
|
});
|
|
2456
147
|
app.use(server);
|
|
2457
|
-
const { freezeStore } = service;
|
|
2458
|
-
editorServer.onListRoutes(async () => ({
|
|
2459
|
-
type: "listRoutes",
|
|
2460
|
-
success: true,
|
|
2461
|
-
routes: service.listRoutes()
|
|
2462
|
-
}));
|
|
2463
|
-
if (freezeStore) {
|
|
2464
|
-
editorServer.onListFreezes(async (params) => ({
|
|
2465
|
-
type: "listFreezes",
|
|
2466
|
-
success: true,
|
|
2467
|
-
freezes: (await freezeStore.list(params.route)).map(
|
|
2468
|
-
({ id, name, route, routePattern, createdAt }) => ({
|
|
2469
|
-
id,
|
|
2470
|
-
name,
|
|
2471
|
-
route,
|
|
2472
|
-
routePattern,
|
|
2473
|
-
createdAt
|
|
2474
|
-
})
|
|
2475
|
-
)
|
|
2476
|
-
}));
|
|
2477
|
-
editorServer.onRenameFreeze(async (params) => ({
|
|
2478
|
-
type: "renameFreeze",
|
|
2479
|
-
success: await freezeStore.rename(params.id, params.name)
|
|
2480
|
-
}));
|
|
2481
|
-
editorServer.onDeleteFreeze(async (params) => ({
|
|
2482
|
-
type: "deleteFreeze",
|
|
2483
|
-
success: await freezeStore.delete(params.id)
|
|
2484
|
-
}));
|
|
2485
|
-
viteServer.watcher.on("change", (changedPath) => {
|
|
2486
|
-
if (changedPath.endsWith(".jay-html") || changedPath.endsWith(".css")) {
|
|
2487
|
-
editorServer.emitFreezeChanged();
|
|
2488
|
-
}
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
editorServer.onLoadRouteParams(async (params) => {
|
|
2492
|
-
const routePath = params.route;
|
|
2493
|
-
try {
|
|
2494
|
-
(async () => {
|
|
2495
|
-
try {
|
|
2496
|
-
for await (const batch of service.loadRouteParams(routePath)) {
|
|
2497
|
-
editorServer.emitRouteParamsBatch({
|
|
2498
|
-
type: "routeParamsBatch",
|
|
2499
|
-
route: routePath,
|
|
2500
|
-
params: batch,
|
|
2501
|
-
hasMore: true
|
|
2502
|
-
});
|
|
2503
|
-
}
|
|
2504
|
-
editorServer.emitRouteParamsBatch({
|
|
2505
|
-
type: "routeParamsBatch",
|
|
2506
|
-
route: routePath,
|
|
2507
|
-
params: [],
|
|
2508
|
-
hasMore: false
|
|
2509
|
-
});
|
|
2510
|
-
} catch (err) {
|
|
2511
|
-
editorServer.emitRouteParamsBatch({
|
|
2512
|
-
type: "routeParamsBatch",
|
|
2513
|
-
route: routePath,
|
|
2514
|
-
params: [],
|
|
2515
|
-
hasMore: false
|
|
2516
|
-
});
|
|
2517
|
-
}
|
|
2518
|
-
})();
|
|
2519
|
-
return { type: "loadRouteParams", success: true };
|
|
2520
|
-
} catch (err) {
|
|
2521
|
-
return { type: "loadRouteParams", success: false, error: err.message };
|
|
2522
|
-
}
|
|
2523
|
-
});
|
|
2524
148
|
const publicPath = path.resolve(resolvedConfig.devServer.publicFolder);
|
|
2525
149
|
if (fs.existsSync(publicPath)) {
|
|
2526
150
|
app.use(express.static(publicPath));
|
|
@@ -2539,7 +163,6 @@ async function startDevServer(options = {}) {
|
|
|
2539
163
|
httpServer.listen(devServerPort, () => {
|
|
2540
164
|
log.important(`🚀 Jay Stack dev server started successfully!`);
|
|
2541
165
|
log.important(`📱 Dev Server: http://localhost:${devServerPort}`);
|
|
2542
|
-
log.important(`🎨 Editor Server: http://localhost:${editorPort} (ID: ${editorId})`);
|
|
2543
166
|
log.important(`📁 Pages directory: ${resolvedConfig.devServer.pagesBase}`);
|
|
2544
167
|
if (fs.existsSync(publicPath)) {
|
|
2545
168
|
log.important(`📁 Public folder: ${resolvedConfig.devServer.publicFolder}`);
|
|
@@ -2557,7 +180,6 @@ async function startDevServer(options = {}) {
|
|
|
2557
180
|
});
|
|
2558
181
|
const shutdown = async () => {
|
|
2559
182
|
log.important("\n🛑 Shutting down servers...");
|
|
2560
|
-
await editorServer.stop();
|
|
2561
183
|
httpServer.closeAllConnections();
|
|
2562
184
|
await new Promise((resolve) => httpServer.close(resolve));
|
|
2563
185
|
process.exit(0);
|
|
@@ -2567,7 +189,6 @@ async function startDevServer(options = {}) {
|
|
|
2567
189
|
res.json({
|
|
2568
190
|
status: "ready",
|
|
2569
191
|
port: devServerPort,
|
|
2570
|
-
editorPort,
|
|
2571
192
|
uptime: process.uptime()
|
|
2572
193
|
});
|
|
2573
194
|
});
|
|
@@ -2588,25 +209,25 @@ async function startDevServer(options = {}) {
|
|
|
2588
209
|
process.on("SIGTERM", shutdown);
|
|
2589
210
|
process.on("SIGINT", shutdown);
|
|
2590
211
|
}
|
|
2591
|
-
async function initializeServicesForCli(projectRoot, viteServer) {
|
|
212
|
+
async function initializeServicesForCli(projectRoot, viteServer, quiet = false) {
|
|
2592
213
|
const path2 = await import("node:path");
|
|
2593
214
|
const fs2 = await import("node:fs");
|
|
2594
215
|
const {
|
|
2595
|
-
runInitCallbacks,
|
|
216
|
+
runInitCallbacks: runInitCallbacks2,
|
|
2596
217
|
getServiceRegistry,
|
|
2597
|
-
discoverPluginsWithInit,
|
|
2598
|
-
sortPluginsByDependencies,
|
|
2599
|
-
executePluginServerInits
|
|
218
|
+
discoverPluginsWithInit: discoverPluginsWithInit2,
|
|
219
|
+
sortPluginsByDependencies: sortPluginsByDependencies2,
|
|
220
|
+
executePluginServerInits: executePluginServerInits2
|
|
2600
221
|
} = await import("@jay-framework/stack-server-runtime");
|
|
2601
222
|
let initErrors = /* @__PURE__ */ new Map();
|
|
2602
223
|
try {
|
|
2603
|
-
const discoveredPlugins = await
|
|
224
|
+
const discoveredPlugins = await discoverPluginsWithInit2({
|
|
2604
225
|
projectRoot,
|
|
2605
226
|
verbose: false
|
|
2606
227
|
});
|
|
2607
|
-
const pluginsWithInit =
|
|
228
|
+
const pluginsWithInit = sortPluginsByDependencies2(discoveredPlugins);
|
|
2608
229
|
try {
|
|
2609
|
-
initErrors = await
|
|
230
|
+
initErrors = await executePluginServerInits2(pluginsWithInit, viteServer, false, quiet);
|
|
2610
231
|
} catch (error) {
|
|
2611
232
|
getLogger().warn(chalk.yellow(`⚠️ Plugin initialization skipped: ${error.message}`));
|
|
2612
233
|
}
|
|
@@ -2621,7 +242,7 @@ async function initializeServicesForCli(projectRoot, viteServer) {
|
|
|
2621
242
|
if (initModule?.init?._serverInit) {
|
|
2622
243
|
await initModule.init._serverInit();
|
|
2623
244
|
}
|
|
2624
|
-
await
|
|
245
|
+
await runInitCallbacks2();
|
|
2625
246
|
} catch (error) {
|
|
2626
247
|
getLogger().warn(chalk.yellow(`⚠️ Service initialization failed: ${error.message}`));
|
|
2627
248
|
getLogger().warn(chalk.gray(" Static contracts will still be listed."));
|
|
@@ -2843,17 +464,13 @@ function collectLocalInterfaces(sourceFile) {
|
|
|
2843
464
|
function collectContractImportedTypes(sourceFile) {
|
|
2844
465
|
const contractTypes = /* @__PURE__ */ new Set();
|
|
2845
466
|
for (const statement of sourceFile.statements) {
|
|
2846
|
-
if (!u.isImportDeclaration(statement))
|
|
2847
|
-
continue;
|
|
467
|
+
if (!u.isImportDeclaration(statement)) continue;
|
|
2848
468
|
const moduleSpecifier = statement.moduleSpecifier;
|
|
2849
|
-
if (!u.isStringLiteral(moduleSpecifier))
|
|
2850
|
-
continue;
|
|
469
|
+
if (!u.isStringLiteral(moduleSpecifier)) continue;
|
|
2851
470
|
const modulePath = moduleSpecifier.text;
|
|
2852
|
-
if (!modulePath.includes(".jay-contract"))
|
|
2853
|
-
continue;
|
|
471
|
+
if (!modulePath.includes(".jay-contract")) continue;
|
|
2854
472
|
const importClause = statement.importClause;
|
|
2855
|
-
if (!importClause)
|
|
2856
|
-
continue;
|
|
473
|
+
if (!importClause) continue;
|
|
2857
474
|
const namedBindings = importClause.namedBindings;
|
|
2858
475
|
if (namedBindings && u.isNamedImports(namedBindings)) {
|
|
2859
476
|
for (const element of namedBindings.elements) {
|
|
@@ -2889,8 +506,7 @@ function visitNode(node, result) {
|
|
|
2889
506
|
}
|
|
2890
507
|
if (node.arguments.length > 0) {
|
|
2891
508
|
const arg = node.arguments[0];
|
|
2892
|
-
if (u.isIdentifier(arg))
|
|
2893
|
-
;
|
|
509
|
+
if (u.isIdentifier(arg)) ;
|
|
2894
510
|
}
|
|
2895
511
|
}
|
|
2896
512
|
}
|
|
@@ -2912,10 +528,8 @@ function extractTypeNames(typeNode) {
|
|
|
2912
528
|
return [];
|
|
2913
529
|
}
|
|
2914
530
|
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
2915
|
-
if (FRAMEWORK_PROP_TYPES.has(propsTypeName))
|
|
2916
|
-
|
|
2917
|
-
if (contractImportedTypes.has(propsTypeName))
|
|
2918
|
-
return;
|
|
531
|
+
if (FRAMEWORK_PROP_TYPES.has(propsTypeName)) return;
|
|
532
|
+
if (contractImportedTypes.has(propsTypeName)) return;
|
|
2919
533
|
const prefix = `[${contractName}]`;
|
|
2920
534
|
const iface = localInterfaces.get(propsTypeName);
|
|
2921
535
|
if (!iface) {
|
|
@@ -2930,8 +544,7 @@ function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedT
|
|
|
2930
544
|
return;
|
|
2931
545
|
}
|
|
2932
546
|
const ownProperties = iface.properties;
|
|
2933
|
-
if (ownProperties.length === 0)
|
|
2934
|
-
return;
|
|
547
|
+
if (ownProperties.length === 0) return;
|
|
2935
548
|
if (!contract.props || contract.props.length === 0) {
|
|
2936
549
|
errors.push({
|
|
2937
550
|
type: "contract-invalid",
|
|
@@ -2975,10 +588,8 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
|
|
|
2975
588
|
suggestion: `Add a params section to the contract (e.g., params: { slug: string })`
|
|
2976
589
|
});
|
|
2977
590
|
for (const typeName of paramsTypeNames) {
|
|
2978
|
-
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2979
|
-
|
|
2980
|
-
if (contractImportedTypes.has(typeName))
|
|
2981
|
-
continue;
|
|
591
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
|
|
592
|
+
if (contractImportedTypes.has(typeName)) continue;
|
|
2982
593
|
const iface = localInterfaces.get(typeName);
|
|
2983
594
|
if (iface) {
|
|
2984
595
|
const ownProps = iface.properties;
|
|
@@ -2991,13 +602,10 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
|
|
|
2991
602
|
return;
|
|
2992
603
|
}
|
|
2993
604
|
for (const typeName of paramsTypeNames) {
|
|
2994
|
-
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2995
|
-
|
|
2996
|
-
if (contractImportedTypes.has(typeName))
|
|
2997
|
-
continue;
|
|
605
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
|
|
606
|
+
if (contractImportedTypes.has(typeName)) continue;
|
|
2998
607
|
const iface = localInterfaces.get(typeName);
|
|
2999
|
-
if (!iface)
|
|
3000
|
-
continue;
|
|
608
|
+
if (!iface) continue;
|
|
3001
609
|
const ownProperties = iface.properties;
|
|
3002
610
|
const contractParamNames = new Set(contract.params.map((p) => p.name));
|
|
3003
611
|
for (const prop of ownProperties) {
|
|
@@ -3029,7 +637,7 @@ const FOLDER_PATH_MAX_SEGMENTS = 32;
|
|
|
3029
637
|
const BLOCKED_TAGS = /<\s*(script|iframe|object|embed)\b[^>]*>[\s\S]*?<\/\s*\1\s*>|<\s*(script|iframe|object|embed)\b[^>]*\/?>/gi;
|
|
3030
638
|
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
|
|
3031
639
|
const JAVASCRIPT_URL = /\b(href|src|xlink:href)\s*=\s*("|')\s*javascript:/gi;
|
|
3032
|
-
function isRecord(value) {
|
|
640
|
+
function isRecord$1(value) {
|
|
3033
641
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3034
642
|
}
|
|
3035
643
|
function byteLengthUtf8(value) {
|
|
@@ -3089,17 +697,14 @@ function requiredString(obj, field, itemPath, errors, code) {
|
|
|
3089
697
|
}
|
|
3090
698
|
function optionalString(obj, field) {
|
|
3091
699
|
const value = obj[field];
|
|
3092
|
-
if (value === void 0)
|
|
3093
|
-
|
|
3094
|
-
if (typeof value !== "string")
|
|
3095
|
-
return void 0;
|
|
700
|
+
if (value === void 0) return void 0;
|
|
701
|
+
if (typeof value !== "string") return void 0;
|
|
3096
702
|
const trimmed = value.trim();
|
|
3097
703
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
3098
704
|
}
|
|
3099
705
|
function validateInteraction(raw, itemPath, errors) {
|
|
3100
|
-
if (raw === void 0)
|
|
3101
|
-
|
|
3102
|
-
if (!isRecord(raw)) {
|
|
706
|
+
if (raw === void 0) return void 0;
|
|
707
|
+
if (!isRecord$1(raw)) {
|
|
3103
708
|
errors.push({
|
|
3104
709
|
path: itemPath,
|
|
3105
710
|
message: "interaction must be an object",
|
|
@@ -3122,9 +727,8 @@ function validateInteraction(raw, itemPath, errors) {
|
|
|
3122
727
|
};
|
|
3123
728
|
}
|
|
3124
729
|
function validatePresentation(raw, itemPath, errors) {
|
|
3125
|
-
if (raw === void 0)
|
|
3126
|
-
|
|
3127
|
-
if (!isRecord(raw)) {
|
|
730
|
+
if (raw === void 0) return void 0;
|
|
731
|
+
if (!isRecord$1(raw)) {
|
|
3128
732
|
errors.push({
|
|
3129
733
|
path: itemPath,
|
|
3130
734
|
message: "presentation must be an object",
|
|
@@ -3143,14 +747,12 @@ function validatePresentation(raw, itemPath, errors) {
|
|
|
3143
747
|
}
|
|
3144
748
|
if (type === "image") {
|
|
3145
749
|
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
3146
|
-
if (!src)
|
|
3147
|
-
return void 0;
|
|
750
|
+
if (!src) return void 0;
|
|
3148
751
|
return { type: "image", src };
|
|
3149
752
|
}
|
|
3150
753
|
if (type === "gif") {
|
|
3151
754
|
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
3152
|
-
if (!src)
|
|
3153
|
-
return void 0;
|
|
755
|
+
if (!src) return void 0;
|
|
3154
756
|
return {
|
|
3155
757
|
type: "gif",
|
|
3156
758
|
src,
|
|
@@ -3195,9 +797,8 @@ function validatePresentation(raw, itemPath, errors) {
|
|
|
3195
797
|
}
|
|
3196
798
|
const BROWSE_SIZES = /* @__PURE__ */ new Set(["large", "medium", "small"]);
|
|
3197
799
|
function validateBrowse(raw, itemPath, errors) {
|
|
3198
|
-
if (raw === void 0)
|
|
3199
|
-
|
|
3200
|
-
if (!isRecord(raw)) {
|
|
800
|
+
if (raw === void 0) return void 0;
|
|
801
|
+
if (!isRecord$1(raw)) {
|
|
3201
802
|
errors.push({
|
|
3202
803
|
path: itemPath,
|
|
3203
804
|
message: "browse must be an object",
|
|
@@ -3220,8 +821,7 @@ function validateBrowse(raw, itemPath, errors) {
|
|
|
3220
821
|
return { size: sizeRaw };
|
|
3221
822
|
}
|
|
3222
823
|
function validateFolderPath(raw, itemPath, errors) {
|
|
3223
|
-
if (raw === void 0)
|
|
3224
|
-
return void 0;
|
|
824
|
+
if (raw === void 0) return void 0;
|
|
3225
825
|
if (!Array.isArray(raw)) {
|
|
3226
826
|
errors.push({
|
|
3227
827
|
path: `${itemPath}.folderPath`,
|
|
@@ -3280,7 +880,7 @@ function validateFolderPath(raw, itemPath, errors) {
|
|
|
3280
880
|
}
|
|
3281
881
|
function validateAddMenuItem(raw, itemPath) {
|
|
3282
882
|
const errors = [];
|
|
3283
|
-
if (!isRecord(raw)) {
|
|
883
|
+
if (!isRecord$1(raw)) {
|
|
3284
884
|
return {
|
|
3285
885
|
item: null,
|
|
3286
886
|
errors: [
|
|
@@ -3331,7 +931,7 @@ function validateAddMenuItem(raw, itemPath) {
|
|
|
3331
931
|
}
|
|
3332
932
|
function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
3333
933
|
const errors = [];
|
|
3334
|
-
if (!isRecord(raw)) {
|
|
934
|
+
if (!isRecord$1(raw)) {
|
|
3335
935
|
return {
|
|
3336
936
|
file: null,
|
|
3337
937
|
errors: [
|
|
@@ -3359,8 +959,7 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
3359
959
|
raw.items.forEach((entry, index) => {
|
|
3360
960
|
const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
|
|
3361
961
|
errors.push(...result.errors);
|
|
3362
|
-
if (result.item)
|
|
3363
|
-
items.push(result.item);
|
|
962
|
+
if (result.item) items.push(result.item);
|
|
3364
963
|
});
|
|
3365
964
|
if (items.length === 0 && errors.length > 0) {
|
|
3366
965
|
return { file: null, errors };
|
|
@@ -3368,11 +967,9 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
3368
967
|
return { file: { items }, errors };
|
|
3369
968
|
}
|
|
3370
969
|
function normalizeAddMenuPresentation(item) {
|
|
3371
|
-
if (item.presentation)
|
|
3372
|
-
return item.presentation;
|
|
970
|
+
if (item.presentation) return item.presentation;
|
|
3373
971
|
const thumbnail = item.thumbnail?.trim();
|
|
3374
|
-
if (!thumbnail)
|
|
3375
|
-
return void 0;
|
|
972
|
+
if (!thumbnail) return void 0;
|
|
3376
973
|
if (/\.gif$/i.test(thumbnail)) {
|
|
3377
974
|
return { type: "gif", src: thumbnail };
|
|
3378
975
|
}
|
|
@@ -3383,15 +980,12 @@ function normalizeAddMenuBrowseSize(item) {
|
|
|
3383
980
|
}
|
|
3384
981
|
function hasSingleRootDiv(html) {
|
|
3385
982
|
const trimmed = html.trim();
|
|
3386
|
-
if (!trimmed.startsWith("<div"))
|
|
3387
|
-
return false;
|
|
983
|
+
if (!trimmed.startsWith("<div")) return false;
|
|
3388
984
|
const openMatch = trimmed.match(/^<div\b[^>]*>/i);
|
|
3389
|
-
if (!openMatch)
|
|
3390
|
-
return false;
|
|
985
|
+
if (!openMatch) return false;
|
|
3391
986
|
const afterOpen = trimmed.slice(openMatch[0].length);
|
|
3392
987
|
const closeIdx = afterOpen.lastIndexOf("</div>");
|
|
3393
|
-
if (closeIdx < 0)
|
|
3394
|
-
return false;
|
|
988
|
+
if (closeIdx < 0) return false;
|
|
3395
989
|
const tail = afterOpen.slice(closeIdx + "</div>".length).trim();
|
|
3396
990
|
return tail.length === 0;
|
|
3397
991
|
}
|
|
@@ -3467,8 +1061,7 @@ function lintHtmlFragment(item, sourcePath) {
|
|
|
3467
1061
|
}
|
|
3468
1062
|
function lintGifPoster(item, sourcePath) {
|
|
3469
1063
|
const presentation = normalizeAddMenuPresentation(item);
|
|
3470
|
-
if (presentation?.type !== "gif" || presentation.poster?.trim())
|
|
3471
|
-
return [];
|
|
1064
|
+
if (presentation?.type !== "gif" || presentation.poster?.trim()) return [];
|
|
3472
1065
|
return [
|
|
3473
1066
|
catalogWarning(
|
|
3474
1067
|
"gif-missing-poster",
|
|
@@ -3479,10 +1072,8 @@ function lintGifPoster(item, sourcePath) {
|
|
|
3479
1072
|
];
|
|
3480
1073
|
}
|
|
3481
1074
|
function lintBrowseLargeWithoutPresentation(item, sourcePath) {
|
|
3482
|
-
if (normalizeAddMenuBrowseSize(item) !== "large")
|
|
3483
|
-
|
|
3484
|
-
if (normalizeAddMenuPresentation(item))
|
|
3485
|
-
return [];
|
|
1075
|
+
if (normalizeAddMenuBrowseSize(item) !== "large") return [];
|
|
1076
|
+
if (normalizeAddMenuPresentation(item)) return [];
|
|
3486
1077
|
return [
|
|
3487
1078
|
catalogWarning(
|
|
3488
1079
|
"browse-large-without-presentation",
|
|
@@ -3555,8 +1146,7 @@ function resolveModulePath$1(basePath) {
|
|
|
3555
1146
|
return void 0;
|
|
3556
1147
|
}
|
|
3557
1148
|
function collectTypeScriptFiles(dir, depth = 0) {
|
|
3558
|
-
if (depth > 4)
|
|
3559
|
-
return [];
|
|
1149
|
+
if (depth > 4) return [];
|
|
3560
1150
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
3561
1151
|
const files = [];
|
|
3562
1152
|
for (const entry of entries) {
|
|
@@ -3575,8 +1165,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
|
3575
1165
|
}
|
|
3576
1166
|
const searchRoots = isNpmPackage ? [path.join(pluginPath, "lib"), path.join(pluginPath, "dist")] : [pluginPath];
|
|
3577
1167
|
for (const root of searchRoots) {
|
|
3578
|
-
if (!fs.existsSync(root))
|
|
3579
|
-
continue;
|
|
1168
|
+
if (!fs.existsSync(root)) continue;
|
|
3580
1169
|
for (const file of collectTypeScriptFiles(root)) {
|
|
3581
1170
|
const content = fs.readFileSync(file, "utf-8");
|
|
3582
1171
|
const definesHandler = new RegExp(
|
|
@@ -3596,8 +1185,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
|
3596
1185
|
if (reExportMatch) {
|
|
3597
1186
|
const importSpec = reExportMatch[1].replace(/\.js$/, "");
|
|
3598
1187
|
const resolved = resolveModulePath$1(path.resolve(path.dirname(file), importSpec));
|
|
3599
|
-
if (resolved)
|
|
3600
|
-
return resolved;
|
|
1188
|
+
if (resolved) return resolved;
|
|
3601
1189
|
}
|
|
3602
1190
|
}
|
|
3603
1191
|
}
|
|
@@ -3607,8 +1195,7 @@ function extractBalancedBlock(source, openBraceIndex) {
|
|
|
3607
1195
|
let depth = 0;
|
|
3608
1196
|
for (let index = openBraceIndex; index < source.length; index++) {
|
|
3609
1197
|
const char = source[index];
|
|
3610
|
-
if (char === "{")
|
|
3611
|
-
depth++;
|
|
1198
|
+
if (char === "{") depth++;
|
|
3612
1199
|
else if (char === "}") {
|
|
3613
1200
|
depth--;
|
|
3614
1201
|
if (depth === 0) {
|
|
@@ -3628,8 +1215,7 @@ function findFunctionBodyOpenBrace(source, searchFrom) {
|
|
|
3628
1215
|
if (parenMatch?.index !== void 0) {
|
|
3629
1216
|
candidates.push(searchFrom + parenMatch.index + parenMatch[0].length - 1);
|
|
3630
1217
|
}
|
|
3631
|
-
if (candidates.length === 0)
|
|
3632
|
-
return -1;
|
|
1218
|
+
if (candidates.length === 0) return -1;
|
|
3633
1219
|
return Math.min(...candidates);
|
|
3634
1220
|
}
|
|
3635
1221
|
function extractFunctionBody(source, functionName) {
|
|
@@ -3646,11 +1232,9 @@ function extractFunctionBody(source, functionName) {
|
|
|
3646
1232
|
];
|
|
3647
1233
|
for (const pattern of patterns) {
|
|
3648
1234
|
const match = pattern.exec(source);
|
|
3649
|
-
if (!match)
|
|
3650
|
-
continue;
|
|
1235
|
+
if (!match) continue;
|
|
3651
1236
|
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
3652
|
-
if (braceIndex === -1)
|
|
3653
|
-
continue;
|
|
1237
|
+
if (braceIndex === -1) continue;
|
|
3654
1238
|
return extractBalancedBlock(source, braceIndex);
|
|
3655
1239
|
}
|
|
3656
1240
|
return null;
|
|
@@ -3663,11 +1247,9 @@ function extractDefaultExportFunctionBody(source) {
|
|
|
3663
1247
|
];
|
|
3664
1248
|
for (const pattern of patterns) {
|
|
3665
1249
|
const match = pattern.exec(source);
|
|
3666
|
-
if (!match)
|
|
3667
|
-
continue;
|
|
1250
|
+
if (!match) continue;
|
|
3668
1251
|
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
3669
|
-
if (braceIndex === -1)
|
|
3670
|
-
continue;
|
|
1252
|
+
if (braceIndex === -1) continue;
|
|
3671
1253
|
return extractBalancedBlock(source, braceIndex);
|
|
3672
1254
|
}
|
|
3673
1255
|
return null;
|
|
@@ -3677,8 +1259,7 @@ function handlerBodyWritesAddMenuCatalog(body) {
|
|
|
3677
1259
|
}
|
|
3678
1260
|
function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
3679
1261
|
const sourceFile = resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage);
|
|
3680
|
-
if (!sourceFile)
|
|
3681
|
-
return null;
|
|
1262
|
+
if (!sourceFile) return null;
|
|
3682
1263
|
const source = fs.readFileSync(sourceFile, "utf-8");
|
|
3683
1264
|
if (isRelativeHandlerRef(handlerRef)) {
|
|
3684
1265
|
return extractDefaultExportFunctionBody(source) ?? extractFunctionBody(source, "setup") ?? extractFunctionBody(source, handlerRef);
|
|
@@ -3686,11 +1267,10 @@ function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
|
3686
1267
|
return extractFunctionBody(source, handlerRef);
|
|
3687
1268
|
}
|
|
3688
1269
|
function suggestionForCode(code) {
|
|
3689
|
-
if (!code)
|
|
3690
|
-
return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
1270
|
+
if (!code) return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3691
1271
|
return ADD_MENU_VALIDATION_SUGGESTIONS[code] ?? `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3692
1272
|
}
|
|
3693
|
-
function mapSchemaError(error, catalogPath) {
|
|
1273
|
+
function mapSchemaError$1(error, catalogPath) {
|
|
3694
1274
|
const code = error.code ?? "catalog-validation-error";
|
|
3695
1275
|
return {
|
|
3696
1276
|
type: "add-menu-catalog",
|
|
@@ -3717,8 +1297,7 @@ function pluginShipsAddMenuCatalog(context) {
|
|
|
3717
1297
|
);
|
|
3718
1298
|
}
|
|
3719
1299
|
function validateAddMenuAgentKitHandler(context, result) {
|
|
3720
|
-
if (!pluginShipsAddMenuCatalog(context))
|
|
3721
|
-
return;
|
|
1300
|
+
if (!pluginShipsAddMenuCatalog(context)) return;
|
|
3722
1301
|
const agentKitHandler = context.manifest.agentkit;
|
|
3723
1302
|
if (!agentKitHandler) {
|
|
3724
1303
|
result.warnings.push({
|
|
@@ -3730,15 +1309,13 @@ function validateAddMenuAgentKitHandler(context, result) {
|
|
|
3730
1309
|
});
|
|
3731
1310
|
}
|
|
3732
1311
|
const setupHandler = typeof context.manifest.setup === "string" ? context.manifest.setup : void 0;
|
|
3733
|
-
if (!setupHandler)
|
|
3734
|
-
return;
|
|
1312
|
+
if (!setupHandler) return;
|
|
3735
1313
|
const setupBody = resolveSetupHandlerFunctionBody(
|
|
3736
1314
|
context.pluginPath,
|
|
3737
1315
|
setupHandler,
|
|
3738
1316
|
context.isNpmPackage
|
|
3739
1317
|
);
|
|
3740
|
-
if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody))
|
|
3741
|
-
return;
|
|
1318
|
+
if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody)) return;
|
|
3742
1319
|
result.warnings.push({
|
|
3743
1320
|
type: "add-menu-catalog",
|
|
3744
1321
|
code: "add-menu-legacy-setup-handler",
|
|
@@ -3764,7 +1341,7 @@ async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
|
|
|
3764
1341
|
return;
|
|
3765
1342
|
}
|
|
3766
1343
|
const validated = validateAddMenuCatalogFile(parsed, relPath);
|
|
3767
|
-
result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
|
|
1344
|
+
result.errors.push(...validated.errors.map((error) => mapSchemaError$1(error, relPath)));
|
|
3768
1345
|
if (!validated.file?.items.length) {
|
|
3769
1346
|
return;
|
|
3770
1347
|
}
|
|
@@ -3780,11 +1357,177 @@ async function validateAddMenuCatalog(context, result) {
|
|
|
3780
1357
|
validateAddMenuAgentKitHandler(context, result);
|
|
3781
1358
|
for (const relPath of ADD_MENU_CATALOG_REL_PATHS) {
|
|
3782
1359
|
const catalogPath = path.join(context.pluginPath, relPath);
|
|
3783
|
-
if (!fs.existsSync(catalogPath))
|
|
3784
|
-
continue;
|
|
1360
|
+
if (!fs.existsSync(catalogPath)) continue;
|
|
3785
1361
|
await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
|
|
3786
1362
|
}
|
|
3787
1363
|
}
|
|
1364
|
+
function isRecord(value) {
|
|
1365
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1366
|
+
}
|
|
1367
|
+
function parseRequires(raw, filePath) {
|
|
1368
|
+
const errors = [];
|
|
1369
|
+
if (raw === void 0) {
|
|
1370
|
+
return { requires: [], errors };
|
|
1371
|
+
}
|
|
1372
|
+
if (!Array.isArray(raw)) {
|
|
1373
|
+
errors.push({
|
|
1374
|
+
path: `${filePath}.requires`,
|
|
1375
|
+
message: "requires must be an array",
|
|
1376
|
+
code: "settings-requires-type"
|
|
1377
|
+
});
|
|
1378
|
+
return { requires: [], errors };
|
|
1379
|
+
}
|
|
1380
|
+
const requires = [];
|
|
1381
|
+
raw.forEach((entry, index) => {
|
|
1382
|
+
if (!isRecord(entry)) {
|
|
1383
|
+
errors.push({
|
|
1384
|
+
path: `${filePath}.requires[${index}]`,
|
|
1385
|
+
message: "require entry must be an object",
|
|
1386
|
+
code: "settings-require-shape"
|
|
1387
|
+
});
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (typeof entry.plugin !== "string" || !entry.plugin.trim()) {
|
|
1391
|
+
errors.push({
|
|
1392
|
+
path: `${filePath}.requires[${index}].plugin`,
|
|
1393
|
+
message: "plugin is required",
|
|
1394
|
+
code: "settings-require-plugin"
|
|
1395
|
+
});
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (entry.status !== "configured") {
|
|
1399
|
+
errors.push({
|
|
1400
|
+
path: `${filePath}.requires[${index}].status`,
|
|
1401
|
+
message: 'status must be "configured"',
|
|
1402
|
+
code: "settings-require-status"
|
|
1403
|
+
});
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
requires.push({ plugin: entry.plugin.trim(), status: "configured" });
|
|
1407
|
+
});
|
|
1408
|
+
return { requires, errors };
|
|
1409
|
+
}
|
|
1410
|
+
function validateAiditorSettingsFile(raw, sourcePath) {
|
|
1411
|
+
const errors = [];
|
|
1412
|
+
if (!isRecord(raw)) {
|
|
1413
|
+
return {
|
|
1414
|
+
errors: [
|
|
1415
|
+
{
|
|
1416
|
+
path: sourcePath,
|
|
1417
|
+
message: "settings file must be a YAML object",
|
|
1418
|
+
code: "settings-root-type"
|
|
1419
|
+
}
|
|
1420
|
+
]
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
if (typeof raw.label !== "string" || !raw.label.trim()) {
|
|
1424
|
+
errors.push({
|
|
1425
|
+
path: `${sourcePath}.label`,
|
|
1426
|
+
message: "label is required",
|
|
1427
|
+
code: "settings-label"
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
if (typeof raw.route !== "string" || !raw.route.trim()) {
|
|
1431
|
+
errors.push({
|
|
1432
|
+
path: `${sourcePath}.route`,
|
|
1433
|
+
message: "route is required",
|
|
1434
|
+
code: "settings-route"
|
|
1435
|
+
});
|
|
1436
|
+
} else if (!raw.route.startsWith("/")) {
|
|
1437
|
+
errors.push({
|
|
1438
|
+
path: `${sourcePath}.route`,
|
|
1439
|
+
message: "route must start with /",
|
|
1440
|
+
code: "settings-route-format"
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
if (raw.pluginName !== void 0 && typeof raw.pluginName !== "string") {
|
|
1444
|
+
errors.push({
|
|
1445
|
+
path: `${sourcePath}.pluginName`,
|
|
1446
|
+
message: "pluginName must be a string",
|
|
1447
|
+
code: "settings-plugin-name-type"
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
const { requires, errors: requireErrors } = parseRequires(raw.requires, sourcePath);
|
|
1451
|
+
errors.push(...requireErrors);
|
|
1452
|
+
if (errors.length > 0) {
|
|
1453
|
+
return { errors };
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
file: {
|
|
1457
|
+
label: raw.label.trim(),
|
|
1458
|
+
route: raw.route.trim(),
|
|
1459
|
+
...typeof raw.pluginName === "string" && raw.pluginName.trim() ? { pluginName: raw.pluginName.trim() } : {},
|
|
1460
|
+
...requires.length > 0 ? { requires } : {}
|
|
1461
|
+
},
|
|
1462
|
+
errors: []
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
const AIDITOR_SETTINGS_TEMPLATE_REL_PATH = "agent-kit/aiditor/settings.template.yaml";
|
|
1466
|
+
function mapSchemaError(error, relPath) {
|
|
1467
|
+
return {
|
|
1468
|
+
type: "schema",
|
|
1469
|
+
message: error.message,
|
|
1470
|
+
location: error.path || relPath,
|
|
1471
|
+
code: error.code
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function validateSettingsTemplateAtPath(catalogPath, relPath, result, manifest) {
|
|
1475
|
+
let parsed;
|
|
1476
|
+
try {
|
|
1477
|
+
parsed = YAML.parse(fs.readFileSync(catalogPath, "utf-8"));
|
|
1478
|
+
} catch (err) {
|
|
1479
|
+
result.errors.push({
|
|
1480
|
+
type: "schema",
|
|
1481
|
+
message: `Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1482
|
+
location: relPath
|
|
1483
|
+
});
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
const validated = validateAiditorSettingsFile(parsed, relPath);
|
|
1487
|
+
result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
|
|
1488
|
+
if (!validated.file) {
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const routeEntry = manifest.routes?.find((route) => route.path === validated.file.route);
|
|
1492
|
+
if (!routeEntry) {
|
|
1493
|
+
result.warnings.push({
|
|
1494
|
+
type: "schema",
|
|
1495
|
+
message: `settings route "${validated.file.route}" is not declared in plugin.yaml routes[]`,
|
|
1496
|
+
location: relPath,
|
|
1497
|
+
code: "settings-route-missing",
|
|
1498
|
+
suggestion: "Add a matching routes[] entry or fix the route in settings.template.yaml"
|
|
1499
|
+
});
|
|
1500
|
+
} else if (routeEntry.devOnly !== true) {
|
|
1501
|
+
result.warnings.push({
|
|
1502
|
+
type: "schema",
|
|
1503
|
+
message: `settings route "${validated.file.route}" should declare devOnly: true on routes[]`,
|
|
1504
|
+
location: "plugin.yaml routes",
|
|
1505
|
+
code: "settings-route-dev-only",
|
|
1506
|
+
suggestion: "Add devOnly: true when the settings page is dev-server tooling (see Design Log #171)"
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
async function validateAiditorSettings(context, result) {
|
|
1511
|
+
const templatePath = path.join(context.pluginPath, AIDITOR_SETTINGS_TEMPLATE_REL_PATH);
|
|
1512
|
+
if (!fs.existsSync(templatePath)) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
validateSettingsTemplateAtPath(
|
|
1516
|
+
templatePath,
|
|
1517
|
+
AIDITOR_SETTINGS_TEMPLATE_REL_PATH,
|
|
1518
|
+
result,
|
|
1519
|
+
context.manifest
|
|
1520
|
+
);
|
|
1521
|
+
if (!context.manifest.agentkit) {
|
|
1522
|
+
result.warnings.push({
|
|
1523
|
+
type: "schema",
|
|
1524
|
+
message: "Plugin ships agent-kit/aiditor/settings.template.yaml but has no agentkit handler",
|
|
1525
|
+
location: "plugin.yaml",
|
|
1526
|
+
code: "settings-missing-agentkit-handler",
|
|
1527
|
+
suggestion: "Declare agentkit in plugin.yaml and materialize to agent-kit/aiditor/settings/<plugin>.yaml on jay-stack agent-kit — see agent-kit/plugin/aiditor-settings-guide.md"
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
3788
1531
|
async function validatePlugin(options = {}) {
|
|
3789
1532
|
const pluginPath = options.pluginPath || process.cwd();
|
|
3790
1533
|
if (options.local) {
|
|
@@ -3852,6 +1595,7 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
3852
1595
|
await validateDynamicContracts(context, result);
|
|
3853
1596
|
}
|
|
3854
1597
|
await validateAddMenuCatalog(context, result);
|
|
1598
|
+
await validateAiditorSettings(context, result);
|
|
3855
1599
|
result.valid = result.errors.length === 0;
|
|
3856
1600
|
return result;
|
|
3857
1601
|
}
|
|
@@ -4161,6 +1905,13 @@ async function validateSchema(context, result) {
|
|
|
4161
1905
|
if (route.css) {
|
|
4162
1906
|
validateDocFile(route.css, `route "${route.path}" css`, context, result);
|
|
4163
1907
|
}
|
|
1908
|
+
if (route.devOnly !== void 0 && typeof route.devOnly !== "boolean") {
|
|
1909
|
+
result.errors.push({
|
|
1910
|
+
type: "schema",
|
|
1911
|
+
message: `Route "${route.path}" devOnly must be a boolean`,
|
|
1912
|
+
location: "plugin.yaml"
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
4164
1915
|
});
|
|
4165
1916
|
}
|
|
4166
1917
|
}
|
|
@@ -4230,16 +1981,14 @@ async function validateSchema(context, result) {
|
|
|
4230
1981
|
}
|
|
4231
1982
|
function checkExportExists(exportName, context) {
|
|
4232
1983
|
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
4233
|
-
if (!fs.existsSync(packageJsonPath))
|
|
4234
|
-
return true;
|
|
1984
|
+
if (!fs.existsSync(packageJsonPath)) return true;
|
|
4235
1985
|
let mainPath;
|
|
4236
1986
|
try {
|
|
4237
1987
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
4238
1988
|
if (packageJson.exports?.["."]) {
|
|
4239
1989
|
const entry = packageJson.exports["."];
|
|
4240
1990
|
const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
|
|
4241
|
-
if (entryPath)
|
|
4242
|
-
mainPath = path.join(context.pluginPath, entryPath);
|
|
1991
|
+
if (entryPath) mainPath = path.join(context.pluginPath, entryPath);
|
|
4243
1992
|
}
|
|
4244
1993
|
if (!mainPath && packageJson.main) {
|
|
4245
1994
|
mainPath = path.join(context.pluginPath, packageJson.main);
|
|
@@ -4247,8 +1996,7 @@ function checkExportExists(exportName, context) {
|
|
|
4247
1996
|
} catch {
|
|
4248
1997
|
return true;
|
|
4249
1998
|
}
|
|
4250
|
-
if (!mainPath || !fs.existsSync(mainPath))
|
|
4251
|
-
return true;
|
|
1999
|
+
if (!mainPath || !fs.existsSync(mainPath)) return true;
|
|
4252
2000
|
try {
|
|
4253
2001
|
const content = fs.readFileSync(mainPath, "utf-8");
|
|
4254
2002
|
const patterns = [
|
|
@@ -4308,8 +2056,7 @@ function resolveContractFile(contractSpec, context) {
|
|
|
4308
2056
|
const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
4309
2057
|
if (resolvedPath) {
|
|
4310
2058
|
const fullPath = path.join(context.pluginPath, resolvedPath);
|
|
4311
|
-
if (fs.existsSync(fullPath))
|
|
4312
|
-
return fullPath;
|
|
2059
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
4313
2060
|
}
|
|
4314
2061
|
}
|
|
4315
2062
|
}
|
|
@@ -4318,8 +2065,7 @@ function resolveContractFile(contractSpec, context) {
|
|
|
4318
2065
|
}
|
|
4319
2066
|
for (const dir of ["dist", "lib", ""]) {
|
|
4320
2067
|
const candidate = path.join(context.pluginPath, dir, contractSpec);
|
|
4321
|
-
if (fs.existsSync(candidate))
|
|
4322
|
-
return candidate;
|
|
2068
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
4323
2069
|
}
|
|
4324
2070
|
return void 0;
|
|
4325
2071
|
} else {
|
|
@@ -4407,8 +2153,7 @@ function hasExportModifier(node) {
|
|
|
4407
2153
|
function resolveModulePath(basePath) {
|
|
4408
2154
|
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
4409
2155
|
const candidate = basePath + ext;
|
|
4410
|
-
if (fs.existsSync(candidate))
|
|
4411
|
-
return candidate;
|
|
2156
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
4412
2157
|
}
|
|
4413
2158
|
return void 0;
|
|
4414
2159
|
}
|
|
@@ -4418,10 +2163,8 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
4418
2163
|
const entryFile = resolveModulePath(entryBase);
|
|
4419
2164
|
const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
4420
2165
|
const sourceEntry = entryFile || libEntryFile;
|
|
4421
|
-
if (!sourceEntry)
|
|
4422
|
-
|
|
4423
|
-
if (!sourceEntry.endsWith(".ts"))
|
|
4424
|
-
return void 0;
|
|
2166
|
+
if (!sourceEntry) return void 0;
|
|
2167
|
+
if (!sourceEntry.endsWith(".ts")) return void 0;
|
|
4425
2168
|
let sourceCode;
|
|
4426
2169
|
try {
|
|
4427
2170
|
sourceCode = fs.readFileSync(sourceEntry, "utf-8");
|
|
@@ -4437,12 +2180,9 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
4437
2180
|
);
|
|
4438
2181
|
const starReexportModules = [];
|
|
4439
2182
|
for (const statement of sourceFile.statements) {
|
|
4440
|
-
if (!u.isExportDeclaration(statement))
|
|
4441
|
-
|
|
4442
|
-
if (!statement.moduleSpecifier)
|
|
4443
|
-
continue;
|
|
4444
|
-
if (!u.isStringLiteral(statement.moduleSpecifier))
|
|
4445
|
-
continue;
|
|
2183
|
+
if (!u.isExportDeclaration(statement)) continue;
|
|
2184
|
+
if (!statement.moduleSpecifier) continue;
|
|
2185
|
+
if (!u.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
4446
2186
|
const moduleSpec = statement.moduleSpecifier.text;
|
|
4447
2187
|
const exportClause = statement.exportClause;
|
|
4448
2188
|
if (!exportClause) {
|
|
@@ -4460,12 +2200,10 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
4460
2200
|
}
|
|
4461
2201
|
}
|
|
4462
2202
|
for (const moduleSpec of starReexportModules) {
|
|
4463
|
-
if (!moduleSpec.startsWith("."))
|
|
4464
|
-
continue;
|
|
2203
|
+
if (!moduleSpec.startsWith(".")) continue;
|
|
4465
2204
|
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
4466
2205
|
const resolvedPath = resolveModulePath(resolvedBase);
|
|
4467
|
-
if (!resolvedPath || !resolvedPath.endsWith(".ts"))
|
|
4468
|
-
continue;
|
|
2206
|
+
if (!resolvedPath || !resolvedPath.endsWith(".ts")) continue;
|
|
4469
2207
|
try {
|
|
4470
2208
|
const modSource = fs.readFileSync(resolvedPath, "utf-8");
|
|
4471
2209
|
const modFile = u.createSourceFile(
|
|
@@ -4498,16 +2236,12 @@ function resolveContractPath(contract, context) {
|
|
|
4498
2236
|
}
|
|
4499
2237
|
async function checkComponentContractConsistency(contract, context, result) {
|
|
4500
2238
|
const componentName = contract.component;
|
|
4501
|
-
if (!componentName)
|
|
4502
|
-
return;
|
|
2239
|
+
if (!componentName) return;
|
|
4503
2240
|
const sourcePath = resolveComponentSourcePath(componentName, context);
|
|
4504
|
-
if (!sourcePath)
|
|
4505
|
-
|
|
4506
|
-
if (!sourcePath.endsWith(".ts"))
|
|
4507
|
-
return;
|
|
2241
|
+
if (!sourcePath) return;
|
|
2242
|
+
if (!sourcePath.endsWith(".ts")) return;
|
|
4508
2243
|
const contractPath = resolveContractPath(contract, context);
|
|
4509
|
-
if (!contractPath)
|
|
4510
|
-
return;
|
|
2244
|
+
if (!contractPath) return;
|
|
4511
2245
|
let contractContent;
|
|
4512
2246
|
try {
|
|
4513
2247
|
contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
@@ -4515,8 +2249,7 @@ async function checkComponentContractConsistency(contract, context, result) {
|
|
|
4515
2249
|
return;
|
|
4516
2250
|
}
|
|
4517
2251
|
const parsed = parseContract(contractContent, path.basename(contractPath));
|
|
4518
|
-
if (parsed.validations.length > 0)
|
|
4519
|
-
return;
|
|
2252
|
+
if (parsed.validations.length > 0) return;
|
|
4520
2253
|
let sourceCode;
|
|
4521
2254
|
try {
|
|
4522
2255
|
sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
|
|
@@ -4635,8 +2368,7 @@ async function validatePackageJson(context, result) {
|
|
|
4635
2368
|
}
|
|
4636
2369
|
async function validateDynamicContracts(context, result) {
|
|
4637
2370
|
const { dynamic_contracts } = context.manifest;
|
|
4638
|
-
if (!dynamic_contracts)
|
|
4639
|
-
return;
|
|
2371
|
+
if (!dynamic_contracts) return;
|
|
4640
2372
|
const dynamicConfigs = Array.isArray(dynamic_contracts) ? dynamic_contracts : [dynamic_contracts];
|
|
4641
2373
|
for (const config of dynamicConfigs) {
|
|
4642
2374
|
const prefix = config.prefix || "(unknown)";
|
|
@@ -4715,8 +2447,7 @@ function extractExpressions(text) {
|
|
|
4715
2447
|
function extractTagPath(expr) {
|
|
4716
2448
|
let cleaned = expr.replace(/^!/, "").trim();
|
|
4717
2449
|
cleaned = cleaned.split(/\s*[!=]==?\s*/)[0].trim();
|
|
4718
|
-
if (cleaned === "." || cleaned === "")
|
|
4719
|
-
return null;
|
|
2450
|
+
if (cleaned === "." || cleaned === "") return null;
|
|
4720
2451
|
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(cleaned)) {
|
|
4721
2452
|
return cleaned;
|
|
4722
2453
|
}
|
|
@@ -4815,8 +2546,7 @@ function collectUsedTags(jayHtml) {
|
|
|
4815
2546
|
const ifVal = element.getAttribute?.("if");
|
|
4816
2547
|
if (ifVal) {
|
|
4817
2548
|
const ifPath = extractTagPath(ifVal);
|
|
4818
|
-
if (ifPath)
|
|
4819
|
-
resolvePath(ifPath, scopes);
|
|
2549
|
+
if (ifPath) resolvePath(ifPath, scopes);
|
|
4820
2550
|
}
|
|
4821
2551
|
const refVal = element.getAttribute?.("ref");
|
|
4822
2552
|
if (refVal) {
|
|
@@ -4824,12 +2554,10 @@ function collectUsedTags(jayHtml) {
|
|
|
4824
2554
|
}
|
|
4825
2555
|
const attrs = element.attributes ?? {};
|
|
4826
2556
|
for (const [name, value] of Object.entries(attrs)) {
|
|
4827
|
-
if (SKIP_ATTRS.has(name))
|
|
4828
|
-
continue;
|
|
2557
|
+
if (SKIP_ATTRS.has(name)) continue;
|
|
4829
2558
|
for (const expr of extractExpressions(value)) {
|
|
4830
2559
|
const p = extractTagPath(expr);
|
|
4831
|
-
if (p)
|
|
4832
|
-
resolvePath(p, scopes);
|
|
2560
|
+
if (p) resolvePath(p, scopes);
|
|
4833
2561
|
}
|
|
4834
2562
|
}
|
|
4835
2563
|
for (const child of element.childNodes ?? []) {
|
|
@@ -4837,8 +2565,7 @@ function collectUsedTags(jayHtml) {
|
|
|
4837
2565
|
const text = child.rawText ?? child.text ?? "";
|
|
4838
2566
|
for (const expr of extractExpressions(text)) {
|
|
4839
2567
|
const p = extractTagPath(expr);
|
|
4840
|
-
if (p)
|
|
4841
|
-
resolvePath(p, childScopes);
|
|
2568
|
+
if (p) resolvePath(p, childScopes);
|
|
4842
2569
|
}
|
|
4843
2570
|
} else if (child.nodeType === 1) {
|
|
4844
2571
|
walkElement(child, childScopes);
|
|
@@ -4851,14 +2578,12 @@ function collectUsedTags(jayHtml) {
|
|
|
4851
2578
|
function analyzeTagCoverage(jayHtml, file) {
|
|
4852
2579
|
const imports = jayHtml.headlessImports;
|
|
4853
2580
|
const withContracts = imports.filter((imp) => imp.contract);
|
|
4854
|
-
if (withContracts.length === 0)
|
|
4855
|
-
return null;
|
|
2581
|
+
if (withContracts.length === 0) return null;
|
|
4856
2582
|
const usedTagsMap = collectUsedTags(jayHtml);
|
|
4857
2583
|
const contracts = [];
|
|
4858
2584
|
for (let i = 0; i < imports.length; i++) {
|
|
4859
2585
|
const imp = imports[i];
|
|
4860
|
-
if (!imp.contract)
|
|
4861
|
-
continue;
|
|
2586
|
+
if (!imp.contract) continue;
|
|
4862
2587
|
const allTags = flattenContractTags(imp.contract.tags);
|
|
4863
2588
|
const usedSet = usedTagsMap.get(i) ?? /* @__PURE__ */ new Set();
|
|
4864
2589
|
const expanded = new Set(usedSet);
|
|
@@ -4887,12 +2612,9 @@ function resolveContractTag(contract, tagPath) {
|
|
|
4887
2612
|
let tags = contract.tags;
|
|
4888
2613
|
for (let i = 0; i < segments.length; i++) {
|
|
4889
2614
|
const tag = tags.find((t) => t.tag === segments[i]);
|
|
4890
|
-
if (!tag)
|
|
4891
|
-
|
|
4892
|
-
if (
|
|
4893
|
-
return tag;
|
|
4894
|
-
if (!tag.tags)
|
|
4895
|
-
return void 0;
|
|
2615
|
+
if (!tag) return void 0;
|
|
2616
|
+
if (i === segments.length - 1) return tag;
|
|
2617
|
+
if (!tag.tags) return void 0;
|
|
4896
2618
|
tags = tag.tags;
|
|
4897
2619
|
}
|
|
4898
2620
|
return void 0;
|
|
@@ -4924,17 +2646,13 @@ function checkRefElementTypes(jayHtml, file) {
|
|
|
4924
2646
|
importIndex = scope.importIndex;
|
|
4925
2647
|
tagPath = scope.prefix ? `${scope.prefix}.${refPath}` : refPath;
|
|
4926
2648
|
}
|
|
4927
|
-
if (importIndex === void 0)
|
|
4928
|
-
return;
|
|
2649
|
+
if (importIndex === void 0) return;
|
|
4929
2650
|
const imp = imports[importIndex];
|
|
4930
|
-
if (!imp.contract)
|
|
4931
|
-
return;
|
|
2651
|
+
if (!imp.contract) return;
|
|
4932
2652
|
const contractTag = resolveContractTag(imp.contract, tagPath);
|
|
4933
|
-
if (!contractTag || !contractTag.elementType)
|
|
4934
|
-
return;
|
|
2653
|
+
if (!contractTag || !contractTag.elementType) return;
|
|
4935
2654
|
const contractTypes = contractTag.elementType;
|
|
4936
|
-
if (contractTypes.includes("HTMLElement"))
|
|
4937
|
-
return;
|
|
2655
|
+
if (contractTypes.includes("HTMLElement")) return;
|
|
4938
2656
|
if (!contractTypes.includes(ref.actualType)) {
|
|
4939
2657
|
const label = imp.key ? `${imp.key}.${tagPath}` : tagPath;
|
|
4940
2658
|
warnings.push(
|
|
@@ -5044,8 +2762,7 @@ function checkRouteParams(parsedFile, filePath, pagesBase) {
|
|
|
5044
2762
|
collectParams(imp.contract.params);
|
|
5045
2763
|
}
|
|
5046
2764
|
}
|
|
5047
|
-
if (requiredParams.size === 0)
|
|
5048
|
-
return [];
|
|
2765
|
+
if (requiredParams.size === 0) return [];
|
|
5049
2766
|
const routeParams = extractRouteParams(filePath, pagesBase);
|
|
5050
2767
|
const headlessProps = extractHeadlessPropsParamNames(parsedFile);
|
|
5051
2768
|
const availableParams = /* @__PURE__ */ new Set([...routeParams, ...headlessProps]);
|
|
@@ -5061,11 +2778,9 @@ function checkRouteParams(parsedFile, filePath, pagesBase) {
|
|
|
5061
2778
|
}
|
|
5062
2779
|
function checkRouteToContractParams(parsedFile, filePath, pagesBase) {
|
|
5063
2780
|
const routeParams = extractRouteParams(filePath, pagesBase);
|
|
5064
|
-
if (routeParams.size === 0)
|
|
5065
|
-
return [];
|
|
2781
|
+
if (routeParams.size === 0) return [];
|
|
5066
2782
|
const hasAnyContract = !!parsedFile.contract || parsedFile.headlessImports.some((imp) => !!imp.contract);
|
|
5067
|
-
if (!hasAnyContract)
|
|
5068
|
-
return [];
|
|
2783
|
+
if (!hasAnyContract) return [];
|
|
5069
2784
|
const declaredParams = /* @__PURE__ */ new Set();
|
|
5070
2785
|
if (parsedFile.contract?.params) {
|
|
5071
2786
|
for (const p of parsedFile.contract.params) {
|
|
@@ -5117,17 +2832,14 @@ function resolveBindingPhase(bindingPath, jayHtml) {
|
|
|
5117
2832
|
const keyedImport = jayHtml.headlessImports.find((i) => i.key === root && i.contract);
|
|
5118
2833
|
if (keyedImport?.contract) {
|
|
5119
2834
|
const tagPath = segments.slice(1).join(".");
|
|
5120
|
-
if (!tagPath)
|
|
5121
|
-
return void 0;
|
|
2835
|
+
if (!tagPath) return void 0;
|
|
5122
2836
|
const tag = resolveContractTag(keyedImport.contract, tagPath);
|
|
5123
|
-
if (!tag)
|
|
5124
|
-
return void 0;
|
|
2837
|
+
if (!tag) return void 0;
|
|
5125
2838
|
return tag.phase || "slow";
|
|
5126
2839
|
}
|
|
5127
2840
|
if (jayHtml.contract) {
|
|
5128
2841
|
const tag = resolveContractTag(jayHtml.contract, bindingPath);
|
|
5129
|
-
if (!tag)
|
|
5130
|
-
return void 0;
|
|
2842
|
+
if (!tag) return void 0;
|
|
5131
2843
|
return tag.phase || "slow";
|
|
5132
2844
|
}
|
|
5133
2845
|
return void 0;
|
|
@@ -5178,15 +2890,12 @@ function checkHeadlessInstanceProps(jayHtml, file) {
|
|
|
5178
2890
|
}
|
|
5179
2891
|
for (const contractProp of contract.props) {
|
|
5180
2892
|
const attrValue = lowerAttrs[contractProp.name.toLowerCase()];
|
|
5181
|
-
if (!attrValue)
|
|
5182
|
-
continue;
|
|
2893
|
+
if (!attrValue) continue;
|
|
5183
2894
|
const bindingMatch = attrValue.match(/^\{(.+)\}$/);
|
|
5184
|
-
if (!bindingMatch)
|
|
5185
|
-
continue;
|
|
2895
|
+
if (!bindingMatch) continue;
|
|
5186
2896
|
const bindingPath = bindingMatch[1];
|
|
5187
2897
|
const sourcePhase = resolveBindingPhase(bindingPath, jayHtml);
|
|
5188
|
-
if (!sourcePhase)
|
|
5189
|
-
continue;
|
|
2898
|
+
if (!sourcePhase) continue;
|
|
5190
2899
|
const propPhase = contractProp.phase ?? "slow";
|
|
5191
2900
|
const sourceOrder = PHASE_ORDER[sourcePhase] ?? 0;
|
|
5192
2901
|
const propOrder = PHASE_ORDER[propPhase] ?? 0;
|
|
@@ -5224,17 +2933,15 @@ function resolveLinkedTags(tags, contractDir) {
|
|
|
5224
2933
|
});
|
|
5225
2934
|
}
|
|
5226
2935
|
function resolveContractLinks(contract, contractPath) {
|
|
5227
|
-
if (!contractPath)
|
|
5228
|
-
return contract;
|
|
2936
|
+
if (!contractPath) return contract;
|
|
5229
2937
|
const contractDir = path.dirname(contractPath);
|
|
5230
2938
|
return { ...contract, tags: resolveLinkedTags(contract.tags, contractDir) };
|
|
5231
2939
|
}
|
|
5232
2940
|
async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
5233
|
-
const scannedPlugins = await scanPlugins
|
|
2941
|
+
const scannedPlugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
5234
2942
|
const loadedValidators = [];
|
|
5235
2943
|
for (const [, plugin] of scannedPlugins) {
|
|
5236
|
-
if (!plugin.manifest.validators)
|
|
5237
|
-
continue;
|
|
2944
|
+
if (!plugin.manifest.validators) continue;
|
|
5238
2945
|
for (const validatorDef of plugin.manifest.validators) {
|
|
5239
2946
|
const source = `${plugin.name}/${validatorDef.name}`;
|
|
5240
2947
|
let validatorFn;
|
|
@@ -5354,12 +3061,18 @@ async function validateJayFiles(options = {}) {
|
|
|
5354
3061
|
const resolvedConfig = getConfigWithDefaults(config);
|
|
5355
3062
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
5356
3063
|
const scanDir = options.path ? path.resolve(options.path) : path.resolve(resolvedConfig.devServer.pagesBase);
|
|
3064
|
+
const componentsDir = path.resolve(resolvedConfig.devServer.componentsBase);
|
|
5357
3065
|
const errors = [];
|
|
5358
3066
|
const warnings = [];
|
|
5359
3067
|
const coverage = [];
|
|
5360
3068
|
const parsedFiles = [];
|
|
5361
|
-
const
|
|
5362
|
-
const
|
|
3069
|
+
const pageJayHtmlFiles = await findJayFiles(scanDir);
|
|
3070
|
+
const componentJayHtmlFiles = await findJayFiles(componentsDir).catch(() => []);
|
|
3071
|
+
const jayHtmlFiles = [...pageJayHtmlFiles, ...componentJayHtmlFiles];
|
|
3072
|
+
const contractFiles = [
|
|
3073
|
+
...await findContractFiles(scanDir),
|
|
3074
|
+
...await findContractFiles(componentsDir).catch(() => [])
|
|
3075
|
+
];
|
|
5363
3076
|
if (options.verbose) {
|
|
5364
3077
|
getLogger().info(chalk.gray(`Scanning directory: ${scanDir}`));
|
|
5365
3078
|
getLogger().info(chalk.gray(`Found ${jayHtmlFiles.length} .jay-html files`));
|
|
@@ -5607,6 +3320,14 @@ function printJayValidationResult(result, options) {
|
|
|
5607
3320
|
} else {
|
|
5608
3321
|
logger.important(chalk.red(`Validation failed — ${result.errors.length} error(s).`));
|
|
5609
3322
|
}
|
|
3323
|
+
const totalIssues = result.errors.length + result.warnings.length + result.coverage.length;
|
|
3324
|
+
if (totalIssues > 0) {
|
|
3325
|
+
logger.important(
|
|
3326
|
+
chalk.gray(
|
|
3327
|
+
"\nSee: agent-kit/designer/validation-guide.md for how to interpret and suppress warnings."
|
|
3328
|
+
)
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
5610
3331
|
}
|
|
5611
3332
|
async function runValidate(scanPath, options) {
|
|
5612
3333
|
const result = await validateJayFiles({
|
|
@@ -5799,8 +3520,7 @@ async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
|
5799
3520
|
const sharedDirs = ["contracts"];
|
|
5800
3521
|
for (const dir of sharedDirs) {
|
|
5801
3522
|
const srcDir = path$1.join(templateDir, dir);
|
|
5802
|
-
if (!fsSync.existsSync(srcDir))
|
|
5803
|
-
continue;
|
|
3523
|
+
if (!fsSync.existsSync(srcDir)) continue;
|
|
5804
3524
|
await copyDirRecursive(srcDir, path$1.join(agentKitDir, dir));
|
|
5805
3525
|
getLogger().info(chalk.gray(` Created agent-kit/${dir}/`));
|
|
5806
3526
|
}
|
|
@@ -5819,14 +3539,13 @@ async function copyDirRecursive(src, dest) {
|
|
|
5819
3539
|
}
|
|
5820
3540
|
}
|
|
5821
3541
|
async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
5822
|
-
const plugins = await scanPlugins
|
|
3542
|
+
const plugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
5823
3543
|
const agentKitDir = path$1.join(projectRoot, "agent-kit");
|
|
5824
3544
|
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
5825
3545
|
const copiedPerRole = /* @__PURE__ */ new Map();
|
|
5826
3546
|
for (const [, plugin] of plugins) {
|
|
5827
3547
|
const pluginAgentKitDir = path$1.join(plugin.pluginPath, "agent-kit");
|
|
5828
|
-
if (!fsSync.existsSync(pluginAgentKitDir))
|
|
5829
|
-
continue;
|
|
3548
|
+
if (!fsSync.existsSync(pluginAgentKitDir)) continue;
|
|
5830
3549
|
for (const role of roles) {
|
|
5831
3550
|
const roleSourceDir = path$1.join(pluginAgentKitDir, role);
|
|
5832
3551
|
let files;
|
|
@@ -5837,8 +3556,7 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
5837
3556
|
} catch {
|
|
5838
3557
|
continue;
|
|
5839
3558
|
}
|
|
5840
|
-
if (files.length === 0)
|
|
5841
|
-
continue;
|
|
3559
|
+
if (files.length === 0) continue;
|
|
5842
3560
|
const roleOutputDir = path$1.join(agentKitDir, role);
|
|
5843
3561
|
await fs$1.mkdir(roleOutputDir, { recursive: true });
|
|
5844
3562
|
for (const filename of files) {
|
|
@@ -5861,8 +3579,7 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
5861
3579
|
}
|
|
5862
3580
|
} catch {
|
|
5863
3581
|
}
|
|
5864
|
-
if (!copiedPerRole.has(role))
|
|
5865
|
-
copiedPerRole.set(role, []);
|
|
3582
|
+
if (!copiedPerRole.has(role)) copiedPerRole.set(role, []);
|
|
5866
3583
|
copiedPerRole.get(role).push({ filename, pluginName: plugin.name, description });
|
|
5867
3584
|
getLogger().info(
|
|
5868
3585
|
chalk.gray(
|
|
@@ -5874,8 +3591,7 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
5874
3591
|
}
|
|
5875
3592
|
for (const [role, entries] of copiedPerRole) {
|
|
5876
3593
|
const instructionsPath = path$1.join(agentKitDir, role, "INSTRUCTIONS.md");
|
|
5877
|
-
if (!fsSync.existsSync(instructionsPath))
|
|
5878
|
-
continue;
|
|
3594
|
+
if (!fsSync.existsSync(instructionsPath)) continue;
|
|
5879
3595
|
const lines = [
|
|
5880
3596
|
"",
|
|
5881
3597
|
"## Plugin-Contributed Guides",
|
|
@@ -5897,8 +3613,7 @@ async function generatePluginAgentKit(projectRoot, options, initErrors, viteServ
|
|
|
5897
3613
|
verbose: options.verbose,
|
|
5898
3614
|
pluginFilter: options.plugin
|
|
5899
3615
|
});
|
|
5900
|
-
if (plugins.length === 0)
|
|
5901
|
-
return;
|
|
3616
|
+
if (plugins.length === 0) return;
|
|
5902
3617
|
const logger = getLogger();
|
|
5903
3618
|
logger.important("");
|
|
5904
3619
|
logger.important(chalk.bold("Generating plugin agent-kit data..."));
|
|
@@ -6127,20 +3842,17 @@ function createAnswersFilePrompt(answers, pluginName) {
|
|
|
6127
3842
|
return {
|
|
6128
3843
|
async input(options) {
|
|
6129
3844
|
const value = answers[options.key];
|
|
6130
|
-
if (value !== void 0)
|
|
6131
|
-
return value;
|
|
3845
|
+
if (value !== void 0) return value;
|
|
6132
3846
|
throw new SetupNeedsAnswerError(pluginName, options.key, "input", options.message);
|
|
6133
3847
|
},
|
|
6134
3848
|
async confirm(options) {
|
|
6135
3849
|
const value = answers[options.key];
|
|
6136
|
-
if (value !== void 0)
|
|
6137
|
-
return value === "true" || value === "yes";
|
|
3850
|
+
if (value !== void 0) return value === "true" || value === "yes";
|
|
6138
3851
|
throw new SetupNeedsAnswerError(pluginName, options.key, "confirm", options.message);
|
|
6139
3852
|
},
|
|
6140
3853
|
async select(options) {
|
|
6141
3854
|
const value = answers[options.key];
|
|
6142
|
-
if (value !== void 0)
|
|
6143
|
-
return value;
|
|
3855
|
+
if (value !== void 0) return value;
|
|
6144
3856
|
throw new SetupNeedsAnswerError(
|
|
6145
3857
|
pluginName,
|
|
6146
3858
|
options.key,
|
|
@@ -6170,19 +3882,17 @@ function createDefaultPrompt(pluginName) {
|
|
|
6170
3882
|
}
|
|
6171
3883
|
};
|
|
6172
3884
|
}
|
|
6173
|
-
async function runSetup(pluginFilter, options, projectRoot
|
|
3885
|
+
async function runSetup(pluginFilter, options, projectRoot) {
|
|
6174
3886
|
let viteServer;
|
|
6175
3887
|
try {
|
|
6176
3888
|
const logger = getLogger();
|
|
6177
|
-
const path2 = await import("node:path");
|
|
6178
3889
|
const jayConfig = loadConfig();
|
|
6179
|
-
const configDir =
|
|
3890
|
+
const configDir = path$1.resolve(projectRoot, jayConfig.devServer?.configBase || "./config");
|
|
6180
3891
|
logger.important(chalk.bold("\n🔧 Setting up plugins...\n"));
|
|
6181
3892
|
if (options.verbose) {
|
|
6182
3893
|
logger.info("Starting Vite for TypeScript support...");
|
|
6183
3894
|
}
|
|
6184
3895
|
viteServer = await createViteForCli({ projectRoot });
|
|
6185
|
-
const { discoverPluginsWithSetup, executePluginSetup } = await import("@jay-framework/stack-server-runtime");
|
|
6186
3896
|
const pluginsWithSetup = await discoverPluginsWithSetup({
|
|
6187
3897
|
projectRoot,
|
|
6188
3898
|
verbose: options.verbose,
|
|
@@ -6203,12 +3913,9 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6203
3913
|
`Found ${pluginsWithSetup.length} plugin(s) with setup: ${pluginsWithSetup.map((p) => p.name).join(", ")}`
|
|
6204
3914
|
);
|
|
6205
3915
|
}
|
|
6206
|
-
const
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
logger.info(chalk.yellow(`⚠️ ${name} init error: ${err.message}`));
|
|
6210
|
-
}
|
|
6211
|
-
}
|
|
3916
|
+
const allPluginsWithInit = sortPluginsByDependencies(
|
|
3917
|
+
await discoverPluginsWithInit({ projectRoot, verbose: options.verbose })
|
|
3918
|
+
);
|
|
6212
3919
|
const interactive = options.interactive === true;
|
|
6213
3920
|
let answersMap;
|
|
6214
3921
|
if (options.answers) {
|
|
@@ -6230,14 +3937,13 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6230
3937
|
force: options.force ?? false,
|
|
6231
3938
|
interactive,
|
|
6232
3939
|
prompt,
|
|
6233
|
-
initError:
|
|
3940
|
+
initError: void 0,
|
|
6234
3941
|
viteServer,
|
|
6235
3942
|
verbose: options.verbose
|
|
6236
3943
|
});
|
|
6237
3944
|
switch (result.status) {
|
|
6238
3945
|
case "configured":
|
|
6239
3946
|
configured++;
|
|
6240
|
-
logger.important(chalk.green(" ✅ Services verified"));
|
|
6241
3947
|
if (result.configCreated?.length) {
|
|
6242
3948
|
for (const cfg of result.configCreated) {
|
|
6243
3949
|
logger.important(chalk.green(` ✅ Created ${cfg}`));
|
|
@@ -6246,6 +3952,7 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6246
3952
|
if (result.message) {
|
|
6247
3953
|
logger.important(chalk.gray(` ${result.message}`));
|
|
6248
3954
|
}
|
|
3955
|
+
await initPlugin(plugin.name, allPluginsWithInit, viteServer, logger);
|
|
6249
3956
|
break;
|
|
6250
3957
|
case "needs-config":
|
|
6251
3958
|
needsConfig++;
|
|
@@ -6304,13 +4011,11 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6304
4011
|
}
|
|
6305
4012
|
logger.important("");
|
|
6306
4013
|
}
|
|
4014
|
+
await runProjectInit(projectRoot, viteServer);
|
|
6307
4015
|
const parts = [];
|
|
6308
|
-
if (configured > 0)
|
|
6309
|
-
|
|
6310
|
-
if (
|
|
6311
|
-
parts.push(`${needsConfig} needs config`);
|
|
6312
|
-
if (errors > 0)
|
|
6313
|
-
parts.push(`${errors} error(s)`);
|
|
4016
|
+
if (configured > 0) parts.push(`${configured} configured`);
|
|
4017
|
+
if (needsConfig > 0) parts.push(`${needsConfig} needs config`);
|
|
4018
|
+
if (errors > 0) parts.push(`${errors} error(s)`);
|
|
6314
4019
|
logger.important(`Setup complete: ${parts.join(", ")}`);
|
|
6315
4020
|
if (errors > 0) {
|
|
6316
4021
|
process.exit(1);
|
|
@@ -6327,6 +4032,35 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6327
4032
|
}
|
|
6328
4033
|
}
|
|
6329
4034
|
}
|
|
4035
|
+
async function initPlugin(pluginName, allPluginsWithInit, viteServer, logger) {
|
|
4036
|
+
const pluginInit = allPluginsWithInit.filter((p) => p.name === pluginName);
|
|
4037
|
+
if (pluginInit.length === 0) return;
|
|
4038
|
+
const initErrors = await executePluginServerInits(pluginInit, viteServer, false, true);
|
|
4039
|
+
if (initErrors.size > 0) {
|
|
4040
|
+
for (const [, err] of initErrors) {
|
|
4041
|
+
logger.important(chalk.yellow(` ⚠️ Init after setup: ${err.message}`));
|
|
4042
|
+
}
|
|
4043
|
+
} else {
|
|
4044
|
+
logger.important(chalk.green(` ✅ Services initialized`));
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
async function runProjectInit(projectRoot, viteServer) {
|
|
4048
|
+
try {
|
|
4049
|
+
const initPathTs = path$1.join(projectRoot, "src", "init.ts");
|
|
4050
|
+
const initPathJs = path$1.join(projectRoot, "src", "init.js");
|
|
4051
|
+
let initModule;
|
|
4052
|
+
if (fsSync.existsSync(initPathTs) && viteServer) {
|
|
4053
|
+
initModule = await viteServer.ssrLoadModule(initPathTs);
|
|
4054
|
+
} else if (fsSync.existsSync(initPathJs)) {
|
|
4055
|
+
initModule = await import(initPathJs);
|
|
4056
|
+
}
|
|
4057
|
+
if (initModule?.init?._serverInit) {
|
|
4058
|
+
await initModule.init._serverInit();
|
|
4059
|
+
}
|
|
4060
|
+
await runInitCallbacks();
|
|
4061
|
+
} catch {
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
6330
4064
|
async function runCommand(commandRef, rawArgs, options, projectRoot, initializeServices) {
|
|
6331
4065
|
let viteServer;
|
|
6332
4066
|
try {
|
|
@@ -6440,8 +4174,7 @@ function printCommandList(commands) {
|
|
|
6440
4174
|
logger.important("\nAvailable plugin commands:\n");
|
|
6441
4175
|
const byPlugin = /* @__PURE__ */ new Map();
|
|
6442
4176
|
for (const cmd of commands) {
|
|
6443
|
-
if (!byPlugin.has(cmd.pluginName))
|
|
6444
|
-
byPlugin.set(cmd.pluginName, []);
|
|
4177
|
+
if (!byPlugin.has(cmd.pluginName)) byPlugin.set(cmd.pluginName, []);
|
|
6445
4178
|
byPlugin.get(cmd.pluginName).push(cmd);
|
|
6446
4179
|
}
|
|
6447
4180
|
for (const [pluginName, cmds] of byPlugin) {
|
|
@@ -6497,8 +4230,7 @@ program.command("build").description("Build production artifacts").option("-p, -
|
|
|
6497
4230
|
await runBuild(options.path, options);
|
|
6498
4231
|
} catch (error) {
|
|
6499
4232
|
getLogger().error(chalk.red("Build failed:") + " " + error.message);
|
|
6500
|
-
if (error.stack)
|
|
6501
|
-
getLogger().error(error.stack);
|
|
4233
|
+
if (error.stack) getLogger().error(error.stack);
|
|
6502
4234
|
process.exit(1);
|
|
6503
4235
|
}
|
|
6504
4236
|
});
|
|
@@ -6507,8 +4239,7 @@ program.command("serve").description("Start production server").option("-p, --pa
|
|
|
6507
4239
|
await runServe(options.path, options);
|
|
6508
4240
|
} catch (error) {
|
|
6509
4241
|
getLogger().error(chalk.red("Server failed:") + " " + error.message);
|
|
6510
|
-
if (error.stack)
|
|
6511
|
-
getLogger().error(error.stack);
|
|
4242
|
+
if (error.stack) getLogger().error(error.stack);
|
|
6512
4243
|
process.exit(1);
|
|
6513
4244
|
}
|
|
6514
4245
|
});
|
|
@@ -6517,8 +4248,7 @@ program.command("rebuild").description("Rebuild instances by contract, route, or
|
|
|
6517
4248
|
await runRebuild(options.path, options);
|
|
6518
4249
|
} catch (error) {
|
|
6519
4250
|
getLogger().error(chalk.red("Rebuild failed:") + " " + error.message);
|
|
6520
|
-
if (error.stack)
|
|
6521
|
-
getLogger().error(error.stack);
|
|
4251
|
+
if (error.stack) getLogger().error(error.stack);
|
|
6522
4252
|
process.exit(1);
|
|
6523
4253
|
}
|
|
6524
4254
|
});
|
|
@@ -6556,7 +4286,7 @@ program.command("validate-plugin").description("Validate a Jay Stack plugin pack
|
|
|
6556
4286
|
}
|
|
6557
4287
|
});
|
|
6558
4288
|
program.command("setup [plugin]").description("Run plugin setup: config templates, credential validation, reference data").option("--force", "Force re-run (overwrite config templates and regenerate references)").option("--interactive", "Prompt for input via terminal (for humans)").option("--answers <file>", "Read answers from YAML file (for agents)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
|
|
6559
|
-
await runSetup(plugin, options, process.cwd()
|
|
4289
|
+
await runSetup(plugin, options, process.cwd());
|
|
6560
4290
|
});
|
|
6561
4291
|
program.command("agent-kit").description("Prepare agent kit: materialize contracts, generate references, write docs").option("-o, --output <dir>", "Output directory (default: agent-kit/materialized-contracts)").option("--yaml", "Output contract index as YAML to stdout").option("--list", "List contracts without writing files").option("--plugin <name>", "Filter to specific plugin").option("--dynamic-only", "Only process dynamic contracts").option("--force", "Force re-materialization").option("--no-references", "Skip reference data generation").option(
|
|
6562
4292
|
"-m, --mode <role>",
|
|
@@ -6582,11 +4312,7 @@ if (!process.argv.slice(2).length) {
|
|
|
6582
4312
|
program.outputHelp();
|
|
6583
4313
|
}
|
|
6584
4314
|
export {
|
|
6585
|
-
createEditorHandlers,
|
|
6586
4315
|
getConfigWithDefaults,
|
|
6587
|
-
getRegisteredVendors,
|
|
6588
|
-
getVendor,
|
|
6589
|
-
hasVendor,
|
|
6590
4316
|
listContracts2 as listContracts,
|
|
6591
4317
|
loadConfig,
|
|
6592
4318
|
materializeContracts2 as materializeContracts,
|