@jay-framework/jay-stack-cli 0.24.1 ā 0.24.3
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/validation-guide.md +32 -1
- package/agent-kit-template/developer/dev-server-service.md +7 -7
- package/agent-kit-template/developer/routing.md +1 -1
- package/agent-kit-template/plugin/actions-guide.md +10 -0
- package/agent-kit-template/plugin/commands-guide.md +15 -0
- package/agent-kit-template/plugin/contracts-guide.md +7 -0
- package/agent-kit-template/plugin/dev-server-service.md +7 -7
- package/agent-kit-template/plugin/plugin-routes.md +43 -2
- package/agent-kit-template/plugin/plugin-structure.md +101 -15
- package/agent-kit-template/plugin/setup-guide.md +7 -4
- package/agent-kit-template/plugin/validation.md +22 -4
- package/dist/index-B9vCxAZ1.js +1602 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1676 -241
- package/package.json +12 -10
package/dist/index.js
CHANGED
|
@@ -3,21 +3,26 @@ import express from "express";
|
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
import { mkDevServer, createViteForCli } from "@jay-framework/dev-server";
|
|
5
5
|
import getPort from "get-port";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import fs, { promises } from "fs";
|
|
6
|
+
import path$1 from "path";
|
|
7
|
+
import fs$1, { promises } from "fs";
|
|
8
8
|
import YAML from "yaml";
|
|
9
9
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
10
|
-
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
11
|
-
import
|
|
12
|
-
import
|
|
10
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseAction, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
11
|
+
import fs$2 from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import path__default from "node:path";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
import jsBeautify from "js-beautify";
|
|
16
|
+
import { scanPlugins, sortPluginsByDependencies, getServiceRegistry, actionRegistry, resolveServices, ActionRegistry, discoverPluginsWithInit, executePluginServerInits, runInitCallbacks, registerService } from "@jay-framework/stack-server-runtime";
|
|
17
|
+
import "node:crypto";
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import fs__default from "node:fs";
|
|
20
|
+
import { createRequire as createRequire$1 } from "node:module";
|
|
21
|
+
import { isJayAction, isJayStreamAction, isJayCliCommand, CONSOLE_CONTEXT } from "@jay-framework/fullstack-component";
|
|
13
22
|
import { Command } from "commander";
|
|
14
23
|
import chalk from "chalk";
|
|
15
|
-
import
|
|
16
|
-
import fs$1 from "node:fs/promises";
|
|
17
|
-
import { loadPluginManifest, JAY_EXTENSION, RuntimeMode, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
18
|
-
import { createRequire } from "module";
|
|
24
|
+
import { loadPluginManifest as loadPluginManifest$1, normalizeActionEntry as normalizeActionEntry$1, JAY_EXTENSION, RuntimeMode as RuntimeMode$1, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
19
25
|
import { glob } from "glob";
|
|
20
|
-
import fsSync from "node:fs";
|
|
21
26
|
import { fileURLToPath } from "node:url";
|
|
22
27
|
import { select, confirm, input } from "@inquirer/prompts";
|
|
23
28
|
const DEFAULT_CONFIG = {
|
|
@@ -29,13 +34,13 @@ const DEFAULT_CONFIG = {
|
|
|
29
34
|
configBase: "./config"
|
|
30
35
|
}
|
|
31
36
|
};
|
|
32
|
-
function loadConfig() {
|
|
33
|
-
const configPath = path.resolve(".jay");
|
|
34
|
-
if (!fs.existsSync(configPath)) {
|
|
37
|
+
function loadConfig(projectRoot) {
|
|
38
|
+
const configPath = projectRoot ? path$1.resolve(projectRoot, ".jay") : path$1.resolve(".jay");
|
|
39
|
+
if (!fs$1.existsSync(configPath)) {
|
|
35
40
|
return DEFAULT_CONFIG;
|
|
36
41
|
}
|
|
37
42
|
try {
|
|
38
|
-
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
43
|
+
const configContent = fs$1.readFileSync(configPath, "utf-8");
|
|
39
44
|
const userConfig = YAML.parse(configContent);
|
|
40
45
|
return {
|
|
41
46
|
devServer: {
|
|
@@ -62,7 +67,7 @@ function getConfigWithDefaults(config) {
|
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
69
|
function updateConfig(updates) {
|
|
65
|
-
const configPath = path.resolve(".jay");
|
|
70
|
+
const configPath = path$1.resolve(".jay");
|
|
66
71
|
try {
|
|
67
72
|
const existingConfig = loadConfig();
|
|
68
73
|
const updatedConfig = {
|
|
@@ -78,7 +83,7 @@ function updateConfig(updates) {
|
|
|
78
83
|
}
|
|
79
84
|
};
|
|
80
85
|
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
81
|
-
fs.writeFileSync(configPath, yamlContent);
|
|
86
|
+
fs$1.writeFileSync(configPath, yamlContent);
|
|
82
87
|
} catch (error) {
|
|
83
88
|
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
84
89
|
}
|
|
@@ -86,14 +91,14 @@ function updateConfig(updates) {
|
|
|
86
91
|
async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
87
92
|
for (const route of routes) {
|
|
88
93
|
const jayHtmlPath = route.fsRoute.jayHtmlPath;
|
|
89
|
-
if (!fs.existsSync(jayHtmlPath)) {
|
|
94
|
+
if (!fs$1.existsSync(jayHtmlPath)) {
|
|
90
95
|
continue;
|
|
91
96
|
}
|
|
92
97
|
const definitionFilePath = jayHtmlPath + ".d.ts";
|
|
93
98
|
try {
|
|
94
99
|
const [sourceStats, defStats] = await Promise.all([
|
|
95
|
-
fs.promises.stat(jayHtmlPath),
|
|
96
|
-
fs.promises.stat(definitionFilePath).catch(() => null)
|
|
100
|
+
fs$1.promises.stat(jayHtmlPath),
|
|
101
|
+
fs$1.promises.stat(definitionFilePath).catch(() => null)
|
|
97
102
|
]);
|
|
98
103
|
if (defStats && defStats.mtime >= sourceStats.mtime) {
|
|
99
104
|
continue;
|
|
@@ -101,9 +106,9 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
101
106
|
} catch (error) {
|
|
102
107
|
}
|
|
103
108
|
try {
|
|
104
|
-
const jayHtml = await fs.promises.readFile(jayHtmlPath, "utf-8");
|
|
105
|
-
const filename = path.basename(jayHtmlPath);
|
|
106
|
-
const dirname = path.dirname(jayHtmlPath);
|
|
109
|
+
const jayHtml = await fs$1.promises.readFile(jayHtmlPath, "utf-8");
|
|
110
|
+
const filename = path$1.basename(jayHtmlPath);
|
|
111
|
+
const dirname = path$1.dirname(jayHtmlPath);
|
|
107
112
|
const parsedJayHtml = await parseJayFile(
|
|
108
113
|
jayHtml,
|
|
109
114
|
filename,
|
|
@@ -119,7 +124,7 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
119
124
|
);
|
|
120
125
|
} else {
|
|
121
126
|
const definitionFilePath2 = jayHtmlPath + ".d.ts";
|
|
122
|
-
await fs.promises.writeFile(definitionFilePath2, definitionFile.val, "utf-8");
|
|
127
|
+
await fs$1.promises.writeFile(definitionFilePath2, definitionFile.val, "utf-8");
|
|
123
128
|
getLogger().info(`š¦ Generated definition file: ${definitionFilePath2}`);
|
|
124
129
|
}
|
|
125
130
|
} catch (error) {
|
|
@@ -143,7 +148,7 @@ async function startDevServer(options = {}) {
|
|
|
143
148
|
const devServerPort = await getPort({ port: resolvedConfig.devServer.portRange });
|
|
144
149
|
const log = getLogger();
|
|
145
150
|
const { server, viteServer, routes, service } = await mkDevServer({
|
|
146
|
-
pagesRootFolder: path.resolve(resolvedConfig.devServer.pagesBase),
|
|
151
|
+
pagesRootFolder: path$1.resolve(resolvedConfig.devServer.pagesBase),
|
|
147
152
|
projectRootFolder: process.cwd(),
|
|
148
153
|
publicBaseUrlPath: "/",
|
|
149
154
|
jayRollupConfig: jayOptions,
|
|
@@ -151,15 +156,15 @@ async function startDevServer(options = {}) {
|
|
|
151
156
|
httpServer
|
|
152
157
|
});
|
|
153
158
|
app.use(server);
|
|
154
|
-
const publicPath = path.resolve(resolvedConfig.devServer.publicFolder);
|
|
155
|
-
if (!fs.existsSync(path.join(publicPath, "sitemap.xml"))) {
|
|
159
|
+
const publicPath = path$1.resolve(resolvedConfig.devServer.publicFolder);
|
|
160
|
+
if (!fs$1.existsSync(path$1.join(publicPath, "sitemap.xml"))) {
|
|
156
161
|
app.get("/sitemap.xml", (_req, res) => {
|
|
157
162
|
res.type("application/xml").send(
|
|
158
163
|
'<?xml version="1.0" encoding="UTF-8"?>\n<!-- Sitemap is generated by the production server from the route manifest. -->\n<!-- Run jay-stack build && jay-stack serve to see the full sitemap. -->\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />\n'
|
|
159
164
|
);
|
|
160
165
|
});
|
|
161
166
|
}
|
|
162
|
-
if (fs.existsSync(publicPath)) {
|
|
167
|
+
if (fs$1.existsSync(publicPath)) {
|
|
163
168
|
app.use(express.static(publicPath));
|
|
164
169
|
} else {
|
|
165
170
|
log.important(`ā ļø Public folder not found: ${resolvedConfig.devServer.publicFolder}`);
|
|
@@ -177,7 +182,7 @@ async function startDevServer(options = {}) {
|
|
|
177
182
|
log.important(`š Jay Stack dev server started successfully!`);
|
|
178
183
|
log.important(`š± Dev Server: http://localhost:${devServerPort}`);
|
|
179
184
|
log.important(`š Pages directory: ${resolvedConfig.devServer.pagesBase}`);
|
|
180
|
-
if (fs.existsSync(publicPath)) {
|
|
185
|
+
if (fs$1.existsSync(publicPath)) {
|
|
181
186
|
log.important(`š Public folder: ${resolvedConfig.devServer.publicFolder}`);
|
|
182
187
|
}
|
|
183
188
|
if (options.testMode) {
|
|
@@ -222,12 +227,1326 @@ async function startDevServer(options = {}) {
|
|
|
222
227
|
process.on("SIGTERM", shutdown);
|
|
223
228
|
process.on("SIGINT", shutdown);
|
|
224
229
|
}
|
|
230
|
+
createRequire(import.meta.url);
|
|
231
|
+
var __defProp2 = Object.defineProperty;
|
|
232
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
233
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
|
|
234
|
+
class JayAtomicType {
|
|
235
|
+
constructor(name) {
|
|
236
|
+
__publicField2(this, "kind", 0);
|
|
237
|
+
this.name = name;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
new JayAtomicType("string");
|
|
241
|
+
new JayAtomicType("string");
|
|
242
|
+
new JayAtomicType("number");
|
|
243
|
+
new JayAtomicType("boolean");
|
|
244
|
+
new JayAtomicType("Date");
|
|
245
|
+
new JayAtomicType("file");
|
|
246
|
+
new JayAtomicType("Unknown");
|
|
247
|
+
class JayObjectType {
|
|
248
|
+
constructor(name, props) {
|
|
249
|
+
__publicField2(this, "kind", 8);
|
|
250
|
+
this.name = name;
|
|
251
|
+
this.props = props;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
new JayObjectType("Error", {
|
|
255
|
+
message: new JayAtomicType("string"),
|
|
256
|
+
name: new JayAtomicType("string"),
|
|
257
|
+
stack: new JayAtomicType("string")
|
|
258
|
+
});
|
|
259
|
+
function isOptionalType(aType) {
|
|
260
|
+
return aType.kind === 14;
|
|
261
|
+
}
|
|
262
|
+
function isAtomicType(aType) {
|
|
263
|
+
return aType.kind === 0;
|
|
264
|
+
}
|
|
265
|
+
function isEnumType(aType) {
|
|
266
|
+
return aType.kind === 2;
|
|
267
|
+
}
|
|
268
|
+
function isImportedType(aType) {
|
|
269
|
+
return aType.kind === 4;
|
|
270
|
+
}
|
|
271
|
+
function isObjectType(aType) {
|
|
272
|
+
return aType.kind === 8;
|
|
273
|
+
}
|
|
274
|
+
function isArrayType(aType) {
|
|
275
|
+
return aType.kind === 9;
|
|
276
|
+
}
|
|
277
|
+
function isRecordType(aType) {
|
|
278
|
+
return aType.kind === 11;
|
|
279
|
+
}
|
|
280
|
+
function jayTypeToJsonSchema(type) {
|
|
281
|
+
if (isOptionalType(type)) {
|
|
282
|
+
return jayTypeToJsonSchema(type.innerType);
|
|
283
|
+
}
|
|
284
|
+
if (isAtomicType(type)) {
|
|
285
|
+
const name = type.name.toLowerCase();
|
|
286
|
+
if (name === "string" || name === "number" || name === "boolean") {
|
|
287
|
+
return { type: name };
|
|
288
|
+
}
|
|
289
|
+
if (name === "file") {
|
|
290
|
+
return { type: "string", description: "Binary file upload (JayFile)" };
|
|
291
|
+
}
|
|
292
|
+
return { type: "string" };
|
|
293
|
+
}
|
|
294
|
+
if (isEnumType(type)) {
|
|
295
|
+
return { type: "string", enum: type.values };
|
|
296
|
+
}
|
|
297
|
+
if (isImportedType(type)) {
|
|
298
|
+
return { type: "object", description: `Contract: ${type.name}` };
|
|
299
|
+
}
|
|
300
|
+
if (isArrayType(type)) {
|
|
301
|
+
const itemSchema = jayTypeToJsonSchema(type.itemType);
|
|
302
|
+
if (itemSchema) {
|
|
303
|
+
return { type: "array", items: itemSchema };
|
|
304
|
+
}
|
|
305
|
+
return { type: "array" };
|
|
306
|
+
}
|
|
307
|
+
if (isRecordType(type)) {
|
|
308
|
+
const valueSchema = jayTypeToJsonSchema(type.itemType);
|
|
309
|
+
if (valueSchema) {
|
|
310
|
+
return { type: "object", additionalProperties: valueSchema };
|
|
311
|
+
}
|
|
312
|
+
return { type: "object" };
|
|
313
|
+
}
|
|
314
|
+
if (isObjectType(type)) {
|
|
315
|
+
const properties = {};
|
|
316
|
+
const required = [];
|
|
317
|
+
for (const [key, propType] of Object.entries(type.props)) {
|
|
318
|
+
const isOpt = isOptionalType(propType);
|
|
319
|
+
const schema = jayTypeToJsonSchema(propType);
|
|
320
|
+
if (schema) {
|
|
321
|
+
properties[key] = schema;
|
|
322
|
+
if (!isOpt) {
|
|
323
|
+
required.push(key);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties,
|
|
330
|
+
...required.length > 0 && { required }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
|
|
336
|
+
RuntimeMode2["MainTrusted"] = "mainTrusted";
|
|
337
|
+
RuntimeMode2["MainSandbox"] = "mainSandbox";
|
|
338
|
+
RuntimeMode2["WorkerTrusted"] = "workerTrusted";
|
|
339
|
+
RuntimeMode2["WorkerSandbox"] = "workerSandbox";
|
|
340
|
+
return RuntimeMode2;
|
|
341
|
+
})(RuntimeMode || {});
|
|
342
|
+
const TS_EXTENSION = ".ts";
|
|
343
|
+
const JAY_QUERY_PREFIX = "?jay-";
|
|
344
|
+
const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
|
|
345
|
+
[
|
|
346
|
+
// Hydrate target
|
|
347
|
+
{
|
|
348
|
+
pattern: JAY_QUERY_HYDRATE,
|
|
349
|
+
buildEnv: "client",
|
|
350
|
+
isHydrate: true
|
|
351
|
+
},
|
|
352
|
+
// Build environments
|
|
353
|
+
{
|
|
354
|
+
pattern: `${JAY_QUERY_PREFIX}${"client"}`,
|
|
355
|
+
buildEnv: "client"
|
|
356
|
+
/* Client */
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
pattern: `${JAY_QUERY_PREFIX}${"server"}`,
|
|
360
|
+
buildEnv: "server"
|
|
361
|
+
/* Server */
|
|
362
|
+
},
|
|
363
|
+
// Runtime modes (with .ts suffix)
|
|
364
|
+
{
|
|
365
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
|
|
366
|
+
runtimeMode: RuntimeMode.MainSandbox
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
|
|
370
|
+
runtimeMode: RuntimeMode.WorkerTrusted
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
|
|
374
|
+
runtimeMode: RuntimeMode.WorkerSandbox
|
|
375
|
+
},
|
|
376
|
+
// Runtime modes (without .ts suffix)
|
|
377
|
+
{
|
|
378
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
|
|
379
|
+
runtimeMode: RuntimeMode.MainSandbox
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
|
|
383
|
+
runtimeMode: RuntimeMode.WorkerTrusted
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
|
|
387
|
+
runtimeMode: RuntimeMode.WorkerSandbox
|
|
388
|
+
}
|
|
389
|
+
];
|
|
390
|
+
const { html: htmlBeautify } = jsBeautify;
|
|
391
|
+
createRequire(import.meta.url);
|
|
392
|
+
function normalizeActionEntry(entry) {
|
|
393
|
+
if (typeof entry === "string") {
|
|
394
|
+
return { name: entry };
|
|
395
|
+
}
|
|
396
|
+
return { name: entry.name, action: entry.action, devOnly: entry.devOnly };
|
|
397
|
+
}
|
|
398
|
+
function loadPluginManifest(pluginDir) {
|
|
399
|
+
const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
|
|
400
|
+
if (!fs$1.existsSync(pluginYamlPath)) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const yamlContent = fs$1.readFileSync(pluginYamlPath, "utf-8");
|
|
405
|
+
return YAML.parse(yamlContent);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript");
|
|
411
|
+
new Proxy(e$1, {
|
|
412
|
+
get(t, r) {
|
|
413
|
+
return t[r];
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
function parseActionMetadata(yamlContent, fileName) {
|
|
417
|
+
try {
|
|
418
|
+
const parsed = parseAction(yamlContent, fileName);
|
|
419
|
+
if (parsed.validations.length > 0) {
|
|
420
|
+
getLogger().warn(
|
|
421
|
+
`[ActionMetadata] ${fileName}: validation errors: ${parsed.validations.join(", ")}`
|
|
422
|
+
);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
if (!parsed.val) {
|
|
426
|
+
getLogger().warn(`[ActionMetadata] ${fileName}: parsing returned no result`);
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const action = parsed.val;
|
|
430
|
+
const inputJsonSchema = jayTypeToJsonSchema(action.inputType);
|
|
431
|
+
let inputSchema;
|
|
432
|
+
if (inputJsonSchema && inputJsonSchema.type === "object") {
|
|
433
|
+
inputSchema = {
|
|
434
|
+
type: "object",
|
|
435
|
+
properties: inputJsonSchema.properties || {},
|
|
436
|
+
...inputJsonSchema.required && inputJsonSchema.required.length > 0 && { required: inputJsonSchema.required }
|
|
437
|
+
};
|
|
438
|
+
} else {
|
|
439
|
+
inputSchema = { type: "object", properties: {} };
|
|
440
|
+
}
|
|
441
|
+
const metadata = {
|
|
442
|
+
name: action.name,
|
|
443
|
+
description: action.description,
|
|
444
|
+
inputSchema
|
|
445
|
+
};
|
|
446
|
+
if (action.outputType) {
|
|
447
|
+
const outputJsonSchema = jayTypeToJsonSchema(action.outputType);
|
|
448
|
+
if (outputJsonSchema) {
|
|
449
|
+
metadata.outputSchema = outputJsonSchema;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return metadata;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
getLogger().error(
|
|
455
|
+
`[ActionMetadata] Failed to parse ${fileName}: ${error instanceof Error ? error.message : error}`
|
|
456
|
+
);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function loadActionMetadata(filePath) {
|
|
461
|
+
if (!fs.existsSync(filePath)) {
|
|
462
|
+
getLogger().warn(`[ActionMetadata] File not found: ${filePath}`);
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const yamlContent = fs.readFileSync(filePath, "utf-8");
|
|
466
|
+
const fileName = path.basename(filePath);
|
|
467
|
+
return parseActionMetadata(yamlContent, fileName);
|
|
468
|
+
}
|
|
469
|
+
function resolveActionMetadataPath(actionPath, pluginDir) {
|
|
470
|
+
return path.resolve(pluginDir, actionPath);
|
|
471
|
+
}
|
|
472
|
+
const require$2 = createRequire$1(import.meta.url);
|
|
473
|
+
async function discoverAndRegisterActions(options) {
|
|
474
|
+
const {
|
|
475
|
+
projectRoot,
|
|
476
|
+
actionsDir = "src/actions",
|
|
477
|
+
registry = actionRegistry,
|
|
478
|
+
verbose = false,
|
|
479
|
+
viteServer
|
|
480
|
+
} = options;
|
|
481
|
+
const result = {
|
|
482
|
+
actionCount: 0,
|
|
483
|
+
actionNames: [],
|
|
484
|
+
scannedFiles: []
|
|
485
|
+
};
|
|
486
|
+
const actionsPath = path.resolve(projectRoot, actionsDir);
|
|
487
|
+
if (!fs.existsSync(actionsPath)) {
|
|
488
|
+
if (verbose) {
|
|
489
|
+
getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
|
|
490
|
+
}
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
const actionFiles = await findActionFiles(actionsPath);
|
|
494
|
+
if (verbose) {
|
|
495
|
+
getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
|
|
496
|
+
}
|
|
497
|
+
for (const filePath of actionFiles) {
|
|
498
|
+
result.scannedFiles.push(filePath);
|
|
499
|
+
try {
|
|
500
|
+
let module;
|
|
501
|
+
if (viteServer) {
|
|
502
|
+
module = await viteServer.ssrLoadModule(filePath);
|
|
503
|
+
} else {
|
|
504
|
+
module = await import(filePath);
|
|
505
|
+
}
|
|
506
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
507
|
+
if (isJayAction(exportValue)) {
|
|
508
|
+
registry.register(exportValue);
|
|
509
|
+
result.actionNames.push(exportValue.actionName);
|
|
510
|
+
result.actionCount++;
|
|
511
|
+
if (verbose) {
|
|
512
|
+
getLogger().info(
|
|
513
|
+
`[Actions] Registered: ${exportValue.actionName}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
} else if (isJayStreamAction(exportValue)) {
|
|
517
|
+
registry.registerStream(exportValue);
|
|
518
|
+
result.actionNames.push(exportValue.actionName);
|
|
519
|
+
result.actionCount++;
|
|
520
|
+
if (verbose) {
|
|
521
|
+
getLogger().info(
|
|
522
|
+
`[Actions] Registered stream: ${exportValue.actionName}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
async function findActionFiles(dir) {
|
|
534
|
+
const files = [];
|
|
535
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
536
|
+
for (const entry of entries) {
|
|
537
|
+
const fullPath = path.join(dir, entry.name);
|
|
538
|
+
if (entry.isDirectory()) {
|
|
539
|
+
const subFiles = await findActionFiles(fullPath);
|
|
540
|
+
files.push(...subFiles);
|
|
541
|
+
} else if (entry.isFile() && entry.name.endsWith(".actions.ts")) {
|
|
542
|
+
files.push(fullPath);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return files;
|
|
546
|
+
}
|
|
547
|
+
async function discoverAllPluginActions(options) {
|
|
548
|
+
const { projectRoot, registry = actionRegistry, verbose = false, viteServer } = options;
|
|
549
|
+
const allActions = [];
|
|
550
|
+
const localPluginsPath = path.join(projectRoot, "src/plugins");
|
|
551
|
+
if (fs.existsSync(localPluginsPath)) {
|
|
552
|
+
const pluginDirs = await fs.promises.readdir(localPluginsPath, { withFileTypes: true });
|
|
553
|
+
for (const entry of pluginDirs) {
|
|
554
|
+
if (entry.isDirectory()) {
|
|
555
|
+
const pluginPath = path.join(localPluginsPath, entry.name);
|
|
556
|
+
const actions = await discoverPluginActions(
|
|
557
|
+
pluginPath,
|
|
558
|
+
projectRoot,
|
|
559
|
+
registry,
|
|
560
|
+
verbose,
|
|
561
|
+
viteServer
|
|
562
|
+
);
|
|
563
|
+
allActions.push(...actions);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const npmActions = await discoverNpmPluginActions(projectRoot, registry, verbose, viteServer);
|
|
568
|
+
allActions.push(...npmActions);
|
|
569
|
+
return allActions;
|
|
570
|
+
}
|
|
571
|
+
async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServer) {
|
|
572
|
+
const allActions = [];
|
|
573
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
574
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
575
|
+
return allActions;
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
|
|
579
|
+
const dependencies = {
|
|
580
|
+
...packageJson.dependencies,
|
|
581
|
+
...packageJson.devDependencies
|
|
582
|
+
};
|
|
583
|
+
for (const packageName of Object.keys(dependencies)) {
|
|
584
|
+
try {
|
|
585
|
+
const pluginYamlPath = tryResolvePluginYaml(packageName, projectRoot);
|
|
586
|
+
if (!pluginYamlPath) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const pluginDir = path.dirname(pluginYamlPath);
|
|
590
|
+
const pluginConfig = loadPluginManifest(pluginDir);
|
|
591
|
+
if (!pluginConfig || !pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (verbose) {
|
|
595
|
+
getLogger().info(
|
|
596
|
+
`[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
const actions = await registerNpmPluginActions(
|
|
600
|
+
packageName,
|
|
601
|
+
pluginConfig,
|
|
602
|
+
pluginDir,
|
|
603
|
+
registry,
|
|
604
|
+
verbose,
|
|
605
|
+
viteServer
|
|
606
|
+
);
|
|
607
|
+
allActions.push(...actions);
|
|
608
|
+
} catch {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
} catch (error) {
|
|
613
|
+
getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
|
|
614
|
+
}
|
|
615
|
+
return allActions;
|
|
616
|
+
}
|
|
617
|
+
function tryResolvePluginYaml(packageName, projectRoot) {
|
|
618
|
+
try {
|
|
619
|
+
return require$2.resolve(`${packageName}/plugin.yaml`, {
|
|
620
|
+
paths: [projectRoot]
|
|
621
|
+
});
|
|
622
|
+
} catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function resolveNpmActionMetadataPath(actionPath, packageName, pluginDir) {
|
|
627
|
+
if (!actionPath.startsWith(".")) {
|
|
628
|
+
try {
|
|
629
|
+
return require$2.resolve(`${packageName}/${actionPath}`, {
|
|
630
|
+
paths: [pluginDir]
|
|
631
|
+
});
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const resolved = resolveActionMetadataPath(actionPath, pluginDir);
|
|
636
|
+
if (fs.existsSync(resolved)) {
|
|
637
|
+
return resolved;
|
|
638
|
+
}
|
|
639
|
+
getLogger().warn(
|
|
640
|
+
`[Actions] Could not resolve .jay-action file "${actionPath}" for package "${packageName}"`
|
|
641
|
+
);
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, registry, verbose, viteServer) {
|
|
645
|
+
const registeredActions = [];
|
|
646
|
+
try {
|
|
647
|
+
let pluginModule;
|
|
648
|
+
if (viteServer) {
|
|
649
|
+
pluginModule = await viteServer.ssrLoadModule(packageName);
|
|
650
|
+
} else {
|
|
651
|
+
pluginModule = await import(packageName);
|
|
652
|
+
}
|
|
653
|
+
let toolsModule;
|
|
654
|
+
const loadToolsModule = async () => {
|
|
655
|
+
if (!toolsModule) {
|
|
656
|
+
const toolsEntry = `${packageName}/tools`;
|
|
657
|
+
toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsEntry) : await import(toolsEntry);
|
|
658
|
+
}
|
|
659
|
+
return toolsModule;
|
|
660
|
+
};
|
|
661
|
+
for (const entry of pluginConfig.actions) {
|
|
662
|
+
const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
|
|
663
|
+
const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
|
|
664
|
+
const actionExport = sourceModule[actionName];
|
|
665
|
+
if (actionExport && isJayAction(actionExport)) {
|
|
666
|
+
registry.register(actionExport);
|
|
667
|
+
const registeredName = actionExport.actionName;
|
|
668
|
+
registeredActions.push(registeredName);
|
|
669
|
+
if (actionPath) {
|
|
670
|
+
const metadataFilePath = resolveNpmActionMetadataPath(
|
|
671
|
+
actionPath,
|
|
672
|
+
packageName,
|
|
673
|
+
pluginDir
|
|
674
|
+
);
|
|
675
|
+
if (metadataFilePath) {
|
|
676
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
677
|
+
if (metadata) {
|
|
678
|
+
registry.setMetadata(registeredName, metadata);
|
|
679
|
+
if (verbose) {
|
|
680
|
+
getLogger().info(
|
|
681
|
+
`[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (verbose) {
|
|
688
|
+
getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
|
|
689
|
+
}
|
|
690
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
691
|
+
registry.registerStream(actionExport);
|
|
692
|
+
const registeredName = actionExport.actionName;
|
|
693
|
+
registeredActions.push(registeredName);
|
|
694
|
+
if (verbose) {
|
|
695
|
+
getLogger().info(`[Actions] Registered NPM plugin stream: ${registeredName}`);
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
getLogger().warn(
|
|
699
|
+
`[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch (importError) {
|
|
704
|
+
getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
|
|
705
|
+
}
|
|
706
|
+
return registeredActions;
|
|
707
|
+
}
|
|
708
|
+
async function discoverPluginActions(pluginPath, projectRoot, registry = actionRegistry, verbose = false, viteServer) {
|
|
709
|
+
const pluginConfig = loadPluginManifest(pluginPath);
|
|
710
|
+
if (!pluginConfig) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
if (!pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
const registeredActions = [];
|
|
717
|
+
const pluginName = pluginConfig.name || path.basename(pluginPath);
|
|
718
|
+
if (verbose) {
|
|
719
|
+
getLogger().info(
|
|
720
|
+
`[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
|
|
724
|
+
if (!fs.existsSync(modulePath)) {
|
|
725
|
+
const tsPath = modulePath + ".ts";
|
|
726
|
+
const jsPath = modulePath + ".js";
|
|
727
|
+
if (fs.existsSync(tsPath)) {
|
|
728
|
+
modulePath = tsPath;
|
|
729
|
+
} else if (fs.existsSync(jsPath)) {
|
|
730
|
+
modulePath = jsPath;
|
|
731
|
+
} else {
|
|
732
|
+
getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const resolveLocalToolsModulePath = () => {
|
|
737
|
+
for (const candidate of ["tools.ts", "tools.js"]) {
|
|
738
|
+
const candidatePath = path.join(pluginPath, candidate);
|
|
739
|
+
if (fs.existsSync(candidatePath)) return candidatePath;
|
|
740
|
+
}
|
|
741
|
+
return null;
|
|
742
|
+
};
|
|
743
|
+
try {
|
|
744
|
+
let pluginModule;
|
|
745
|
+
if (viteServer) {
|
|
746
|
+
pluginModule = await viteServer.ssrLoadModule(modulePath);
|
|
747
|
+
} else {
|
|
748
|
+
pluginModule = await import(modulePath);
|
|
749
|
+
}
|
|
750
|
+
let toolsModule;
|
|
751
|
+
const loadToolsModule = async () => {
|
|
752
|
+
if (toolsModule) return toolsModule;
|
|
753
|
+
const toolsPath = resolveLocalToolsModulePath();
|
|
754
|
+
if (!toolsPath) {
|
|
755
|
+
getLogger().warn(
|
|
756
|
+
`[Actions] Plugin "${pluginName}" declares a devOnly action but has no tools.ts/tools.js module`
|
|
757
|
+
);
|
|
758
|
+
return void 0;
|
|
759
|
+
}
|
|
760
|
+
toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsPath) : await import(toolsPath);
|
|
761
|
+
return toolsModule;
|
|
762
|
+
};
|
|
763
|
+
for (const entry of pluginConfig.actions) {
|
|
764
|
+
const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
|
|
765
|
+
const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
|
|
766
|
+
const actionExport = sourceModule?.[actionName];
|
|
767
|
+
if (actionExport && isJayAction(actionExport)) {
|
|
768
|
+
registry.register(actionExport);
|
|
769
|
+
const registeredName = actionExport.actionName;
|
|
770
|
+
registeredActions.push(registeredName);
|
|
771
|
+
if (actionPath) {
|
|
772
|
+
const metadataFilePath = resolveActionMetadataPath(actionPath, pluginPath);
|
|
773
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
774
|
+
if (metadata) {
|
|
775
|
+
registry.setMetadata(registeredName, metadata);
|
|
776
|
+
if (verbose) {
|
|
777
|
+
getLogger().info(
|
|
778
|
+
`[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (verbose) {
|
|
784
|
+
getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
|
|
785
|
+
}
|
|
786
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
787
|
+
registry.registerStream(actionExport);
|
|
788
|
+
const registeredName = actionExport.actionName;
|
|
789
|
+
registeredActions.push(registeredName);
|
|
790
|
+
if (verbose) {
|
|
791
|
+
getLogger().info(`[Actions] Registered plugin stream: ${registeredName}`);
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
getLogger().warn(
|
|
795
|
+
`[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
} catch (importError) {
|
|
800
|
+
getLogger().error(
|
|
801
|
+
`[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
return registeredActions;
|
|
805
|
+
}
|
|
806
|
+
const require$1 = createRequire(import.meta.url);
|
|
807
|
+
async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
|
|
808
|
+
const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
|
|
809
|
+
if (!config.generator) {
|
|
810
|
+
throw new Error(
|
|
811
|
+
`Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
|
|
815
|
+
let generator;
|
|
816
|
+
if (isFilePath) {
|
|
817
|
+
let generatorPath;
|
|
818
|
+
if (!isLocal) {
|
|
819
|
+
try {
|
|
820
|
+
generatorPath = require$1.resolve(`${packageName}/${config.generator}`, {
|
|
821
|
+
paths: [projectRoot]
|
|
822
|
+
});
|
|
823
|
+
} catch {
|
|
824
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
828
|
+
}
|
|
829
|
+
if (!fs.existsSync(generatorPath)) {
|
|
830
|
+
const withTs = generatorPath + ".ts";
|
|
831
|
+
const withJs = generatorPath + ".js";
|
|
832
|
+
if (fs.existsSync(withTs)) {
|
|
833
|
+
generatorPath = withTs;
|
|
834
|
+
} else if (fs.existsSync(withJs)) {
|
|
835
|
+
generatorPath = withJs;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (!fs.existsSync(generatorPath)) {
|
|
839
|
+
throw new Error(
|
|
840
|
+
`Generator file not found for plugin "${pluginName}": ${config.generator}`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
if (verbose) {
|
|
844
|
+
getLogger().info(` Loading generator from file: ${generatorPath}`);
|
|
845
|
+
}
|
|
846
|
+
let generatorModule;
|
|
847
|
+
if (viteServer) {
|
|
848
|
+
generatorModule = await viteServer.ssrLoadModule(generatorPath);
|
|
849
|
+
} else {
|
|
850
|
+
generatorModule = await import(generatorPath);
|
|
851
|
+
}
|
|
852
|
+
generator = generatorModule.generator || generatorModule.default;
|
|
853
|
+
} else {
|
|
854
|
+
if (verbose) {
|
|
855
|
+
getLogger().info(
|
|
856
|
+
` Loading generator export: ${config.generator} from ${packageName}`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
let pluginModule;
|
|
860
|
+
if (viteServer) {
|
|
861
|
+
pluginModule = await viteServer.ssrLoadModule(packageName);
|
|
862
|
+
} else {
|
|
863
|
+
pluginModule = await import(packageName);
|
|
864
|
+
}
|
|
865
|
+
generator = pluginModule[config.generator];
|
|
866
|
+
if (!generator) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
`Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (!generator || typeof generator.generate !== "function") {
|
|
873
|
+
throw new Error(
|
|
874
|
+
`Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
const resolvedServices = [];
|
|
878
|
+
for (const marker of generator.services) {
|
|
879
|
+
const service = services.get(marker);
|
|
880
|
+
if (!service) {
|
|
881
|
+
const markerName = marker.description ?? "unknown";
|
|
882
|
+
throw new Error(
|
|
883
|
+
`Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
resolvedServices.push(service);
|
|
887
|
+
}
|
|
888
|
+
if (verbose) {
|
|
889
|
+
getLogger().info(` Executing generator...`);
|
|
890
|
+
}
|
|
891
|
+
return await generator.generate(...resolvedServices);
|
|
892
|
+
}
|
|
893
|
+
function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
|
|
894
|
+
const { pluginPath, isLocal, packageName } = plugin;
|
|
895
|
+
if (!isLocal) {
|
|
896
|
+
try {
|
|
897
|
+
return require$1.resolve(`${packageName}/${contractSpec}`, {
|
|
898
|
+
paths: [projectRoot]
|
|
899
|
+
});
|
|
900
|
+
} catch {
|
|
901
|
+
const possiblePaths = [
|
|
902
|
+
path.join(pluginPath, "dist", contractSpec),
|
|
903
|
+
path.join(pluginPath, "lib", contractSpec),
|
|
904
|
+
path.join(pluginPath, contractSpec)
|
|
905
|
+
];
|
|
906
|
+
const found = possiblePaths.find((p) => fs.existsSync(p));
|
|
907
|
+
return found || possiblePaths[0];
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
return path.join(pluginPath, contractSpec);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, projectRoot) {
|
|
914
|
+
if (!isLocal && !actionPath.startsWith(".")) {
|
|
915
|
+
try {
|
|
916
|
+
return require$1.resolve(`${packageName}/${actionPath}`, {
|
|
917
|
+
paths: [projectRoot]
|
|
918
|
+
});
|
|
919
|
+
} catch {
|
|
920
|
+
const possiblePaths = [
|
|
921
|
+
path.join(pluginPath, "dist", actionPath),
|
|
922
|
+
path.join(pluginPath, "lib", actionPath),
|
|
923
|
+
path.join(pluginPath, actionPath)
|
|
924
|
+
];
|
|
925
|
+
const found = possiblePaths.find((p) => fs.existsSync(p));
|
|
926
|
+
return found || null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const resolved = resolveActionMetadataPath(actionPath, pluginPath);
|
|
930
|
+
return fs.existsSync(resolved) ? resolved : null;
|
|
931
|
+
}
|
|
932
|
+
function toKebabCase(str) {
|
|
933
|
+
return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
934
|
+
}
|
|
935
|
+
async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
|
|
936
|
+
const {
|
|
937
|
+
projectRoot,
|
|
938
|
+
outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
|
|
939
|
+
dynamicOnly = false,
|
|
940
|
+
pluginFilter,
|
|
941
|
+
verbose = false,
|
|
942
|
+
viteServer
|
|
943
|
+
} = options;
|
|
944
|
+
const pluginsIndexMap = /* @__PURE__ */ new Map();
|
|
945
|
+
let staticCount = 0;
|
|
946
|
+
let dynamicCount = 0;
|
|
947
|
+
if (verbose) {
|
|
948
|
+
getLogger().info("Scanning for plugins...");
|
|
949
|
+
}
|
|
950
|
+
const plugins = await scanPlugins({
|
|
951
|
+
projectRoot,
|
|
952
|
+
verbose,
|
|
953
|
+
includeDevDeps: true
|
|
954
|
+
// Include dev deps for contract discovery
|
|
955
|
+
});
|
|
956
|
+
if (verbose) {
|
|
957
|
+
getLogger().info(`Found ${plugins.size} plugin(s)`);
|
|
958
|
+
}
|
|
959
|
+
for (const [pluginKey, plugin] of plugins) {
|
|
960
|
+
if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter) continue;
|
|
961
|
+
if (verbose) {
|
|
962
|
+
getLogger().info(`
|
|
963
|
+
š¦ Processing plugin: ${plugin.name}`);
|
|
964
|
+
}
|
|
965
|
+
const { manifest } = plugin;
|
|
966
|
+
const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
|
|
967
|
+
if (!pluginsIndexMap.has(plugin.name)) {
|
|
968
|
+
const entry = {
|
|
969
|
+
path: "./" + pluginRelPath.replace(/\\/g, "/"),
|
|
970
|
+
contracts: [],
|
|
971
|
+
actions: []
|
|
972
|
+
};
|
|
973
|
+
if (manifest.services?.length) {
|
|
974
|
+
entry.services = manifest.services.map((s2) => {
|
|
975
|
+
const docPath = s2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, s2.doc)) : void 0;
|
|
976
|
+
return {
|
|
977
|
+
name: s2.name,
|
|
978
|
+
marker: s2.marker,
|
|
979
|
+
...s2.description && { description: s2.description },
|
|
980
|
+
...docPath && { doc: docPath }
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
if (manifest.contexts?.length) {
|
|
985
|
+
entry.contexts = manifest.contexts.map((c) => {
|
|
986
|
+
const docPath = c.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, c.doc)) : void 0;
|
|
987
|
+
return {
|
|
988
|
+
name: c.name,
|
|
989
|
+
marker: c.marker,
|
|
990
|
+
...c.description && { description: c.description },
|
|
991
|
+
...docPath && { doc: docPath }
|
|
992
|
+
};
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (manifest.routes?.length) {
|
|
996
|
+
entry.routes = manifest.routes.map((r) => ({
|
|
997
|
+
path: r.path,
|
|
998
|
+
...r.description && { description: r.description }
|
|
999
|
+
}));
|
|
1000
|
+
}
|
|
1001
|
+
if (manifest.commands?.length) {
|
|
1002
|
+
entry.commands = manifest.commands.map((c) => {
|
|
1003
|
+
let description;
|
|
1004
|
+
if (c.command) {
|
|
1005
|
+
try {
|
|
1006
|
+
const cmdPath = path.resolve(plugin.pluginPath, c.command);
|
|
1007
|
+
const cmdContent = fs.readFileSync(cmdPath, "utf-8");
|
|
1008
|
+
const parsed = YAML.parse(cmdContent);
|
|
1009
|
+
description = parsed?.description;
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
name: c.name,
|
|
1015
|
+
...description && { description }
|
|
1016
|
+
};
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
pluginsIndexMap.set(plugin.name, entry);
|
|
1020
|
+
}
|
|
1021
|
+
if (!dynamicOnly && manifest.contracts) {
|
|
1022
|
+
for (const contract of manifest.contracts) {
|
|
1023
|
+
const contractPath = resolveStaticContractPath(
|
|
1024
|
+
plugin,
|
|
1025
|
+
contract.contract,
|
|
1026
|
+
projectRoot
|
|
1027
|
+
);
|
|
1028
|
+
const relativePath = path.relative(projectRoot, contractPath);
|
|
1029
|
+
let description = contract.description;
|
|
1030
|
+
if (!description) {
|
|
1031
|
+
try {
|
|
1032
|
+
const contractContent = fs.readFileSync(contractPath, "utf-8");
|
|
1033
|
+
const parsed = YAML.parse(contractContent);
|
|
1034
|
+
if (parsed?.description && typeof parsed.description === "string") {
|
|
1035
|
+
description = parsed.description;
|
|
1036
|
+
}
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
pluginsIndexMap.get(plugin.name).contracts.push({
|
|
1041
|
+
name: contract.name,
|
|
1042
|
+
...description && { description },
|
|
1043
|
+
type: "static",
|
|
1044
|
+
path: "./" + relativePath
|
|
1045
|
+
});
|
|
1046
|
+
staticCount++;
|
|
1047
|
+
if (verbose) {
|
|
1048
|
+
getLogger().info(` š Static: ${contract.name}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (manifest.dynamic_contracts) {
|
|
1053
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
1054
|
+
const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
|
|
1055
|
+
fs.mkdirSync(pluginOutputDir, { recursive: true });
|
|
1056
|
+
for (const config of dynamicConfigs) {
|
|
1057
|
+
if (verbose) {
|
|
1058
|
+
getLogger().info(` ā” Dynamic contracts (prefix: ${config.prefix})`);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const generatedContracts = await executeDynamicGenerator(
|
|
1062
|
+
plugin,
|
|
1063
|
+
config,
|
|
1064
|
+
projectRoot,
|
|
1065
|
+
services,
|
|
1066
|
+
verbose,
|
|
1067
|
+
viteServer
|
|
1068
|
+
);
|
|
1069
|
+
const prefix = config.prefix;
|
|
1070
|
+
if (generatedContracts.length > 1) {
|
|
1071
|
+
const missing = generatedContracts.filter((c) => !c.name);
|
|
1072
|
+
if (missing.length > 0) {
|
|
1073
|
+
getLogger().error(
|
|
1074
|
+
` ā Dynamic contract generator for "${prefix}" returned ${generatedContracts.length} contracts but ${missing.length} are missing a name. Names are required when a generator returns multiple contracts.`
|
|
1075
|
+
);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
for (const generated of generatedContracts) {
|
|
1080
|
+
const kebabName = generated.name ? toKebabCase(generated.name) : null;
|
|
1081
|
+
const fullName = kebabName ? `${prefix}/${kebabName}` : prefix;
|
|
1082
|
+
const fileName = kebabName ? `${prefix}-${kebabName}.jay-contract` : `${prefix}.jay-contract`;
|
|
1083
|
+
const filePath = path.join(pluginOutputDir, fileName);
|
|
1084
|
+
fs.writeFileSync(filePath, generated.yaml, "utf-8");
|
|
1085
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
1086
|
+
let dynDescription;
|
|
1087
|
+
try {
|
|
1088
|
+
const parsedYaml = YAML.parse(generated.yaml);
|
|
1089
|
+
if (parsedYaml?.description && typeof parsedYaml.description === "string") {
|
|
1090
|
+
dynDescription = parsedYaml.description;
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
const contractEntry = {
|
|
1095
|
+
name: fullName,
|
|
1096
|
+
...dynDescription && { description: dynDescription },
|
|
1097
|
+
type: "dynamic",
|
|
1098
|
+
path: "./" + relativePath,
|
|
1099
|
+
...generated.metadata && { metadata: generated.metadata }
|
|
1100
|
+
};
|
|
1101
|
+
pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
|
|
1102
|
+
dynamicCount++;
|
|
1103
|
+
if (verbose) {
|
|
1104
|
+
getLogger().info(` ā” Materialized: ${fullName}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
getLogger().error(
|
|
1109
|
+
` ā Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (manifest.actions && Array.isArray(manifest.actions)) {
|
|
1115
|
+
for (const entry of manifest.actions) {
|
|
1116
|
+
const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
|
|
1117
|
+
if (!actionPath) continue;
|
|
1118
|
+
const metadataFilePath = resolveActionFilePath(
|
|
1119
|
+
actionPath,
|
|
1120
|
+
plugin.packageName,
|
|
1121
|
+
plugin.pluginPath,
|
|
1122
|
+
plugin.isLocal,
|
|
1123
|
+
projectRoot
|
|
1124
|
+
);
|
|
1125
|
+
if (!metadataFilePath) continue;
|
|
1126
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
1127
|
+
if (!metadata) continue;
|
|
1128
|
+
const actionRelPath = path.relative(projectRoot, metadataFilePath);
|
|
1129
|
+
const pluginEntry = pluginsIndexMap.get(plugin.name);
|
|
1130
|
+
if (!pluginEntry.actions) pluginEntry.actions = [];
|
|
1131
|
+
pluginEntry.actions.push({
|
|
1132
|
+
name: metadata.name,
|
|
1133
|
+
description: metadata.description,
|
|
1134
|
+
path: "./" + actionRelPath.replace(/\\/g, "/")
|
|
1135
|
+
});
|
|
1136
|
+
if (verbose) {
|
|
1137
|
+
getLogger().info(` š§ Action: ${metadata.name} (${actionPath})`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const pluginsIndex = {
|
|
1143
|
+
plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
|
|
1144
|
+
name,
|
|
1145
|
+
path: data.path,
|
|
1146
|
+
contracts: data.contracts,
|
|
1147
|
+
...data.actions && data.actions.length > 0 && { actions: data.actions },
|
|
1148
|
+
...data.services?.length && { services: data.services },
|
|
1149
|
+
...data.contexts?.length && { contexts: data.contexts },
|
|
1150
|
+
...data.routes?.length && { routes: data.routes },
|
|
1151
|
+
...data.commands?.length && { commands: data.commands }
|
|
1152
|
+
}))
|
|
1153
|
+
};
|
|
1154
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1155
|
+
const agentKitDir = path.dirname(outputDir);
|
|
1156
|
+
const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
|
|
1157
|
+
fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
|
|
1158
|
+
if (verbose) {
|
|
1159
|
+
getLogger().info(`
|
|
1160
|
+
ā
Plugins index written to: ${pluginsIndexPath}`);
|
|
1161
|
+
}
|
|
1162
|
+
return {
|
|
1163
|
+
pluginsIndex,
|
|
1164
|
+
staticCount,
|
|
1165
|
+
dynamicCount,
|
|
1166
|
+
outputDir
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
async function listContracts(options) {
|
|
1170
|
+
const { projectRoot, dynamicOnly = false, pluginFilter } = options;
|
|
1171
|
+
const pluginsMap = /* @__PURE__ */ new Map();
|
|
1172
|
+
const plugins = await scanPlugins({
|
|
1173
|
+
projectRoot,
|
|
1174
|
+
includeDevDeps: true
|
|
1175
|
+
});
|
|
1176
|
+
for (const [pluginKey, plugin] of plugins) {
|
|
1177
|
+
if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter) continue;
|
|
1178
|
+
const { manifest } = plugin;
|
|
1179
|
+
const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
|
|
1180
|
+
if (!pluginsMap.has(plugin.name)) {
|
|
1181
|
+
const entry = {
|
|
1182
|
+
path: "./" + pluginRelPath.replace(/\\/g, "/"),
|
|
1183
|
+
contracts: []
|
|
1184
|
+
};
|
|
1185
|
+
if (manifest.services?.length) {
|
|
1186
|
+
entry.services = manifest.services.map((s2) => {
|
|
1187
|
+
const docPath = s2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, s2.doc)) : void 0;
|
|
1188
|
+
return {
|
|
1189
|
+
name: s2.name,
|
|
1190
|
+
marker: s2.marker,
|
|
1191
|
+
...s2.description && { description: s2.description },
|
|
1192
|
+
...docPath && { doc: docPath }
|
|
1193
|
+
};
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
if (manifest.contexts?.length) {
|
|
1197
|
+
entry.contexts = manifest.contexts.map((c) => {
|
|
1198
|
+
const docPath = c.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, c.doc)) : void 0;
|
|
1199
|
+
return {
|
|
1200
|
+
name: c.name,
|
|
1201
|
+
marker: c.marker,
|
|
1202
|
+
...c.description && { description: c.description },
|
|
1203
|
+
...docPath && { doc: docPath }
|
|
1204
|
+
};
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (manifest.routes?.length) {
|
|
1208
|
+
entry.routes = manifest.routes.map((r) => ({
|
|
1209
|
+
path: r.path,
|
|
1210
|
+
...r.description && { description: r.description }
|
|
1211
|
+
}));
|
|
1212
|
+
}
|
|
1213
|
+
if (manifest.commands?.length) {
|
|
1214
|
+
entry.commands = manifest.commands.map((c) => {
|
|
1215
|
+
let description;
|
|
1216
|
+
if (c.command) {
|
|
1217
|
+
try {
|
|
1218
|
+
const cmdPath = path.resolve(plugin.pluginPath, c.command);
|
|
1219
|
+
const cmdContent = fs.readFileSync(cmdPath, "utf-8");
|
|
1220
|
+
const parsed = YAML.parse(cmdContent);
|
|
1221
|
+
description = parsed?.description;
|
|
1222
|
+
} catch {
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
name: c.name,
|
|
1227
|
+
...description && { description }
|
|
1228
|
+
};
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
pluginsMap.set(plugin.name, entry);
|
|
1232
|
+
}
|
|
1233
|
+
if (!dynamicOnly && manifest.contracts) {
|
|
1234
|
+
for (const contract of manifest.contracts) {
|
|
1235
|
+
const contractPath = resolveStaticContractPath(
|
|
1236
|
+
plugin,
|
|
1237
|
+
contract.contract,
|
|
1238
|
+
projectRoot
|
|
1239
|
+
);
|
|
1240
|
+
const relativePath = path.relative(projectRoot, contractPath);
|
|
1241
|
+
let listDescription = contract.description;
|
|
1242
|
+
if (!listDescription) {
|
|
1243
|
+
try {
|
|
1244
|
+
const contractContent = fs.readFileSync(contractPath, "utf-8");
|
|
1245
|
+
const parsed = YAML.parse(contractContent);
|
|
1246
|
+
if (parsed?.description && typeof parsed.description === "string") {
|
|
1247
|
+
listDescription = parsed.description;
|
|
1248
|
+
}
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
pluginsMap.get(plugin.name).contracts.push({
|
|
1253
|
+
name: contract.name,
|
|
1254
|
+
...listDescription && { description: listDescription },
|
|
1255
|
+
type: "static",
|
|
1256
|
+
path: "./" + relativePath
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (manifest.dynamic_contracts) {
|
|
1261
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
1262
|
+
for (const config of dynamicConfigs) {
|
|
1263
|
+
pluginsMap.get(plugin.name).contracts.push({
|
|
1264
|
+
name: `${config.prefix}/*`,
|
|
1265
|
+
type: "dynamic",
|
|
1266
|
+
path: "(run materialization to generate)"
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
plugins: Array.from(pluginsMap.entries()).map(([name, data]) => ({
|
|
1273
|
+
name,
|
|
1274
|
+
path: data.path,
|
|
1275
|
+
contracts: data.contracts,
|
|
1276
|
+
...data.services?.length && { services: data.services },
|
|
1277
|
+
...data.contexts?.length && { contexts: data.contexts },
|
|
1278
|
+
...data.routes?.length && { routes: data.routes },
|
|
1279
|
+
...data.commands?.length && { commands: data.commands }
|
|
1280
|
+
}))
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
async function discoverPluginCommands(options) {
|
|
1284
|
+
const { projectRoot, verbose, pluginFilter } = options;
|
|
1285
|
+
const allPlugins = await scanPlugins({
|
|
1286
|
+
projectRoot,
|
|
1287
|
+
verbose,
|
|
1288
|
+
discoverTransitive: true,
|
|
1289
|
+
includeDevDeps: true
|
|
1290
|
+
});
|
|
1291
|
+
const commands = [];
|
|
1292
|
+
for (const [packageName, plugin] of allPlugins) {
|
|
1293
|
+
if (!plugin.manifest.commands || plugin.manifest.commands.length === 0) continue;
|
|
1294
|
+
if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
for (const cmd of plugin.manifest.commands) {
|
|
1298
|
+
let metadata;
|
|
1299
|
+
let metadataPath;
|
|
1300
|
+
if (cmd.command) {
|
|
1301
|
+
metadataPath = path.resolve(plugin.pluginPath, cmd.command);
|
|
1302
|
+
metadata = loadCommandMetadata(metadataPath);
|
|
1303
|
+
}
|
|
1304
|
+
commands.push({
|
|
1305
|
+
pluginName: plugin.name,
|
|
1306
|
+
pluginPath: plugin.pluginPath,
|
|
1307
|
+
packageName: plugin.packageName,
|
|
1308
|
+
isLocal: plugin.isLocal,
|
|
1309
|
+
commandName: cmd.name,
|
|
1310
|
+
handlerExport: cmd.name,
|
|
1311
|
+
pluginModule: plugin.manifest.module,
|
|
1312
|
+
metadata,
|
|
1313
|
+
metadataPath
|
|
1314
|
+
});
|
|
1315
|
+
if (verbose) {
|
|
1316
|
+
getLogger().info(`[Commands] Found ${plugin.name}/${cmd.name}`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return commands;
|
|
1321
|
+
}
|
|
1322
|
+
function loadCommandMetadata(filePath) {
|
|
1323
|
+
try {
|
|
1324
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1325
|
+
return YAML.parse(content);
|
|
1326
|
+
} catch {
|
|
1327
|
+
return void 0;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function commandSchemaToFlags(inputSchema) {
|
|
1331
|
+
const flags = [];
|
|
1332
|
+
for (const [field, type] of Object.entries(inputSchema)) {
|
|
1333
|
+
const isOptional = field.endsWith("?");
|
|
1334
|
+
const cleanName = isOptional ? field.slice(0, -1) : field;
|
|
1335
|
+
const kebabName = camelToKebab(cleanName);
|
|
1336
|
+
const cleanType = type.toLowerCase().trim();
|
|
1337
|
+
const isBoolean = cleanType === "boolean";
|
|
1338
|
+
flags.push({
|
|
1339
|
+
flag: isBoolean ? `--${kebabName}` : `--${kebabName} <value>`,
|
|
1340
|
+
description: "",
|
|
1341
|
+
required: !isOptional,
|
|
1342
|
+
type: cleanType === "number" ? "number" : isBoolean ? "boolean" : "string"
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
return flags;
|
|
1346
|
+
}
|
|
1347
|
+
function camelToKebab(str) {
|
|
1348
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1349
|
+
}
|
|
1350
|
+
function parseInputFromFlags(rawOptions, schema) {
|
|
1351
|
+
const input2 = {};
|
|
1352
|
+
for (const [field, type] of Object.entries(schema)) {
|
|
1353
|
+
const isOptional = field.endsWith("?");
|
|
1354
|
+
const cleanName = isOptional ? field.slice(0, -1) : field;
|
|
1355
|
+
const kebabName = camelToKebab(cleanName);
|
|
1356
|
+
const value = rawOptions[kebabName];
|
|
1357
|
+
if (value === void 0) {
|
|
1358
|
+
if (!isOptional) {
|
|
1359
|
+
throw new Error(`Missing required flag: --${kebabName}`);
|
|
1360
|
+
}
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
const cleanType = type.toLowerCase().trim();
|
|
1364
|
+
if (cleanType === "number") {
|
|
1365
|
+
const parsed = Number(value);
|
|
1366
|
+
if (isNaN(parsed)) throw new Error(`Flag --${kebabName} must be a number`);
|
|
1367
|
+
input2[cleanName] = parsed;
|
|
1368
|
+
} else if (cleanType === "boolean") {
|
|
1369
|
+
input2[cleanName] = value === true || value === "true";
|
|
1370
|
+
} else {
|
|
1371
|
+
input2[cleanName] = String(value);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return input2;
|
|
1375
|
+
}
|
|
1376
|
+
async function executePluginCommand(command, input2, viteServer) {
|
|
1377
|
+
const cliCommand = await loadCommandHandler(command, viteServer);
|
|
1378
|
+
const services = resolveServices(cliCommand.services);
|
|
1379
|
+
return cliCommand.handler(input2, ...services);
|
|
1380
|
+
}
|
|
1381
|
+
async function loadCommandHandler(command, viteServer) {
|
|
1382
|
+
let module;
|
|
1383
|
+
if (command.isLocal) {
|
|
1384
|
+
const moduleFile = command.pluginModule || "index";
|
|
1385
|
+
const modulePath = path.resolve(command.pluginPath, moduleFile);
|
|
1386
|
+
if (viteServer) {
|
|
1387
|
+
module = await viteServer.ssrLoadModule(modulePath);
|
|
1388
|
+
} else {
|
|
1389
|
+
module = await import(modulePath);
|
|
1390
|
+
}
|
|
1391
|
+
} else {
|
|
1392
|
+
const toolsEntry = `${command.packageName}/tools`;
|
|
1393
|
+
if (viteServer) {
|
|
1394
|
+
module = await viteServer.ssrLoadModule(toolsEntry);
|
|
1395
|
+
} else {
|
|
1396
|
+
module = await import(toolsEntry);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
for (const [, exported] of Object.entries(module)) {
|
|
1400
|
+
if (isJayCliCommand(exported) && exported.commandName === command.commandName) {
|
|
1401
|
+
return exported;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
const byName = module[command.handlerExport];
|
|
1405
|
+
if (byName && isJayCliCommand(byName)) {
|
|
1406
|
+
return byName;
|
|
1407
|
+
}
|
|
1408
|
+
throw new Error(
|
|
1409
|
+
`CLI command "${command.commandName}" not found as export in "${command.isLocal ? command.pluginPath : command.packageName}". Available exports: ${Object.keys(module).join(", ")}`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
class SetupNeedsAnswerError extends Error {
|
|
1413
|
+
constructor(plugin, key, type, promptMessage, choices) {
|
|
1414
|
+
super(`Setup needs answer for "${key}": ${promptMessage}`);
|
|
1415
|
+
this.plugin = plugin;
|
|
1416
|
+
this.key = key;
|
|
1417
|
+
this.type = type;
|
|
1418
|
+
this.promptMessage = promptMessage;
|
|
1419
|
+
this.choices = choices;
|
|
1420
|
+
this.name = "SetupNeedsAnswerError";
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
async function discoverPluginsWithSetup(options) {
|
|
1424
|
+
const { projectRoot, verbose, pluginFilter } = options;
|
|
1425
|
+
const allPlugins = await scanPlugins({
|
|
1426
|
+
projectRoot,
|
|
1427
|
+
verbose,
|
|
1428
|
+
includeDevDeps: true,
|
|
1429
|
+
discoverTransitive: true
|
|
1430
|
+
});
|
|
1431
|
+
const pluginsWithSetup = [];
|
|
1432
|
+
for (const [packageName, plugin] of allPlugins) {
|
|
1433
|
+
if (!plugin.manifest.setup) continue;
|
|
1434
|
+
if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
pluginsWithSetup.push({
|
|
1438
|
+
name: plugin.name,
|
|
1439
|
+
pluginPath: plugin.pluginPath,
|
|
1440
|
+
packageName: plugin.packageName,
|
|
1441
|
+
isLocal: plugin.isLocal,
|
|
1442
|
+
setupHandler: plugin.manifest.setup,
|
|
1443
|
+
setupDescription: plugin.manifest.description,
|
|
1444
|
+
dependencies: plugin.dependencies
|
|
1445
|
+
});
|
|
1446
|
+
if (verbose) {
|
|
1447
|
+
getLogger().info(`[Setup] Found plugin with setup: ${plugin.name}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return sortPluginsByDependencies(pluginsWithSetup);
|
|
1451
|
+
}
|
|
1452
|
+
async function discoverPluginsWithAgentKit(options) {
|
|
1453
|
+
const { projectRoot, verbose, pluginFilter } = options;
|
|
1454
|
+
const allPlugins = await scanPlugins({
|
|
1455
|
+
projectRoot,
|
|
1456
|
+
verbose,
|
|
1457
|
+
includeDevDeps: true,
|
|
1458
|
+
discoverTransitive: true
|
|
1459
|
+
});
|
|
1460
|
+
const pluginsWithAgentKit = [];
|
|
1461
|
+
for (const [packageName, plugin] of allPlugins) {
|
|
1462
|
+
if (!plugin.manifest.agentkit) continue;
|
|
1463
|
+
if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
pluginsWithAgentKit.push({
|
|
1467
|
+
name: plugin.name,
|
|
1468
|
+
pluginPath: plugin.pluginPath,
|
|
1469
|
+
packageName: plugin.packageName,
|
|
1470
|
+
isLocal: plugin.isLocal,
|
|
1471
|
+
agentKitHandler: plugin.manifest.agentkit,
|
|
1472
|
+
dependencies: plugin.dependencies
|
|
1473
|
+
});
|
|
1474
|
+
if (verbose) {
|
|
1475
|
+
getLogger().info(`[AgentKit] Found plugin with agent-kit handler: ${plugin.name}`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return sortPluginsByDependencies(pluginsWithAgentKit);
|
|
1479
|
+
}
|
|
1480
|
+
async function executePluginSetup(plugin, options) {
|
|
1481
|
+
const { projectRoot, configDir, force, interactive, prompt, initError, viteServer } = options;
|
|
1482
|
+
const context = {
|
|
1483
|
+
pluginName: plugin.name,
|
|
1484
|
+
projectRoot,
|
|
1485
|
+
configDir,
|
|
1486
|
+
services: getServiceRegistry(),
|
|
1487
|
+
initError,
|
|
1488
|
+
force,
|
|
1489
|
+
interactive,
|
|
1490
|
+
prompt
|
|
1491
|
+
};
|
|
1492
|
+
const handler = await loadHandler(plugin, plugin.setupHandler, viteServer);
|
|
1493
|
+
return handler(context);
|
|
1494
|
+
}
|
|
1495
|
+
async function executePluginAgentKit(plugin, options) {
|
|
1496
|
+
const { projectRoot, force, initError, viteServer } = options;
|
|
1497
|
+
const referencesDir = path.join(projectRoot, "agent-kit", "references", plugin.name);
|
|
1498
|
+
const context = {
|
|
1499
|
+
pluginName: plugin.name,
|
|
1500
|
+
projectRoot,
|
|
1501
|
+
referencesDir,
|
|
1502
|
+
services: getServiceRegistry(),
|
|
1503
|
+
initError,
|
|
1504
|
+
force
|
|
1505
|
+
};
|
|
1506
|
+
const handler = await loadHandler(
|
|
1507
|
+
plugin,
|
|
1508
|
+
plugin.agentKitHandler,
|
|
1509
|
+
viteServer
|
|
1510
|
+
);
|
|
1511
|
+
return handler(context);
|
|
1512
|
+
}
|
|
1513
|
+
async function loadHandler(plugin, handlerName, viteServer) {
|
|
1514
|
+
let module;
|
|
1515
|
+
if (plugin.isLocal) {
|
|
1516
|
+
const handlerPath = path.resolve(plugin.pluginPath, handlerName);
|
|
1517
|
+
if (viteServer) {
|
|
1518
|
+
module = await viteServer.ssrLoadModule(handlerPath);
|
|
1519
|
+
} else {
|
|
1520
|
+
module = await import(handlerPath);
|
|
1521
|
+
}
|
|
1522
|
+
if (typeof module[handlerName] === "function") return module[handlerName];
|
|
1523
|
+
if (typeof module.agentkit === "function") return module.agentkit;
|
|
1524
|
+
if (typeof module.setup === "function") return module.setup;
|
|
1525
|
+
if (typeof module.default === "function") return module.default;
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
`Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
|
|
1528
|
+
);
|
|
1529
|
+
} else {
|
|
1530
|
+
const toolsEntry = `${plugin.packageName}/tools`;
|
|
1531
|
+
if (viteServer) {
|
|
1532
|
+
module = await viteServer.ssrLoadModule(toolsEntry);
|
|
1533
|
+
} else {
|
|
1534
|
+
module = await import(toolsEntry);
|
|
1535
|
+
}
|
|
1536
|
+
if (typeof module[handlerName] !== "function") {
|
|
1537
|
+
throw new Error(
|
|
1538
|
+
`Handler "${handlerName}" not found as export in "${toolsEntry}". Available exports: ${Object.keys(module).join(", ")}`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
return module[handlerName];
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
225
1544
|
async function initializeServicesForCli(projectRoot, viteServer, quiet = false) {
|
|
226
1545
|
const path2 = await import("node:path");
|
|
227
1546
|
const fs2 = await import("node:fs");
|
|
228
1547
|
const {
|
|
229
1548
|
runInitCallbacks: runInitCallbacks2,
|
|
230
|
-
getServiceRegistry,
|
|
1549
|
+
getServiceRegistry: getServiceRegistry2,
|
|
231
1550
|
discoverPluginsWithInit: discoverPluginsWithInit2,
|
|
232
1551
|
sortPluginsByDependencies: sortPluginsByDependencies2,
|
|
233
1552
|
executePluginServerInits: executePluginServerInits2
|
|
@@ -260,7 +1579,7 @@ async function initializeServicesForCli(projectRoot, viteServer, quiet = false)
|
|
|
260
1579
|
getLogger().warn(chalk.yellow(`ā ļø Service initialization failed: ${error.message}`));
|
|
261
1580
|
getLogger().warn(chalk.gray(" Static contracts will still be listed."));
|
|
262
1581
|
}
|
|
263
|
-
return { services:
|
|
1582
|
+
return { services: getServiceRegistry2(), initErrors };
|
|
264
1583
|
}
|
|
265
1584
|
async function runDev(projectPath, options) {
|
|
266
1585
|
const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
|
|
@@ -274,12 +1593,12 @@ async function runDev(projectPath, options) {
|
|
|
274
1593
|
});
|
|
275
1594
|
}
|
|
276
1595
|
async function resolveProductionContext(projectPath, versionOverride) {
|
|
277
|
-
const resolvedPath =
|
|
278
|
-
const jayConfigPath =
|
|
1596
|
+
const resolvedPath = path__default.resolve(projectPath || process.cwd());
|
|
1597
|
+
const jayConfigPath = path__default.join(resolvedPath, ".jay");
|
|
279
1598
|
let pagesBase = "./src/pages";
|
|
280
1599
|
let siteBaseUrl;
|
|
281
1600
|
try {
|
|
282
|
-
const jayConfig = YAML.parse(await fs$
|
|
1601
|
+
const jayConfig = YAML.parse(await fs$2.readFile(jayConfigPath, "utf-8"));
|
|
283
1602
|
pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
|
|
284
1603
|
siteBaseUrl = jayConfig?.site?.baseUrl;
|
|
285
1604
|
} catch {
|
|
@@ -287,17 +1606,17 @@ async function resolveProductionContext(projectPath, versionOverride) {
|
|
|
287
1606
|
const version = versionOverride || await resolveVersionFromPackageJson(resolvedPath);
|
|
288
1607
|
return {
|
|
289
1608
|
resolvedPath,
|
|
290
|
-
pagesRoot:
|
|
291
|
-
buildRoot:
|
|
1609
|
+
pagesRoot: path__default.resolve(resolvedPath, pagesBase),
|
|
1610
|
+
buildRoot: path__default.join(resolvedPath, "build"),
|
|
292
1611
|
version,
|
|
293
|
-
tsConfigFilePath:
|
|
1612
|
+
tsConfigFilePath: path__default.join(resolvedPath, "tsconfig.json"),
|
|
294
1613
|
siteBaseUrl
|
|
295
1614
|
};
|
|
296
1615
|
}
|
|
297
1616
|
async function resolveVersionFromPackageJson(projectRoot) {
|
|
298
1617
|
try {
|
|
299
1618
|
const pkgJson = JSON.parse(
|
|
300
|
-
await fs$
|
|
1619
|
+
await fs$2.readFile(path__default.join(projectRoot, "package.json"), "utf-8")
|
|
301
1620
|
);
|
|
302
1621
|
if (pkgJson.version) {
|
|
303
1622
|
return pkgJson.version;
|
|
@@ -313,7 +1632,7 @@ function initLogger(verbose) {
|
|
|
313
1632
|
async function runBuild(projectPath, options) {
|
|
314
1633
|
initLogger(options.verbose);
|
|
315
1634
|
const ctx = await resolveProductionContext(projectPath, options.version);
|
|
316
|
-
const { buildVersion } = await import("
|
|
1635
|
+
const { buildVersion } = await import("./index-B9vCxAZ1.js");
|
|
317
1636
|
await buildVersion({
|
|
318
1637
|
version: ctx.version,
|
|
319
1638
|
projectRoot: ctx.resolvedPath,
|
|
@@ -1158,7 +2477,7 @@ function isRelativeHandlerRef(value) {
|
|
|
1158
2477
|
function resolveModulePath$1(basePath) {
|
|
1159
2478
|
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
1160
2479
|
const candidate = basePath + ext;
|
|
1161
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
2480
|
+
if (fs$1.existsSync(candidate) && fs$1.statSync(candidate).isFile()) {
|
|
1162
2481
|
return candidate;
|
|
1163
2482
|
}
|
|
1164
2483
|
}
|
|
@@ -1166,10 +2485,10 @@ function resolveModulePath$1(basePath) {
|
|
|
1166
2485
|
}
|
|
1167
2486
|
function collectTypeScriptFiles(dir, depth = 0) {
|
|
1168
2487
|
if (depth > 4) return [];
|
|
1169
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2488
|
+
const entries = fs$1.readdirSync(dir, { withFileTypes: true });
|
|
1170
2489
|
const files = [];
|
|
1171
2490
|
for (const entry of entries) {
|
|
1172
|
-
const fullPath = path.join(dir, entry.name);
|
|
2491
|
+
const fullPath = path$1.join(dir, entry.name);
|
|
1173
2492
|
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "test") {
|
|
1174
2493
|
files.push(...collectTypeScriptFiles(fullPath, depth + 1));
|
|
1175
2494
|
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
|
@@ -1180,13 +2499,13 @@ function collectTypeScriptFiles(dir, depth = 0) {
|
|
|
1180
2499
|
}
|
|
1181
2500
|
function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
1182
2501
|
if (isRelativeHandlerRef(handlerRef)) {
|
|
1183
|
-
return resolveModulePath$1(path.join(pluginPath, handlerRef)) ?? null;
|
|
2502
|
+
return resolveModulePath$1(path$1.join(pluginPath, handlerRef)) ?? null;
|
|
1184
2503
|
}
|
|
1185
|
-
const searchRoots = isNpmPackage ? [path.join(pluginPath, "lib"), path.join(pluginPath, "dist")] : [pluginPath];
|
|
2504
|
+
const searchRoots = isNpmPackage ? [path$1.join(pluginPath, "lib"), path$1.join(pluginPath, "dist")] : [pluginPath];
|
|
1186
2505
|
for (const root of searchRoots) {
|
|
1187
|
-
if (!fs.existsSync(root)) continue;
|
|
2506
|
+
if (!fs$1.existsSync(root)) continue;
|
|
1188
2507
|
for (const file of collectTypeScriptFiles(root)) {
|
|
1189
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
2508
|
+
const content = fs$1.readFileSync(file, "utf-8");
|
|
1190
2509
|
const definesHandler = new RegExp(
|
|
1191
2510
|
`export\\s+(?:async\\s+)?function\\s+${handlerRef}\\b`
|
|
1192
2511
|
).test(content);
|
|
@@ -1203,7 +2522,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
|
1203
2522
|
);
|
|
1204
2523
|
if (reExportMatch) {
|
|
1205
2524
|
const importSpec = reExportMatch[1].replace(/\.js$/, "");
|
|
1206
|
-
const resolved = resolveModulePath$1(path.resolve(path.dirname(file), importSpec));
|
|
2525
|
+
const resolved = resolveModulePath$1(path$1.resolve(path$1.dirname(file), importSpec));
|
|
1207
2526
|
if (resolved) return resolved;
|
|
1208
2527
|
}
|
|
1209
2528
|
}
|
|
@@ -1279,7 +2598,7 @@ function handlerBodyWritesAddMenuCatalog(body) {
|
|
|
1279
2598
|
function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
1280
2599
|
const sourceFile = resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage);
|
|
1281
2600
|
if (!sourceFile) return null;
|
|
1282
|
-
const source = fs.readFileSync(sourceFile, "utf-8");
|
|
2601
|
+
const source = fs$1.readFileSync(sourceFile, "utf-8");
|
|
1283
2602
|
if (isRelativeHandlerRef(handlerRef)) {
|
|
1284
2603
|
return extractDefaultExportFunctionBody(source) ?? extractFunctionBody(source, "setup") ?? extractFunctionBody(source, handlerRef);
|
|
1285
2604
|
}
|
|
@@ -1312,7 +2631,7 @@ function mapLintFinding(finding, catalogPath, severity) {
|
|
|
1312
2631
|
}
|
|
1313
2632
|
function pluginShipsAddMenuCatalog(context) {
|
|
1314
2633
|
return ADD_MENU_CATALOG_REL_PATHS.some(
|
|
1315
|
-
(relPath) => fs.existsSync(path.join(context.pluginPath, relPath))
|
|
2634
|
+
(relPath) => fs$1.existsSync(path$1.join(context.pluginPath, relPath))
|
|
1316
2635
|
);
|
|
1317
2636
|
}
|
|
1318
2637
|
function validateAddMenuAgentKitHandler(context, result) {
|
|
@@ -1346,7 +2665,7 @@ function validateAddMenuAgentKitHandler(context, result) {
|
|
|
1346
2665
|
async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
|
|
1347
2666
|
let parsed;
|
|
1348
2667
|
try {
|
|
1349
|
-
const content = await fs.promises.readFile(catalogPath, "utf-8");
|
|
2668
|
+
const content = await fs$1.promises.readFile(catalogPath, "utf-8");
|
|
1350
2669
|
parsed = YAML.parse(content);
|
|
1351
2670
|
} catch (error) {
|
|
1352
2671
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1375,8 +2694,8 @@ async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
|
|
|
1375
2694
|
async function validateAddMenuCatalog(context, result) {
|
|
1376
2695
|
validateAddMenuAgentKitHandler(context, result);
|
|
1377
2696
|
for (const relPath of ADD_MENU_CATALOG_REL_PATHS) {
|
|
1378
|
-
const catalogPath = path.join(context.pluginPath, relPath);
|
|
1379
|
-
if (!fs.existsSync(catalogPath)) continue;
|
|
2697
|
+
const catalogPath = path$1.join(context.pluginPath, relPath);
|
|
2698
|
+
if (!fs$1.existsSync(catalogPath)) continue;
|
|
1380
2699
|
await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
|
|
1381
2700
|
}
|
|
1382
2701
|
}
|
|
@@ -1493,7 +2812,7 @@ function mapSchemaError(error, relPath) {
|
|
|
1493
2812
|
function validateSettingsTemplateAtPath(catalogPath, relPath, result, manifest) {
|
|
1494
2813
|
let parsed;
|
|
1495
2814
|
try {
|
|
1496
|
-
parsed = YAML.parse(fs.readFileSync(catalogPath, "utf-8"));
|
|
2815
|
+
parsed = YAML.parse(fs$1.readFileSync(catalogPath, "utf-8"));
|
|
1497
2816
|
} catch (err) {
|
|
1498
2817
|
result.errors.push({
|
|
1499
2818
|
type: "schema",
|
|
@@ -1527,8 +2846,8 @@ function validateSettingsTemplateAtPath(catalogPath, relPath, result, manifest)
|
|
|
1527
2846
|
}
|
|
1528
2847
|
}
|
|
1529
2848
|
async function validateAiditorSettings(context, result) {
|
|
1530
|
-
const templatePath = path.join(context.pluginPath, AIDITOR_SETTINGS_TEMPLATE_REL_PATH);
|
|
1531
|
-
if (!fs.existsSync(templatePath)) {
|
|
2849
|
+
const templatePath = path$1.join(context.pluginPath, AIDITOR_SETTINGS_TEMPLATE_REL_PATH);
|
|
2850
|
+
if (!fs$1.existsSync(templatePath)) {
|
|
1532
2851
|
return;
|
|
1533
2852
|
}
|
|
1534
2853
|
validateSettingsTemplateAtPath(
|
|
@@ -1563,10 +2882,10 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
1563
2882
|
contractsChecked: 0,
|
|
1564
2883
|
componentsChecked: 0
|
|
1565
2884
|
};
|
|
1566
|
-
const pluginYamlPath = path.join(pluginPath, "plugin.yaml");
|
|
1567
|
-
const pluginManifest = loadPluginManifest(pluginPath);
|
|
2885
|
+
const pluginYamlPath = path$1.join(pluginPath, "plugin.yaml");
|
|
2886
|
+
const pluginManifest = loadPluginManifest$1(pluginPath);
|
|
1568
2887
|
if (!pluginManifest) {
|
|
1569
|
-
if (!fs.existsSync(pluginYamlPath)) {
|
|
2888
|
+
if (!fs$1.existsSync(pluginYamlPath)) {
|
|
1570
2889
|
result.errors.push({
|
|
1571
2890
|
type: "file-missing",
|
|
1572
2891
|
message: "plugin.yaml not found",
|
|
@@ -1587,7 +2906,7 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
1587
2906
|
const context = {
|
|
1588
2907
|
manifest: pluginManifest,
|
|
1589
2908
|
pluginPath,
|
|
1590
|
-
isNpmPackage: fs.existsSync(path.join(pluginPath, "package.json"))
|
|
2909
|
+
isNpmPackage: fs$1.existsSync(path$1.join(pluginPath, "package.json"))
|
|
1591
2910
|
};
|
|
1592
2911
|
await validateSchema(context, result);
|
|
1593
2912
|
if (pluginManifest.contracts) {
|
|
@@ -1615,12 +2934,13 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
1615
2934
|
}
|
|
1616
2935
|
await validateAddMenuCatalog(context, result);
|
|
1617
2936
|
await validateAiditorSettings(context, result);
|
|
2937
|
+
validateNoCompilerLeak(context, result);
|
|
1618
2938
|
result.valid = result.errors.length === 0;
|
|
1619
2939
|
return result;
|
|
1620
2940
|
}
|
|
1621
2941
|
async function validateLocalPlugins(projectPath, options) {
|
|
1622
|
-
const pluginsPath = path.join(projectPath, "src/plugins");
|
|
1623
|
-
if (!fs.existsSync(pluginsPath)) {
|
|
2942
|
+
const pluginsPath = path$1.join(projectPath, "src/plugins");
|
|
2943
|
+
if (!fs$1.existsSync(pluginsPath)) {
|
|
1624
2944
|
return {
|
|
1625
2945
|
valid: false,
|
|
1626
2946
|
errors: [
|
|
@@ -1634,10 +2954,10 @@ async function validateLocalPlugins(projectPath, options) {
|
|
|
1634
2954
|
warnings: []
|
|
1635
2955
|
};
|
|
1636
2956
|
}
|
|
1637
|
-
const pluginDirs = fs.readdirSync(pluginsPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
2957
|
+
const pluginDirs = fs$1.readdirSync(pluginsPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
1638
2958
|
const allResults = [];
|
|
1639
2959
|
for (const pluginDir of pluginDirs) {
|
|
1640
|
-
const pluginPath = path.join(pluginsPath, pluginDir.name);
|
|
2960
|
+
const pluginPath = path$1.join(pluginsPath, pluginDir.name);
|
|
1641
2961
|
const result = await validatePluginPackage(pluginPath, options);
|
|
1642
2962
|
allResults.push(result);
|
|
1643
2963
|
}
|
|
@@ -1651,8 +2971,8 @@ async function validateLocalPlugins(projectPath, options) {
|
|
|
1651
2971
|
};
|
|
1652
2972
|
}
|
|
1653
2973
|
function validateDocFile(docPath, label, context, result) {
|
|
1654
|
-
const resolvedPath = path.join(context.pluginPath, docPath);
|
|
1655
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
2974
|
+
const resolvedPath = path$1.join(context.pluginPath, docPath);
|
|
2975
|
+
if (!fs$1.existsSync(resolvedPath)) {
|
|
1656
2976
|
result.errors.push({
|
|
1657
2977
|
type: "file-missing",
|
|
1658
2978
|
message: `Doc file for ${label} not found: ${docPath}`,
|
|
@@ -1662,9 +2982,9 @@ function validateDocFile(docPath, label, context, result) {
|
|
|
1662
2982
|
return;
|
|
1663
2983
|
}
|
|
1664
2984
|
if (context.isNpmPackage) {
|
|
1665
|
-
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2985
|
+
const packageJsonPath = path$1.join(context.pluginPath, "package.json");
|
|
1666
2986
|
try {
|
|
1667
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
2987
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
1668
2988
|
if (packageJson.exports) {
|
|
1669
2989
|
const exportKey = "./" + docPath.replace(/^\.\//, "");
|
|
1670
2990
|
if (!packageJson.exports[exportKey]) {
|
|
@@ -1788,13 +3108,28 @@ async function validateSchema(context, result) {
|
|
|
1788
3108
|
}
|
|
1789
3109
|
}
|
|
1790
3110
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
3111
|
+
const hasCapability = Boolean(
|
|
3112
|
+
manifest.contracts || manifest.dynamic_contracts || manifest.actions || manifest.validators || manifest.routes || manifest.services || manifest.contexts || manifest.init || manifest.setup || manifest.agentkit || manifest.commands
|
|
3113
|
+
);
|
|
3114
|
+
if (!hasCapability) {
|
|
3115
|
+
if (manifest.global === true) {
|
|
3116
|
+
const hasGlobalEntry = !context.isNpmPackage || checkExportExists("init", context, ".") || checkExportExists("setup", context, ".");
|
|
3117
|
+
if (!hasGlobalEntry) {
|
|
3118
|
+
result.errors.push({
|
|
3119
|
+
type: "export-mismatch",
|
|
3120
|
+
message: 'Plugin declares "global: true" but exports no init/setup handler to run on each page',
|
|
3121
|
+
location: "plugin.yaml",
|
|
3122
|
+
suggestion: 'Export an "init" (or "setup") handler from the package entry, or declare a capability'
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
} else {
|
|
3126
|
+
result.warnings.push({
|
|
3127
|
+
type: "schema",
|
|
3128
|
+
message: "Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, routes, services, contexts, init, setup, agentkit, commands)",
|
|
3129
|
+
location: "plugin.yaml",
|
|
3130
|
+
suggestion: "Declare at least one capability. See agent-kit/plugin/plugin-structure.md"
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
1798
3133
|
}
|
|
1799
3134
|
if (manifest.services) {
|
|
1800
3135
|
if (!Array.isArray(manifest.services)) {
|
|
@@ -1858,14 +3193,22 @@ async function validateSchema(context, result) {
|
|
|
1858
3193
|
}
|
|
1859
3194
|
if (manifest.actions) {
|
|
1860
3195
|
for (const entry of manifest.actions) {
|
|
1861
|
-
|
|
3196
|
+
if (typeof entry === "object" && entry.devOnly !== void 0 && typeof entry.devOnly !== "boolean") {
|
|
3197
|
+
result.errors.push({
|
|
3198
|
+
type: "schema",
|
|
3199
|
+
message: `Action "${entry.name}" devOnly must be a boolean`,
|
|
3200
|
+
location: "plugin.yaml actions"
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
const { name: exportName, devOnly } = normalizeActionEntry$1(entry);
|
|
1862
3204
|
if (exportName) {
|
|
1863
3205
|
validateHandlerRef(
|
|
1864
3206
|
exportName,
|
|
1865
3207
|
`Action "${exportName}"`,
|
|
1866
3208
|
"plugin.yaml actions",
|
|
1867
3209
|
context,
|
|
1868
|
-
result
|
|
3210
|
+
result,
|
|
3211
|
+
devOnly ? "./tools" : "."
|
|
1869
3212
|
);
|
|
1870
3213
|
}
|
|
1871
3214
|
}
|
|
@@ -1910,7 +3253,8 @@ async function validateSchema(context, result) {
|
|
|
1910
3253
|
`Route "${route.path}" component`,
|
|
1911
3254
|
`plugin.yaml routes`,
|
|
1912
3255
|
context,
|
|
1913
|
-
result
|
|
3256
|
+
result,
|
|
3257
|
+
route.devOnly ? "./tools" : "."
|
|
1914
3258
|
);
|
|
1915
3259
|
}
|
|
1916
3260
|
if (route.jayHtml) {
|
|
@@ -1964,7 +3308,8 @@ async function validateSchema(context, result) {
|
|
|
1964
3308
|
`Validator "${validator.name}" handler`,
|
|
1965
3309
|
"plugin.yaml validators",
|
|
1966
3310
|
context,
|
|
1967
|
-
result
|
|
3311
|
+
result,
|
|
3312
|
+
"./tools"
|
|
1968
3313
|
);
|
|
1969
3314
|
}
|
|
1970
3315
|
});
|
|
@@ -1984,7 +3329,8 @@ async function validateSchema(context, result) {
|
|
|
1984
3329
|
"Setup handler",
|
|
1985
3330
|
"plugin.yaml setup",
|
|
1986
3331
|
context,
|
|
1987
|
-
result
|
|
3332
|
+
result,
|
|
3333
|
+
"./tools"
|
|
1988
3334
|
);
|
|
1989
3335
|
}
|
|
1990
3336
|
}
|
|
@@ -1994,30 +3340,31 @@ async function validateSchema(context, result) {
|
|
|
1994
3340
|
"Agent-kit handler",
|
|
1995
3341
|
"plugin.yaml agentkit",
|
|
1996
3342
|
context,
|
|
1997
|
-
result
|
|
3343
|
+
result,
|
|
3344
|
+
"./tools"
|
|
1998
3345
|
);
|
|
1999
3346
|
}
|
|
2000
3347
|
}
|
|
2001
|
-
function checkExportExists(exportName, context) {
|
|
2002
|
-
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2003
|
-
if (!fs.existsSync(packageJsonPath)) return true;
|
|
3348
|
+
function checkExportExists(exportName, context, exportKey = ".") {
|
|
3349
|
+
const packageJsonPath = path$1.join(context.pluginPath, "package.json");
|
|
3350
|
+
if (!fs$1.existsSync(packageJsonPath)) return true;
|
|
2004
3351
|
let mainPath;
|
|
2005
3352
|
try {
|
|
2006
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
2007
|
-
if (packageJson.exports?.[
|
|
2008
|
-
const entry = packageJson.exports[
|
|
3353
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
3354
|
+
if (packageJson.exports?.[exportKey]) {
|
|
3355
|
+
const entry = packageJson.exports[exportKey];
|
|
2009
3356
|
const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
|
|
2010
|
-
if (entryPath) mainPath = path.join(context.pluginPath, entryPath);
|
|
3357
|
+
if (entryPath) mainPath = path$1.join(context.pluginPath, entryPath);
|
|
2011
3358
|
}
|
|
2012
|
-
if (!mainPath && packageJson.main) {
|
|
2013
|
-
mainPath = path.join(context.pluginPath, packageJson.main);
|
|
3359
|
+
if (!mainPath && exportKey === "." && packageJson.main) {
|
|
3360
|
+
mainPath = path$1.join(context.pluginPath, packageJson.main);
|
|
2014
3361
|
}
|
|
2015
3362
|
} catch {
|
|
2016
3363
|
return true;
|
|
2017
3364
|
}
|
|
2018
|
-
if (!mainPath || !fs.existsSync(mainPath)) return true;
|
|
3365
|
+
if (!mainPath || !fs$1.existsSync(mainPath)) return true;
|
|
2019
3366
|
try {
|
|
2020
|
-
const content = fs.readFileSync(mainPath, "utf-8");
|
|
3367
|
+
const content = fs$1.readFileSync(mainPath, "utf-8");
|
|
2021
3368
|
const patterns = [
|
|
2022
3369
|
new RegExp(`export\\s*\\{[^}]*\\b${exportName}\\b[^}]*\\}`, "m"),
|
|
2023
3370
|
new RegExp(`export\\s+(?:async\\s+)?function\\s+${exportName}\\b`),
|
|
@@ -2031,7 +3378,63 @@ function checkExportExists(exportName, context) {
|
|
|
2031
3378
|
function isRelativePath(value) {
|
|
2032
3379
|
return value.startsWith("./") || value.startsWith("../");
|
|
2033
3380
|
}
|
|
2034
|
-
function
|
|
3381
|
+
function resolveEntryFile(context, exportKey) {
|
|
3382
|
+
const packageJsonPath = path$1.join(context.pluginPath, "package.json");
|
|
3383
|
+
if (!fs$1.existsSync(packageJsonPath)) return void 0;
|
|
3384
|
+
try {
|
|
3385
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
3386
|
+
const entry = packageJson.exports?.[exportKey];
|
|
3387
|
+
const entryPath = typeof entry === "string" ? entry : entry?.default || entry?.import || void 0;
|
|
3388
|
+
const resolved = entryPath || (exportKey === "." ? packageJson.main : void 0) ? path$1.join(context.pluginPath, entryPath || packageJson.main) : void 0;
|
|
3389
|
+
return resolved && fs$1.existsSync(resolved) ? resolved : void 0;
|
|
3390
|
+
} catch {
|
|
3391
|
+
return void 0;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
function hasComponentCapability(context) {
|
|
3395
|
+
const m = context.manifest;
|
|
3396
|
+
return Boolean(m.contracts || m.dynamic_contracts || m.routes);
|
|
3397
|
+
}
|
|
3398
|
+
function needsToolsEntry(manifest) {
|
|
3399
|
+
if (manifest.validators || manifest.commands || manifest.agentkit || manifest.setup) {
|
|
3400
|
+
return true;
|
|
3401
|
+
}
|
|
3402
|
+
if (manifest.actions) {
|
|
3403
|
+
return manifest.actions.some((entry) => normalizeActionEntry$1(entry).devOnly === true);
|
|
3404
|
+
}
|
|
3405
|
+
return false;
|
|
3406
|
+
}
|
|
3407
|
+
function detectInteractivePhase(context) {
|
|
3408
|
+
if (!context.isNpmPackage) return "unknown";
|
|
3409
|
+
const entryFile = resolveEntryFile(context, ".");
|
|
3410
|
+
if (!entryFile) return "unknown";
|
|
3411
|
+
try {
|
|
3412
|
+
const content = fs$1.readFileSync(entryFile, "utf-8");
|
|
3413
|
+
return content.includes("withInteractiveMark(");
|
|
3414
|
+
} catch {
|
|
3415
|
+
return "unknown";
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
function validateNoCompilerLeak(context, result) {
|
|
3419
|
+
if (!context.isNpmPackage) return;
|
|
3420
|
+
const entryFile = resolveEntryFile(context, ".");
|
|
3421
|
+
if (!entryFile) return;
|
|
3422
|
+
let content;
|
|
3423
|
+
try {
|
|
3424
|
+
content = fs$1.readFileSync(entryFile, "utf-8");
|
|
3425
|
+
} catch {
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
if (content.includes("@jay-framework/compiler-")) {
|
|
3429
|
+
result.errors.push({
|
|
3430
|
+
type: "compiler-leak",
|
|
3431
|
+
message: 'Serve entry "." (dist/index.js) imports "@jay-framework/compiler-ā¦" ā the compiler must not reach the production serve bundle',
|
|
3432
|
+
location: entryFile,
|
|
3433
|
+
suggestion: 'Move the compiler-using handler (validator, agentkit, setup, or a devOnly action) to lib/tools.ts (the "./tools" export) and remove its re-export from lib/index.ts. A compiler-using action is really a command or a devOnly action (DL#179/#180).'
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
function validateHandlerRef(value, label, location, context, result, exportKey = ".") {
|
|
2035
3438
|
if (context.isNpmPackage) {
|
|
2036
3439
|
if (isRelativePath(value)) {
|
|
2037
3440
|
result.errors.push({
|
|
@@ -2040,18 +3443,19 @@ function validateHandlerRef(value, label, location, context, result) {
|
|
|
2040
3443
|
location,
|
|
2041
3444
|
suggestion: `Export the function from the package entry point and use the export name instead of a path`
|
|
2042
3445
|
});
|
|
2043
|
-
} else if (!checkExportExists(value, context)) {
|
|
3446
|
+
} else if (!checkExportExists(value, context, exportKey)) {
|
|
3447
|
+
const entryFile = exportKey === "./tools" ? "lib/tools.ts" : exportKey === "./client" ? "lib/index.client.ts" : "lib/index.ts";
|
|
2044
3448
|
result.errors.push({
|
|
2045
3449
|
type: "export-mismatch",
|
|
2046
|
-
message: `${label} "${value}" is not exported from the
|
|
3450
|
+
message: `${label} "${value}" is not exported from the "${exportKey}" entry`,
|
|
2047
3451
|
location,
|
|
2048
|
-
suggestion: `Add "export { ${value} } from '...'" to the
|
|
3452
|
+
suggestion: `Add "export { ${value} } from '...'" to ${entryFile} (the "${exportKey}" export)`
|
|
2049
3453
|
});
|
|
2050
3454
|
}
|
|
2051
3455
|
} else if (isRelativePath(value)) {
|
|
2052
|
-
const handlerPath = path.join(context.pluginPath, value);
|
|
3456
|
+
const handlerPath = path$1.join(context.pluginPath, value);
|
|
2053
3457
|
const extensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
2054
|
-
const found = extensions.some((ext) => fs.existsSync(handlerPath + ext));
|
|
3458
|
+
const found = extensions.some((ext) => fs$1.existsSync(handlerPath + ext));
|
|
2055
3459
|
if (!found) {
|
|
2056
3460
|
result.errors.push({
|
|
2057
3461
|
type: "file-missing",
|
|
@@ -2064,18 +3468,18 @@ function validateHandlerRef(value, label, location, context, result) {
|
|
|
2064
3468
|
}
|
|
2065
3469
|
function resolveContractFile(contractSpec, context) {
|
|
2066
3470
|
if (context.isNpmPackage) {
|
|
2067
|
-
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2068
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
3471
|
+
const packageJsonPath = path$1.join(context.pluginPath, "package.json");
|
|
3472
|
+
if (fs$1.existsSync(packageJsonPath)) {
|
|
2069
3473
|
try {
|
|
2070
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
3474
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
2071
3475
|
if (packageJson.exports) {
|
|
2072
3476
|
const exportKey = "./" + contractSpec;
|
|
2073
3477
|
const exportValue = packageJson.exports[exportKey];
|
|
2074
3478
|
if (exportValue) {
|
|
2075
3479
|
const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
2076
3480
|
if (resolvedPath) {
|
|
2077
|
-
const fullPath = path.join(context.pluginPath, resolvedPath);
|
|
2078
|
-
if (fs.existsSync(fullPath)) return fullPath;
|
|
3481
|
+
const fullPath = path$1.join(context.pluginPath, resolvedPath);
|
|
3482
|
+
if (fs$1.existsSync(fullPath)) return fullPath;
|
|
2079
3483
|
}
|
|
2080
3484
|
}
|
|
2081
3485
|
}
|
|
@@ -2083,13 +3487,13 @@ function resolveContractFile(contractSpec, context) {
|
|
|
2083
3487
|
}
|
|
2084
3488
|
}
|
|
2085
3489
|
for (const dir of ["dist", "lib", ""]) {
|
|
2086
|
-
const candidate = path.join(context.pluginPath, dir, contractSpec);
|
|
2087
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
3490
|
+
const candidate = path$1.join(context.pluginPath, dir, contractSpec);
|
|
3491
|
+
if (fs$1.existsSync(candidate)) return candidate;
|
|
2088
3492
|
}
|
|
2089
3493
|
return void 0;
|
|
2090
3494
|
} else {
|
|
2091
|
-
const candidate = path.join(context.pluginPath, contractSpec);
|
|
2092
|
-
return fs.existsSync(candidate) ? candidate : void 0;
|
|
3495
|
+
const candidate = path$1.join(context.pluginPath, contractSpec);
|
|
3496
|
+
return fs$1.existsSync(candidate) ? candidate : void 0;
|
|
2093
3497
|
}
|
|
2094
3498
|
}
|
|
2095
3499
|
async function validateContract(contract, index, context, generateTypes, result) {
|
|
@@ -2100,12 +3504,12 @@ async function validateContract(contract, index, context, generateTypes, result)
|
|
|
2100
3504
|
type: "file-missing",
|
|
2101
3505
|
message: `Contract file not found: ${contract.contract}`,
|
|
2102
3506
|
location: `plugin.yaml contracts[${index}]`,
|
|
2103
|
-
suggestion: context.isNpmPackage ? `Ensure the contract is exported in package.json and the file exists` : `Create the contract file at ${path.join(context.pluginPath, contract.contract)}`
|
|
3507
|
+
suggestion: context.isNpmPackage ? `Ensure the contract is exported in package.json and the file exists` : `Create the contract file at ${path$1.join(context.pluginPath, contract.contract)}`
|
|
2104
3508
|
});
|
|
2105
3509
|
return;
|
|
2106
3510
|
}
|
|
2107
3511
|
try {
|
|
2108
|
-
const contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
3512
|
+
const contractContent = await fs$1.promises.readFile(contractPath, "utf-8");
|
|
2109
3513
|
const parsedContract = YAML.parse(contractContent);
|
|
2110
3514
|
if (!parsedContract.name) {
|
|
2111
3515
|
result.errors.push({
|
|
@@ -2172,21 +3576,21 @@ function hasExportModifier(node) {
|
|
|
2172
3576
|
function resolveModulePath(basePath) {
|
|
2173
3577
|
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
2174
3578
|
const candidate = basePath + ext;
|
|
2175
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
3579
|
+
if (fs$1.existsSync(candidate)) return candidate;
|
|
2176
3580
|
}
|
|
2177
3581
|
return void 0;
|
|
2178
3582
|
}
|
|
2179
3583
|
function resolveComponentSourcePath(componentName, context) {
|
|
2180
3584
|
const modulePath = context.manifest.module || "index";
|
|
2181
|
-
const entryBase = path.join(context.pluginPath, modulePath);
|
|
3585
|
+
const entryBase = path$1.join(context.pluginPath, modulePath);
|
|
2182
3586
|
const entryFile = resolveModulePath(entryBase);
|
|
2183
|
-
const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
3587
|
+
const libEntryFile = !entryFile ? resolveModulePath(path$1.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
2184
3588
|
const sourceEntry = entryFile || libEntryFile;
|
|
2185
3589
|
if (!sourceEntry) return void 0;
|
|
2186
3590
|
if (!sourceEntry.endsWith(".ts")) return void 0;
|
|
2187
3591
|
let sourceCode;
|
|
2188
3592
|
try {
|
|
2189
|
-
sourceCode = fs.readFileSync(sourceEntry, "utf-8");
|
|
3593
|
+
sourceCode = fs$1.readFileSync(sourceEntry, "utf-8");
|
|
2190
3594
|
} catch {
|
|
2191
3595
|
return void 0;
|
|
2192
3596
|
}
|
|
@@ -2212,7 +3616,7 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
2212
3616
|
for (const element of exportClause.elements) {
|
|
2213
3617
|
const exportedName = element.name.text;
|
|
2214
3618
|
if (exportedName === componentName) {
|
|
2215
|
-
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3619
|
+
const resolvedBase = path$1.resolve(path$1.dirname(sourceEntry), moduleSpec);
|
|
2216
3620
|
return resolveModulePath(resolvedBase);
|
|
2217
3621
|
}
|
|
2218
3622
|
}
|
|
@@ -2220,11 +3624,11 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
2220
3624
|
}
|
|
2221
3625
|
for (const moduleSpec of starReexportModules) {
|
|
2222
3626
|
if (!moduleSpec.startsWith(".")) continue;
|
|
2223
|
-
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3627
|
+
const resolvedBase = path$1.resolve(path$1.dirname(sourceEntry), moduleSpec);
|
|
2224
3628
|
const resolvedPath = resolveModulePath(resolvedBase);
|
|
2225
3629
|
if (!resolvedPath || !resolvedPath.endsWith(".ts")) continue;
|
|
2226
3630
|
try {
|
|
2227
|
-
const modSource = fs.readFileSync(resolvedPath, "utf-8");
|
|
3631
|
+
const modSource = fs$1.readFileSync(resolvedPath, "utf-8");
|
|
2228
3632
|
const modFile = u.createSourceFile(
|
|
2229
3633
|
resolvedPath,
|
|
2230
3634
|
modSource,
|
|
@@ -2263,15 +3667,15 @@ async function checkComponentContractConsistency(contract, context, result) {
|
|
|
2263
3667
|
if (!contractPath) return;
|
|
2264
3668
|
let contractContent;
|
|
2265
3669
|
try {
|
|
2266
|
-
contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
3670
|
+
contractContent = await fs$1.promises.readFile(contractPath, "utf-8");
|
|
2267
3671
|
} catch {
|
|
2268
3672
|
return;
|
|
2269
3673
|
}
|
|
2270
|
-
const parsed = parseContract(contractContent, path.basename(contractPath));
|
|
3674
|
+
const parsed = parseContract(contractContent, path$1.basename(contractPath));
|
|
2271
3675
|
if (parsed.validations.length > 0) return;
|
|
2272
3676
|
let sourceCode;
|
|
2273
3677
|
try {
|
|
2274
|
-
sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
|
|
3678
|
+
sourceCode = await fs$1.promises.readFile(sourcePath, "utf-8");
|
|
2275
3679
|
} catch {
|
|
2276
3680
|
return;
|
|
2277
3681
|
}
|
|
@@ -2290,8 +3694,8 @@ async function checkComponentContractConsistency(contract, context, result) {
|
|
|
2290
3694
|
result.warnings.push(...checkResult.warnings);
|
|
2291
3695
|
}
|
|
2292
3696
|
async function validatePackageJson(context, result) {
|
|
2293
|
-
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2294
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
3697
|
+
const packageJsonPath = path$1.join(context.pluginPath, "package.json");
|
|
3698
|
+
if (!fs$1.existsSync(packageJsonPath)) {
|
|
2295
3699
|
result.warnings.push({
|
|
2296
3700
|
type: "file-missing",
|
|
2297
3701
|
message: "package.json not found",
|
|
@@ -2301,7 +3705,7 @@ async function validatePackageJson(context, result) {
|
|
|
2301
3705
|
return;
|
|
2302
3706
|
}
|
|
2303
3707
|
try {
|
|
2304
|
-
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
|
|
3708
|
+
const packageJson = JSON.parse(await fs$1.promises.readFile(packageJsonPath, "utf-8"));
|
|
2305
3709
|
if (!packageJson.exports) {
|
|
2306
3710
|
result.warnings.push({
|
|
2307
3711
|
type: "export-mismatch",
|
|
@@ -2319,11 +3723,30 @@ async function validatePackageJson(context, result) {
|
|
|
2319
3723
|
});
|
|
2320
3724
|
}
|
|
2321
3725
|
if (!packageJson.exports["./client"]) {
|
|
2322
|
-
|
|
3726
|
+
const interactivity = detectInteractivePhase(context);
|
|
3727
|
+
const needsClient = context.manifest.contexts !== void 0 || interactivity === true;
|
|
3728
|
+
if (needsClient) {
|
|
3729
|
+
result.errors.push({
|
|
3730
|
+
type: "export-mismatch",
|
|
3731
|
+
message: 'package.json exports missing "./client" entry point, but the plugin ' + (context.manifest.contexts !== void 0 ? "declares contexts (client-side by definition)" : "provides an interactive component"),
|
|
3732
|
+
location: packageJsonPath,
|
|
3733
|
+
suggestion: 'Add "./client": "./dist/index.client.js" to exports. The client bundle provides components for hydration and client-side contexts. Build with: vite build (client) + vite build --ssr (server)'
|
|
3734
|
+
});
|
|
3735
|
+
} else if (interactivity === "unknown" && hasComponentCapability(context)) {
|
|
3736
|
+
result.warnings.push({
|
|
3737
|
+
type: "export-mismatch",
|
|
3738
|
+
message: 'package.json exports missing "./client" entry point; could not determine whether any component is interactive (build the plugin before validating)',
|
|
3739
|
+
location: packageJsonPath,
|
|
3740
|
+
suggestion: 'If any component declares an interactive phase, add "./client": "./dist/index.client.js"'
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
if (!packageJson.exports["./tools"] && needsToolsEntry(context.manifest)) {
|
|
3745
|
+
result.errors.push({
|
|
2323
3746
|
type: "export-mismatch",
|
|
2324
|
-
message: 'package.json exports missing "./
|
|
3747
|
+
message: 'package.json exports missing "./tools" entry point, but the plugin declares tools capabilities (validators, commands, agentkit, setup, or devOnly actions)',
|
|
2325
3748
|
location: packageJsonPath,
|
|
2326
|
-
suggestion: 'Add "./
|
|
3749
|
+
suggestion: 'Add "./tools": "./dist/tools.js" to exports and re-export those handlers from lib/tools.ts'
|
|
2327
3750
|
});
|
|
2328
3751
|
}
|
|
2329
3752
|
if (context.manifest.contracts) {
|
|
@@ -2365,8 +3788,8 @@ async function validatePackageJson(context, result) {
|
|
|
2365
3788
|
suggestion: 'Add "./plugin.yaml": "./plugin.yaml" to exports field'
|
|
2366
3789
|
});
|
|
2367
3790
|
}
|
|
2368
|
-
const agentKitDir = path.join(context.pluginPath, "agent-kit");
|
|
2369
|
-
if (fs.existsSync(agentKitDir) && fs.statSync(agentKitDir).isDirectory()) {
|
|
3791
|
+
const agentKitDir = path$1.join(context.pluginPath, "agent-kit");
|
|
3792
|
+
if (fs$1.existsSync(agentKitDir) && fs$1.statSync(agentKitDir).isDirectory()) {
|
|
2370
3793
|
const filesArray = packageJson.files;
|
|
2371
3794
|
if (!filesArray || !filesArray.includes("agent-kit")) {
|
|
2372
3795
|
result.warnings.push({
|
|
@@ -2390,7 +3813,7 @@ function isBareFunctionExport(exportName, context) {
|
|
|
2390
3813
|
if (!sourcePath) return false;
|
|
2391
3814
|
let sourceCode;
|
|
2392
3815
|
try {
|
|
2393
|
-
sourceCode = fs.readFileSync(sourcePath, "utf-8");
|
|
3816
|
+
sourceCode = fs$1.readFileSync(sourcePath, "utf-8");
|
|
2394
3817
|
} catch {
|
|
2395
3818
|
return false;
|
|
2396
3819
|
}
|
|
@@ -2418,8 +3841,8 @@ function resolveModulePathWithJsToTs(basePath) {
|
|
|
2418
3841
|
}
|
|
2419
3842
|
function resolveExportSourceFile(exportName, context) {
|
|
2420
3843
|
const modulePath = context.manifest.module || "index";
|
|
2421
|
-
const entryBase = path.join(context.pluginPath, modulePath);
|
|
2422
|
-
const libEntryBase = path.join(context.pluginPath, "lib", modulePath);
|
|
3844
|
+
const entryBase = path$1.join(context.pluginPath, modulePath);
|
|
3845
|
+
const libEntryBase = path$1.join(context.pluginPath, "lib", modulePath);
|
|
2423
3846
|
const sourceEntry = resolveModulePath(entryBase) || resolveModulePath(libEntryBase);
|
|
2424
3847
|
if (!sourceEntry || !sourceEntry.endsWith(".ts")) return void 0;
|
|
2425
3848
|
return followExportChain(exportName, sourceEntry);
|
|
@@ -2427,7 +3850,7 @@ function resolveExportSourceFile(exportName, context) {
|
|
|
2427
3850
|
function followExportChain(exportName, filePath) {
|
|
2428
3851
|
let sourceCode;
|
|
2429
3852
|
try {
|
|
2430
|
-
sourceCode = fs.readFileSync(filePath, "utf-8");
|
|
3853
|
+
sourceCode = fs$1.readFileSync(filePath, "utf-8");
|
|
2431
3854
|
} catch {
|
|
2432
3855
|
return void 0;
|
|
2433
3856
|
}
|
|
@@ -2450,7 +3873,7 @@ function followExportChain(exportName, filePath) {
|
|
|
2450
3873
|
if (u.isNamedExports(statement.exportClause)) {
|
|
2451
3874
|
for (const element of statement.exportClause.elements) {
|
|
2452
3875
|
if (element.name.text === exportName) {
|
|
2453
|
-
const resolvedBase = path.resolve(path.dirname(filePath), moduleSpec);
|
|
3876
|
+
const resolvedBase = path$1.resolve(path$1.dirname(filePath), moduleSpec);
|
|
2454
3877
|
return resolveModulePathWithJsToTs(resolvedBase);
|
|
2455
3878
|
}
|
|
2456
3879
|
}
|
|
@@ -2467,7 +3890,7 @@ function followExportChain(exportName, filePath) {
|
|
|
2467
3890
|
}
|
|
2468
3891
|
for (const moduleSpec of starReexportModules) {
|
|
2469
3892
|
if (!moduleSpec.startsWith(".")) continue;
|
|
2470
|
-
const resolvedBase = path.resolve(path.dirname(filePath), moduleSpec);
|
|
3893
|
+
const resolvedBase = path$1.resolve(path$1.dirname(filePath), moduleSpec);
|
|
2471
3894
|
const resolved = resolveModulePathWithJsToTs(resolvedBase);
|
|
2472
3895
|
if (!resolved) continue;
|
|
2473
3896
|
const found = followExportChain(exportName, resolved);
|
|
@@ -2484,11 +3907,11 @@ async function validateDynamicContracts(context, result) {
|
|
|
2484
3907
|
if (config.generator) {
|
|
2485
3908
|
const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
|
|
2486
3909
|
if (isFilePath) {
|
|
2487
|
-
const generatorPath = path.join(context.pluginPath, config.generator);
|
|
3910
|
+
const generatorPath = path$1.join(context.pluginPath, config.generator);
|
|
2488
3911
|
const possibleExtensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
2489
3912
|
let found = false;
|
|
2490
3913
|
for (const ext of possibleExtensions) {
|
|
2491
|
-
if (fs.existsSync(generatorPath + ext)) {
|
|
3914
|
+
if (fs$1.existsSync(generatorPath + ext)) {
|
|
2492
3915
|
found = true;
|
|
2493
3916
|
break;
|
|
2494
3917
|
}
|
|
@@ -2513,11 +3936,11 @@ async function validateDynamicContracts(context, result) {
|
|
|
2513
3936
|
if (config.component) {
|
|
2514
3937
|
const isFilePath = config.component.startsWith("./") || config.component.startsWith("/") || config.component.includes(".ts") || config.component.includes(".js");
|
|
2515
3938
|
if (isFilePath) {
|
|
2516
|
-
const componentPath = path.join(context.pluginPath, config.component);
|
|
3939
|
+
const componentPath = path$1.join(context.pluginPath, config.component);
|
|
2517
3940
|
const possibleExtensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
2518
3941
|
let found = false;
|
|
2519
3942
|
for (const ext of possibleExtensions) {
|
|
2520
|
-
if (fs.existsSync(componentPath + ext)) {
|
|
3943
|
+
if (fs$1.existsSync(componentPath + ext)) {
|
|
2521
3944
|
found = true;
|
|
2522
3945
|
break;
|
|
2523
3946
|
}
|
|
@@ -2838,12 +4261,12 @@ function checkRefElementTypes(jayHtml, file) {
|
|
|
2838
4261
|
return warnings;
|
|
2839
4262
|
}
|
|
2840
4263
|
function checkPageComponentExport(jayHtmlPath) {
|
|
2841
|
-
const dirname = path.dirname(jayHtmlPath);
|
|
2842
|
-
const compPath = path.join(dirname, "page.ts");
|
|
2843
|
-
if (!fs.existsSync(compPath)) return null;
|
|
4264
|
+
const dirname = path$1.dirname(jayHtmlPath);
|
|
4265
|
+
const compPath = path$1.join(dirname, "page.ts");
|
|
4266
|
+
if (!fs$1.existsSync(compPath)) return null;
|
|
2844
4267
|
let content;
|
|
2845
4268
|
try {
|
|
2846
|
-
content = fs.readFileSync(compPath, "utf-8");
|
|
4269
|
+
content = fs$1.readFileSync(compPath, "utf-8");
|
|
2847
4270
|
} catch {
|
|
2848
4271
|
return null;
|
|
2849
4272
|
}
|
|
@@ -2854,7 +4277,7 @@ function checkPageComponentExport(jayHtmlPath) {
|
|
|
2854
4277
|
new RegExp(`export\\s+(?:const|let|var)\\s+${exportName}\\b`)
|
|
2855
4278
|
];
|
|
2856
4279
|
if (patterns.some((p) => p.test(content))) return null;
|
|
2857
|
-
return `${path.relative(dirname, compPath)} exists but does not export "${exportName}". Remove the file or add the export.`;
|
|
4280
|
+
return `${path$1.relative(dirname, compPath)} exists but does not export "${exportName}". Remove the file or add the export.`;
|
|
2858
4281
|
}
|
|
2859
4282
|
const DOCUMENT_ACCESS_PATTERNS = [
|
|
2860
4283
|
/document\.getElementById\b/,
|
|
@@ -2867,15 +4290,15 @@ const DOCUMENT_ACCESS_PATTERNS = [
|
|
|
2867
4290
|
];
|
|
2868
4291
|
const DOM_SUPPRESS_COMMENT = "jay-dom: allow";
|
|
2869
4292
|
function checkDirectDocumentAccess(jayHtmlPath) {
|
|
2870
|
-
const dirname = path.dirname(jayHtmlPath);
|
|
2871
|
-
const basename = path.basename(jayHtmlPath, JAY_EXTENSION);
|
|
2872
|
-
const candidates = [path.join(dirname, `${basename}.ts`), path.join(dirname, "page.ts")];
|
|
2873
|
-
const compPath = candidates.find((p) => fs.existsSync(p));
|
|
4293
|
+
const dirname = path$1.dirname(jayHtmlPath);
|
|
4294
|
+
const basename = path$1.basename(jayHtmlPath, JAY_EXTENSION);
|
|
4295
|
+
const candidates = [path$1.join(dirname, `${basename}.ts`), path$1.join(dirname, "page.ts")];
|
|
4296
|
+
const compPath = candidates.find((p) => fs$1.existsSync(p));
|
|
2874
4297
|
if (!compPath) return [];
|
|
2875
|
-
const compName = path.basename(compPath);
|
|
4298
|
+
const compName = path$1.basename(compPath);
|
|
2876
4299
|
let content;
|
|
2877
4300
|
try {
|
|
2878
|
-
content = fs.readFileSync(compPath, "utf-8");
|
|
4301
|
+
content = fs$1.readFileSync(compPath, "utf-8");
|
|
2879
4302
|
} catch {
|
|
2880
4303
|
return [];
|
|
2881
4304
|
}
|
|
@@ -2898,8 +4321,8 @@ function checkDirectDocumentAccess(jayHtmlPath) {
|
|
|
2898
4321
|
}
|
|
2899
4322
|
const PARSE_PARAM = /^\[(\[)?(\.\.\.)?([^\]]+)\]?\]$/;
|
|
2900
4323
|
function extractRouteParams(filePath, pagesBase) {
|
|
2901
|
-
const relative = path.relative(pagesBase, filePath);
|
|
2902
|
-
const segments = relative.split(path.sep);
|
|
4324
|
+
const relative = path$1.relative(pagesBase, filePath);
|
|
4325
|
+
const segments = relative.split(path$1.sep);
|
|
2903
4326
|
const params = /* @__PURE__ */ new Set();
|
|
2904
4327
|
for (const segment of segments) {
|
|
2905
4328
|
const match = PARSE_PARAM.exec(segment);
|
|
@@ -3109,7 +4532,7 @@ function resolveLinkedTags(tags, contractDir) {
|
|
|
3109
4532
|
}
|
|
3110
4533
|
function resolveContractLinks(contract, contractPath) {
|
|
3111
4534
|
if (!contractPath) return contract;
|
|
3112
|
-
const contractDir = path.dirname(contractPath);
|
|
4535
|
+
const contractDir = path$1.dirname(contractPath);
|
|
3113
4536
|
return { ...contract, tags: resolveLinkedTags(contract.tags, contractDir) };
|
|
3114
4537
|
}
|
|
3115
4538
|
async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
@@ -3123,10 +4546,10 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
3123
4546
|
try {
|
|
3124
4547
|
let handlerModule;
|
|
3125
4548
|
if (plugin.isLocal) {
|
|
3126
|
-
const handlerPath = path.resolve(plugin.pluginPath, validatorDef.handler);
|
|
4549
|
+
const handlerPath = path$1.resolve(plugin.pluginPath, validatorDef.handler);
|
|
3127
4550
|
handlerModule = await import(handlerPath);
|
|
3128
4551
|
} else {
|
|
3129
|
-
handlerModule = await import(plugin.packageName);
|
|
4552
|
+
handlerModule = await import(`${plugin.packageName}/tools`);
|
|
3130
4553
|
}
|
|
3131
4554
|
validatorFn = plugin.isLocal ? handlerModule.validate ?? handlerModule.default : handlerModule[validatorDef.handler];
|
|
3132
4555
|
if (typeof validatorFn !== "function") {
|
|
@@ -3151,8 +4574,8 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
3151
4574
|
}
|
|
3152
4575
|
loadedValidators.push(source);
|
|
3153
4576
|
for (const { relativePath, parsed } of parsedFiles) {
|
|
3154
|
-
const pageContractPath = parsed.contractRef ? path.resolve(
|
|
3155
|
-
path.dirname(path.resolve(projectRoot, relativePath)),
|
|
4577
|
+
const pageContractPath = parsed.contractRef ? path$1.resolve(
|
|
4578
|
+
path$1.dirname(path$1.resolve(projectRoot, relativePath)),
|
|
3156
4579
|
parsed.contractRef
|
|
3157
4580
|
) : void 0;
|
|
3158
4581
|
const resolvedPageContract = parsed.contract ? resolveContractLinks(parsed.contract, pageContractPath) : void 0;
|
|
@@ -3196,7 +4619,8 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
3196
4619
|
providedHeadTags
|
|
3197
4620
|
};
|
|
3198
4621
|
}),
|
|
3199
|
-
projectRoot
|
|
4622
|
+
projectRoot,
|
|
4623
|
+
validationOverrides: parsed.validationOverrides
|
|
3200
4624
|
};
|
|
3201
4625
|
try {
|
|
3202
4626
|
const findings = await validatorFn(ctx);
|
|
@@ -3232,11 +4656,11 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
3232
4656
|
return loadedValidators;
|
|
3233
4657
|
}
|
|
3234
4658
|
async function validateJayFiles(options = {}) {
|
|
3235
|
-
const config = loadConfig();
|
|
3236
|
-
const resolvedConfig = getConfigWithDefaults(config);
|
|
3237
4659
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3238
|
-
const
|
|
3239
|
-
const
|
|
4660
|
+
const config = loadConfig(projectRoot);
|
|
4661
|
+
const resolvedConfig = getConfigWithDefaults(config);
|
|
4662
|
+
const scanDir = options.path ? path$1.resolve(options.path) : path$1.resolve(resolvedConfig.devServer.pagesBase);
|
|
4663
|
+
const componentsDir = path$1.resolve(resolvedConfig.devServer.componentsBase);
|
|
3240
4664
|
const errors = [];
|
|
3241
4665
|
const warnings = [];
|
|
3242
4666
|
const coverage = [];
|
|
@@ -3255,10 +4679,10 @@ async function validateJayFiles(options = {}) {
|
|
|
3255
4679
|
`));
|
|
3256
4680
|
}
|
|
3257
4681
|
for (const contractFile of contractFiles) {
|
|
3258
|
-
const relativePath = path.relative(projectRoot, contractFile);
|
|
4682
|
+
const relativePath = path$1.relative(projectRoot, contractFile);
|
|
3259
4683
|
try {
|
|
3260
4684
|
const content = await promises.readFile(contractFile, "utf-8");
|
|
3261
|
-
const result = parseContract(content, path.basename(contractFile));
|
|
4685
|
+
const result = parseContract(content, path$1.basename(contractFile));
|
|
3262
4686
|
if (result.validations.length > 0) {
|
|
3263
4687
|
for (const validation of result.validations) {
|
|
3264
4688
|
errors.push({
|
|
@@ -3285,9 +4709,9 @@ async function validateJayFiles(options = {}) {
|
|
|
3285
4709
|
}
|
|
3286
4710
|
}
|
|
3287
4711
|
for (const jayFile of jayHtmlFiles) {
|
|
3288
|
-
const relativePath = path.relative(projectRoot, jayFile);
|
|
3289
|
-
const filename = path.basename(jayFile.replace(JAY_EXTENSION, ""));
|
|
3290
|
-
const dirname = path.dirname(jayFile);
|
|
4712
|
+
const relativePath = path$1.relative(projectRoot, jayFile);
|
|
4713
|
+
const filename = path$1.basename(jayFile.replace(JAY_EXTENSION, ""));
|
|
4714
|
+
const dirname = path$1.dirname(jayFile);
|
|
3291
4715
|
try {
|
|
3292
4716
|
const content = await promises.readFile(jayFile, "utf-8");
|
|
3293
4717
|
const parsedFile = await parseJayFile(
|
|
@@ -3313,9 +4737,10 @@ async function validateJayFiles(options = {}) {
|
|
|
3313
4737
|
}
|
|
3314
4738
|
parsedFiles.push({ relativePath, parsed: parsedFile.val });
|
|
3315
4739
|
if (content.includes("application/jay-params")) {
|
|
3316
|
-
|
|
4740
|
+
errors.push({
|
|
3317
4741
|
file: relativePath,
|
|
3318
|
-
message: '<script type="application/jay-params"> is
|
|
4742
|
+
message: '<script type="application/jay-params"> is no longer supported and is ignored. Move the values into the YAML body of the headless component that uses them. See agent-kit/developer/routing.md for details.',
|
|
4743
|
+
stage: "parse"
|
|
3319
4744
|
});
|
|
3320
4745
|
}
|
|
3321
4746
|
const pageExportError = checkPageComponentExport(jayFile);
|
|
@@ -3346,17 +4771,34 @@ async function validateJayFiles(options = {}) {
|
|
|
3346
4771
|
for (const msg of refTypeErrors) {
|
|
3347
4772
|
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
3348
4773
|
}
|
|
3349
|
-
const
|
|
3350
|
-
for (const msg of
|
|
3351
|
-
|
|
4774
|
+
const headlessPropResults = checkHeadlessInstanceProps(parsedFile.val, relativePath);
|
|
4775
|
+
for (const msg of headlessPropResults) {
|
|
4776
|
+
if (msg.includes("is missing required prop") || msg.includes("source phase must be")) {
|
|
4777
|
+
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
4778
|
+
} else {
|
|
4779
|
+
warnings.push({ file: relativePath, message: msg });
|
|
4780
|
+
}
|
|
3352
4781
|
}
|
|
3353
4782
|
const fileCoverage = analyzeTagCoverage(parsedFile.val, relativePath);
|
|
3354
4783
|
if (fileCoverage) {
|
|
3355
4784
|
coverage.push(fileCoverage);
|
|
4785
|
+
const allowedUnused = parsedFile.val.validationOverrides?.["jay-stack"]?.["allow-unused-tags"];
|
|
4786
|
+
const allowedSet = new Set(Array.isArray(allowedUnused) ? allowedUnused : []);
|
|
4787
|
+
for (const contract of fileCoverage.contracts) {
|
|
4788
|
+
for (const tag of contract.requiredUnusedTags) {
|
|
4789
|
+
const qualifiedTag = contract.key ? `${contract.key}.${tag}` : tag;
|
|
4790
|
+
if (allowedSet.has(qualifiedTag) || allowedSet.has(tag)) continue;
|
|
4791
|
+
const label = contract.key ? `${contract.key} (${contract.contractName})` : contract.contractName;
|
|
4792
|
+
warnings.push({
|
|
4793
|
+
file: relativePath,
|
|
4794
|
+
message: `Required tag "${tag}" from contract "${label}" is not used in the template. Suppress with jay-stack: { allow-unused-tags: ["${qualifiedTag}"] } in <script type="application/jay-validations">. See agent-kit/designer/validation-guide.md`
|
|
4795
|
+
});
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
3356
4798
|
}
|
|
3357
4799
|
const generatedFile = generateElementFile(
|
|
3358
4800
|
parsedFile.val,
|
|
3359
|
-
RuntimeMode.MainTrusted,
|
|
4801
|
+
RuntimeMode$1.MainTrusted,
|
|
3360
4802
|
GenerateTarget.jay
|
|
3361
4803
|
);
|
|
3362
4804
|
if (generatedFile.validations.length > 0) {
|
|
@@ -3394,8 +4836,8 @@ async function validateJayFiles(options = {}) {
|
|
|
3394
4836
|
}
|
|
3395
4837
|
}
|
|
3396
4838
|
}
|
|
3397
|
-
const robotsTxtPath = path.resolve(projectRoot, "public/robots.txt");
|
|
3398
|
-
if (!fs.existsSync(robotsTxtPath)) {
|
|
4839
|
+
const robotsTxtPath = path$1.resolve(projectRoot, "public/robots.txt");
|
|
4840
|
+
if (!fs$1.existsSync(robotsTxtPath)) {
|
|
3399
4841
|
warnings.push({
|
|
3400
4842
|
file: "public/robots.txt",
|
|
3401
4843
|
message: "public/robots.txt not found ā search engines may crawl pages you don't intend to expose.",
|
|
@@ -3406,7 +4848,7 @@ async function validateJayFiles(options = {}) {
|
|
|
3406
4848
|
warnings.push({
|
|
3407
4849
|
file: ".jay",
|
|
3408
4850
|
message: "site.baseUrl not configured ā sitemap.xml will not be generated in production.",
|
|
3409
|
-
suggestion: "Add to .jay config
|
|
4851
|
+
suggestion: "Add to .jay config:\n site:\n baseUrl: https://your-domain.com"
|
|
3410
4852
|
});
|
|
3411
4853
|
}
|
|
3412
4854
|
const pluginValidators = await runPluginValidators(projectRoot, parsedFiles, errors, warnings);
|
|
@@ -3491,7 +4933,7 @@ function printJayValidationResult(result, options) {
|
|
|
3491
4933
|
}
|
|
3492
4934
|
}
|
|
3493
4935
|
}
|
|
3494
|
-
if (result.coverage.length > 0) {
|
|
4936
|
+
if (options.verbose && result.coverage.length > 0) {
|
|
3495
4937
|
logger.important("");
|
|
3496
4938
|
logger.important(chalk.bold("š¦ Tag Coverage"));
|
|
3497
4939
|
for (const fileCov of result.coverage) {
|
|
@@ -3506,23 +4948,26 @@ function printJayValidationResult(result, options) {
|
|
|
3506
4948
|
chalk.gray(` Unused: ${contract.unusedTags.join(", ")}`)
|
|
3507
4949
|
);
|
|
3508
4950
|
}
|
|
3509
|
-
if (contract.requiredUnusedTags.length > 0) {
|
|
3510
|
-
logger.important(
|
|
3511
|
-
chalk.yellow(
|
|
3512
|
-
` ā Required unused: ${contract.requiredUnusedTags.join(", ")}`
|
|
3513
|
-
)
|
|
3514
|
-
);
|
|
3515
|
-
}
|
|
3516
4951
|
}
|
|
3517
4952
|
}
|
|
3518
4953
|
}
|
|
3519
4954
|
logger.important("");
|
|
3520
|
-
if (result.valid) {
|
|
4955
|
+
if (result.valid && result.warnings.length === 0) {
|
|
3521
4956
|
logger.important(chalk.green("Validation passed."));
|
|
4957
|
+
} else if (result.valid) {
|
|
4958
|
+
logger.important(
|
|
4959
|
+
chalk.yellow(
|
|
4960
|
+
`Validation passed with ${result.warnings.length} warning(s). Warnings must be fixed or explicitly suppressed ā do not ignore them.`
|
|
4961
|
+
)
|
|
4962
|
+
);
|
|
3522
4963
|
} else {
|
|
3523
|
-
logger.important(
|
|
4964
|
+
logger.important(
|
|
4965
|
+
chalk.red(
|
|
4966
|
+
`Validation failed ā ${result.errors.length} error(s)` + (result.warnings.length > 0 ? `, ${result.warnings.length} warning(s). Errors must be fixed. Warnings must be fixed or explicitly suppressed.` : ".")
|
|
4967
|
+
)
|
|
4968
|
+
);
|
|
3524
4969
|
}
|
|
3525
|
-
const totalIssues = result.errors.length + result.warnings.length
|
|
4970
|
+
const totalIssues = result.errors.length + result.warnings.length;
|
|
3526
4971
|
if (totalIssues > 0) {
|
|
3527
4972
|
logger.important(
|
|
3528
4973
|
chalk.gray(
|
|
@@ -3626,7 +5071,7 @@ async function runAgentKit(options) {
|
|
|
3626
5071
|
}
|
|
3627
5072
|
}
|
|
3628
5073
|
async function runMaterialize(projectRoot, options, defaultOutputRelative, keepViteAlive = false) {
|
|
3629
|
-
const outputDir = options.output ??
|
|
5074
|
+
const outputDir = options.output ?? path__default.join(projectRoot, defaultOutputRelative);
|
|
3630
5075
|
let viteServer;
|
|
3631
5076
|
let initErrors = /* @__PURE__ */ new Map();
|
|
3632
5077
|
try {
|
|
@@ -3692,81 +5137,81 @@ Materialized ${totalContracts} contracts`));
|
|
|
3692
5137
|
return { initErrors };
|
|
3693
5138
|
}
|
|
3694
5139
|
async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
3695
|
-
const agentKitDir =
|
|
3696
|
-
const thisDir =
|
|
3697
|
-
const templateDir =
|
|
5140
|
+
const agentKitDir = path__default.join(projectRoot, "agent-kit");
|
|
5141
|
+
const thisDir = path__default.dirname(fileURLToPath(import.meta.url));
|
|
5142
|
+
const templateDir = path__default.resolve(thisDir, "..", "agent-kit-template");
|
|
3698
5143
|
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
3699
5144
|
for (const role of roles) {
|
|
3700
|
-
const roleTemplateDir =
|
|
3701
|
-
const roleOutputDir =
|
|
5145
|
+
const roleTemplateDir = path__default.join(templateDir, role);
|
|
5146
|
+
const roleOutputDir = path__default.join(agentKitDir, role);
|
|
3702
5147
|
let files;
|
|
3703
5148
|
try {
|
|
3704
|
-
files = (await fs$
|
|
5149
|
+
files = (await fs$2.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
|
|
3705
5150
|
} catch {
|
|
3706
5151
|
continue;
|
|
3707
5152
|
}
|
|
3708
|
-
await fs$
|
|
5153
|
+
await fs$2.mkdir(roleOutputDir, { recursive: true });
|
|
3709
5154
|
for (const filename of files) {
|
|
3710
|
-
await fs$
|
|
3711
|
-
|
|
3712
|
-
|
|
5155
|
+
await fs$2.copyFile(
|
|
5156
|
+
path__default.join(roleTemplateDir, filename),
|
|
5157
|
+
path__default.join(roleOutputDir, filename)
|
|
3713
5158
|
);
|
|
3714
5159
|
getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
|
|
3715
5160
|
}
|
|
3716
5161
|
}
|
|
3717
|
-
const topLevelFiles = (await fs$
|
|
5162
|
+
const topLevelFiles = (await fs$2.readdir(templateDir)).filter((f) => f.endsWith(".md"));
|
|
3718
5163
|
for (const filename of topLevelFiles) {
|
|
3719
|
-
await fs$
|
|
5164
|
+
await fs$2.copyFile(path__default.join(templateDir, filename), path__default.join(agentKitDir, filename));
|
|
3720
5165
|
getLogger().info(chalk.gray(` Created agent-kit/${filename}`));
|
|
3721
5166
|
}
|
|
3722
5167
|
const sharedDirs = ["contracts"];
|
|
3723
5168
|
for (const dir of sharedDirs) {
|
|
3724
|
-
const srcDir =
|
|
3725
|
-
if (!
|
|
3726
|
-
await copyDirRecursive(srcDir,
|
|
5169
|
+
const srcDir = path__default.join(templateDir, dir);
|
|
5170
|
+
if (!fs__default.existsSync(srcDir)) continue;
|
|
5171
|
+
await copyDirRecursive(srcDir, path__default.join(agentKitDir, dir));
|
|
3727
5172
|
getLogger().info(chalk.gray(` Created agent-kit/${dir}/`));
|
|
3728
5173
|
}
|
|
3729
5174
|
}
|
|
3730
5175
|
async function copyDirRecursive(src, dest) {
|
|
3731
|
-
await fs$
|
|
3732
|
-
const entries = await fs$
|
|
5176
|
+
await fs$2.mkdir(dest, { recursive: true });
|
|
5177
|
+
const entries = await fs$2.readdir(src, { withFileTypes: true });
|
|
3733
5178
|
for (const entry of entries) {
|
|
3734
|
-
const srcPath =
|
|
3735
|
-
const destPath =
|
|
5179
|
+
const srcPath = path__default.join(src, entry.name);
|
|
5180
|
+
const destPath = path__default.join(dest, entry.name);
|
|
3736
5181
|
if (entry.isDirectory()) {
|
|
3737
5182
|
await copyDirRecursive(srcPath, destPath);
|
|
3738
5183
|
} else if (entry.name.endsWith(".md")) {
|
|
3739
|
-
await fs$
|
|
5184
|
+
await fs$2.copyFile(srcPath, destPath);
|
|
3740
5185
|
}
|
|
3741
5186
|
}
|
|
3742
5187
|
}
|
|
3743
5188
|
async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
3744
5189
|
const plugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
3745
|
-
const agentKitDir =
|
|
5190
|
+
const agentKitDir = path__default.join(projectRoot, "agent-kit");
|
|
3746
5191
|
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
3747
5192
|
const copiedPerRole = /* @__PURE__ */ new Map();
|
|
3748
5193
|
for (const [, plugin] of plugins) {
|
|
3749
|
-
const pluginAgentKitDir =
|
|
3750
|
-
if (!
|
|
5194
|
+
const pluginAgentKitDir = path__default.join(plugin.pluginPath, "agent-kit");
|
|
5195
|
+
if (!fs__default.existsSync(pluginAgentKitDir)) continue;
|
|
3751
5196
|
for (const role of roles) {
|
|
3752
|
-
const roleSourceDir =
|
|
5197
|
+
const roleSourceDir = path__default.join(pluginAgentKitDir, role);
|
|
3753
5198
|
let files;
|
|
3754
5199
|
try {
|
|
3755
|
-
files = (await fs$
|
|
5200
|
+
files = (await fs$2.readdir(roleSourceDir)).filter(
|
|
3756
5201
|
(f) => f.endsWith(".md") && f !== "INSTRUCTIONS.md"
|
|
3757
5202
|
);
|
|
3758
5203
|
} catch {
|
|
3759
5204
|
continue;
|
|
3760
5205
|
}
|
|
3761
5206
|
if (files.length === 0) continue;
|
|
3762
|
-
const roleOutputDir =
|
|
3763
|
-
await fs$
|
|
5207
|
+
const roleOutputDir = path__default.join(agentKitDir, role);
|
|
5208
|
+
await fs$2.mkdir(roleOutputDir, { recursive: true });
|
|
3764
5209
|
for (const filename of files) {
|
|
3765
|
-
const sourcePath =
|
|
3766
|
-
await fs$
|
|
5210
|
+
const sourcePath = path__default.join(roleSourceDir, filename);
|
|
5211
|
+
await fs$2.copyFile(sourcePath, path__default.join(roleOutputDir, filename));
|
|
3767
5212
|
let description = "";
|
|
3768
5213
|
try {
|
|
3769
|
-
const content = await fs$
|
|
5214
|
+
const content = await fs$2.readFile(sourcePath, "utf-8");
|
|
3770
5215
|
const lines = content.split("\n");
|
|
3771
5216
|
let pastHeading = false;
|
|
3772
5217
|
for (const line of lines) {
|
|
@@ -3792,8 +5237,8 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
3792
5237
|
}
|
|
3793
5238
|
}
|
|
3794
5239
|
for (const [role, entries] of copiedPerRole) {
|
|
3795
|
-
const instructionsPath =
|
|
3796
|
-
if (!
|
|
5240
|
+
const instructionsPath = path__default.join(agentKitDir, role, "INSTRUCTIONS.md");
|
|
5241
|
+
if (!fs__default.existsSync(instructionsPath)) continue;
|
|
3797
5242
|
const lines = [
|
|
3798
5243
|
"",
|
|
3799
5244
|
"## Plugin-Contributed Guides",
|
|
@@ -3805,11 +5250,10 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
3805
5250
|
lines.push(`| [${filename}](${filename}) | ${pluginName} | ${description} |`);
|
|
3806
5251
|
}
|
|
3807
5252
|
lines.push("");
|
|
3808
|
-
await fs$
|
|
5253
|
+
await fs$2.appendFile(instructionsPath, lines.join("\n"));
|
|
3809
5254
|
}
|
|
3810
5255
|
}
|
|
3811
5256
|
async function generatePluginAgentKit(projectRoot, options, initErrors, viteServer) {
|
|
3812
|
-
const { discoverPluginsWithAgentKit, executePluginAgentKit } = await import("@jay-framework/stack-server-runtime");
|
|
3813
5257
|
const plugins = await discoverPluginsWithAgentKit({
|
|
3814
5258
|
projectRoot,
|
|
3815
5259
|
verbose: options.verbose,
|
|
@@ -3882,7 +5326,6 @@ async function runAction(actionRef, options, projectRoot, initializeServices) {
|
|
|
3882
5326
|
}
|
|
3883
5327
|
viteServer = await createViteForCli({ projectRoot });
|
|
3884
5328
|
await initializeServices(projectRoot, viteServer);
|
|
3885
|
-
const { discoverAndRegisterActions, discoverAllPluginActions, ActionRegistry } = await import("@jay-framework/stack-server-runtime");
|
|
3886
5329
|
const registry = new ActionRegistry();
|
|
3887
5330
|
await discoverAndRegisterActions({
|
|
3888
5331
|
projectRoot,
|
|
@@ -3994,8 +5437,8 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
3994
5437
|
);
|
|
3995
5438
|
process.exit(1);
|
|
3996
5439
|
}
|
|
3997
|
-
const { resolveServices } = await import("@jay-framework/stack-server-runtime");
|
|
3998
|
-
const resolvedServices =
|
|
5440
|
+
const { resolveServices: resolveServices2 } = await import("@jay-framework/stack-server-runtime");
|
|
5441
|
+
const resolvedServices = resolveServices2(component.services || []);
|
|
3999
5442
|
const paramsGenerator = component.loadParams(resolvedServices);
|
|
4000
5443
|
let total = 0;
|
|
4001
5444
|
for await (const batch of paramsGenerator) {
|
|
@@ -4089,7 +5532,7 @@ async function runSetup(pluginFilter, options, projectRoot) {
|
|
|
4089
5532
|
try {
|
|
4090
5533
|
const logger = getLogger();
|
|
4091
5534
|
const jayConfig = loadConfig();
|
|
4092
|
-
const configDir =
|
|
5535
|
+
const configDir = path__default.resolve(projectRoot, jayConfig.devServer?.configBase || "./config");
|
|
4093
5536
|
logger.important(chalk.bold("\nš§ Setting up plugins...\n"));
|
|
4094
5537
|
if (options.verbose) {
|
|
4095
5538
|
logger.info("Starting Vite for TypeScript support...");
|
|
@@ -4121,7 +5564,7 @@ async function runSetup(pluginFilter, options, projectRoot) {
|
|
|
4121
5564
|
const interactive = options.interactive === true;
|
|
4122
5565
|
let answersMap;
|
|
4123
5566
|
if (options.answers) {
|
|
4124
|
-
answersMap = YAML.parse(
|
|
5567
|
+
answersMap = YAML.parse(fs__default.readFileSync(options.answers, "utf-8")) || {};
|
|
4125
5568
|
}
|
|
4126
5569
|
let configured = 0;
|
|
4127
5570
|
let needsConfig = 0;
|
|
@@ -4248,12 +5691,12 @@ async function initPlugin(pluginName, allPluginsWithInit, viteServer, logger) {
|
|
|
4248
5691
|
}
|
|
4249
5692
|
async function runProjectInit(projectRoot, viteServer) {
|
|
4250
5693
|
try {
|
|
4251
|
-
const initPathTs =
|
|
4252
|
-
const initPathJs =
|
|
5694
|
+
const initPathTs = path__default.join(projectRoot, "src", "init.ts");
|
|
5695
|
+
const initPathJs = path__default.join(projectRoot, "src", "init.js");
|
|
4253
5696
|
let initModule;
|
|
4254
|
-
if (
|
|
5697
|
+
if (fs__default.existsSync(initPathTs) && viteServer) {
|
|
4255
5698
|
initModule = await viteServer.ssrLoadModule(initPathTs);
|
|
4256
|
-
} else if (
|
|
5699
|
+
} else if (fs__default.existsSync(initPathJs)) {
|
|
4257
5700
|
initModule = await import(initPathJs);
|
|
4258
5701
|
}
|
|
4259
5702
|
if (initModule?.init?._serverInit) {
|
|
@@ -4266,12 +5709,6 @@ async function runProjectInit(projectRoot, viteServer) {
|
|
|
4266
5709
|
async function runCommand(commandRef, rawArgs, options, projectRoot, initializeServices) {
|
|
4267
5710
|
let viteServer;
|
|
4268
5711
|
try {
|
|
4269
|
-
const {
|
|
4270
|
-
discoverPluginCommands,
|
|
4271
|
-
commandSchemaToFlags,
|
|
4272
|
-
parseInputFromFlags,
|
|
4273
|
-
executePluginCommand
|
|
4274
|
-
} = await import("@jay-framework/stack-server-runtime");
|
|
4275
5712
|
const commands = await discoverPluginCommands({
|
|
4276
5713
|
projectRoot,
|
|
4277
5714
|
verbose: options.verbose
|
|
@@ -4327,21 +5764,19 @@ async function runCommand(commandRef, rawArgs, options, projectRoot, initializeS
|
|
|
4327
5764
|
}
|
|
4328
5765
|
viteServer = await createViteForCli({ projectRoot });
|
|
4329
5766
|
await initializeServices(projectRoot, viteServer);
|
|
4330
|
-
const { registerService } = await import("@jay-framework/stack-server-runtime");
|
|
4331
|
-
const { CONSOLE_CONTEXT } = await import("@jay-framework/fullstack-component");
|
|
4332
5767
|
const jayConfig = loadConfig();
|
|
4333
|
-
const publicFolder =
|
|
5768
|
+
const publicFolder = path__default.resolve(
|
|
4334
5769
|
projectRoot,
|
|
4335
5770
|
jayConfig.devServer?.publicFolder || "public"
|
|
4336
5771
|
);
|
|
4337
5772
|
const version = await resolveVersionFromPackageJson(projectRoot);
|
|
4338
|
-
const buildRoot =
|
|
5773
|
+
const buildRoot = path__default.resolve(projectRoot, `build/v${version}`);
|
|
4339
5774
|
registerService(CONSOLE_CONTEXT, {
|
|
4340
5775
|
projectRoot,
|
|
4341
5776
|
publicFolder,
|
|
4342
5777
|
build: {
|
|
4343
|
-
frontend:
|
|
4344
|
-
backend:
|
|
5778
|
+
frontend: path__default.join(buildRoot, "frontend"),
|
|
5779
|
+
backend: path__default.join(buildRoot, "backend")
|
|
4345
5780
|
},
|
|
4346
5781
|
verbose: options.verbose ?? false,
|
|
4347
5782
|
log: (msg) => getLogger().important(msg),
|
|
@@ -4515,9 +5950,9 @@ if (!process.argv.slice(2).length) {
|
|
|
4515
5950
|
}
|
|
4516
5951
|
export {
|
|
4517
5952
|
getConfigWithDefaults,
|
|
4518
|
-
|
|
5953
|
+
listContracts,
|
|
4519
5954
|
loadConfig,
|
|
4520
|
-
|
|
5955
|
+
materializeContracts,
|
|
4521
5956
|
startDevServer,
|
|
4522
5957
|
updateConfig
|
|
4523
5958
|
};
|