@jay-framework/plugin-validator 0.24.2 → 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/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ interface ValidationResult {
18
18
  typesGenerated?: number;
19
19
  }
20
20
  interface ValidationError {
21
- type: 'schema' | 'file-missing' | 'export-mismatch' | 'contract-invalid' | 'component-contract-mismatch' | 'type-generation-failed' | 'add-menu-catalog';
21
+ type: 'schema' | 'file-missing' | 'export-mismatch' | 'contract-invalid' | 'component-contract-mismatch' | 'type-generation-failed' | 'add-menu-catalog' | 'compiler-leak';
22
22
  message: string;
23
23
  location?: string;
24
24
  suggestion?: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import YAML from "yaml";
4
- import { loadPluginManifest } from "@jay-framework/compiler-shared";
4
+ import { loadPluginManifest, normalizeActionEntry } from "@jay-framework/compiler-shared";
5
5
  import { parseContract } from "@jay-framework/compiler-jay-html";
6
6
  import { ts } from "@jay-framework/typescript-bridge";
7
7
  const FRAMEWORK_PROP_TYPES = /* @__PURE__ */ new Set(["PageProps", "RequestQuery"]);
@@ -1225,6 +1225,7 @@ async function validatePluginPackage(pluginPath, options) {
1225
1225
  }
1226
1226
  await validateAddMenuCatalog(context, result);
1227
1227
  await validateAiditorSettings(context, result);
1228
+ validateNoCompilerLeak(context, result);
1228
1229
  result.valid = result.errors.length === 0;
1229
1230
  return result;
1230
1231
  }
@@ -1398,13 +1399,28 @@ async function validateSchema(context, result) {
1398
1399
  }
1399
1400
  }
1400
1401
  }
1401
- if (!manifest.contracts && !manifest.dynamic_contracts) {
1402
- result.warnings.push({
1403
- type: "schema",
1404
- message: "Plugin has no contracts or dynamic_contracts defined",
1405
- location: "plugin.yaml",
1406
- suggestion: 'Add either "contracts" or "dynamic_contracts" to expose functionality'
1407
- });
1402
+ const hasCapability = Boolean(
1403
+ manifest.contracts || manifest.dynamic_contracts || manifest.actions || manifest.validators || manifest.routes || manifest.services || manifest.contexts || manifest.init || manifest.setup || manifest.agentkit || manifest.commands
1404
+ );
1405
+ if (!hasCapability) {
1406
+ if (manifest.global === true) {
1407
+ const hasGlobalEntry = !context.isNpmPackage || checkExportExists("init", context, ".") || checkExportExists("setup", context, ".");
1408
+ if (!hasGlobalEntry) {
1409
+ result.errors.push({
1410
+ type: "export-mismatch",
1411
+ message: 'Plugin declares "global: true" but exports no init/setup handler to run on each page',
1412
+ location: "plugin.yaml",
1413
+ suggestion: 'Export an "init" (or "setup") handler from the package entry, or declare a capability'
1414
+ });
1415
+ }
1416
+ } else {
1417
+ result.warnings.push({
1418
+ type: "schema",
1419
+ message: "Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, routes, services, contexts, init, setup, agentkit, commands)",
1420
+ location: "plugin.yaml",
1421
+ suggestion: "Declare at least one capability. See agent-kit/plugin/plugin-structure.md"
1422
+ });
1423
+ }
1408
1424
  }
1409
1425
  if (manifest.services) {
1410
1426
  if (!Array.isArray(manifest.services)) {
@@ -1468,14 +1484,22 @@ async function validateSchema(context, result) {
1468
1484
  }
1469
1485
  if (manifest.actions) {
1470
1486
  for (const entry of manifest.actions) {
1471
- const exportName = typeof entry === "string" ? entry : entry.name;
1487
+ if (typeof entry === "object" && entry.devOnly !== void 0 && typeof entry.devOnly !== "boolean") {
1488
+ result.errors.push({
1489
+ type: "schema",
1490
+ message: `Action "${entry.name}" devOnly must be a boolean`,
1491
+ location: "plugin.yaml actions"
1492
+ });
1493
+ }
1494
+ const { name: exportName, devOnly } = normalizeActionEntry(entry);
1472
1495
  if (exportName) {
1473
1496
  validateHandlerRef(
1474
1497
  exportName,
1475
1498
  `Action "${exportName}"`,
1476
1499
  "plugin.yaml actions",
1477
1500
  context,
1478
- result
1501
+ result,
1502
+ devOnly ? "./tools" : "."
1479
1503
  );
1480
1504
  }
1481
1505
  }
@@ -1520,7 +1544,8 @@ async function validateSchema(context, result) {
1520
1544
  `Route "${route.path}" component`,
1521
1545
  `plugin.yaml routes`,
1522
1546
  context,
1523
- result
1547
+ result,
1548
+ route.devOnly ? "./tools" : "."
1524
1549
  );
1525
1550
  }
1526
1551
  if (route.jayHtml) {
@@ -1574,7 +1599,8 @@ async function validateSchema(context, result) {
1574
1599
  `Validator "${validator.name}" handler`,
1575
1600
  "plugin.yaml validators",
1576
1601
  context,
1577
- result
1602
+ result,
1603
+ "./tools"
1578
1604
  );
1579
1605
  }
1580
1606
  });
@@ -1594,7 +1620,8 @@ async function validateSchema(context, result) {
1594
1620
  "Setup handler",
1595
1621
  "plugin.yaml setup",
1596
1622
  context,
1597
- result
1623
+ result,
1624
+ "./tools"
1598
1625
  );
1599
1626
  }
