@hypequery/cli 1.18.1 → 1.18.3

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/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAgQD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAmQD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -143,6 +143,9 @@ program
143
143
  .option('--tables <names>', 'Only generate for specific tables (comma-separated)')
144
144
  .option('--exclude-tables <names>', 'Exclude specific tables (comma-separated)')
145
145
  .option('--tenant-column <column>', 'Set tenantKey to this column on tables that have it (requires a trusted runtime tenant scope)')
146
+ .option('--force', 'Replace an existing output file when generated contents differ')
147
+ .option('--check', 'Exit with an error when the output file is missing or out of date')
148
+ .option('--diff', 'Print generated changes without writing them')
146
149
  .action(runCommand(async (options) => {
147
150
  await generateDatasetsCommand(options);
148
151
  }));
@@ -10,6 +10,9 @@ export interface GenerateDatasetsOptions {
10
10
  tables?: string;
11
11
  excludeTables?: string;
12
12
  tenantColumn?: string;
13
+ force?: boolean;
14
+ check?: boolean;
15
+ diff?: boolean;
13
16
  }
14
17
  export declare function generateDatasetsCommand(options?: GenerateDatasetsOptions): Promise<void>;
15
18
  //# sourceMappingURL=generate-datasets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAqBD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA8GlF"}
1
+ {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA2BD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBAqKlF"}
@@ -11,6 +11,8 @@ import { getTableCount } from '../utils/detect-database.js';
11
11
  import { generateDatasets } from '../generators/dataset-generator.js';
12
12
  import { redactConnectionUrl } from '../utils/redact-connection-url.js';
13
13
  import { logDatasetGenerationWarnings } from '../utils/dataset-generation-warnings.js';
14
+ import { formatGeneratedFileDiff } from '../utils/generated-file-diff.js';
15
+ import { readGeneratedFile, writeGeneratedFileAtomically } from '../utils/generated-file.js';
14
16
  /**
15
17
  * Turns the written path into something pasteable into an import statement:
16
18
  * strips the `.ts` extension and makes it explicitly relative.
@@ -20,6 +22,11 @@ function toImportSpecifier(relativePath) {
20
22
  const normalized = withoutExtension.split(path.sep).join('/');
21
23
  return normalized.startsWith('.') ? normalized : `./${normalized}`;
22
24
  }
25
+ function refuseOverwrite(relativeOutput) {
26
+ logger.warn(`Refusing to overwrite existing dataset definitions: ${relativeOutput}`);
27
+ logger.info('Run again with --diff to inspect changes or --force to replace the file.');
28
+ process.exitCode = 1;
29
+ }
23
30
  function parseTableList(value) {
24
31
  const parsed = value
25
32
  ?.split(',')
@@ -43,6 +50,11 @@ export async function generateDatasetsCommand(options = {}) {
43
50
  }
44
51
  const parsedTables = parseTableList(options.tables);
45
52
  const excludedTables = parseTableList(options.excludeTables);
53
+ if (options.force && (options.check || options.diff)) {
54
+ logger.error('--force cannot be combined with --check or --diff.');
55
+ process.exitCode = 1;
56
+ return;
57
+ }
46
58
  logger.newline();
47
59
  logger.header('hypequery generate datasets');
48
60
  const spinner = ora('Connecting to ClickHouse...').start();
@@ -62,13 +74,59 @@ export async function generateDatasetsCommand(options = {}) {
62
74
  // Regeneration replaces the whole file, so a tenant boundary configured at
63
75
  // init has to be restated here or it is dropped on every refresh.
64
76
  tenantColumn: options.tenantColumn,
77
+ writeOutput: false,
65
78
  });
66
79
  const generatedTables = generated?.tables ?? parsedTables ?? [];
67
80
  const generatedCount = generatedTables.length || tableCount;
68
81
  datasetSpinner.succeed(`Generated dataset definitions for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
69
- const relativeOutput = path.relative(process.cwd(), outputPath);
70
- logger.success(`Created ${relativeOutput}`);
71
82
  logDatasetGenerationWarnings(generated?.warnings);
83
+ const relativeOutput = path.relative(process.cwd(), outputPath);
84
+ const currentContents = await readGeneratedFile(outputPath);
85
+ const contentsMatch = currentContents === generated.contents;
86
+ if (contentsMatch) {
87
+ logger.success(options.check || options.diff
88
+ ? `Dataset definitions are up to date: ${relativeOutput}`
89
+ : `Unchanged ${relativeOutput}`);
90
+ if (options.check || options.diff) {
91
+ return;
92
+ }
93
+ }
94
+ else if (options.diff) {
95
+ logger.raw(formatGeneratedFileDiff(currentContents ?? '', generated.contents, relativeOutput));
96
+ logger.error(`Generated dataset definitions differ: ${relativeOutput}`);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+ else if (options.check) {
101
+ logger.error(currentContents === undefined
102
+ ? `Dataset definitions are missing: ${relativeOutput}`
103
+ : `Generated dataset definitions are out of date: ${relativeOutput}`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ else if (currentContents !== undefined && !options.force) {
108
+ refuseOverwrite(relativeOutput);
109
+ return;
110
+ }
111
+ else {
112
+ try {
113
+ // Without --force this is an exclusive create, so a file written
114
+ // between the read above and here is refused rather than clobbered.
115
+ await writeGeneratedFileAtomically(outputPath, generated.contents, {
116
+ overwrite: options.force === true,
117
+ });
118
+ }
119
+ catch (error) {
120
+ if (error.code === 'EEXIST') {
121
+ refuseOverwrite(relativeOutput);
122
+ return;
123
+ }
124
+ throw error;
125
+ }
126
+ logger.success(currentContents === undefined
127
+ ? `Created ${relativeOutput}`
128
+ : `Updated ${relativeOutput}`);
129
+ }
72
130
  logger.newline();
73
131
  logger.header('Next steps:');
74
132
  logger.indent('1. Review and customize the generated datasets');
@@ -19,6 +19,8 @@ export interface DatasetGeneratorOptions {
19
19
  * being silently left unscoped.
20
20
  */
21
21
  tenantColumn?: string;
22
+ /** Whether to write the output file. Defaults to true; false renders only. */
23
+ writeOutput?: boolean;
22
24
  }
