@jskit-ai/jskit-cli 0.2.65 → 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"
|
|
@@ -196,7 +196,10 @@ async function applyFileMutations(
|
|
|
196
196
|
managedMigrations,
|
|
197
197
|
touchedFiles,
|
|
198
198
|
warnings = [],
|
|
199
|
-
existingManagedFiles = []
|
|
199
|
+
existingManagedFiles = [],
|
|
200
|
+
{
|
|
201
|
+
reapplyManagedAppFiles = false
|
|
202
|
+
} = {}
|
|
200
203
|
) {
|
|
201
204
|
const existingManagedFilesByPath = new Map();
|
|
202
205
|
for (const managedFileValue of ensureArray(existingManagedFiles)) {
|
|
@@ -242,8 +245,16 @@ async function applyFileMutations(
|
|
|
242
245
|
const relativeTargetPath = normalizeRelativePath(appRoot, targetPath);
|
|
243
246
|
const previous = await readFileBufferIfExists(targetPath);
|
|
244
247
|
const existingManaged = existingManagedFilesByPath.get(relativeTargetPath);
|
|
248
|
+
const existingManagedHash = String(existingManaged?.hash || "").trim();
|
|
249
|
+
const currentContentMatchesManagedVersion =
|
|
250
|
+
previous.exists &&
|
|
251
|
+
existingManagedHash &&
|
|
252
|
+
hashBuffer(previous.buffer) === existingManagedHash;
|
|
253
|
+
const canSafelyReapplyManagedAppFile =
|
|
254
|
+
reapplyManagedAppFiles === true &&
|
|
255
|
+
(!previous.exists || currentContentMatchesManagedVersion);
|
|
245
256
|
|
|
246
|
-
if (mutation.ownership === "app" && existingManaged) {
|
|
257
|
+
if (mutation.ownership === "app" && existingManaged && !canSafelyReapplyManagedAppFile) {
|
|
247
258
|
managedFiles.push({
|
|
248
259
|
...existingManaged,
|
|
249
260
|
path: relativeTargetPath,
|
|
@@ -503,7 +503,10 @@ async function applyPackageInstall({
|
|
|
503
503
|
managedRecord.managed.migrations,
|
|
504
504
|
touchedFiles,
|
|
505
505
|
mutationWarnings,
|
|
506
|
-
ensureArray(existingManaged.files)
|
|
506
|
+
ensureArray(existingManaged.files),
|
|
507
|
+
{
|
|
508
|
+
reapplyManagedAppFiles: Object.keys(existingInstall).length > 0
|
|
509
|
+
}
|
|
507
510
|
);
|
|
508
511
|
|
|
509
512
|
await applyTextMutations(
|
|
@@ -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
|
|