@jay-framework/plugin-validator 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +161 -128
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -93,17 +93,13 @@ function collectLocalInterfaces(sourceFile) {
|
|
|
93
93
|
function collectContractImportedTypes(sourceFile) {
|
|
94
94
|
const contractTypes = /* @__PURE__ */ new Set();
|
|
95
95
|
for (const statement of sourceFile.statements) {
|
|
96
|
-
if (!ts.isImportDeclaration(statement))
|
|
97
|
-
continue;
|
|
96
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
98
97
|
const moduleSpecifier = statement.moduleSpecifier;
|
|
99
|
-
if (!ts.isStringLiteral(moduleSpecifier))
|
|
100
|
-
continue;
|
|
98
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
101
99
|
const modulePath = moduleSpecifier.text;
|
|
102
|
-
if (!modulePath.includes(".jay-contract"))
|
|
103
|
-
continue;
|
|
100
|
+
if (!modulePath.includes(".jay-contract")) continue;
|
|
104
101
|
const importClause = statement.importClause;
|
|
105
|
-
if (!importClause)
|
|
106
|
-
continue;
|
|
102
|
+
if (!importClause) continue;
|
|
107
103
|
const namedBindings = importClause.namedBindings;
|
|
108
104
|
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
109
105
|
for (const element of namedBindings.elements) {
|
|
@@ -139,8 +135,7 @@ function visitNode(node, result) {
|
|
|
139
135
|
}
|
|
140
136
|
if (node.arguments.length > 0) {
|
|
141
137
|
const arg = node.arguments[0];
|
|
142
|
-
if (ts.isIdentifier(arg))
|
|
143
|
-
;
|
|
138
|
+
if (ts.isIdentifier(arg)) ;
|
|
144
139
|
}
|
|
145
140
|
}
|
|
146
141
|
}
|
|
@@ -162,10 +157,8 @@ function extractTypeNames(typeNode) {
|
|
|
162
157
|
return [];
|
|
163
158
|
}
|
|
164
159
|
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
165
|
-
if (FRAMEWORK_PROP_TYPES.has(propsTypeName))
|
|
166
|
-
|
|
167
|
-
if (contractImportedTypes.has(propsTypeName))
|
|
168
|
-
return;
|
|
160
|
+
if (FRAMEWORK_PROP_TYPES.has(propsTypeName)) return;
|
|
161
|
+
if (contractImportedTypes.has(propsTypeName)) return;
|
|
169
162
|
const prefix = `[${contractName}]`;
|
|
170
163
|
const iface = localInterfaces.get(propsTypeName);
|
|
171
164
|
if (!iface) {
|
|
@@ -180,8 +173,7 @@ function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedT
|
|
|
180
173
|
return;
|
|
181
174
|
}
|
|
182
175
|
const ownProperties = iface.properties;
|
|
183
|
-
if (ownProperties.length === 0)
|
|
184
|
-
return;
|
|
176
|
+
if (ownProperties.length === 0) return;
|
|
185
177
|
if (!contract.props || contract.props.length === 0) {
|
|
186
178
|
errors.push({
|
|
187
179
|
type: "contract-invalid",
|
|
@@ -225,10 +217,8 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
|
|
|
225
217
|
suggestion: `Add a params section to the contract (e.g., params: { slug: string })`
|
|
226
218
|
});
|
|
227
219
|
for (const typeName of paramsTypeNames) {
|
|
228
|
-
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
229
|
-
|
|
230
|
-
if (contractImportedTypes.has(typeName))
|
|
231
|
-
continue;
|
|
220
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
|
|
221
|
+
if (contractImportedTypes.has(typeName)) continue;
|
|
232
222
|
const iface = localInterfaces.get(typeName);
|
|
233
223
|
if (iface) {
|
|
234
224
|
const ownProps = iface.properties;
|
|
@@ -241,13 +231,10 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
|
|
|
241
231
|
return;
|
|
242
232
|
}
|
|
243
233
|
for (const typeName of paramsTypeNames) {
|
|
244
|
-
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
245
|
-
|
|
246
|
-
if (contractImportedTypes.has(typeName))
|
|
247
|
-
continue;
|
|
234
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
|
|
235
|
+
if (contractImportedTypes.has(typeName)) continue;
|
|
248
236
|
const iface = localInterfaces.get(typeName);
|
|
249
|
-
if (!iface)
|
|
250
|
-
continue;
|
|
237
|
+
if (!iface) continue;
|
|
251
238
|
const ownProperties = iface.properties;
|
|
252
239
|
const contractParamNames = new Set(contract.params.map((p) => p.name));
|
|
253
240
|
for (const prop of ownProperties) {
|
|
@@ -339,16 +326,13 @@ function requiredString(obj, field, itemPath, errors, code) {
|
|
|
339
326
|
}
|
|
340
327
|
function optionalString(obj, field) {
|
|
341
328
|
const value = obj[field];
|
|
342
|
-
if (value === void 0)
|
|
343
|
-
|
|
344
|
-
if (typeof value !== "string")
|
|
345
|
-
return void 0;
|
|
329
|
+
if (value === void 0) return void 0;
|
|
330
|
+
if (typeof value !== "string") return void 0;
|
|
346
331
|
const trimmed = value.trim();
|
|
347
332
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
348
333
|
}
|
|
349
334
|
function validateInteraction(raw, itemPath, errors) {
|
|
350
|
-
if (raw === void 0)
|
|
351
|
-
return void 0;
|
|
335
|
+
if (raw === void 0) return void 0;
|
|
352
336
|
if (!isRecord$1(raw)) {
|
|
353
337
|
errors.push({
|
|
354
338
|
path: itemPath,
|
|
@@ -372,8 +356,7 @@ function validateInteraction(raw, itemPath, errors) {
|
|
|
372
356
|
};
|
|
373
357
|
}
|
|
374
358
|
function validatePresentation(raw, itemPath, errors) {
|
|
375
|
-
if (raw === void 0)
|
|
376
|
-
return void 0;
|
|
359
|
+
if (raw === void 0) return void 0;
|
|
377
360
|
if (!isRecord$1(raw)) {
|
|
378
361
|
errors.push({
|
|
379
362
|
path: itemPath,
|
|
@@ -393,14 +376,12 @@ function validatePresentation(raw, itemPath, errors) {
|
|
|
393
376
|
}
|
|
394
377
|
if (type === "image") {
|
|
395
378
|
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
396
|
-
if (!src)
|
|
397
|
-
return void 0;
|
|
379
|
+
if (!src) return void 0;
|
|
398
380
|
return { type: "image", src };
|
|
399
381
|
}
|
|
400
382
|
if (type === "gif") {
|
|
401
383
|
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
402
|
-
if (!src)
|
|
403
|
-
return void 0;
|
|
384
|
+
if (!src) return void 0;
|
|
404
385
|
return {
|
|
405
386
|
type: "gif",
|
|
406
387
|
src,
|
|
@@ -445,8 +426,7 @@ function validatePresentation(raw, itemPath, errors) {
|
|
|
445
426
|
}
|
|
446
427
|
const BROWSE_SIZES = /* @__PURE__ */ new Set(["large", "medium", "small"]);
|
|
447
428
|
function validateBrowse(raw, itemPath, errors) {
|
|
448
|
-
if (raw === void 0)
|
|
449
|
-
return void 0;
|
|
429
|
+
if (raw === void 0) return void 0;
|
|
450
430
|
if (!isRecord$1(raw)) {
|
|
451
431
|
errors.push({
|
|
452
432
|
path: itemPath,
|
|
@@ -470,8 +450,7 @@ function validateBrowse(raw, itemPath, errors) {
|
|
|
470
450
|
return { size: sizeRaw };
|
|
471
451
|
}
|
|
472
452
|
function validateFolderPath(raw, itemPath, errors) {
|
|
473
|
-
if (raw === void 0)
|
|
474
|
-
return void 0;
|
|
453
|
+
if (raw === void 0) return void 0;
|
|
475
454
|
if (!Array.isArray(raw)) {
|
|
476
455
|
errors.push({
|
|
477
456
|
path: `${itemPath}.folderPath`,
|
|
@@ -609,8 +588,7 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
609
588
|
raw.items.forEach((entry, index) => {
|
|
610
589
|
const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
|
|
611
590
|
errors.push(...result.errors);
|
|
612
|
-
if (result.item)
|
|
613
|
-
items.push(result.item);
|
|
591
|
+
if (result.item) items.push(result.item);
|
|
614
592
|
});
|
|
615
593
|
if (items.length === 0 && errors.length > 0) {
|
|
616
594
|
return { file: null, errors };
|
|
@@ -618,11 +596,9 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
618
596
|
return { file: { items }, errors };
|
|
619
597
|
}
|
|
620
598
|
function normalizeAddMenuPresentation(item) {
|
|
621
|
-
if (item.presentation)
|
|
622
|
-
return item.presentation;
|
|
599
|
+
if (item.presentation) return item.presentation;
|
|
623
600
|
const thumbnail = item.thumbnail?.trim();
|
|
624
|
-
if (!thumbnail)
|
|
625
|
-
return void 0;
|
|
601
|
+
if (!thumbnail) return void 0;
|
|
626
602
|
if (/\.gif$/i.test(thumbnail)) {
|
|
627
603
|
return { type: "gif", src: thumbnail };
|
|
628
604
|
}
|
|
@@ -633,15 +609,12 @@ function normalizeAddMenuBrowseSize(item) {
|
|
|
633
609
|
}
|
|
634
610
|
function hasSingleRootDiv(html) {
|
|
635
611
|
const trimmed = html.trim();
|
|
636
|
-
if (!trimmed.startsWith("<div"))
|
|
637
|
-
return false;
|
|
612
|
+
if (!trimmed.startsWith("<div")) return false;
|
|
638
613
|
const openMatch = trimmed.match(/^<div\b[^>]*>/i);
|
|
639
|
-
if (!openMatch)
|
|
640
|
-
return false;
|
|
614
|
+
if (!openMatch) return false;
|
|
641
615
|
const afterOpen = trimmed.slice(openMatch[0].length);
|
|
642
616
|
const closeIdx = afterOpen.lastIndexOf("</div>");
|
|
643
|
-
if (closeIdx < 0)
|
|
644
|
-
return false;
|
|
617
|
+
if (closeIdx < 0) return false;
|
|
645
618
|
const tail = afterOpen.slice(closeIdx + "</div>".length).trim();
|
|
646
619
|
return tail.length === 0;
|
|
647
620
|
}
|
|
@@ -717,8 +690,7 @@ function lintHtmlFragment(item, sourcePath) {
|
|
|
717
690
|
}
|
|
718
691
|
function lintGifPoster(item, sourcePath) {
|
|
719
692
|
const presentation = normalizeAddMenuPresentation(item);
|
|
720
|
-
if (presentation?.type !== "gif" || presentation.poster?.trim())
|
|
721
|
-
return [];
|
|
693
|
+
if (presentation?.type !== "gif" || presentation.poster?.trim()) return [];
|
|
722
694
|
return [
|
|
723
695
|
catalogWarning(
|
|
724
696
|
"gif-missing-poster",
|
|
@@ -729,10 +701,8 @@ function lintGifPoster(item, sourcePath) {
|
|
|
729
701
|
];
|
|
730
702
|
}
|
|
731
703
|
function lintBrowseLargeWithoutPresentation(item, sourcePath) {
|
|
732
|
-
if (normalizeAddMenuBrowseSize(item) !== "large")
|
|
733
|
-
|
|
734
|
-
if (normalizeAddMenuPresentation(item))
|
|
735
|
-
return [];
|
|
704
|
+
if (normalizeAddMenuBrowseSize(item) !== "large") return [];
|
|
705
|
+
if (normalizeAddMenuPresentation(item)) return [];
|
|
736
706
|
return [
|
|
737
707
|
catalogWarning(
|
|
738
708
|
"browse-large-without-presentation",
|
|
@@ -805,8 +775,7 @@ function resolveModulePath$1(basePath) {
|
|
|
805
775
|
return void 0;
|
|
806
776
|
}
|
|
807
777
|
function collectTypeScriptFiles(dir, depth = 0) {
|
|
808
|
-
if (depth > 4)
|
|
809
|
-
return [];
|
|
778
|
+
if (depth > 4) return [];
|
|
810
779
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
811
780
|
const files = [];
|
|
812
781
|
for (const entry of entries) {
|
|
@@ -825,8 +794,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
|
825
794
|
}
|
|
826
795
|
const searchRoots = isNpmPackage ? [path.join(pluginPath, "lib"), path.join(pluginPath, "dist")] : [pluginPath];
|
|
827
796
|
for (const root of searchRoots) {
|
|
828
|
-
if (!fs.existsSync(root))
|
|
829
|
-
continue;
|
|
797
|
+
if (!fs.existsSync(root)) continue;
|
|
830
798
|
for (const file of collectTypeScriptFiles(root)) {
|
|
831
799
|
const content = fs.readFileSync(file, "utf-8");
|
|
832
800
|
const definesHandler = new RegExp(
|
|
@@ -846,8 +814,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
|
846
814
|
if (reExportMatch) {
|
|
847
815
|
const importSpec = reExportMatch[1].replace(/\.js$/, "");
|
|
848
816
|
const resolved = resolveModulePath$1(path.resolve(path.dirname(file), importSpec));
|
|
849
|
-
if (resolved)
|
|
850
|
-
return resolved;
|
|
817
|
+
if (resolved) return resolved;
|
|
851
818
|
}
|
|
852
819
|
}
|
|
853
820
|
}
|
|
@@ -857,8 +824,7 @@ function extractBalancedBlock(source, openBraceIndex) {
|
|
|
857
824
|
let depth = 0;
|
|
858
825
|
for (let index = openBraceIndex; index < source.length; index++) {
|
|
859
826
|
const char = source[index];
|
|
860
|
-
if (char === "{")
|
|
861
|
-
depth++;
|
|
827
|
+
if (char === "{") depth++;
|
|
862
828
|
else if (char === "}") {
|
|
863
829
|
depth--;
|
|
864
830
|
if (depth === 0) {
|
|
@@ -878,8 +844,7 @@ function findFunctionBodyOpenBrace(source, searchFrom) {
|
|
|
878
844
|
if (parenMatch?.index !== void 0) {
|
|
879
845
|
candidates.push(searchFrom + parenMatch.index + parenMatch[0].length - 1);
|
|
880
846
|
}
|
|
881
|
-
if (candidates.length === 0)
|
|
882
|
-
return -1;
|
|
847
|
+
if (candidates.length === 0) return -1;
|
|
883
848
|
return Math.min(...candidates);
|
|
884
849
|
}
|
|
885
850
|
function extractFunctionBody(source, functionName) {
|
|
@@ -896,11 +861,9 @@ function extractFunctionBody(source, functionName) {
|
|
|
896
861
|
];
|
|
897
862
|
for (const pattern of patterns) {
|
|
898
863
|
const match = pattern.exec(source);
|
|
899
|
-
if (!match)
|
|
900
|
-
continue;
|
|
864
|
+
if (!match) continue;
|
|
901
865
|
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
902
|
-
if (braceIndex === -1)
|
|
903
|
-
continue;
|
|
866
|
+
if (braceIndex === -1) continue;
|
|
904
867
|
return extractBalancedBlock(source, braceIndex);
|
|
905
868
|
}
|
|
906
869
|
return null;
|
|
@@ -913,11 +876,9 @@ function extractDefaultExportFunctionBody(source) {
|
|
|
913
876
|
];
|
|
914
877
|
for (const pattern of patterns) {
|
|
915
878
|
const match = pattern.exec(source);
|
|
916
|
-
if (!match)
|
|
917
|
-
continue;
|
|
879
|
+
if (!match) continue;
|
|
918
880
|
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
919
|
-
if (braceIndex === -1)
|
|
920
|
-
continue;
|
|
881
|
+
if (braceIndex === -1) continue;
|
|
921
882
|
return extractBalancedBlock(source, braceIndex);
|
|
922
883
|
}
|
|
923
884
|
return null;
|
|
@@ -927,8 +888,7 @@ function handlerBodyWritesAddMenuCatalog(body) {
|
|
|
927
888
|
}
|
|
928
889
|
function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
929
890
|
const sourceFile = resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage);
|
|
930
|
-
if (!sourceFile)
|
|
931
|
-
return null;
|
|
891
|
+
if (!sourceFile) return null;
|
|
932
892
|
const source = fs.readFileSync(sourceFile, "utf-8");
|
|
933
893
|
if (isRelativeHandlerRef(handlerRef)) {
|
|
934
894
|
return extractDefaultExportFunctionBody(source) ?? extractFunctionBody(source, "setup") ?? extractFunctionBody(source, handlerRef);
|
|
@@ -936,8 +896,7 @@ function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
|
936
896
|
return extractFunctionBody(source, handlerRef);
|
|
937
897
|
}
|
|
938
898
|
function suggestionForCode(code) {
|
|
939
|
-
if (!code)
|
|
940
|
-
return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
899
|
+
if (!code) return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
941
900
|
return ADD_MENU_VALIDATION_SUGGESTIONS[code] ?? `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
942
901
|
}
|
|
943
902
|
function mapSchemaError$1(error, catalogPath) {
|
|
@@ -967,8 +926,7 @@ function pluginShipsAddMenuCatalog(context) {
|
|
|
967
926
|
);
|
|
968
927
|
}
|
|
969
928
|
function validateAddMenuAgentKitHandler(context, result) {
|
|
970
|
-
if (!pluginShipsAddMenuCatalog(context))
|
|
971
|
-
return;
|
|
929
|
+
if (!pluginShipsAddMenuCatalog(context)) return;
|
|
972
930
|
const agentKitHandler = context.manifest.agentkit;
|
|
973
931
|
if (!agentKitHandler) {
|
|
974
932
|
result.warnings.push({
|
|
@@ -980,15 +938,13 @@ function validateAddMenuAgentKitHandler(context, result) {
|
|
|
980
938
|
});
|
|
981
939
|
}
|
|
982
940
|
const setupHandler = typeof context.manifest.setup === "string" ? context.manifest.setup : void 0;
|
|
983
|
-
if (!setupHandler)
|
|
984
|
-
return;
|
|
941
|
+
if (!setupHandler) return;
|
|
985
942
|
const setupBody = resolveSetupHandlerFunctionBody(
|
|
986
943
|
context.pluginPath,
|
|
987
944
|
setupHandler,
|
|
988
945
|
context.isNpmPackage
|
|
989
946
|
);
|
|
990
|
-
if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody))
|
|
991
|
-
return;
|
|
947
|
+
if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody)) return;
|
|
992
948
|
result.warnings.push({
|
|
993
949
|
type: "add-menu-catalog",
|
|
994
950
|
code: "add-menu-legacy-setup-handler",
|
|
@@ -1030,8 +986,7 @@ async function validateAddMenuCatalog(context, result) {
|
|
|
1030
986
|
validateAddMenuAgentKitHandler(context, result);
|
|
1031
987
|
for (const relPath of ADD_MENU_CATALOG_REL_PATHS) {
|
|
1032
988
|
const catalogPath = path.join(context.pluginPath, relPath);
|
|
1033
|
-
if (!fs.existsSync(catalogPath))
|
|
1034
|
-
continue;
|
|
989
|
+
if (!fs.existsSync(catalogPath)) continue;
|
|
1035
990
|
await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
|
|
1036
991
|
}
|
|
1037
992
|
}
|
|
@@ -1655,16 +1610,14 @@ async function validateSchema(context, result) {
|
|
|
1655
1610
|
}
|
|
1656
1611
|
function checkExportExists(exportName, context) {
|
|
1657
1612
|
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
1658
|
-
if (!fs.existsSync(packageJsonPath))
|
|
1659
|
-
return true;
|
|
1613
|
+
if (!fs.existsSync(packageJsonPath)) return true;
|
|
1660
1614
|
let mainPath;
|
|
1661
1615
|
try {
|
|
1662
1616
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
1663
1617
|
if (packageJson.exports?.["."]) {
|
|
1664
1618
|
const entry = packageJson.exports["."];
|
|
1665
1619
|
const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
|
|
1666
|
-
if (entryPath)
|
|
1667
|
-
mainPath = path.join(context.pluginPath, entryPath);
|
|
1620
|
+
if (entryPath) mainPath = path.join(context.pluginPath, entryPath);
|
|
1668
1621
|
}
|
|
1669
1622
|
if (!mainPath && packageJson.main) {
|
|
1670
1623
|
mainPath = path.join(context.pluginPath, packageJson.main);
|
|
@@ -1672,8 +1625,7 @@ function checkExportExists(exportName, context) {
|
|
|
1672
1625
|
} catch {
|
|
1673
1626
|
return true;
|
|
1674
1627
|
}
|
|
1675
|
-
if (!mainPath || !fs.existsSync(mainPath))
|
|
1676
|
-
return true;
|
|
1628
|
+
if (!mainPath || !fs.existsSync(mainPath)) return true;
|
|
1677
1629
|
try {
|
|
1678
1630
|
const content = fs.readFileSync(mainPath, "utf-8");
|
|
1679
1631
|
const patterns = [
|
|
@@ -1733,8 +1685,7 @@ function resolveContractFile(contractSpec, context) {
|
|
|
1733
1685
|
const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
1734
1686
|
if (resolvedPath) {
|
|
1735
1687
|
const fullPath = path.join(context.pluginPath, resolvedPath);
|
|
1736
|
-
if (fs.existsSync(fullPath))
|
|
1737
|
-
return fullPath;
|
|
1688
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
1738
1689
|
}
|
|
1739
1690
|
}
|
|
1740
1691
|
}
|
|
@@ -1743,8 +1694,7 @@ function resolveContractFile(contractSpec, context) {
|
|
|
1743
1694
|
}
|
|
1744
1695
|
for (const dir of ["dist", "lib", ""]) {
|
|
1745
1696
|
const candidate = path.join(context.pluginPath, dir, contractSpec);
|
|
1746
|
-
if (fs.existsSync(candidate))
|
|
1747
|
-
return candidate;
|
|
1697
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1748
1698
|
}
|
|
1749
1699
|
return void 0;
|
|
1750
1700
|
} else {
|
|
@@ -1832,8 +1782,7 @@ function hasExportModifier(node) {
|
|
|
1832
1782
|
function resolveModulePath(basePath) {
|
|
1833
1783
|
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
1834
1784
|
const candidate = basePath + ext;
|
|
1835
|
-
if (fs.existsSync(candidate))
|
|
1836
|
-
return candidate;
|
|
1785
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1837
1786
|
}
|
|
1838
1787
|
return void 0;
|
|
1839
1788
|
}
|
|
@@ -1843,10 +1792,8 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
1843
1792
|
const entryFile = resolveModulePath(entryBase);
|
|
1844
1793
|
const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
1845
1794
|
const sourceEntry = entryFile || libEntryFile;
|
|
1846
|
-
if (!sourceEntry)
|
|
1847
|
-
|
|
1848
|
-
if (!sourceEntry.endsWith(".ts"))
|
|
1849
|
-
return void 0;
|
|
1795
|
+
if (!sourceEntry) return void 0;
|
|
1796
|
+
if (!sourceEntry.endsWith(".ts")) return void 0;
|
|
1850
1797
|
let sourceCode;
|
|
1851
1798
|
try {
|
|
1852
1799
|
sourceCode = fs.readFileSync(sourceEntry, "utf-8");
|
|
@@ -1862,12 +1809,9 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
1862
1809
|
);
|
|
1863
1810
|
const starReexportModules = [];
|
|
1864
1811
|
for (const statement of sourceFile.statements) {
|
|
1865
|
-
if (!ts.isExportDeclaration(statement))
|
|
1866
|
-
|
|
1867
|
-
if (!statement.moduleSpecifier)
|
|
1868
|
-
continue;
|
|
1869
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier))
|
|
1870
|
-
continue;
|
|
1812
|
+
if (!ts.isExportDeclaration(statement)) continue;
|
|
1813
|
+
if (!statement.moduleSpecifier) continue;
|
|
1814
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
1871
1815
|
const moduleSpec = statement.moduleSpecifier.text;
|
|
1872
1816
|
const exportClause = statement.exportClause;
|
|
1873
1817
|
if (!exportClause) {
|
|
@@ -1885,12 +1829,10 @@ function resolveComponentSourcePath(componentName, context) {
|
|
|
1885
1829
|
}
|
|
1886
1830
|
}
|
|
1887
1831
|
for (const moduleSpec of starReexportModules) {
|
|
1888
|
-
if (!moduleSpec.startsWith("."))
|
|
1889
|
-
continue;
|
|
1832
|
+
if (!moduleSpec.startsWith(".")) continue;
|
|
1890
1833
|
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
1891
1834
|
const resolvedPath = resolveModulePath(resolvedBase);
|
|
1892
|
-
if (!resolvedPath || !resolvedPath.endsWith(".ts"))
|
|
1893
|
-
continue;
|
|
1835
|
+
if (!resolvedPath || !resolvedPath.endsWith(".ts")) continue;
|
|
1894
1836
|
try {
|
|
1895
1837
|
const modSource = fs.readFileSync(resolvedPath, "utf-8");
|
|
1896
1838
|
const modFile = ts.createSourceFile(
|
|
@@ -1923,16 +1865,12 @@ function resolveContractPath(contract, context) {
|
|
|
1923
1865
|
}
|
|
1924
1866
|
async function checkComponentContractConsistency(contract, context, result) {
|
|
1925
1867
|
const componentName = contract.component;
|
|
1926
|
-
if (!componentName)
|
|
1927
|
-
return;
|
|
1868
|
+
if (!componentName) return;
|
|
1928
1869
|
const sourcePath = resolveComponentSourcePath(componentName, context);
|
|
1929
|
-
if (!sourcePath)
|
|
1930
|
-
|
|
1931
|
-
if (!sourcePath.endsWith(".ts"))
|
|
1932
|
-
return;
|
|
1870
|
+
if (!sourcePath) return;
|
|
1871
|
+
if (!sourcePath.endsWith(".ts")) return;
|
|
1933
1872
|
const contractPath = resolveContractPath(contract, context);
|
|
1934
|
-
if (!contractPath)
|
|
1935
|
-
return;
|
|
1873
|
+
if (!contractPath) return;
|
|
1936
1874
|
let contractContent;
|
|
1937
1875
|
try {
|
|
1938
1876
|
contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
@@ -1940,8 +1878,7 @@ async function checkComponentContractConsistency(contract, context, result) {
|
|
|
1940
1878
|
return;
|
|
1941
1879
|
}
|
|
1942
1880
|
const parsed = parseContract(contractContent, path.basename(contractPath));
|
|
1943
|
-
if (parsed.validations.length > 0)
|
|
1944
|
-
return;
|
|
1881
|
+
if (parsed.validations.length > 0) return;
|
|
1945
1882
|
let sourceCode;
|
|
1946
1883
|
try {
|
|
1947
1884
|
sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
|
|
@@ -2058,10 +1995,99 @@ async function validatePackageJson(context, result) {
|
|
|
2058
1995
|
});
|
|
2059
1996
|
}
|
|
2060
1997
|
}
|
|
1998
|
+
function isBareFunctionExport(exportName, context) {
|
|
1999
|
+
const sourcePath = resolveExportSourceFile(exportName, context);
|
|
2000
|
+
if (!sourcePath) return false;
|
|
2001
|
+
let sourceCode;
|
|
2002
|
+
try {
|
|
2003
|
+
sourceCode = fs.readFileSync(sourcePath, "utf-8");
|
|
2004
|
+
} catch {
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
const sourceFile = ts.createSourceFile(
|
|
2008
|
+
sourcePath,
|
|
2009
|
+
sourceCode,
|
|
2010
|
+
ts.ScriptTarget.Latest,
|
|
2011
|
+
true,
|
|
2012
|
+
ts.ScriptKind.TS
|
|
2013
|
+
);
|
|
2014
|
+
for (const statement of sourceFile.statements) {
|
|
2015
|
+
if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement) && statement.name?.text === exportName) {
|
|
2016
|
+
return true;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return false;
|
|
2020
|
+
}
|
|
2021
|
+
function resolveModulePathWithJsToTs(basePath) {
|
|
2022
|
+
const result = resolveModulePath(basePath);
|
|
2023
|
+
if (result) return result;
|
|
2024
|
+
if (basePath.endsWith(".js")) {
|
|
2025
|
+
return resolveModulePath(basePath.slice(0, -3) + ".ts");
|
|
2026
|
+
}
|
|
2027
|
+
return void 0;
|
|
2028
|
+
}
|
|
2029
|
+
function resolveExportSourceFile(exportName, context) {
|
|
2030
|
+
const modulePath = context.manifest.module || "index";
|
|
2031
|
+
const entryBase = path.join(context.pluginPath, modulePath);
|
|
2032
|
+
const libEntryBase = path.join(context.pluginPath, "lib", modulePath);
|
|
2033
|
+
const sourceEntry = resolveModulePath(entryBase) || resolveModulePath(libEntryBase);
|
|
2034
|
+
if (!sourceEntry || !sourceEntry.endsWith(".ts")) return void 0;
|
|
2035
|
+
return followExportChain(exportName, sourceEntry);
|
|
2036
|
+
}
|
|
2037
|
+
function followExportChain(exportName, filePath) {
|
|
2038
|
+
let sourceCode;
|
|
2039
|
+
try {
|
|
2040
|
+
sourceCode = fs.readFileSync(filePath, "utf-8");
|
|
2041
|
+
} catch {
|
|
2042
|
+
return void 0;
|
|
2043
|
+
}
|
|
2044
|
+
const sourceFile = ts.createSourceFile(
|
|
2045
|
+
filePath,
|
|
2046
|
+
sourceCode,
|
|
2047
|
+
ts.ScriptTarget.Latest,
|
|
2048
|
+
true,
|
|
2049
|
+
ts.ScriptKind.TS
|
|
2050
|
+
);
|
|
2051
|
+
const starReexportModules = [];
|
|
2052
|
+
for (const statement of sourceFile.statements) {
|
|
2053
|
+
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier) {
|
|
2054
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
2055
|
+
const moduleSpec = statement.moduleSpecifier.text;
|
|
2056
|
+
if (!statement.exportClause) {
|
|
2057
|
+
starReexportModules.push(moduleSpec);
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
if (ts.isNamedExports(statement.exportClause)) {
|
|
2061
|
+
for (const element of statement.exportClause.elements) {
|
|
2062
|
+
if (element.name.text === exportName) {
|
|
2063
|
+
const resolvedBase = path.resolve(path.dirname(filePath), moduleSpec);
|
|
2064
|
+
return resolveModulePathWithJsToTs(resolvedBase);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
|
|
2070
|
+
if (statement.name?.text === exportName) return filePath;
|
|
2071
|
+
}
|
|
2072
|
+
if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
|
|
2073
|
+
for (const decl of statement.declarationList.declarations) {
|
|
2074
|
+
if (ts.isIdentifier(decl.name) && decl.name.text === exportName) return filePath;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
for (const moduleSpec of starReexportModules) {
|
|
2079
|
+
if (!moduleSpec.startsWith(".")) continue;
|
|
2080
|
+
const resolvedBase = path.resolve(path.dirname(filePath), moduleSpec);
|
|
2081
|
+
const resolved = resolveModulePathWithJsToTs(resolvedBase);
|
|
2082
|
+
if (!resolved) continue;
|
|
2083
|
+
const found = followExportChain(exportName, resolved);
|
|
2084
|
+
if (found) return found;
|
|
2085
|
+
}
|
|
2086
|
+
return void 0;
|
|
2087
|
+
}
|
|
2061
2088
|
async function validateDynamicContracts(context, result) {
|
|
2062
2089
|
const { dynamic_contracts } = context.manifest;
|
|
2063
|
-
if (!dynamic_contracts)
|
|
2064
|
-
return;
|
|
2090
|
+
if (!dynamic_contracts) return;
|
|
2065
2091
|
const dynamicConfigs = Array.isArray(dynamic_contracts) ? dynamic_contracts : [dynamic_contracts];
|
|
2066
2092
|
for (const config of dynamicConfigs) {
|
|
2067
2093
|
const prefix = config.prefix || "(unknown)";
|
|
@@ -2085,6 +2111,13 @@ async function validateDynamicContracts(context, result) {
|
|
|
2085
2111
|
suggestion: `Create generator file at ${generatorPath}.ts`
|
|
2086
2112
|
});
|
|
2087
2113
|
}
|
|
2114
|
+
} else if (isBareFunctionExport(config.generator, context)) {
|
|
2115
|
+
result.errors.push({
|
|
2116
|
+
type: "export-mismatch",
|
|
2117
|
+
message: `Generator "${config.generator}" for ${prefix} is a bare function — it must be a DynamicContractGenerator object`,
|
|
2118
|
+
location: "plugin.yaml dynamic_contracts",
|
|
2119
|
+
suggestion: `Use makeContractGenerator().generateWith(...) from @jay-framework/fullstack-component instead of exporting a plain function`
|
|
2120
|
+
});
|
|
2088
2121
|
}
|
|
2089
2122
|
}
|
|
2090
2123
|
if (config.component) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/plugin-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
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.
|
|
29
|
-
"@jay-framework/compiler-shared": "^0.
|
|
30
|
-
"@jay-framework/typescript-bridge": "^0.
|
|
28
|
+
"@jay-framework/compiler-jay-html": "^0.24.0",
|
|
29
|
+
"@jay-framework/compiler-shared": "^0.24.0",
|
|
30
|
+
"@jay-framework/typescript-bridge": "^0.24.0",
|
|
31
31
|
"chalk": "^4.1.2",
|
|
32
32
|
"yaml": "^2.3.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@jay-framework/dev-environment": "^0.
|
|
35
|
+
"@jay-framework/dev-environment": "^0.24.0",
|
|
36
36
|
"@types/node": "^22.15.21",
|
|
37
37
|
"rimraf": "^5.0.5",
|
|
38
38
|
"tsup": "^8.0.1",
|