23
25
  export type DatasetGenerationWarning = {
24
26
  kind: 'tenant-key-candidate';
@@ -41,5 +43,6 @@ export declare function generateDatasets(options: DatasetGeneratorOptions): Prom
41
43
  exportName: string;
42
44
  }[];
43
45
  warnings: DatasetGenerationWarning[];
46
+ contents: string;
44
47
  }>;
45
48
  //# sourceMappingURL=dataset-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAkSN;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;;GAsEtE"}
1
+ {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAkSN;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;;;GAyEtE"}
@@ -277,11 +277,13 @@ ${tables.map((table) => ` ${table.name}: ${tableToPascalCase(table.name)}Datase
277
277
  };
278
278
  `;
279
279
  const output = header + datasetDefinitions.join('\n') + footer;
280
- // Ensure output directory exists
281
- const outputDir = path.dirname(path.resolve(options.outputPath));
282
- await fs.mkdir(outputDir, { recursive: true });
283
- // Write the file
284
- await fs.writeFile(path.resolve(options.outputPath), output);
280
+ if (options.writeOutput !== false) {
281
+ // Ensure output directory exists
282
+ const outputDir = path.dirname(path.resolve(options.outputPath));
283
+ await fs.mkdir(outputDir, { recursive: true });
284
+ // Write the file
285
+ await fs.writeFile(path.resolve(options.outputPath), output);
286
+ }
285
287
  // Returned so the CLI can report what it actually generated, and print an
286
288
  // example that matches the file rather than a hardcoded placeholder.
287
289
  return {
@@ -291,5 +293,6 @@ ${tables.map((table) => ` ${table.name}: ${tableToPascalCase(table.name)}Datase
291
293
  exportName: `${tableToPascalCase(table.name)}Dataset`,
292
294
  })),
293
295
  warnings,
296
+ contents: output,
294
297
  };
295
298
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Formats one unified hunk spanning the changed region, with nearby context.
3
+ * Generated files are replaced as a unit, so a compact single hunk is easier
4
+ * to review than treating every regenerated line as an unrelated edit.
5
+ */
6
+ export declare function formatGeneratedFileDiff(currentContents: string, generatedContents: string, displayPath: string): string;
7
+ //# sourceMappingURL=generated-file-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-file-diff.d.ts","sourceRoot":"","sources":["../../src/utils/generated-file-diff.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAClB,MAAM,CA+CR"}
@@ -0,0 +1,52 @@
1
+ function splitLines(contents) {
2
+ if (contents.length === 0) {
3
+ return [];
4
+ }
5
+ // Keep a trailing empty line so newline-only changes remain visible.
6
+ return contents.split('\n');
7
+ }
8
+ /**
9
+ * Formats one unified hunk spanning the changed region, with nearby context.
10
+ * Generated files are replaced as a unit, so a compact single hunk is easier
11
+ * to review than treating every regenerated line as an unrelated edit.
12
+ */
13
+ export function formatGeneratedFileDiff(currentContents, generatedContents, displayPath) {
14
+ if (currentContents === generatedContents) {
15
+ return '';
16
+ }
17
+ const currentLines = splitLines(currentContents);
18
+ const generatedLines = splitLines(generatedContents);
19
+ let sharedPrefix = 0;
20
+ while (sharedPrefix < currentLines.length
21
+ && sharedPrefix < generatedLines.length
22
+ && currentLines[sharedPrefix] === generatedLines[sharedPrefix]) {
23
+ sharedPrefix += 1;
24
+ }
25
+ let sharedSuffix = 0;
26
+ while (sharedSuffix < currentLines.length - sharedPrefix
27
+ && sharedSuffix < generatedLines.length - sharedPrefix
28
+ && currentLines[currentLines.length - sharedSuffix - 1]
29
+ === generatedLines[generatedLines.length - sharedSuffix - 1]) {
30
+ sharedSuffix += 1;
31
+ }
32
+ const contextLines = 3;
33
+ const currentStart = Math.max(0, sharedPrefix - contextLines);
34
+ const generatedStart = Math.max(0, sharedPrefix - contextLines);
35
+ const currentChangedEnd = currentLines.length - sharedSuffix;
36
+ const generatedChangedEnd = generatedLines.length - sharedSuffix;
37
+ const currentEnd = Math.min(currentLines.length, currentChangedEnd + contextLines);
38
+ const generatedEnd = Math.min(generatedLines.length, generatedChangedEnd + contextLines);
39
+ const prefixContext = currentLines.slice(currentStart, sharedPrefix);
40
+ const suffixContext = currentLines.slice(currentChangedEnd, currentEnd);
41
+ const removedLines = currentLines.slice(sharedPrefix, currentChangedEnd);
42
+ const addedLines = generatedLines.slice(sharedPrefix, generatedChangedEnd);
43
+ return [
44
+ `--- ${displayPath}`,
45
+ `+++ ${displayPath} (generated)`,
46
+ `@@ -${currentStart + 1},${currentEnd - currentStart} +${generatedStart + 1},${generatedEnd - generatedStart} @@`,
47
+ ...prefixContext.map((line) => ` ${line}`),
48
+ ...removedLines.map((line) => `-${line}`),
49
+ ...addedLines.map((line) => `+${line}`),
50
+ ...suffixContext.map((line) => ` ${line}`),
51
+ ].join('\n');
52
+ }
@@ -0,0 +1,22 @@
1
+ export interface WriteGeneratedFileOptions {
2
+ /**
3
+ * Whether an existing destination may be replaced.
4
+ *
5
+ * Defaults to `false`, which makes the write an exclusive create: the
6
+ * existence check and the write are a single filesystem operation, so a file
7
+ * that appeared since the caller last looked raises
8
+ * {@link GeneratedFileExistsError} instead of being silently clobbered.
9
+ */
10
+ overwrite?: boolean;
11
+ }
12
+ /**
13
+ * Thrown when a non-overwriting write finds the destination already present.
14
+ */
15
+ export declare class GeneratedFileExistsError extends Error {
16
+ readonly filePath: string;
17
+ readonly code = "EEXIST";
18
+ constructor(filePath: string);
19
+ }
20
+ export declare function readGeneratedFile(filePath: string): Promise<string | undefined>;
21
+ export declare function writeGeneratedFileAtomically(filePath: string, contents: string, options?: WriteGeneratedFileOptions): Promise<void>;
22
+ //# sourceMappingURL=generated-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-file.d.ts","sourceRoot":"","sources":["../../src/utils/generated-file.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IACxC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IAGrC,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAFrC,QAAQ,CAAC,IAAI,YAAY;gBAEJ,QAAQ,EAAE,MAAM;CAItC;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CASrF;AAgCD,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CA4Df"}
@@ -0,0 +1,100 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmod, mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ /**
5
+ * Thrown when a non-overwriting write finds the destination already present.
6
+ */
7
+ export class GeneratedFileExistsError extends Error {
8
+ filePath;
9
+ code = 'EEXIST';
10
+ constructor(filePath) {
11
+ super(`Refusing to overwrite existing file: ${filePath}`);
12
+ this.filePath = filePath;
13
+ this.name = 'GeneratedFileExistsError';
14
+ }
15
+ }
16
+ export async function readGeneratedFile(filePath) {
17
+ try {
18
+ return await readFile(filePath, 'utf8');
19
+ }
20
+ catch (error) {
21
+ if (error.code === 'ENOENT') {
22
+ return undefined;
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ /**
28
+ * Tightest of the permission bits observed on a destination, ignoring the
29
+ * observations where it did not exist.
30
+ *
31
+ * A `chmod` that lands between two observations must not be undone by the
32
+ * earlier snapshot, and the safe direction is always the more restrictive one:
33
+ * the replacement is never more permissive than anything the destination was
34
+ * seen to be.
35
+ */
36
+ function mostRestrictiveMode(...modes) {
37
+ const observed = modes.filter((mode) => mode !== undefined);
38
+ return observed.length > 0 ? observed.reduce((tightest, mode) => tightest & mode) : undefined;
39
+ }
40
+ /**
41
+ * Permission bits of an existing file, or undefined when it does not exist.
42
+ */
43
+ async function readExistingMode(filePath) {
44
+ try {
45
+ const stats = await stat(filePath);
46
+ return stats.mode & 0o777;
47
+ }
48
+ catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ return undefined;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ export async function writeGeneratedFileAtomically(filePath, contents, options = {}) {
56
+ const resolvedPath = path.resolve(filePath);
57
+ const outputDirectory = path.dirname(resolvedPath);
58
+ await mkdir(outputDirectory, { recursive: true });
59
+ if (!options.overwrite) {
60
+ try {
61
+ // O_EXCL: the destination is created only if it is still absent, so a
62
+ // concurrent create between the caller's check and this write is never
63
+ // lost.
64
+ await writeFile(resolvedPath, contents, { flag: 'wx' });
65
+ return;
66
+ }
67
+ catch (error) {
68
+ if (error.code === 'EEXIST') {
69
+ throw new GeneratedFileExistsError(resolvedPath);
70
+ }
71
+ throw error;
72
+ }
73
+ }
74
+ const temporaryPath = path.join(outputDirectory, `.${path.basename(resolvedPath)}.${process.pid}.${randomUUID()}.tmp`);
75
+ // Read the destination's mode first: the temporary file must never be created
76
+ // more permissively than the file it replaces, or its contents are briefly
77
+ // exposed to other local users in a shared directory.
78
+ const existingMode = await readExistingMode(resolvedPath);
79
+ try {
80
+ await writeFile(temporaryPath, contents, existingMode !== undefined ? { mode: existingMode } : {});
81
+ // The destination can be chmod'ed — or created — while the contents above
82
+ // are being written. Renaming with only the pre-write snapshot would undo
83
+ // that, replacing a file just tightened to 0600 with a 0644 one, so the
84
+ // tighter of the two observations wins.
85
+ const destinationMode = mostRestrictiveMode(existingMode, await readExistingMode(resolvedPath));
86
+ // open() also applies the umask, and rename() swaps in a new inode, so the
87
+ // destination's exact mode has to be restored explicitly or a customized
88
+ // mode is silently reset. A chmod landing after this read still races the
89
+ // rename — rename() cannot be made conditional on the destination's mode —
90
+ // but the window is now two syscalls rather than the whole write.
91
+ if (destinationMode !== undefined) {
92
+ await chmod(temporaryPath, destinationMode);
93
+ }
94
+ await rename(temporaryPath, resolvedPath);
95
+ }
96
+ catch (error) {
97
+ await unlink(temporaryPath).catch(() => undefined);
98
+ throw error;
99
+ }
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.18.1",
3
+ "version": "1.18.3",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -30,8 +30,8 @@
30
30
  "open": "^10.0.0",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/protocol": "0.11.0",
34
- "@hypequery/deployment": "0.7.3"
33
+ "@hypequery/protocol": "0.11.1",
34
+ "@hypequery/deployment": "0.7.4"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "1.3.0"
@@ -45,7 +45,7 @@
45
45
  "@vitest/coverage-v8": "^3.2.6",
46
46
  "typescript": "^5.7.3",
47
47
  "vitest": "^3.2.6",
48
- "@hypequery/serve": "0.16.1"
48
+ "@hypequery/serve": "0.16.2"
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",