@jskit-ai/jskit-cli 0.2.66 → 0.2.67
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.67",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.66",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.58",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.57"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "20.x"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { mkdir, rm, symlink } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdir, readFile, readlink, rm, symlink } from "node:fs/promises";
|
|
3
3
|
import {
|
|
4
4
|
discoverLocalPackageMap,
|
|
5
5
|
fileExists,
|
|
@@ -8,6 +8,117 @@ import {
|
|
|
8
8
|
resolveSymlinkType
|
|
9
9
|
} from "./shared.js";
|
|
10
10
|
|
|
11
|
+
const COMPANION_PACKAGES = Object.freeze([
|
|
12
|
+
Object.freeze({
|
|
13
|
+
packageName: "json-rest-schema",
|
|
14
|
+
repoDirName: "json-rest-schema"
|
|
15
|
+
}),
|
|
16
|
+
Object.freeze({
|
|
17
|
+
packageName: "json-rest-stores",
|
|
18
|
+
repoDirName: "json-rest-stores"
|
|
19
|
+
})
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function collectDeclaredPackageNames(packageJson = {}) {
|
|
23
|
+
const names = new Set();
|
|
24
|
+
const sections = [
|
|
25
|
+
packageJson?.dependencies,
|
|
26
|
+
packageJson?.devDependencies,
|
|
27
|
+
packageJson?.optionalDependencies,
|
|
28
|
+
packageJson?.peerDependencies
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const section of sections) {
|
|
32
|
+
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const packageName of Object.keys(section)) {
|
|
37
|
+
const normalizedPackageName = String(packageName || "").trim();
|
|
38
|
+
if (normalizedPackageName) {
|
|
39
|
+
names.add(normalizedPackageName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function verifySymlinkTarget(targetPath = "", sourceDir = "", {
|
|
48
|
+
packageName = ""
|
|
49
|
+
} = {}) {
|
|
50
|
+
const stats = await lstat(targetPath);
|
|
51
|
+
if (!stats.isSymbolicLink()) {
|
|
52
|
+
throw new Error(`[link-local] expected ${packageName || targetPath} to be a symlink after linking.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const linkedTarget = await readlink(targetPath);
|
|
56
|
+
if (linkedTarget !== sourceDir) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[link-local] expected ${packageName || targetPath} to link to ${sourceDir}, got ${linkedTarget}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function maybeLinkCompanionPackages({
|
|
64
|
+
appRoot = "",
|
|
65
|
+
repoRoot = "",
|
|
66
|
+
stdout,
|
|
67
|
+
createCliError
|
|
68
|
+
}) {
|
|
69
|
+
const companionRoot = path.dirname(repoRoot);
|
|
70
|
+
const appPackageJsonPath = path.join(appRoot, "package.json");
|
|
71
|
+
let appPackageJson = {};
|
|
72
|
+
try {
|
|
73
|
+
appPackageJson = JSON.parse(await readFile(appPackageJsonPath, "utf8"));
|
|
74
|
+
} catch {
|
|
75
|
+
appPackageJson = {};
|
|
76
|
+
}
|
|
77
|
+
const declaredPackageNames = collectDeclaredPackageNames(appPackageJson);
|
|
78
|
+
let linkedCount = 0;
|
|
79
|
+
|
|
80
|
+
for (const companion of COMPANION_PACKAGES) {
|
|
81
|
+
if (!declaredPackageNames.has(companion.packageName)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sourceDir = path.join(companionRoot, companion.repoDirName);
|
|
86
|
+
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
87
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
88
|
+
throw createCliError(
|
|
89
|
+
`[link-local] companion package ${companion.packageName} is declared by the app but local source was not found at ${sourceDir}.`,
|
|
90
|
+
{ exitCode: 1 }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let packageJson = {};
|
|
95
|
+
try {
|
|
96
|
+
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (String(packageJson?.name || "").trim() !== companion.packageName) {
|
|
102
|
+
throw createCliError(
|
|
103
|
+
`[link-local] companion source at ${sourceDir} does not match expected package ${companion.packageName}.`,
|
|
104
|
+
{ exitCode: 1 }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const targetPath = path.join(appRoot, "node_modules", companion.packageName);
|
|
109
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
110
|
+
await rm(targetPath, { recursive: true, force: true });
|
|
111
|
+
await symlink(sourceDir, targetPath, resolveSymlinkType());
|
|
112
|
+
await verifySymlinkTarget(targetPath, sourceDir, {
|
|
113
|
+
packageName: companion.packageName
|
|
114
|
+
});
|
|
115
|
+
stdout.write(`[link-local] linked ${companion.packageName} -> ${sourceDir}\n`);
|
|
116
|
+
linkedCount += 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return linkedCount;
|
|
120
|
+
}
|
|
121
|
+
|
|
11
122
|
async function runAppLinkLocalPackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout }) {
|
|
12
123
|
const { createCliError } = ctx;
|
|
13
124
|
const explicitRepoRoot = String(options?.inlineOptions?.["repo-root"] || "").trim();
|
|
@@ -51,6 +162,13 @@ async function runAppLinkLocalPackagesCommand(ctx = {}, { appRoot = "", options
|
|
|
51
162
|
linkedCount += 1;
|
|
52
163
|
}
|
|
53
164
|
|
|
165
|
+
linkedCount += await maybeLinkCompanionPackages({
|
|
166
|
+
appRoot,
|
|
167
|
+
repoRoot,
|
|
168
|
+
stdout,
|
|
169
|
+
createCliError
|
|
170
|
+
});
|
|
171
|
+
|
|
54
172
|
if (await fileExists(viteCacheDirectory)) {
|
|
55
173
|
await rm(viteCacheDirectory, { recursive: true, force: true });
|
|
56
174
|
stdout.write(`[link-local] cleared Vite cache at ${viteCacheDirectory}\n`);
|
|
@@ -75,6 +75,11 @@ function createHealthCommands(ctx = {}) {
|
|
|
75
75
|
"createCrudListFilters",
|
|
76
76
|
"useCrudListFilters"
|
|
77
77
|
]);
|
|
78
|
+
const CRUD_TRANSPORT_RUNTIME_CALLEES = Object.freeze([
|
|
79
|
+
"useCrudList",
|
|
80
|
+
"useCrudView",
|
|
81
|
+
"useCrudAddEdit"
|
|
82
|
+
]);
|
|
78
83
|
|
|
79
84
|
function collectDescriptorContainerTokens({ packageId, side, values, issues }) {
|
|
80
85
|
const declaredTokens = new Set();
|
|
@@ -485,6 +490,146 @@ function createHealthCommands(ctx = {}) {
|
|
|
485
490
|
return bindings;
|
|
486
491
|
}
|
|
487
492
|
|
|
493
|
+
function hasTopLevelObjectProperty(sourceText = "", propertyName = "") {
|
|
494
|
+
const normalizedPropertyName = String(propertyName || "").trim();
|
|
495
|
+
const normalizedSourceText = String(sourceText || "").trim();
|
|
496
|
+
if (!normalizedPropertyName || !normalizedSourceText.startsWith("{")) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let parenDepth = 0;
|
|
501
|
+
let braceDepth = 0;
|
|
502
|
+
let bracketDepth = 0;
|
|
503
|
+
let inLineComment = false;
|
|
504
|
+
let inBlockComment = false;
|
|
505
|
+
|
|
506
|
+
for (let index = 0; index < normalizedSourceText.length; index += 1) {
|
|
507
|
+
const character = normalizedSourceText[index];
|
|
508
|
+
const nextCharacter = normalizedSourceText[index + 1] || "";
|
|
509
|
+
|
|
510
|
+
if (inLineComment) {
|
|
511
|
+
if (character === "\n") {
|
|
512
|
+
inLineComment = false;
|
|
513
|
+
}
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (inBlockComment) {
|
|
518
|
+
if (character === "*" && nextCharacter === "/") {
|
|
519
|
+
inBlockComment = false;
|
|
520
|
+
index += 1;
|
|
521
|
+
}
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (character === "/" && nextCharacter === "/") {
|
|
526
|
+
inLineComment = true;
|
|
527
|
+
index += 1;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (character === "/" && nextCharacter === "*") {
|
|
532
|
+
inBlockComment = true;
|
|
533
|
+
index += 1;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (character === "'" || character === "\"") {
|
|
538
|
+
const quote = character;
|
|
539
|
+
const stringStart = index + 1;
|
|
540
|
+
let stringEnd = stringStart;
|
|
541
|
+
for (; stringEnd < normalizedSourceText.length; stringEnd += 1) {
|
|
542
|
+
if (
|
|
543
|
+
normalizedSourceText[stringEnd] === quote &&
|
|
544
|
+
!isEscapedCharacter(normalizedSourceText, stringEnd)
|
|
545
|
+
) {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const stringValue = normalizedSourceText.slice(stringStart, stringEnd);
|
|
551
|
+
index = stringEnd;
|
|
552
|
+
if (braceDepth === 1 && parenDepth === 0 && bracketDepth === 0 && stringValue === normalizedPropertyName) {
|
|
553
|
+
let cursor = index + 1;
|
|
554
|
+
while (/\s/u.test(normalizedSourceText[cursor] || "")) {
|
|
555
|
+
cursor += 1;
|
|
556
|
+
}
|
|
557
|
+
if (normalizedSourceText[cursor] === ":") {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (character === "`") {
|
|
565
|
+
for (index += 1; index < normalizedSourceText.length; index += 1) {
|
|
566
|
+
if (
|
|
567
|
+
normalizedSourceText[index] === "`" &&
|
|
568
|
+
!isEscapedCharacter(normalizedSourceText, index)
|
|
569
|
+
) {
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (character === "(") {
|
|
577
|
+
parenDepth += 1;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (character === ")") {
|
|
581
|
+
parenDepth -= 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (character === "{") {
|
|
585
|
+
braceDepth += 1;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (character === "}") {
|
|
589
|
+
braceDepth -= 1;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (character === "[") {
|
|
593
|
+
bracketDepth += 1;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (character === "]") {
|
|
597
|
+
bracketDepth -= 1;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (
|
|
602
|
+
braceDepth === 1 &&
|
|
603
|
+
parenDepth === 0 &&
|
|
604
|
+
bracketDepth === 0 &&
|
|
605
|
+
/[A-Za-z_$]/u.test(character)
|
|
606
|
+
) {
|
|
607
|
+
const identifierStart = index;
|
|
608
|
+
for (index += 1; index < normalizedSourceText.length; index += 1) {
|
|
609
|
+
if (!/[\w$]/u.test(normalizedSourceText[index] || "")) {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const identifier = normalizedSourceText.slice(identifierStart, index);
|
|
615
|
+
index -= 1;
|
|
616
|
+
if (identifier !== normalizedPropertyName) {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
let cursor = index + 1;
|
|
621
|
+
while (/\s/u.test(normalizedSourceText[cursor] || "")) {
|
|
622
|
+
cursor += 1;
|
|
623
|
+
}
|
|
624
|
+
if (normalizedSourceText[cursor] === ":") {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
488
633
|
function isSharedListFiltersImportSource(sourcePath = "") {
|
|
489
634
|
return /(^|\/)shared\/[^/'"]*ListFilters(?:\.[A-Za-z0-9]+)?$/u.test(String(sourcePath || "").trim());
|
|
490
635
|
}
|
|
@@ -570,6 +715,26 @@ function createHealthCommands(ctx = {}) {
|
|
|
570
715
|
}
|
|
571
716
|
}
|
|
572
717
|
|
|
718
|
+
function collectCrudTransportOwnershipIssues({
|
|
719
|
+
sourceText = "",
|
|
720
|
+
relativePath = "",
|
|
721
|
+
issues = []
|
|
722
|
+
}) {
|
|
723
|
+
for (const calleeName of CRUD_TRANSPORT_RUNTIME_CALLEES) {
|
|
724
|
+
for (const callSite of findCallSites(sourceText, calleeName)) {
|
|
725
|
+
const firstArgument = extractFirstArgumentText(callSite.argsText).trim();
|
|
726
|
+
if (!hasTopLevelObjectProperty(firstArgument, "transport")) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const lineNumber = resolveLineNumberFromIndex(sourceText, callSite.index);
|
|
731
|
+
issues.push(
|
|
732
|
+
`${relativePath}:${lineNumber}: [crud:transport-derived] do not pass explicit transport to ${calleeName}(...). Let the shared CRUD resource derive JSON:API transport automatically, or drop to useList/useView/useAddEdit/usersWebHttpClient.request(...) for custom transport behavior.`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
573
738
|
async function collectMdiSvgDoctorIssues({ appRoot, issues }) {
|
|
574
739
|
if (!(await appUsesVuetifyMdiSvg(appRoot))) {
|
|
575
740
|
return;
|
|
@@ -609,7 +774,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
609
774
|
if (
|
|
610
775
|
!sourceText.includes("useCrudListFilters") &&
|
|
611
776
|
!sourceText.includes("createCrudListFilters") &&
|
|
612
|
-
!sourceText.includes("createQueryValidator")
|
|
777
|
+
!sourceText.includes("createQueryValidator") &&
|
|
778
|
+
!sourceText.includes("useCrudList") &&
|
|
779
|
+
!sourceText.includes("useCrudView") &&
|
|
780
|
+
!sourceText.includes("useCrudAddEdit")
|
|
613
781
|
) {
|
|
614
782
|
continue;
|
|
615
783
|
}
|
|
@@ -625,6 +793,11 @@ function createHealthCommands(ctx = {}) {
|
|
|
625
793
|
relativePath,
|
|
626
794
|
issues
|
|
627
795
|
});
|
|
796
|
+
collectCrudTransportOwnershipIssues({
|
|
797
|
+
sourceText,
|
|
798
|
+
relativePath,
|
|
799
|
+
issues
|
|
800
|
+
});
|
|
628
801
|
}
|
|
629
802
|
}
|
|
630
803
|
|