@housekit/kit 0.1.12 → 0.1.14

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.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  export { type HouseKitConfig } from './config';
3
+ export { validateConfig } from './validation';
package/dist/index.js CHANGED
@@ -229,11 +229,11 @@ import chalk2 from "chalk";
229
229
  import { createRequire } from "module";
230
230
 
231
231
  // src/commands/generate.ts
232
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
232
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
233
233
  import { join } from "path";
234
234
 
235
235
  // src/loader.ts
236
- import { resolve } from "path";
236
+ import { resolve as resolve2 } from "path";
237
237
 
238
238
  // ../../node_modules/.bun/minimatch@9.0.5/node_modules/minimatch/dist/esm/index.js
239
239
  var import_brace_expansion = __toESM(require_brace_expansion(), 1);
@@ -5646,6 +5646,132 @@ function quoteComment(comment) {
5646
5646
 
5647
5647
  // src/loader.ts
5648
5648
  import { createJiti } from "jiti";
5649
+
5650
+ // src/validation.ts
5651
+ import { existsSync } from "fs";
5652
+ import { resolve } from "path";
5653
+ function validateConfig(config, options = {}) {
5654
+ const errors = [];
5655
+ const warnings = [];
5656
+ const rootDir = options.rootDir || process.cwd();
5657
+ validateRequiredFields(config, errors);
5658
+ if (!options.skipPathValidation) {
5659
+ validateSchemaPath(config, rootDir, errors, warnings);
5660
+ validateOutPath(config, rootDir, errors);
5661
+ }
5662
+ validateDatabases(config, errors, warnings);
5663
+ validateSchemaMapping(config, errors, warnings);
5664
+ return {
5665
+ valid: errors.length === 0,
5666
+ errors,
5667
+ warnings
5668
+ };
5669
+ }
5670
+ function validateRequiredFields(config, errors) {
5671
+ if (!config.schema) {
5672
+ errors.push({
5673
+ field: "schema",
5674
+ message: "Schema path is required",
5675
+ severity: "error"
5676
+ });
5677
+ }
5678
+ if (!config.out) {
5679
+ errors.push({
5680
+ field: "out",
5681
+ message: "Output directory is required",
5682
+ severity: "error"
5683
+ });
5684
+ }
5685
+ if (!config.databases || Object.keys(config.databases).length === 0) {
5686
+ errors.push({
5687
+ field: "databases",
5688
+ message: "At least one database must be configured",
5689
+ severity: "error"
5690
+ });
5691
+ }
5692
+ }
5693
+ function validateSchemaPath(config, rootDir, errors, warnings) {
5694
+ const schemaPath = typeof config.schema === "string" ? config.schema : Object.values(config.schema)[0];
5695
+ if (!schemaPath)
5696
+ return;
5697
+ const fullPath = resolve(rootDir, schemaPath);
5698
+ if (!existsSync(fullPath)) {
5699
+ warnings.push({
5700
+ field: "schema",
5701
+ message: `Schema directory does not exist: ${schemaPath}`,
5702
+ severity: "warning"
5703
+ });
5704
+ }
5705
+ }
5706
+ function validateOutPath(config, rootDir, errors) {
5707
+ const fullPath = resolve(rootDir, config.out);
5708
+ if (!existsSync(fullPath)) {
5709
+ errors.push({
5710
+ field: "out",
5711
+ message: `Output directory does not exist: ${config.out}`,
5712
+ severity: "error"
5713
+ });
5714
+ }
5715
+ }
5716
+ function validateDatabases(config, errors, warnings) {
5717
+ if (!config.databases)
5718
+ return;
5719
+ for (const [name, db] of Object.entries(config.databases)) {
5720
+ const prefix2 = `databases.${name}`;
5721
+ if (!db.database) {
5722
+ errors.push({
5723
+ field: `${prefix2}.database`,
5724
+ message: `Database name is required for "${name}"`,
5725
+ severity: "error"
5726
+ });
5727
+ }
5728
+ if (db.url && db.host) {
5729
+ warnings.push({
5730
+ field: `${prefix2}`,
5731
+ message: `Both url and host are specified for "${name}". Using url.`,
5732
+ severity: "warning"
5733
+ });
5734
+ }
5735
+ if (db.port !== undefined && (db.port < 1 || db.port > 65535)) {
5736
+ errors.push({
5737
+ field: `${prefix2}.port`,
5738
+ message: `Invalid port ${db.port} for "${name}". Port must be between 1 and 65535.`,
5739
+ severity: "error"
5740
+ });
5741
+ }
5742
+ if (!db.url && !db.host) {
5743
+ warnings.push({
5744
+ field: `${prefix2}`,
5745
+ message: `No url or host specified for "${name}". Using default http://localhost:8123.`,
5746
+ severity: "warning"
5747
+ });
5748
+ }
5749
+ }
5750
+ }
5751
+ function validateSchemaMapping(config, errors, warnings) {
5752
+ if (typeof config.schema !== "string" && config.schema) {
5753
+ const schemaDatabases = Object.keys(config.schema);
5754
+ const configDatabases = Object.keys(config.databases || {});
5755
+ const missingInSchema = configDatabases.filter((db) => !schemaDatabases.includes(db));
5756
+ const missingInConfig = schemaDatabases.filter((db) => !configDatabases.includes(db));
5757
+ if (missingInSchema.length > 0) {
5758
+ errors.push({
5759
+ field: "schema",
5760
+ message: `Databases defined in config but not in schema mapping: ${missingInSchema.join(", ")}`,
5761
+ severity: "error"
5762
+ });
5763
+ }
5764
+ if (missingInConfig.length > 0) {
5765
+ warnings.push({
5766
+ field: "schema",
5767
+ message: `Databases defined in schema mapping but not in config: ${missingInConfig.join(", ")}`,
5768
+ severity: "warning"
5769
+ });
5770
+ }
5771
+ }
5772
+ }
5773
+
5774
+ // src/loader.ts
5649
5775
  var jiti = createJiti(import.meta.url);
5650
5776
  async function loadSchema(schemaPath, options) {
5651
5777
  if (!options?.quiet) {
@@ -5661,7 +5787,7 @@ async function loadSchema(schemaPath, options) {
5661
5787
  }
5662
5788
  const foundTables = [];
5663
5789
  for (const file of allFiles) {
5664
- const fullPath = resolve(process.cwd(), file);
5790
+ const fullPath = resolve2(process.cwd(), file);
5665
5791
  try {
5666
5792
  const module = await jiti.import(fullPath);
5667
5793
  for (const [key, value] of Object.entries(module)) {
@@ -5704,7 +5830,7 @@ async function findConfigPaths(root) {
5704
5830
  for await (const file of glob2) {
5705
5831
  if (file.includes("node_modules") || file.includes(".git"))
5706
5832
  continue;
5707
- matches.push(resolve(root, file));
5833
+ matches.push(resolve2(root, file));
5708
5834
  if (matches.length > 1)
5709
5835
  break;
5710
5836
  }
@@ -5726,7 +5852,20 @@ async function getConfigPath(root = process.cwd()) {
5726
5852
  async function loadConfig() {
5727
5853
  const configPath = await getConfigPath();
5728
5854
  const module = await jiti.import(configPath);
5729
- return module.default;
5855
+ const config = module.default;
5856
+ const validation = validateConfig(config);
5857
+ if (validation.warnings.length > 0) {
5858
+ for (const warning of validation.warnings) {
5859
+ warn(`Config warning [${warning.field}]: ${warning.message}`);
5860
+ }
5861
+ }
5862
+ if (!validation.valid) {
5863
+ for (const err of validation.errors) {
5864
+ error(`Config error [${err.field}]: ${err.message}`);
5865
+ }
5866
+ throw new Error("Configuration validation failed");
5867
+ }
5868
+ return config;
5730
5869
  }
5731
5870
 
5732
5871
  // src/config.ts
@@ -5750,7 +5889,7 @@ async function generateCommand() {
5750
5889
  const databases = getDatabaseConfigs(config);
5751
5890
  const schemaMapping = getSchemaMapping(config);
5752
5891
  const outDir = config.out || "./housekit";
5753
- if (!existsSync(outDir))
5892
+ if (!existsSync2(outDir))
5754
5893
  mkdirSync(outDir, { recursive: true });
5755
5894
  let hasChanges = false;
5756
5895
  for (const [dbName, dbConfig] of Object.entries(databases)) {
@@ -5765,11 +5904,11 @@ async function generateCommand() {
5765
5904
  continue;
5766
5905
  }
5767
5906
  const dbOutDir = join(outDir, dbName);
5768
- if (!existsSync(dbOutDir))
5907
+ if (!existsSync2(dbOutDir))
5769
5908
  mkdirSync(dbOutDir, { recursive: true });
5770
5909
  const snapshotPath = join(dbOutDir, "snapshot.json");
5771
5910
  let previousTables = {};
5772
- if (existsSync(snapshotPath)) {
5911
+ if (existsSync2(snapshotPath)) {
5773
5912
  previousTables = JSON.parse(readFileSync(snapshotPath, "utf-8"));
5774
5913
  }
5775
5914
  const sqlStatements = [];
@@ -5825,7 +5964,7 @@ async function generateCommand() {
5825
5964
  }
5826
5965
 
5827
5966
  // src/commands/push.ts
5828
- import { existsSync as existsSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
5967
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
5829
5968
  import { join as join2 } from "path";
5830
5969
 
5831
5970
  // src/db.ts
@@ -6833,7 +6972,7 @@ async function dumpExplain(client, statements, tableName, shadow = false) {
6833
6972
  return;
6834
6973
  try {
6835
6974
  const outDir = join2(process.cwd(), ".housekit");
6836
- if (!existsSync2(outDir))
6975
+ if (!existsSync3(outDir))
6837
6976
  mkdirSync2(outDir, { recursive: true });
6838
6977
  const explains = [];
6839
6978
  for (const stmt of statements) {
@@ -7014,7 +7153,7 @@ async function migrateCommand(options) {
7014
7153
  }
7015
7154
 
7016
7155
  // src/commands/pull.ts
7017
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
7156
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
7018
7157
  import { join as join4 } from "path";
7019
7158
  function sanitizeName(name) {
7020
7159
  const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_");
@@ -7295,7 +7434,7 @@ async function pullCommand(options) {
7295
7434
  return true;
7296
7435
  });
7297
7436
  const migrationDir = join4(process.cwd(), outDir, migrationFolderName);
7298
- if (!existsSync3(migrationDir))
7437
+ if (!existsSync4(migrationDir))
7299
7438
  mkdirSync3(migrationDir, { recursive: true });
7300
7439
  const downloadMode2 = await listPrompt("How would you like to download the tables?", [
7301
7440
  { name: "Download all tables", value: "all" },
@@ -7379,7 +7518,7 @@ async function pullCommand(options) {
7379
7518
  if (useSubdirectory && subdirectoryName) {
7380
7519
  targetDir = join4(targetDir, subdirectoryName);
7381
7520
  }
7382
- if (!existsSync3(targetDir))
7521
+ if (!existsSync4(targetDir))
7383
7522
  mkdirSync3(targetDir, { recursive: true });
7384
7523
  let downloadedCount = 0;
7385
7524
  const fileExtension = fileFormat;
@@ -7852,7 +7991,7 @@ async function schemaCommand(options) {
7852
7991
  }
7853
7992
 
7854
7993
  // src/commands/check.ts
7855
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
7994
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
7856
7995
  import { join as join5 } from "path";
7857
7996
  function validateMigrationFile(file, content, previousTimestamp) {
7858
7997
  const issues = [];
@@ -7935,7 +8074,7 @@ async function checkCommand() {
7935
8074
  let totalWarnings = 0;
7936
8075
  for (const dbName of Object.keys(databases)) {
7937
8076
  const dbOutDir = join5(outDir, dbName);
7938
- if (!existsSync4(dbOutDir)) {
8077
+ if (!existsSync5(dbOutDir)) {
7939
8078
  warn(`Migration directory not found for ${quoteName(dbName)}: ${dbOutDir}`);
7940
8079
  continue;
7941
8080
  }
@@ -7976,7 +8115,7 @@ async function checkCommand() {
7976
8115
  }
7977
8116
  }
7978
8117
  const snapshotPath = join5(dbOutDir, "snapshot.json");
7979
- if (existsSync4(snapshotPath)) {
8118
+ if (existsSync5(snapshotPath)) {
7980
8119
  try {
7981
8120
  const snapshot = JSON.parse(readFileSync4(snapshotPath, "utf-8"));
7982
8121
  const snapshotTables = Object.keys(snapshot);
@@ -8400,7 +8539,7 @@ async function dryRunCommand(options) {
8400
8539
  }
8401
8540
 
8402
8541
  // src/commands/status.ts
8403
- import { readFileSync as readFileSync5, readdirSync as readdirSync4, existsSync as existsSync5 } from "fs";
8542
+ import { readFileSync as readFileSync5, readdirSync as readdirSync4, existsSync as existsSync6 } from "fs";
8404
8543
  import { join as join6 } from "path";
8405
8544
  import Table2 from "cli-table3";
8406
8545
  async function isStatementAlreadyApplied2(client, parsed) {
@@ -8447,7 +8586,7 @@ async function statusCommand(options) {
8447
8586
  const { client, name: dbName } = resolveDatabase(config, options.database);
8448
8587
  const outDir = config.out || "./housekit";
8449
8588
  const dbOutDir = join6(outDir, dbName);
8450
- if (!existsSync5(dbOutDir)) {
8589
+ if (!existsSync6(dbOutDir)) {
8451
8590
  spinner.warn("No migration directory found");
8452
8591
  info(`Migration directory: ${dbOutDir}`);
8453
8592
  info('Run "housekit generate" to create migrations.');
@@ -8557,12 +8696,12 @@ async function statusCommand(options) {
8557
8696
  }
8558
8697
 
8559
8698
  // src/commands/init.ts
8560
- import { existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
8699
+ import { existsSync as existsSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
8561
8700
  import { join as join7 } from "path";
8562
8701
  async function initCommand() {
8563
8702
  const schemaDir = "./src/schema";
8564
8703
  const migrationsDir = "./housekit";
8565
- if (existsSync6("housekit.config.js") || existsSync6("housekit.config.ts") || existsSync6("housekit.config.mjs") || existsSync6("housekit.config.cjs")) {
8704
+ if (existsSync7("housekit.config.js") || existsSync7("housekit.config.ts") || existsSync7("housekit.config.mjs") || existsSync7("housekit.config.cjs")) {
8566
8705
  const overwrite = await confirmPrompt("Configuration file already exists. Overwrite?", false);
8567
8706
  if (!overwrite) {
8568
8707
  info("Initialization cancelled.");
@@ -8588,7 +8727,7 @@ async function initCommand() {
8588
8727
  const schemaPath = await inputPrompt("Schema directory", schemaDir);
8589
8728
  const migrationsPath = await inputPrompt("Migrations output directory", migrationsDir);
8590
8729
  const configPath = `housekit.config.${fileType}`;
8591
- const configContent = fileType === "ts" ? `import type { HouseKitConfig } from '@housekit/kit';
8730
+ const configContent = fileType === "ts" ? `import type { HouseKitConfig } from '@housekit/orm';
8592
8731
 
8593
8732
  export default {
8594
8733
  schema: "${schemaPath}",
@@ -8621,25 +8760,24 @@ export default {
8621
8760
  `;
8622
8761
  writeFileSync4(configPath, configContent);
8623
8762
  success(`Created ${quoteName(configPath)}`);
8624
- if (!existsSync6(schemaPath)) {
8763
+ if (!existsSync7(schemaPath)) {
8625
8764
  mkdirSync4(schemaPath, { recursive: true });
8626
8765
  success(`Created schema directory: ${quoteName(schemaPath)}`);
8627
8766
  const exampleSchemaPath = join7(schemaPath, `logs.${fileType}`);
8628
8767
  const exampleSchemaContent = `// Example table - This is a sample schema file to demonstrate HouseKit usage
8629
8768
  import { t, defineTable, Engine } from '@housekit/orm';
8630
8769
 
8631
- export const logs = defineTable('logs', (t) => ({
8770
+ export const logs = defineTable('logs', {
8632
8771
  id: t.uuid('id').autoGenerate().primaryKey(),
8633
- message: t.text('message').comment('Log message content'),
8772
+ message: t.string('message').comment('Log message content'),
8634
8773
  level: t.enum('level', ['info', 'warning', 'error']).default('info').comment('Log severity level'),
8635
- tags: t.array(t.text('tag')).nullable().comment('Array of tags for categorizing logs'),
8774
+ tags: t.array(t.string('tag')).nullable().comment('Array of tags for categorizing logs'),
8636
8775
  metadata: t.json('metadata').nullable().comment('Additional log metadata'),
8637
8776
  createdAt: t.timestamp('created_at').default('now()'),
8638
- }), {
8777
+ }, {
8639
8778
  engine: Engine.MergeTree(),
8640
8779
  orderBy: ['createdAt', 'id'],
8641
- appendOnly: false,
8642
- metadataVersion: "1.2.0"
8780
+ appendOnly: false
8643
8781
  });
8644
8782
  `;
8645
8783
  writeFileSync4(exampleSchemaPath, exampleSchemaContent);
@@ -8647,7 +8785,7 @@ export const logs = defineTable('logs', (t) => ({
8647
8785
  } else {
8648
8786
  info(`Schema directory already exists: ${quoteName(schemaPath)}`);
8649
8787
  }
8650
- if (!existsSync6(migrationsPath)) {
8788
+ if (!existsSync7(migrationsPath)) {
8651
8789
  mkdirSync4(migrationsPath, { recursive: true });
8652
8790
  success(`Created migrations directory: ${quoteName(migrationsPath)}`);
8653
8791
  } else {
@@ -8972,3 +9110,6 @@ program.command("list").description("List all tables in the database with basic
8972
9110
  program.command("validate").description("Validate that code schema matches the database schema").option("-d, --database <name>", "Database to use").option("--auto-upgrade-metadata", "Automatically upgrade housekit metadata to the version declared in code").action(withErrorHandling(validateCommand));
8973
9111
  program.command("help").description("Show help").action(() => program.outputHelp());
8974
9112
  await program.parseAsync(process.argv);
9113
+ export {
9114
+ validateConfig
9115
+ };
@@ -0,0 +1,19 @@
1
+ import type { HouseKitConfig } from './config';
2
+ export interface ValidationError {
3
+ field: string;
4
+ message: string;
5
+ severity: 'error' | 'warning';
6
+ }
7
+ export interface ValidationResult {
8
+ valid: boolean;
9
+ errors: ValidationError[];
10
+ warnings: ValidationError[];
11
+ }
12
+ export interface ValidateConfigOptions {
13
+ rootDir?: string;
14
+ skipPathValidation?: boolean;
15
+ }
16
+ /**
17
+ * Validate HouseKit configuration
18
+ */
19
+ export declare function validateConfig(config: HouseKitConfig, options?: ValidateConfigOptions): ValidationResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@housekit/kit",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "CLI tool for HouseKit - manage ClickHouse schemas, migrations, and database operations with type-safe workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@clickhouse/client": "^1.14.0",
51
- "@housekit/orm": "^0.1.12",
51
+ "@housekit/orm": "^0.1.13",
52
52
  "chalk": "^5.6.2",
53
53
  "cli-table3": "^0.6.5",
54
54
  "commander": "^12.0.0",
@@ -67,4 +67,4 @@
67
67
  "engines": {
68
68
  "node": ">=18.0.0"
69
69
  }
70
- }
70
+ }