1600
1627
  }
@@ -1604,22 +1631,23 @@ async function validateSchema(context, result) {
1604
1631
  "Agent-kit handler",
1605
1632
  "plugin.yaml agentkit",
1606
1633
  context,
1607
- result
1634
+ result,
1635
+ "./tools"
1608
1636
  );
1609
1637
  }
1610
1638
  }
1611
- function checkExportExists(exportName, context) {
1639
+ function checkExportExists(exportName, context, exportKey = ".") {
1612
1640
  const packageJsonPath = path.join(context.pluginPath, "package.json");
1613
1641
  if (!fs.existsSync(packageJsonPath)) return true;
1614
1642
  let mainPath;
1615
1643
  try {
1616
1644
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1617
- if (packageJson.exports?.["."]) {
1618
- const entry = packageJson.exports["."];
1645
+ if (packageJson.exports?.[exportKey]) {
1646
+ const entry = packageJson.exports[exportKey];
1619
1647
  const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
1620
1648
  if (entryPath) mainPath = path.join(context.pluginPath, entryPath);
1621
1649
  }
1622
- if (!mainPath && packageJson.main) {
1650
+ if (!mainPath && exportKey === "." && packageJson.main) {
1623
1651
  mainPath = path.join(context.pluginPath, packageJson.main);
1624
1652
  }
1625
1653
  } catch {
@@ -1641,7 +1669,63 @@ function checkExportExists(exportName, context) {
1641
1669
  function isRelativePath(value) {
1642
1670
  return value.startsWith("./") || value.startsWith("../");
1643
1671
  }
1644
- function validateHandlerRef(value, label, location, context, result) {
1672
+ function resolveEntryFile(context, exportKey) {
1673
+ const packageJsonPath = path.join(context.pluginPath, "package.json");
1674
+ if (!fs.existsSync(packageJsonPath)) return void 0;
1675
+ try {
1676
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1677
+ const entry = packageJson.exports?.[exportKey];
1678
+ const entryPath = typeof entry === "string" ? entry : entry?.default || entry?.import || void 0;
1679
+ const resolved = entryPath || (exportKey === "." ? packageJson.main : void 0) ? path.join(context.pluginPath, entryPath || packageJson.main) : void 0;
1680
+ return resolved && fs.existsSync(resolved) ? resolved : void 0;
1681
+ } catch {
1682
+ return void 0;
1683
+ }
1684
+ }
1685
+ function hasComponentCapability(context) {
1686
+ const m = context.manifest;
1687
+ return Boolean(m.contracts || m.dynamic_contracts || m.routes);
1688
+ }
1689
+ function needsToolsEntry(manifest) {
1690
+ if (manifest.validators || manifest.commands || manifest.agentkit || manifest.setup) {
1691
+ return true;
1692
+ }
1693
+ if (manifest.actions) {
1694
+ return manifest.actions.some((entry) => normalizeActionEntry(entry).devOnly === true);
1695
+ }
1696
+ return false;
1697
+ }
1698
+ function detectInteractivePhase(context) {
1699
+ if (!context.isNpmPackage) return "unknown";
1700
+ const entryFile = resolveEntryFile(context, ".");
1701
+ if (!entryFile) return "unknown";
1702
+ try {
1703
+ const content = fs.readFileSync(entryFile, "utf-8");
1704
+ return content.includes("withInteractiveMark(");
1705
+ } catch {
1706
+ return "unknown";
1707
+ }
1708
+ }
1709
+ function validateNoCompilerLeak(context, result) {
1710
+ if (!context.isNpmPackage) return;
1711
+ const entryFile = resolveEntryFile(context, ".");
1712
+ if (!entryFile) return;
1713
+ let content;
1714
+ try {
1715
+ content = fs.readFileSync(entryFile, "utf-8");
1716
+ } catch {
1717
+ return;
1718
+ }
1719
+ if (content.includes("@jay-framework/compiler-")) {
1720
+ result.errors.push({
1721
+ type: "compiler-leak",
1722
+ message: 'Serve entry "." (dist/index.js) imports "@jay-framework/compiler-…" — the compiler must not reach the production serve bundle',
1723
+ location: entryFile,
1724
+ 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).'
1725
+ });
1726
+ }
1727
+ }
1728
+ function validateHandlerRef(value, label, location, context, result, exportKey = ".") {
1645
1729
  if (context.isNpmPackage) {
1646
1730
  if (isRelativePath(value)) {
1647
1731
  result.errors.push({
@@ -1650,12 +1734,13 @@ function validateHandlerRef(value, label, location, context, result) {
1650
1734
  location,
1651
1735
  suggestion: `Export the function from the package entry point and use the export name instead of a path`
1652
1736
  });
1653
- } else if (!checkExportExists(value, context)) {
1737
+ } else if (!checkExportExists(value, context, exportKey)) {
1738
+ const entryFile = exportKey === "./tools" ? "lib/tools.ts" : exportKey === "./client" ? "lib/index.client.ts" : "lib/index.ts";
1654
1739
  result.errors.push({
1655
1740
  type: "export-mismatch",
1656
- message: `${label} "${value}" is not exported from the package`,
1741
+ message: `${label} "${value}" is not exported from the "${exportKey}" entry`,
1657
1742
  location,
1658
- suggestion: `Add "export { ${value} } from '...'" to the package entry point`
1743
+ suggestion: `Add "export { ${value} } from '...'" to ${entryFile} (the "${exportKey}" export)`
1659
1744
  });
1660
1745
  }
1661
1746
  } else if (isRelativePath(value)) {
@@ -1929,11 +2014,30 @@ async function validatePackageJson(context, result) {
1929
2014
  });
1930
2015
  }
1931
2016
  if (!packageJson.exports["./client"]) {
1932
- result.warnings.push({
2017
+ const interactivity = detectInteractivePhase(context);
2018
+ const needsClient = context.manifest.contexts !== void 0 || interactivity === true;
2019
+ if (needsClient) {
2020
+ result.errors.push({
2021
+ type: "export-mismatch",
2022
+ 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"),
2023
+ location: packageJsonPath,
2024
+ 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)'
2025
+ });
2026
+ } else if (interactivity === "unknown" && hasComponentCapability(context)) {
2027
+ result.warnings.push({
2028
+ type: "export-mismatch",
2029
+ message: 'package.json exports missing "./client" entry point; could not determine whether any component is interactive (build the plugin before validating)',
2030
+ location: packageJsonPath,
2031
+ suggestion: 'If any component declares an interactive phase, add "./client": "./dist/index.client.js"'
2032
+ });
2033
+ }
2034
+ }
2035
+ if (!packageJson.exports["./tools"] && needsToolsEntry(context.manifest)) {
2036
+ result.errors.push({
1933
2037
  type: "export-mismatch",
1934
- message: 'package.json exports missing "./client" entry point',
2038
+ message: 'package.json exports missing "./tools" entry point, but the plugin declares tools capabilities (validators, commands, agentkit, setup, or devOnly actions)',
1935
2039
  location: packageJsonPath,
1936
- 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)'
2040
+ suggestion: 'Add "./tools": "./dist/tools.js" to exports and re-export those handlers from lib/tools.ts'
1937
2041
  });
1938
2042
  }
1939
2043
  if (context.manifest.contracts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/plugin-validator",
3
- "version": "0.24.2",
3
+ "version": "0.24.3",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Validation tool for Jay Stack plugins",
@@ -25,14 +25,14 @@
25
25
  "test:watch": "vitest"
26
26
  },
27
27
  "dependencies": {
28
- "@jay-framework/compiler-jay-html": "^0.24.2",
29
- "@jay-framework/compiler-shared": "^0.24.2",
30
- "@jay-framework/typescript-bridge": "^0.24.2",
28
+ "@jay-framework/compiler-jay-html": "^0.24.3",
29
+ "@jay-framework/compiler-shared": "^0.24.3",
30
+ "@jay-framework/typescript-bridge": "^0.24.3",
31
31
  "chalk": "^4.1.2",
32
32
  "yaml": "^2.3.4"
33
33
  },
34
34
  "devDependencies": {
35
- "@jay-framework/dev-environment": "^0.24.2",
35
+ "@jay-framework/dev-environment": "^0.24.3",
36
36
  "@types/node": "^22.15.21",
37
37
  "rimraf": "^5.0.5",
38
38
  "tsup": "^8.0.1",