clisma 0.1.0 โ†’ 0.1.1

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/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # ๐Ÿ’Š clisma
2
+
3
+ **A ClickHouse migrations CLI with templated SQL and environment-aware config.**
4
+
5
+ <small>_"clisma" is a mashup of ClickHouse + Prisma. A dumb pun, but it stuck._</small>
6
+
7
+ This project borrows ideas from tools we like:
8
+
9
+ - **[Atlas](https://atlasgo.io/)** for the idea of [templated migrations](https://atlasgo.io/concepts/migrations#template) and [config-driven environments](https://atlasgo.io/concepts/dev-database).
10
+
11
+ - **[Prisma](https://www.prisma.io/)** for the simple, friendly CLI experience.
12
+
13
+ ### So why it exists?
14
+
15
+ - **Templates in migrations** โ€” Atlas has this, but it is paid; clisma keeps it simple and open.
16
+ - **Multi-statement migrations** โ€” write real SQL without splitting into tiny files.
17
+ - **Declarative environments** โ€” keep local/staging/prod configs in one place.
18
+
19
+ ## ๐Ÿ“ฆ How to use it
20
+
21
+ ### Global installation
22
+
23
+ ```bash
24
+ npm install -g clisma
25
+ ```
26
+
27
+ ### NPM
28
+
29
+ ```bash
30
+ npm install --save-dev clisma
31
+ ```
32
+
33
+ ### NPX
34
+
35
+ ```bash
36
+ npx clisma
37
+ ```
38
+
39
+ ## ๐Ÿš€ Quickstart
40
+
41
+ Create `clisma.hcl`:
42
+
43
+ ```hcl
44
+ env "local" {
45
+ url = "http://default:password@localhost:8123/mydb"
46
+
47
+ migrations {
48
+ dir = "migrations"
49
+ }
50
+ }
51
+ ```
52
+
53
+ Run migrations:
54
+
55
+ ```bash
56
+ clisma run --env local
57
+ clisma status --env local
58
+ ```
59
+
60
+ ## ๐Ÿงฉ Config basics
61
+
62
+ - `env "name"` defines an environment.
63
+ - `migrations` holds migration settings.
64
+ - `variable "name"` defines inputs for `var.*`.
65
+ - `env("NAME")` reads environment variables.
66
+
67
+ ### Example with variables and templates
68
+
69
+ ```hcl
70
+ variable "ttl_days" {
71
+ type = string
72
+ default = "30"
73
+ }
74
+
75
+ env "production" {
76
+ url = env("CLICKHOUSE_PROD_URL")
77
+ cluster_name = "prod-cluster"
78
+
79
+ migrations {
80
+ dir = "migrations"
81
+ vars = {
82
+ is_replicated = true
83
+ create_table_options = "ON CLUSTER prod-cluster"
84
+ ttl_days = var.ttl_days
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ **`cluster_name`** affects how the migrations tracking table is created (replicated or not). And the CLI will warn if the actual cluster does not match the config.
91
+
92
+ #### If your ClickHouse server has clusters configured, `cluster_name` is required
93
+
94
+ ## ๐Ÿงช Templates
95
+
96
+ Templates are [Handlebars](https://handlebarsjs.com/guide/expressions.html). Variables come from `migrations.vars` (and
97
+ `cluster_name` is available as `{{cluster_name}}`).
98
+
99
+ ```sql
100
+ CREATE TABLE IF NOT EXISTS events {{create_table_options}} (
101
+ id UUID,
102
+ created_at DateTime DEFAULT now()
103
+ )
104
+ {{#if is_replicated}}
105
+ ENGINE = ReplicatedMergeTree('/clickhouse/tables/{cluster}/events', '{replica}')
106
+ {{else}}
107
+ ENGINE = MergeTree()
108
+ {{/if}}
109
+ ORDER BY id;
110
+ ```
111
+
112
+ Multi-statement migrations are supported (split on semicolons outside strings/comments).
113
+
114
+ ## ๐Ÿ› ๏ธ CLI
115
+
116
+ Common commands:
117
+
118
+ ```bash
119
+ clisma run --env local
120
+ clisma status --env local
121
+ clisma create --name create_events
122
+ clisma checksum ./migrations/20240101123045_create_events.sql
123
+ ```
124
+
125
+ ### Additional flags
126
+
127
+ - `--config <path>`
128
+ - `--env <name>`
129
+ - `--env-file <path>`
130
+ - `--var <key=value>` (repeatable)
131
+
132
+ The CLI requires a config file. Use `--config` or place `clisma.hcl` in the current directory.
133
+
134
+ Example with variables and env file:
135
+
136
+ ```bash
137
+ clisma run --env local --var ttl_days=30 --env-file .env
138
+ ```
package/dist/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clisma",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "ClickHouse migration CLI",
5
5
  "author": "github.com/will-work-for-meal",
6
6
  "license": "MIT",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../../src/tests/integration.test.ts"],"names":[],"mappings":""}
@@ -1,80 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { execFile } from "node:child_process";
4
- import { promisify } from "node:util";
5
- import fs from "node:fs/promises";
6
- import os from "node:os";
7
- import path from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- const exec = promisify(execFile);
10
- const isDockerAvailable = async () => {
11
- try {
12
- await exec("docker", ["--version"]);
13
- await exec("docker", ["compose", "version"]);
14
- return true;
15
- }
16
- catch {
17
- return false;
18
- }
19
- };
20
- const waitForClickHouse = async (baseUrl) => {
21
- const deadline = Date.now() + 30_000;
22
- while (Date.now() < deadline) {
23
- try {
24
- const response = await fetch(`${baseUrl}/ping`);
25
- if (response.ok) {
26
- return;
27
- }
28
- }
29
- catch {
30
- // Ignore until ready.
31
- }
32
- await new Promise((resolve) => setTimeout(resolve, 500));
33
- }
34
- throw new Error("ClickHouse did not become ready in time");
35
- };
36
- const runCli = async (repoRoot, args) => {
37
- const cliPath = path.join(repoRoot, "packages/cli/src/cli.ts");
38
- await exec("node", ["--import", "tsx", cliPath, ...args], { cwd: repoRoot });
39
- };
40
- const queryClickHouse = async (baseUrl, query) => {
41
- const response = await fetch(`${baseUrl}/?query=${encodeURIComponent(query)}`);
42
- if (!response.ok) {
43
- const text = await response.text();
44
- throw new Error(`ClickHouse query failed: ${text}`);
45
- }
46
- return response.text();
47
- };
48
- test("cli applies migrations against ClickHouse", async (t) => {
49
- if (!(await isDockerAvailable())) {
50
- t.skip("Docker not available");
51
- return;
52
- }
53
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../..");
54
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clisma-it-"));
55
- const migrationsDir = path.join(tempDir, "migrations");
56
- await fs.mkdir(migrationsDir, { recursive: true });
57
- const baseUrl = "http://localhost:8123";
58
- const dbName = `clisma_it_${Date.now()}`;
59
- await exec("docker", ["compose", "up", "-d"], { cwd: repoRoot });
60
- t.after(async () => {
61
- try {
62
- await exec("docker", ["compose", "down", "-v"], { cwd: repoRoot });
63
- }
64
- catch {
65
- // Ignore cleanup failures.
66
- }
67
- });
68
- await waitForClickHouse(baseUrl);
69
- await queryClickHouse(baseUrl, `CREATE DATABASE IF NOT EXISTS ${dbName}`);
70
- const configPath = path.join(tempDir, "clisma.hcl");
71
- await fs.writeFile(configPath, `env "local" {\n url = "http://default:@localhost:8123/${dbName}"\n\n migrations {\n dir = "migrations"\n }\n}\n`, "utf8");
72
- const migrationFile = path.join(migrationsDir, "20240101120000_create_table.sql");
73
- await fs.writeFile(migrationFile, "CREATE TABLE IF NOT EXISTS test_table (id UInt64) ENGINE = MergeTree() ORDER BY id;", "utf8");
74
- await runCli(repoRoot, ["run", "--config", configPath, "--env", "local"]);
75
- const tableExists = await queryClickHouse(baseUrl, `EXISTS TABLE ${dbName}.test_table`);
76
- assert.equal(tableExists.trim(), "1");
77
- const migrationsCount = await queryClickHouse(baseUrl, `SELECT count() FROM ${dbName}.schema_migrations`);
78
- assert.equal(migrationsCount.trim(), "1");
79
- await queryClickHouse(baseUrl, `DROP DATABASE IF EXISTS ${dbName}`);
80
- });