@jskit-ai/jskit-cli 0.2.150 → 0.2.152
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.152",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"test": "node --test"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
25
|
-
"@jskit-ai/kernel": "0.1.
|
|
26
|
-
"@jskit-ai/shell-web": "0.1.
|
|
24
|
+
"@jskit-ai/jskit-catalog": "0.1.146",
|
|
25
|
+
"@jskit-ai/kernel": "0.1.131",
|
|
26
|
+
"@jskit-ai/shell-web": "0.1.132",
|
|
27
27
|
"@vue/compiler-sfc": "^3.5.29",
|
|
28
28
|
"ts-morph": "^28.0.0",
|
|
29
29
|
"yaml": "^2.8.3"
|
|
30
30
|
},
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": "^22.
|
|
32
|
+
"node": "^22.13.0 || ^24.0.0 || ^26.0.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
@@ -604,6 +604,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
604
604
|
packageId,
|
|
605
605
|
packagePath,
|
|
606
606
|
tableName,
|
|
607
|
+
idColumn: normalizeDbIdentifier(entry.idColumn) || "id",
|
|
607
608
|
provenance: String(entry.provenance || "").trim().toLowerCase(),
|
|
608
609
|
ownerKind: String(entry.ownerKind || "").trim().toLowerCase(),
|
|
609
610
|
providerEntrypoint: String(entry.providerEntrypoint || "").trim(),
|
|
@@ -805,7 +806,8 @@ function createHealthCommands(ctx = {}) {
|
|
|
805
806
|
applicable: false,
|
|
806
807
|
tableNames: [],
|
|
807
808
|
columnsByTable: new Map(),
|
|
808
|
-
foreignKeysByTable: new Map()
|
|
809
|
+
foreignKeysByTable: new Map(),
|
|
810
|
+
primaryKeyColumnsByTable: new Map()
|
|
809
811
|
};
|
|
810
812
|
}
|
|
811
813
|
|
|
@@ -817,6 +819,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
817
819
|
let tableRows = [];
|
|
818
820
|
let columnRows = [];
|
|
819
821
|
let foreignKeyRows = [];
|
|
822
|
+
let primaryKeyRows = [];
|
|
820
823
|
if (clientId.startsWith("mysql")) {
|
|
821
824
|
tableRows = normalizeKnexRawRows(await knex.raw(
|
|
822
825
|
"SELECT TABLE_NAME AS tableName FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME"
|
|
@@ -825,7 +828,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
825
828
|
"SELECT TABLE_NAME AS tableName, COLUMN_NAME AS columnName FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME, ORDINAL_POSITION"
|
|
826
829
|
));
|
|
827
830
|
foreignKeyRows = normalizeKnexRawRows(await knex.raw(
|
|
828
|
-
"SELECT TABLE_NAME AS tableName, COLUMN_NAME AS columnName, REFERENCED_TABLE_NAME AS referencedTableName, REFERENCED_COLUMN_NAME AS referencedColumnName FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION"
|
|
831
|
+
"SELECT TABLE_NAME AS tableName, CONSTRAINT_NAME AS constraintName, COLUMN_NAME AS columnName, REFERENCED_TABLE_NAME AS referencedTableName, REFERENCED_COLUMN_NAME AS referencedColumnName, ORDINAL_POSITION AS ordinalPosition FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION"
|
|
832
|
+
));
|
|
833
|
+
primaryKeyRows = normalizeKnexRawRows(await knex.raw(
|
|
834
|
+
"SELECT TABLE_NAME AS tableName, COLUMN_NAME AS columnName, ORDINAL_POSITION AS ordinalPosition FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND CONSTRAINT_NAME = 'PRIMARY' ORDER BY TABLE_NAME, ORDINAL_POSITION"
|
|
829
835
|
));
|
|
830
836
|
} else if (clientId === "pg" || clientId.startsWith("postgres")) {
|
|
831
837
|
tableRows = normalizeKnexRawRows(await knex.raw(
|
|
@@ -835,7 +841,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
835
841
|
'SELECT table_name AS "tableName", column_name AS "columnName" FROM information_schema.columns WHERE table_schema = current_schema() ORDER BY table_name, ordinal_position'
|
|
836
842
|
));
|
|
837
843
|
foreignKeyRows = normalizeKnexRawRows(await knex.raw(
|
|
838
|
-
'SELECT kcu.table_name AS "tableName", kcu.column_name AS "columnName", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnName" FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.
|
|
844
|
+
'SELECT kcu.table_name AS "tableName", kcu.constraint_name AS "constraintName", kcu.column_name AS "columnName", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnName", kcu.ordinal_position AS "ordinalPosition" FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.constraint_schema = kcu.constraint_schema JOIN information_schema.referential_constraints rc ON rc.constraint_name = tc.constraint_name AND rc.constraint_schema = tc.constraint_schema JOIN information_schema.key_column_usage ccu ON ccu.constraint_name = rc.unique_constraint_name AND ccu.constraint_schema = rc.unique_constraint_schema AND ccu.ordinal_position = kcu.position_in_unique_constraint WHERE tc.constraint_type = \'FOREIGN KEY\' AND tc.table_schema = current_schema() ORDER BY kcu.table_name, kcu.constraint_name, kcu.ordinal_position'
|
|
845
|
+
));
|
|
846
|
+
primaryKeyRows = normalizeKnexRawRows(await knex.raw(
|
|
847
|
+
'SELECT kcu.table_name AS "tableName", kcu.column_name AS "columnName", kcu.ordinal_position AS "ordinalPosition" FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.constraint_schema = kcu.constraint_schema WHERE tc.constraint_type = \'PRIMARY KEY\' AND tc.table_schema = current_schema() ORDER BY kcu.table_name, kcu.ordinal_position'
|
|
839
848
|
));
|
|
840
849
|
} else {
|
|
841
850
|
throw new Error(`Unsupported knex client for doctor table ownership audit: ${clientId || "<empty>"}.`);
|
|
@@ -852,10 +861,12 @@ function createHealthCommands(ctx = {}) {
|
|
|
852
861
|
const liveTableNameSet = new Set(tableNames);
|
|
853
862
|
const columnsByTable = new Map();
|
|
854
863
|
const foreignKeysByTable = new Map();
|
|
864
|
+
const primaryKeyColumnsByTable = new Map();
|
|
855
865
|
|
|
856
866
|
for (const tableName of tableNames) {
|
|
857
867
|
columnsByTable.set(tableName, new Set());
|
|
858
868
|
foreignKeysByTable.set(tableName, []);
|
|
869
|
+
primaryKeyColumnsByTable.set(tableName, []);
|
|
859
870
|
}
|
|
860
871
|
|
|
861
872
|
for (const rawRow of columnRows) {
|
|
@@ -878,21 +889,56 @@ function createHealthCommands(ctx = {}) {
|
|
|
878
889
|
const referencedColumnName = normalizeDbIdentifier(
|
|
879
890
|
row.referencedColumnName || row.REFERENCED_COLUMN_NAME || row.referencedcolumnname
|
|
880
891
|
);
|
|
892
|
+
const constraintName = normalizeDbIdentifier(
|
|
893
|
+
row.constraintName || row.CONSTRAINT_NAME || row.constraintname
|
|
894
|
+
);
|
|
895
|
+
const ordinalPosition = Number.parseInt(
|
|
896
|
+
String(row.ordinalPosition || row.ORDINAL_POSITION || row.ordinalposition || "0"),
|
|
897
|
+
10
|
|
898
|
+
);
|
|
881
899
|
if (!tableName || !referencedTableName || !liveTableNameSet.has(tableName)) {
|
|
882
900
|
continue;
|
|
883
901
|
}
|
|
884
902
|
setMapValue(foreignKeysByTable, tableName, () => []).push({
|
|
903
|
+
constraintName,
|
|
885
904
|
columnName,
|
|
886
905
|
referencedTableName,
|
|
887
|
-
referencedColumnName
|
|
906
|
+
referencedColumnName,
|
|
907
|
+
ordinalPosition: Number.isFinite(ordinalPosition) ? ordinalPosition : 0
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
for (const rawRow of primaryKeyRows) {
|
|
912
|
+
const row = ensureObject(rawRow);
|
|
913
|
+
const tableName = normalizeDbIdentifier(row.tableName || row.TABLE_NAME || row.tablename);
|
|
914
|
+
const columnName = normalizeDbIdentifier(row.columnName || row.COLUMN_NAME || row.columnname);
|
|
915
|
+
const ordinalPosition = Number.parseInt(
|
|
916
|
+
String(row.ordinalPosition || row.ORDINAL_POSITION || row.ordinalposition || "0"),
|
|
917
|
+
10
|
|
918
|
+
);
|
|
919
|
+
if (!tableName || !columnName || !liveTableNameSet.has(tableName)) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
setMapValue(primaryKeyColumnsByTable, tableName, () => []).push({
|
|
923
|
+
columnName,
|
|
924
|
+
ordinalPosition: Number.isFinite(ordinalPosition) ? ordinalPosition : 0
|
|
888
925
|
});
|
|
889
926
|
}
|
|
927
|
+
for (const [tableName, primaryKeyColumns] of primaryKeyColumnsByTable.entries()) {
|
|
928
|
+
primaryKeyColumnsByTable.set(
|
|
929
|
+
tableName,
|
|
930
|
+
primaryKeyColumns
|
|
931
|
+
.sort((left, right) => left.ordinalPosition - right.ordinalPosition)
|
|
932
|
+
.map((entry) => entry.columnName)
|
|
933
|
+
);
|
|
934
|
+
}
|
|
890
935
|
|
|
891
936
|
return {
|
|
892
937
|
applicable: true,
|
|
893
938
|
tableNames,
|
|
894
939
|
columnsByTable,
|
|
895
|
-
foreignKeysByTable
|
|
940
|
+
foreignKeysByTable,
|
|
941
|
+
primaryKeyColumnsByTable
|
|
896
942
|
};
|
|
897
943
|
} finally {
|
|
898
944
|
if (knex && typeof knex.destroy === "function") {
|
|
@@ -925,6 +971,74 @@ function createHealthCommands(ctx = {}) {
|
|
|
925
971
|
return ownersByTable;
|
|
926
972
|
}
|
|
927
973
|
|
|
974
|
+
function groupForeignKeysByConstraint(foreignKeys = []) {
|
|
975
|
+
const grouped = new Map();
|
|
976
|
+
|
|
977
|
+
for (const foreignKey of ensureArray(foreignKeys)) {
|
|
978
|
+
const tableName = normalizeDbIdentifier(foreignKey?.referencedTableName);
|
|
979
|
+
const columnName = normalizeDbIdentifier(foreignKey?.columnName);
|
|
980
|
+
const referencedColumnName = normalizeDbIdentifier(foreignKey?.referencedColumnName);
|
|
981
|
+
const explicitConstraintName = normalizeDbIdentifier(foreignKey?.constraintName);
|
|
982
|
+
const constraintName =
|
|
983
|
+
explicitConstraintName ||
|
|
984
|
+
`${columnName || "<unknown>"}->${tableName || "<unknown>"}.${referencedColumnName || "<unknown>"}`;
|
|
985
|
+
setMapValue(grouped, constraintName, () => []).push(foreignKey);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return grouped;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function collectGeneratedCrudSchemaIssues({
|
|
992
|
+
liveTableNames,
|
|
993
|
+
ownedTablesByName,
|
|
994
|
+
foreignKeysByTable,
|
|
995
|
+
primaryKeyColumnsByTable,
|
|
996
|
+
issues
|
|
997
|
+
}) {
|
|
998
|
+
const liveTableSet = new Set(ensureArray(liveTableNames));
|
|
999
|
+
|
|
1000
|
+
for (const [tableName, ownershipEntry] of ownedTablesByName.entries()) {
|
|
1001
|
+
if (
|
|
1002
|
+
ownershipEntry?.provenance !== "crud-server-generator" ||
|
|
1003
|
+
!liveTableSet.has(tableName)
|
|
1004
|
+
) {
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const idColumn = normalizeDbIdentifier(ownershipEntry.idColumn) || "id";
|
|
1009
|
+
const primaryKeyColumns = ensureArray(primaryKeyColumnsByTable.get(tableName));
|
|
1010
|
+
if (primaryKeyColumns.length !== 1 || primaryKeyColumns[0] !== idColumn) {
|
|
1011
|
+
const actual = primaryKeyColumns.length > 0 ? primaryKeyColumns.join(", ") : "none";
|
|
1012
|
+
issues.push(
|
|
1013
|
+
`${ownershipEntry.packagePath}: [crud-schema:primary-key] generated CRUD table "${tableName}" must have exactly one primary-key column "${idColumn}"; live primary key is ${actual}.`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const groupedForeignKeys = groupForeignKeysByConstraint(
|
|
1018
|
+
foreignKeysByTable.get(tableName)
|
|
1019
|
+
);
|
|
1020
|
+
for (const [constraintName, foreignKeyColumns] of groupedForeignKeys.entries()) {
|
|
1021
|
+
if (foreignKeyColumns.length !== 1) {
|
|
1022
|
+
issues.push(
|
|
1023
|
+
`${ownershipEntry.packagePath}: [crud-schema:composite-foreign-key] generated CRUD table "${tableName}" foreign key "${constraintName}" has ${foreignKeyColumns.length} columns. Generated CRUD relationships must be single-column.`
|
|
1024
|
+
);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const foreignKey = foreignKeyColumns[0];
|
|
1029
|
+
const targetTable = normalizeDbIdentifier(foreignKey?.referencedTableName);
|
|
1030
|
+
const targetColumn = normalizeDbIdentifier(foreignKey?.referencedColumnName);
|
|
1031
|
+
const targetPrimaryKey = ensureArray(primaryKeyColumnsByTable.get(targetTable));
|
|
1032
|
+
if (targetPrimaryKey.length !== 1 || targetPrimaryKey[0] !== targetColumn) {
|
|
1033
|
+
const actual = targetPrimaryKey.length > 0 ? targetPrimaryKey.join(", ") : "none";
|
|
1034
|
+
issues.push(
|
|
1035
|
+
`${ownershipEntry.packagePath}: [crud-schema:foreign-key-target] generated CRUD table "${tableName}" foreign key "${constraintName}" targets "${targetTable}.${targetColumn}", but relationships must target the table's single-column primary key (live primary key: ${actual}).`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
928
1042
|
function normalizeDirectOwnerKinds(columnNames = new Set()) {
|
|
929
1043
|
const normalizedColumns = columnNames instanceof Set ? columnNames : new Set();
|
|
930
1044
|
const ownerKinds = new Set();
|
|
@@ -1597,11 +1711,22 @@ function createHealthCommands(ctx = {}) {
|
|
|
1597
1711
|
const liveTables = ensureArray(liveSchema?.tableNames);
|
|
1598
1712
|
const columnsByTable = liveSchema?.columnsByTable instanceof Map ? liveSchema.columnsByTable : new Map();
|
|
1599
1713
|
const foreignKeysByTable = liveSchema?.foreignKeysByTable instanceof Map ? liveSchema.foreignKeysByTable : new Map();
|
|
1714
|
+
const primaryKeyColumnsByTable =
|
|
1715
|
+
liveSchema?.primaryKeyColumnsByTable instanceof Map
|
|
1716
|
+
? liveSchema.primaryKeyColumnsByTable
|
|
1717
|
+
: new Map();
|
|
1600
1718
|
const ownedTablesByName = collectInstalledOwnedTables({
|
|
1601
1719
|
installedPackageIds,
|
|
1602
1720
|
packageRegistry,
|
|
1603
1721
|
issues
|
|
1604
1722
|
});
|
|
1723
|
+
collectGeneratedCrudSchemaIssues({
|
|
1724
|
+
liveTableNames: liveTables,
|
|
1725
|
+
ownedTablesByName,
|
|
1726
|
+
foreignKeysByTable,
|
|
1727
|
+
primaryKeyColumnsByTable,
|
|
1728
|
+
issues
|
|
1729
|
+
});
|
|
1605
1730
|
const crudOwnershipByTable = await resolveAppLocalCrudOwnershipFilters({
|
|
1606
1731
|
appRoot,
|
|
1607
1732
|
appLocalRegistry,
|