@jskit-ai/jskit-cli 0.2.149 → 0.2.151

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.149",
3
+ "version": "0.2.151",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -21,9 +21,9 @@
21
21
  "test": "node --test"
22
22
  },
23
23
  "dependencies": {
24
- "@jskit-ai/jskit-catalog": "0.1.143",
25
- "@jskit-ai/kernel": "0.1.129",
26
- "@jskit-ai/shell-web": "0.1.130",
24
+ "@jskit-ai/jskit-catalog": "0.1.145",
25
+ "@jskit-ai/kernel": "0.1.130",
26
+ "@jskit-ai/shell-web": "0.1.131",
27
27
  "@vue/compiler-sfc": "^3.5.29",
28
28
  "ts-morph": "^28.0.0",
29
29
  "yaml": "^2.8.3"
@@ -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.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = \'FOREIGN KEY\' AND tc.table_schema = current_schema() ORDER BY kcu.table_name, kcu.constraint_name, kcu.ordinal_position'
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,
@@ -53,7 +53,8 @@ async function runPackageMigrationsCommand(ctx = {}, { positional, options, cwd,
53
53
  await hydratePackageRegistryFromInstalledNodeModules({
54
54
  appRoot,
55
55
  packageRegistry: combinedPackageRegistry,
56
- seedPackageIds: installedPackageIds
56
+ seedPackageIds: installedPackageIds,
57
+ preferInstalledDescriptors: true
57
58
  });
58
59
 
59
60
  let requestedPackageIds = [];