@jay-framework/jay-stack-cli 0.22.1 ā 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-kit-template/designer/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 +277 -2445
- 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,20 +2,19 @@
|
|
|
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 {
|
|
12
|
-
import {
|
|
13
|
-
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts, SetupNeedsAnswerError } from "@jay-framework/stack-server-runtime";
|
|
10
|
+
import { JAY_IMPORT_RESOLVER, parseJayFile, generateElementDefinitionFile, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
11
|
+
import { scanPlugins, listContracts, materializeContracts, SetupNeedsAnswerError, discoverPluginsWithSetup, discoverPluginsWithInit, sortPluginsByDependencies, 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";
|
|
@@ -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,2317 +41,42 @@ 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
44
|
}
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
}
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
getLogger().warn(`Failed to parse .jay YAML config file, using defaults: ${error}`);
|
|
48
|
+
return DEFAULT_CONFIG;
|
|
2000
49
|
}
|
|
2001
|
-
return {
|
|
2002
|
-
name: pageName,
|
|
2003
|
-
url: pageUrl,
|
|
2004
|
-
filePath: pageFilePath,
|
|
2005
|
-
contract,
|
|
2006
|
-
usedComponents
|
|
2007
|
-
};
|
|
2008
50
|
}
|
|
2009
|
-
|
|
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
|
-
});
|
|
51
|
+
function getConfigWithDefaults(config) {
|
|
2020
52
|
return {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
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
|
|
59
|
+
}
|
|
2026
60
|
};
|
|
2027
61
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
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) {
|
|
62
|
+
function updateConfig(updates) {
|
|
63
|
+
const configPath = path.resolve(".jay");
|
|
2068
64
|
try {
|
|
2069
|
-
const
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
await fs.promises.writeFile(contractPath, component.contract, "utf-8");
|
|
2078
|
-
}
|
|
2079
|
-
const createdJayHtml = {
|
|
2080
|
-
jayHtml: component.jayHtml,
|
|
2081
|
-
filename,
|
|
2082
|
-
dirname,
|
|
2083
|
-
fullPath
|
|
65
|
+
const existingConfig = loadConfig();
|
|
66
|
+
const updatedConfig = {
|
|
67
|
+
...existingConfig,
|
|
68
|
+
...updates,
|
|
69
|
+
devServer: {
|
|
70
|
+
...existingConfig.devServer,
|
|
71
|
+
...updates.devServer
|
|
72
|
+
}
|
|
2084
73
|
};
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
{
|
|
2088
|
-
success: true,
|
|
2089
|
-
filePath: fullPath,
|
|
2090
|
-
contractPath
|
|
2091
|
-
},
|
|
2092
|
-
createdJayHtml
|
|
2093
|
-
];
|
|
74
|
+
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
75
|
+
fs.writeFileSync(configPath, yamlContent);
|
|
2094
76
|
} catch (error) {
|
|
2095
|
-
getLogger().
|
|
2096
|
-
return [
|
|
2097
|
-
{
|
|
2098
|
-
success: false,
|
|
2099
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2100
|
-
},
|
|
2101
|
-
void 0
|
|
2102
|
-
];
|
|
77
|
+
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
2103
78
|
}
|
|
2104
79
|
}
|
|
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
|
-
}
|
|
2305
|
-
return {
|
|
2306
|
-
type: "export",
|
|
2307
|
-
success: true,
|
|
2308
|
-
vendorSourcePath: vendorFilePath
|
|
2309
|
-
};
|
|
2310
|
-
} catch (error) {
|
|
2311
|
-
getLogger().error("Failed to export vendor document:", error);
|
|
2312
|
-
return {
|
|
2313
|
-
type: "export",
|
|
2314
|
-
success: false,
|
|
2315
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2316
|
-
};
|
|
2317
|
-
}
|
|
2318
|
-
};
|
|
2319
|
-
const onImport = async (params) => {
|
|
2320
|
-
try {
|
|
2321
|
-
const pagesBasePath = path.resolve(config.devServer.pagesBase);
|
|
2322
|
-
const { vendorId, pageUrl } = params;
|
|
2323
|
-
const dirname = pageUrlToDirectoryPath(pageUrl, pagesBasePath);
|
|
2324
|
-
const vendorFilename = `page.${vendorId}.json`;
|
|
2325
|
-
const vendorFilePath = path.join(dirname, vendorFilename);
|
|
2326
|
-
if (!fs.existsSync(vendorFilePath)) {
|
|
2327
|
-
return {
|
|
2328
|
-
type: "import",
|
|
2329
|
-
success: false,
|
|
2330
|
-
error: `No ${vendorId} document found at ${pageUrl}. File not found: ${vendorFilePath}`
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
const fileContent = await fs.promises.readFile(vendorFilePath, "utf-8");
|
|
2334
|
-
const vendorDoc = JSON.parse(fileContent);
|
|
2335
|
-
getLogger().info(`š„ Imported ${vendorId} document from: ${vendorFilePath}`);
|
|
2336
|
-
return {
|
|
2337
|
-
type: "import",
|
|
2338
|
-
success: true,
|
|
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
|
-
};
|
|
2358
|
-
}
|
|
2359
80
|
async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
2360
81
|
for (const route of routes) {
|
|
2361
82
|
const jayHtmlPath = route.fsRoute.jayHtmlPath;
|
|
@@ -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."));
|
|
@@ -3029,7 +650,7 @@ const FOLDER_PATH_MAX_SEGMENTS = 32;
|
|
|
3029
650
|
const BLOCKED_TAGS = /<\s*(script|iframe|object|embed)\b[^>]*>[\s\S]*?<\/\s*\1\s*>|<\s*(script|iframe|object|embed)\b[^>]*\/?>/gi;
|
|
3030
651
|
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
|
|
3031
652
|
const JAVASCRIPT_URL = /\b(href|src|xlink:href)\s*=\s*("|')\s*javascript:/gi;
|
|
3032
|
-
function isRecord(value) {
|
|
653
|
+
function isRecord$1(value) {
|
|
3033
654
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3034
655
|
}
|
|
3035
656
|
function byteLengthUtf8(value) {
|
|
@@ -3099,7 +720,7 @@ function optionalString(obj, field) {
|
|
|
3099
720
|
function validateInteraction(raw, itemPath, errors) {
|
|
3100
721
|
if (raw === void 0)
|
|
3101
722
|
return void 0;
|
|
3102
|
-
if (!isRecord(raw)) {
|
|
723
|
+
if (!isRecord$1(raw)) {
|
|
3103
724
|
errors.push({
|
|
3104
725
|
path: itemPath,
|
|
3105
726
|
message: "interaction must be an object",
|
|
@@ -3124,7 +745,7 @@ function validateInteraction(raw, itemPath, errors) {
|
|
|
3124
745
|
function validatePresentation(raw, itemPath, errors) {
|
|
3125
746
|
if (raw === void 0)
|
|
3126
747
|
return void 0;
|
|
3127
|
-
if (!isRecord(raw)) {
|
|
748
|
+
if (!isRecord$1(raw)) {
|
|
3128
749
|
errors.push({
|
|
3129
750
|
path: itemPath,
|
|
3130
751
|
message: "presentation must be an object",
|
|
@@ -3197,7 +818,7 @@ const BROWSE_SIZES = /* @__PURE__ */ new Set(["large", "medium", "small"]);
|
|
|
3197
818
|
function validateBrowse(raw, itemPath, errors) {
|
|
3198
819
|
if (raw === void 0)
|
|
3199
820
|
return void 0;
|
|
3200
|
-
if (!isRecord(raw)) {
|
|
821
|
+
if (!isRecord$1(raw)) {
|
|
3201
822
|
errors.push({
|
|
3202
823
|
path: itemPath,
|
|
3203
824
|
message: "browse must be an object",
|
|
@@ -3280,7 +901,7 @@ function validateFolderPath(raw, itemPath, errors) {
|
|
|
3280
901
|
}
|
|
3281
902
|
function validateAddMenuItem(raw, itemPath) {
|
|
3282
903
|
const errors = [];
|
|
3283
|
-
if (!isRecord(raw)) {
|
|
904
|
+
if (!isRecord$1(raw)) {
|
|
3284
905
|
return {
|
|
3285
906
|
item: null,
|
|
3286
907
|
errors: [
|
|
@@ -3331,7 +952,7 @@ function validateAddMenuItem(raw, itemPath) {
|
|
|
3331
952
|
}
|
|
3332
953
|
function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
3333
954
|
const errors = [];
|
|
3334
|
-
if (!isRecord(raw)) {
|
|
955
|
+
if (!isRecord$1(raw)) {
|
|
3335
956
|
return {
|
|
3336
957
|
file: null,
|
|
3337
958
|
errors: [
|
|
@@ -3690,7 +1311,7 @@ function suggestionForCode(code) {
|
|
|
3690
1311
|
return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3691
1312
|
return ADD_MENU_VALIDATION_SUGGESTIONS[code] ?? `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3692
1313
|
}
|
|
3693
|
-
function mapSchemaError(error, catalogPath) {
|
|
1314
|
+
function mapSchemaError$1(error, catalogPath) {
|
|
3694
1315
|
const code = error.code ?? "catalog-validation-error";
|
|
3695
1316
|
return {
|
|
3696
1317
|
type: "add-menu-catalog",
|
|
@@ -3764,7 +1385,7 @@ async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
|
|
|
3764
1385
|
return;
|
|
3765
1386
|
}
|
|
3766
1387
|
const validated = validateAddMenuCatalogFile(parsed, relPath);
|
|
3767
|
-
result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
|
|
1388
|
+
result.errors.push(...validated.errors.map((error) => mapSchemaError$1(error, relPath)));
|
|
3768
1389
|
if (!validated.file?.items.length) {
|
|
3769
1390
|
return;
|
|
3770
1391
|
}
|
|
@@ -3785,6 +1406,173 @@ async function validateAddMenuCatalog(context, result) {
|
|
|
3785
1406
|
await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
|
|
3786
1407
|
}
|
|
3787
1408
|
}
|
|
1409
|
+
function isRecord(value) {
|
|
1410
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1411
|
+
}
|
|
1412
|
+
function parseRequires(raw, filePath) {
|
|
1413
|
+
const errors = [];
|
|
1414
|
+
if (raw === void 0) {
|
|
1415
|
+
return { requires: [], errors };
|
|
1416
|
+
}
|
|
1417
|
+
if (!Array.isArray(raw)) {
|
|
1418
|
+
errors.push({
|
|
1419
|
+
path: `${filePath}.requires`,
|
|
1420
|
+
message: "requires must be an array",
|
|
1421
|
+
code: "settings-requires-type"
|
|
1422
|
+
});
|
|
1423
|
+
return { requires: [], errors };
|
|
1424
|
+
}
|
|
1425
|
+
const requires = [];
|
|
1426
|
+
raw.forEach((entry, index) => {
|
|
1427
|
+
if (!isRecord(entry)) {
|
|
1428
|
+
errors.push({
|
|
1429
|
+
path: `${filePath}.requires[${index}]`,
|
|
1430
|
+
message: "require entry must be an object",
|
|
1431
|
+
code: "settings-require-shape"
|
|
1432
|
+
});
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
if (typeof entry.plugin !== "string" || !entry.plugin.trim()) {
|
|
1436
|
+
errors.push({
|
|
1437
|
+
path: `${filePath}.requires[${index}].plugin`,
|
|
1438
|
+
message: "plugin is required",
|
|
1439
|
+
code: "settings-require-plugin"
|
|
1440
|
+
});
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
if (entry.status !== "configured") {
|
|
1444
|
+
errors.push({
|
|
1445
|
+
path: `${filePath}.requires[${index}].status`,
|
|
1446
|
+
message: 'status must be "configured"',
|
|
1447
|
+
code: "settings-require-status"
|
|
1448
|
+
});
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
requires.push({ plugin: entry.plugin.trim(), status: "configured" });
|
|
1452
|
+
});
|
|
1453
|
+
return { requires, errors };
|
|
1454
|
+
}
|
|
1455
|
+
function validateAiditorSettingsFile(raw, sourcePath) {
|
|
1456
|
+
const errors = [];
|
|
1457
|
+
if (!isRecord(raw)) {
|
|
1458
|
+
return {
|
|
1459
|
+
errors: [
|
|
1460
|
+
{
|
|
1461
|
+
path: sourcePath,
|
|
1462
|
+
message: "settings file must be a YAML object",
|
|
1463
|
+
code: "settings-root-type"
|
|
1464
|
+
}
|
|
1465
|
+
]
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof raw.label !== "string" || !raw.label.trim()) {
|
|
1469
|
+
errors.push({
|
|
1470
|
+
path: `${sourcePath}.label`,
|
|
1471
|
+
message: "label is required",
|
|
1472
|
+
code: "settings-label"
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
if (typeof raw.route !== "string" || !raw.route.trim()) {
|
|
1476
|
+
errors.push({
|
|
1477
|
+
path: `${sourcePath}.route`,
|
|
1478
|
+
message: "route is required",
|
|
1479
|
+
code: "settings-route"
|
|
1480
|
+
});
|
|
1481
|
+
} else if (!raw.route.startsWith("/")) {
|
|
1482
|
+
errors.push({
|
|
1483
|
+
path: `${sourcePath}.route`,
|
|
1484
|
+
message: "route must start with /",
|
|
1485
|
+
code: "settings-route-format"
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
if (raw.pluginName !== void 0 && typeof raw.pluginName !== "string") {
|
|
1489
|
+
errors.push({
|
|
1490
|
+
path: `${sourcePath}.pluginName`,
|
|
1491
|
+
message: "pluginName must be a string",
|
|
1492
|
+
code: "settings-plugin-name-type"
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
const { requires, errors: requireErrors } = parseRequires(raw.requires, sourcePath);
|
|
1496
|
+
errors.push(...requireErrors);
|
|
1497
|
+
if (errors.length > 0) {
|
|
1498
|
+
return { errors };
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
file: {
|
|
1502
|
+
label: raw.label.trim(),
|
|
1503
|
+
route: raw.route.trim(),
|
|
1504
|
+
...typeof raw.pluginName === "string" && raw.pluginName.trim() ? { pluginName: raw.pluginName.trim() } : {},
|
|
1505
|
+
...requires.length > 0 ? { requires } : {}
|
|
1506
|
+
},
|
|
1507
|
+
errors: []
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
const AIDITOR_SETTINGS_TEMPLATE_REL_PATH = "agent-kit/aiditor/settings.template.yaml";
|
|
1511
|
+
function mapSchemaError(error, relPath) {
|
|
1512
|
+
return {
|
|
1513
|
+
type: "schema",
|
|
1514
|
+
message: error.message,
|
|
1515
|
+
location: error.path || relPath,
|
|
1516
|
+
code: error.code
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
function validateSettingsTemplateAtPath(catalogPath, relPath, result, manifest) {
|
|
1520
|
+
let parsed;
|
|
1521
|
+
try {
|
|
1522
|
+
parsed = YAML.parse(fs.readFileSync(catalogPath, "utf-8"));
|
|
1523
|
+
} catch (err) {
|
|
1524
|
+
result.errors.push({
|
|
1525
|
+
type: "schema",
|
|
1526
|
+
message: `Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1527
|
+
location: relPath
|
|
1528
|
+
});
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const validated = validateAiditorSettingsFile(parsed, relPath);
|
|
1532
|
+
result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
|
|
1533
|
+
if (!validated.file) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
const routeEntry = manifest.routes?.find((route) => route.path === validated.file.route);
|
|
1537
|
+
if (!routeEntry) {
|
|
1538
|
+
result.warnings.push({
|
|
1539
|
+
type: "schema",
|
|
1540
|
+
message: `settings route "${validated.file.route}" is not declared in plugin.yaml routes[]`,
|
|
1541
|
+
location: relPath,
|
|
1542
|
+
code: "settings-route-missing",
|
|
1543
|
+
suggestion: "Add a matching routes[] entry or fix the route in settings.template.yaml"
|
|
1544
|
+
});
|
|
1545
|
+
} else if (routeEntry.devOnly !== true) {
|
|
1546
|
+
result.warnings.push({
|
|
1547
|
+
type: "schema",
|
|
1548
|
+
message: `settings route "${validated.file.route}" should declare devOnly: true on routes[]`,
|
|
1549
|
+
location: "plugin.yaml routes",
|
|
1550
|
+
code: "settings-route-dev-only",
|
|
1551
|
+
suggestion: "Add devOnly: true when the settings page is dev-server tooling (see Design Log #171)"
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async function validateAiditorSettings(context, result) {
|
|
1556
|
+
const templatePath = path.join(context.pluginPath, AIDITOR_SETTINGS_TEMPLATE_REL_PATH);
|
|
1557
|
+
if (!fs.existsSync(templatePath)) {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
validateSettingsTemplateAtPath(
|
|
1561
|
+
templatePath,
|
|
1562
|
+
AIDITOR_SETTINGS_TEMPLATE_REL_PATH,
|
|
1563
|
+
result,
|
|
1564
|
+
context.manifest
|
|
1565
|
+
);
|
|
1566
|
+
if (!context.manifest.agentkit) {
|
|
1567
|
+
result.warnings.push({
|
|
1568
|
+
type: "schema",
|
|
1569
|
+
message: "Plugin ships agent-kit/aiditor/settings.template.yaml but has no agentkit handler",
|
|
1570
|
+
location: "plugin.yaml",
|
|
1571
|
+
code: "settings-missing-agentkit-handler",
|
|
1572
|
+
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"
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
3788
1576
|
async function validatePlugin(options = {}) {
|
|
3789
1577
|
const pluginPath = options.pluginPath || process.cwd();
|
|
3790
1578
|
if (options.local) {
|
|
@@ -3852,6 +1640,7 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
3852
1640
|
await validateDynamicContracts(context, result);
|
|
3853
1641
|
}
|
|
3854
1642
|
await validateAddMenuCatalog(context, result);
|
|
1643
|
+
await validateAiditorSettings(context, result);
|
|
3855
1644
|
result.valid = result.errors.length === 0;
|
|
3856
1645
|
return result;
|
|
3857
1646
|
}
|
|
@@ -4161,6 +1950,13 @@ async function validateSchema(context, result) {
|
|
|
4161
1950
|
if (route.css) {
|
|
4162
1951
|
validateDocFile(route.css, `route "${route.path}" css`, context, result);
|
|
4163
1952
|
}
|
|
1953
|
+
if (route.devOnly !== void 0 && typeof route.devOnly !== "boolean") {
|
|
1954
|
+
result.errors.push({
|
|
1955
|
+
type: "schema",
|
|
1956
|
+
message: `Route "${route.path}" devOnly must be a boolean`,
|
|
1957
|
+
location: "plugin.yaml"
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
4164
1960
|
});
|
|
4165
1961
|
}
|
|
4166
1962
|
}
|
|
@@ -5230,7 +3026,7 @@ function resolveContractLinks(contract, contractPath) {
|
|
|
5230
3026
|
return { ...contract, tags: resolveLinkedTags(contract.tags, contractDir) };
|
|
5231
3027
|
}
|
|
5232
3028
|
async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
5233
|
-
const scannedPlugins = await scanPlugins
|
|
3029
|
+
const scannedPlugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
5234
3030
|
const loadedValidators = [];
|
|
5235
3031
|
for (const [, plugin] of scannedPlugins) {
|
|
5236
3032
|
if (!plugin.manifest.validators)
|
|
@@ -5354,12 +3150,18 @@ async function validateJayFiles(options = {}) {
|
|
|
5354
3150
|
const resolvedConfig = getConfigWithDefaults(config);
|
|
5355
3151
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
5356
3152
|
const scanDir = options.path ? path.resolve(options.path) : path.resolve(resolvedConfig.devServer.pagesBase);
|
|
3153
|
+
const componentsDir = path.resolve(resolvedConfig.devServer.componentsBase);
|
|
5357
3154
|
const errors = [];
|
|
5358
3155
|
const warnings = [];
|
|
5359
3156
|
const coverage = [];
|
|
5360
3157
|
const parsedFiles = [];
|
|
5361
|
-
const
|
|
5362
|
-
const
|
|
3158
|
+
const pageJayHtmlFiles = await findJayFiles(scanDir);
|
|
3159
|
+
const componentJayHtmlFiles = await findJayFiles(componentsDir).catch(() => []);
|
|
3160
|
+
const jayHtmlFiles = [...pageJayHtmlFiles, ...componentJayHtmlFiles];
|
|
3161
|
+
const contractFiles = [
|
|
3162
|
+
...await findContractFiles(scanDir),
|
|
3163
|
+
...await findContractFiles(componentsDir).catch(() => [])
|
|
3164
|
+
];
|
|
5363
3165
|
if (options.verbose) {
|
|
5364
3166
|
getLogger().info(chalk.gray(`Scanning directory: ${scanDir}`));
|
|
5365
3167
|
getLogger().info(chalk.gray(`Found ${jayHtmlFiles.length} .jay-html files`));
|
|
@@ -5607,6 +3409,14 @@ function printJayValidationResult(result, options) {
|
|
|
5607
3409
|
} else {
|
|
5608
3410
|
logger.important(chalk.red(`Validation failed ā ${result.errors.length} error(s).`));
|
|
5609
3411
|
}
|
|
3412
|
+
const totalIssues = result.errors.length + result.warnings.length + result.coverage.length;
|
|
3413
|
+
if (totalIssues > 0) {
|
|
3414
|
+
logger.important(
|
|
3415
|
+
chalk.gray(
|
|
3416
|
+
"\nSee: agent-kit/designer/validation-guide.md for how to interpret and suppress warnings."
|
|
3417
|
+
)
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
5610
3420
|
}
|
|
5611
3421
|
async function runValidate(scanPath, options) {
|
|
5612
3422
|
const result = await validateJayFiles({
|
|
@@ -5819,7 +3629,7 @@ async function copyDirRecursive(src, dest) {
|
|
|
5819
3629
|
}
|
|
5820
3630
|
}
|
|
5821
3631
|
async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
5822
|
-
const plugins = await scanPlugins
|
|
3632
|
+
const plugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
5823
3633
|
const agentKitDir = path$1.join(projectRoot, "agent-kit");
|
|
5824
3634
|
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
5825
3635
|
const copiedPerRole = /* @__PURE__ */ new Map();
|
|
@@ -6170,19 +3980,17 @@ function createDefaultPrompt(pluginName) {
|
|
|
6170
3980
|
}
|
|
6171
3981
|
};
|
|
6172
3982
|
}
|
|
6173
|
-
async function runSetup(pluginFilter, options, projectRoot
|
|
3983
|
+
async function runSetup(pluginFilter, options, projectRoot) {
|
|
6174
3984
|
let viteServer;
|
|
6175
3985
|
try {
|
|
6176
3986
|
const logger = getLogger();
|
|
6177
|
-
const path2 = await import("node:path");
|
|
6178
3987
|
const jayConfig = loadConfig();
|
|
6179
|
-
const configDir =
|
|
3988
|
+
const configDir = path$1.resolve(projectRoot, jayConfig.devServer?.configBase || "./config");
|
|
6180
3989
|
logger.important(chalk.bold("\nš§ Setting up plugins...\n"));
|
|
6181
3990
|
if (options.verbose) {
|
|
6182
3991
|
logger.info("Starting Vite for TypeScript support...");
|
|
6183
3992
|
}
|
|
6184
3993
|
viteServer = await createViteForCli({ projectRoot });
|
|
6185
|
-
const { discoverPluginsWithSetup, executePluginSetup } = await import("@jay-framework/stack-server-runtime");
|
|
6186
3994
|
const pluginsWithSetup = await discoverPluginsWithSetup({
|
|
6187
3995
|
projectRoot,
|
|
6188
3996
|
verbose: options.verbose,
|
|
@@ -6203,12 +4011,9 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6203
4011
|
`Found ${pluginsWithSetup.length} plugin(s) with setup: ${pluginsWithSetup.map((p) => p.name).join(", ")}`
|
|
6204
4012
|
);
|
|
6205
4013
|
}
|
|
6206
|
-
const
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
logger.info(chalk.yellow(`ā ļø ${name} init error: ${err.message}`));
|
|
6210
|
-
}
|
|
6211
|
-
}
|
|
4014
|
+
const allPluginsWithInit = sortPluginsByDependencies(
|
|
4015
|
+
await discoverPluginsWithInit({ projectRoot, verbose: options.verbose })
|
|
4016
|
+
);
|
|
6212
4017
|
const interactive = options.interactive === true;
|
|
6213
4018
|
let answersMap;
|
|
6214
4019
|
if (options.answers) {
|
|
@@ -6230,14 +4035,13 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6230
4035
|
force: options.force ?? false,
|
|
6231
4036
|
interactive,
|
|
6232
4037
|
prompt,
|
|
6233
|
-
initError:
|
|
4038
|
+
initError: void 0,
|
|
6234
4039
|
viteServer,
|
|
6235
4040
|
verbose: options.verbose
|
|
6236
4041
|
});
|
|
6237
4042
|
switch (result.status) {
|
|
6238
4043
|
case "configured":
|
|
6239
4044
|
configured++;
|
|
6240
|
-
logger.important(chalk.green(" ā
Services verified"));
|
|
6241
4045
|
if (result.configCreated?.length) {
|
|
6242
4046
|
for (const cfg of result.configCreated) {
|
|
6243
4047
|
logger.important(chalk.green(` ā
Created ${cfg}`));
|
|
@@ -6246,6 +4050,7 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6246
4050
|
if (result.message) {
|
|
6247
4051
|
logger.important(chalk.gray(` ${result.message}`));
|
|
6248
4052
|
}
|
|
4053
|
+
await initPlugin(plugin.name, allPluginsWithInit, viteServer, logger);
|
|
6249
4054
|
break;
|
|
6250
4055
|
case "needs-config":
|
|
6251
4056
|
needsConfig++;
|
|
@@ -6304,6 +4109,7 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6304
4109
|
}
|
|
6305
4110
|
logger.important("");
|
|
6306
4111
|
}
|
|
4112
|
+
await runProjectInit(projectRoot, viteServer);
|
|
6307
4113
|
const parts = [];
|
|
6308
4114
|
if (configured > 0)
|
|
6309
4115
|
parts.push(`${configured} configured`);
|
|
@@ -6327,6 +4133,36 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
6327
4133
|
}
|
|
6328
4134
|
}
|
|
6329
4135
|
}
|
|
4136
|
+
async function initPlugin(pluginName, allPluginsWithInit, viteServer, logger) {
|
|
4137
|
+
const pluginInit = allPluginsWithInit.filter((p) => p.name === pluginName);
|
|
4138
|
+
if (pluginInit.length === 0)
|
|
4139
|
+
return;
|
|
4140
|
+
const initErrors = await executePluginServerInits(pluginInit, viteServer, false, true);
|
|
4141
|
+
if (initErrors.size > 0) {
|
|
4142
|
+
for (const [, err] of initErrors) {
|
|
4143
|
+
logger.important(chalk.yellow(` ā ļø Init after setup: ${err.message}`));
|
|
4144
|
+
}
|
|
4145
|
+
} else {
|
|
4146
|
+
logger.important(chalk.green(` ā
Services initialized`));
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
async function runProjectInit(projectRoot, viteServer) {
|
|
4150
|
+
try {
|
|
4151
|
+
const initPathTs = path$1.join(projectRoot, "src", "init.ts");
|
|
4152
|
+
const initPathJs = path$1.join(projectRoot, "src", "init.js");
|
|
4153
|
+
let initModule;
|
|
4154
|
+
if (fsSync.existsSync(initPathTs) && viteServer) {
|
|
4155
|
+
initModule = await viteServer.ssrLoadModule(initPathTs);
|
|
4156
|
+
} else if (fsSync.existsSync(initPathJs)) {
|
|
4157
|
+
initModule = await import(initPathJs);
|
|
4158
|
+
}
|
|
4159
|
+
if (initModule?.init?._serverInit) {
|
|
4160
|
+
await initModule.init._serverInit();
|
|
4161
|
+
}
|
|
4162
|
+
await runInitCallbacks();
|
|
4163
|
+
} catch {
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
6330
4166
|
async function runCommand(commandRef, rawArgs, options, projectRoot, initializeServices) {
|
|
6331
4167
|
let viteServer;
|
|
6332
4168
|
try {
|
|
@@ -6556,7 +4392,7 @@ program.command("validate-plugin").description("Validate a Jay Stack plugin pack
|
|
|
6556
4392
|
}
|
|
6557
4393
|
});
|
|
6558
4394
|
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()
|
|
4395
|
+
await runSetup(plugin, options, process.cwd());
|
|
6560
4396
|
});
|
|
6561
4397
|
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
4398
|
"-m, --mode <role>",
|
|
@@ -6582,11 +4418,7 @@ if (!process.argv.slice(2).length) {
|
|
|
6582
4418
|
program.outputHelp();
|
|
6583
4419
|
}
|
|
6584
4420
|
export {
|
|
6585
|
-
createEditorHandlers,
|
|
6586
4421
|
getConfigWithDefaults,
|
|
6587
|
-
getRegisteredVendors,
|
|
6588
|
-
getVendor,
|
|
6589
|
-
hasVendor,
|
|
6590
4422
|
listContracts2 as listContracts,
|
|
6591
4423
|
loadConfig,
|
|
6592
4424
|
materializeContracts2 as materializeContracts,
|