metabase-iac 0.1.0

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.
Files changed (63) hide show
  1. package/README.md +206 -0
  2. package/bin/metabase-iac.mjs +18 -0
  3. package/dist/auth.d.ts +3 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +13 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +97 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/client.d.ts +39 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +118 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/commands/apply.d.ts +3 -0
  16. package/dist/commands/apply.d.ts.map +1 -0
  17. package/dist/commands/apply.js +32 -0
  18. package/dist/commands/apply.js.map +1 -0
  19. package/dist/commands/plan.d.ts +3 -0
  20. package/dist/commands/plan.d.ts.map +1 -0
  21. package/dist/commands/plan.js +20 -0
  22. package/dist/commands/plan.js.map +1 -0
  23. package/dist/commands/pull.d.ts +4 -0
  24. package/dist/commands/pull.d.ts.map +1 -0
  25. package/dist/commands/pull.js +240 -0
  26. package/dist/commands/pull.js.map +1 -0
  27. package/dist/config.d.ts +9 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +16 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/loader.d.ts +7 -0
  32. package/dist/loader.d.ts.map +1 -0
  33. package/dist/loader.js +46 -0
  34. package/dist/loader.js.map +1 -0
  35. package/dist/reconciler.d.ts +11 -0
  36. package/dist/reconciler.d.ts.map +1 -0
  37. package/dist/reconciler.js +184 -0
  38. package/dist/reconciler.js.map +1 -0
  39. package/dist/resolver.d.ts +28 -0
  40. package/dist/resolver.d.ts.map +1 -0
  41. package/dist/resolver.js +139 -0
  42. package/dist/resolver.js.map +1 -0
  43. package/dist/state.d.ts +6 -0
  44. package/dist/state.d.ts.map +1 -0
  45. package/dist/state.js +26 -0
  46. package/dist/state.js.map +1 -0
  47. package/dist/types.d.ts +159 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +21 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +53 -0
  52. package/src/auth.ts +16 -0
  53. package/src/cli.ts +98 -0
  54. package/src/client.ts +166 -0
  55. package/src/commands/apply.ts +42 -0
  56. package/src/commands/plan.ts +26 -0
  57. package/src/commands/pull.ts +286 -0
  58. package/src/config.ts +27 -0
  59. package/src/loader.ts +49 -0
  60. package/src/reconciler.ts +241 -0
  61. package/src/resolver.ts +150 -0
  62. package/src/state.ts +38 -0
  63. package/src/types.ts +204 -0
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # metabase-iac
2
+
3
+ Infrastructure as Code for [Metabase](https://www.metabase.com). Manage collections, questions (SQL, MongoDB, structured), dashboards, and text cards as code.
4
+
5
+ Similar to how Grafana dashboards can be managed as JSON, `metabase-iac` lets you version-control your Metabase resources and apply changes through a CLI.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add metabase-iac
11
+ # or
12
+ npm install metabase-iac
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```bash
18
+ # 1. Create a new directory for your Metabase instance
19
+ mkdir metabase-prod && cd metabase-prod
20
+ pnpm init && pnpm add metabase-iac
21
+
22
+ # 2. Configure credentials
23
+ cat > .env <<EOF
24
+ METABASE_URL=https://your-metabase.example.com
25
+ METABASE_USERNAME=admin@example.com
26
+ METABASE_PASSWORD=your-password
27
+ EOF
28
+
29
+ # 3. Pull all existing resources from Metabase
30
+ npx metabase-iac pull
31
+
32
+ # 4. You now have collections/, questions/, dashboards/ — commit them to git
33
+ git init && git add -A && git commit -m "Initial pull from Metabase"
34
+ ```
35
+
36
+ ## Commands
37
+
38
+ | Command | Description |
39
+ |---|---|
40
+ | `metabase-iac pull` | Import collections, questions, and dashboards from Metabase |
41
+ | `metabase-iac plan` | Preview what changes would be applied (dry run) |
42
+ | `metabase-iac apply` | Push local resource definitions to Metabase |
43
+ | `metabase-iac databases` | List connected databases |
44
+ | `metabase-iac collections` | List collections |
45
+
46
+ ### Options
47
+
48
+ ```
49
+ pull
50
+ -o, --output <dir> Output directory (default: ".")
51
+ --import-base <path> Import path for types in generated files (default: "metabase-iac/types")
52
+
53
+ plan / apply
54
+ -d, --dir <dir> Resources directory (default: ".")
55
+ ```
56
+
57
+ ## Authentication
58
+
59
+ Configure via environment variables or a `.env` file:
60
+
61
+ ```env
62
+ METABASE_URL=https://your-metabase.example.com
63
+
64
+ # Option 1: Username + password
65
+ METABASE_USERNAME=admin@example.com
66
+ METABASE_PASSWORD=your-password
67
+
68
+ # Option 2: Session token
69
+ METABASE_SESSION_TOKEN=your-session-token
70
+
71
+ # Option 3: API key
72
+ METABASE_API_KEY=mb_xxxx
73
+ ```
74
+
75
+ ## Resource types
76
+
77
+ ### Collections
78
+
79
+ ```typescript
80
+ import { collection } from 'metabase-iac/types'
81
+
82
+ export default collection({
83
+ id: 23,
84
+ slug: 'engineering',
85
+ name: 'Engineering',
86
+ description: 'Engineering team dashboards',
87
+ parent: 'Teams', // optional — omit for root
88
+ })
89
+ ```
90
+
91
+ ### Native queries (SQL)
92
+
93
+ Two files: a `.sql` with the query and a `.meta.json` with metadata.
94
+
95
+ ```sql
96
+ -- questions/daily-signups.sql
97
+ SELECT date_trunc('day', created_at) AS day, count(*)
98
+ FROM users
99
+ GROUP BY 1
100
+ ORDER BY 1 DESC
101
+ LIMIT 30
102
+ ```
103
+
104
+ ```json
105
+ {
106
+ "id": 42,
107
+ "name": "Daily Signups",
108
+ "collection": "Engineering",
109
+ "database": "production",
110
+ "display": "line",
111
+ "description": "Daily user signups over the last 30 days"
112
+ }
113
+ ```
114
+
115
+ ### Native queries (MongoDB)
116
+
117
+ Same pattern, with `.mongodb` extension:
118
+
119
+ ```json
120
+ [
121
+ { "$match": { "status": "active" } },
122
+ { "$group": { "_id": "$region", "count": { "$sum": 1 } } },
123
+ { "$sort": { "count": -1 } }
124
+ ]
125
+ ```
126
+
127
+ ### Structured queries (MBQL)
128
+
129
+ ```typescript
130
+ import { structuredQuestion } from 'metabase-iac/types'
131
+
132
+ export default structuredQuestion({
133
+ id: 37,
134
+ name: 'accounts-count',
135
+ collection: 'Engineering',
136
+ database: 'production',
137
+ display: 'scalar',
138
+ datasetQuery: {
139
+ stages: [{ aggregation: [["count", {}]], "source-table": 77 }],
140
+ database: 2,
141
+ },
142
+ })
143
+ ```
144
+
145
+ ### Dashboards
146
+
147
+ ```typescript
148
+ import { dashboard } from 'metabase-iac/types'
149
+
150
+ export default dashboard({
151
+ id: 3,
152
+ name: 'Engineering Overview',
153
+ collection: 'Engineering',
154
+ cards: [
155
+ { question: 'Daily Signups', questionId: 42, width: 12, height: 6, row: 0, col: 0 },
156
+ { question: 'accounts-count', questionId: 37, width: 6, height: 3, row: 0, col: 12 },
157
+ { text: '## Notes\nUpdated daily.', width: 24, height: 2, row: 6, col: 0 },
158
+ ],
159
+ })
160
+ ```
161
+
162
+ ## Workflow
163
+
164
+ ```
165
+ pull → review diff → edit → plan → apply → commit
166
+ ```
167
+
168
+ 1. `metabase-iac pull` — import current state from Metabase
169
+ 2. Review changes with `git diff`
170
+ 3. Edit `.ts`/`.sql`/`.mongodb` files as needed
171
+ 4. `metabase-iac plan` — preview what will change
172
+ 5. `metabase-iac apply` — push to Metabase
173
+ 6. `git commit` — version control your changes
174
+
175
+ ## Project structure
176
+
177
+ A consumer repo looks like this:
178
+
179
+ ```
180
+ my-metabase/
181
+ ├── collections/
182
+ │ └── *.ts
183
+ ├── questions/
184
+ │ ├── *.ts # Structured (MBQL) queries
185
+ │ ├── *.sql # Native SQL queries
186
+ │ ├── *.mongodb # Native MongoDB queries
187
+ │ └── *.meta.json # Metadata for native queries
188
+ ├── dashboards/
189
+ │ └── *.ts
190
+ ├── .env # Credentials (gitignored)
191
+ ├── .gitignore
192
+ ├── package.json
193
+ └── tsconfig.json # Optional — for IDE type checking
194
+ ```
195
+
196
+ ## How it works
197
+
198
+ - **Pull** reads from the Metabase REST API and generates typed resource files
199
+ - **Plan/Apply** loads local resource files, diffs against a state file (`.metabase-state.json`), and creates/updates resources via the API
200
+ - Resources are matched by name within their collection
201
+ - Ordering is automatic: collections are created before questions, questions before dashboards
202
+ - Both SQL and MongoDB databases are fully supported — native queries are stored as plain text files
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { createRequire } from 'node:module'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+ const require = createRequire(join(__dirname, '..', 'package.json'))
10
+ const tsxPath = require.resolve('tsx')
11
+ const cli = join(__dirname, '..', 'dist', 'cli.js')
12
+
13
+ const child = spawn(process.execPath, ['--import', tsxPath, cli, ...process.argv.slice(2)], {
14
+ stdio: 'inherit',
15
+ cwd: process.cwd(),
16
+ })
17
+
18
+ child.on('exit', (code) => process.exit(code ?? 0))
package/dist/auth.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { MetabaseClient } from './client.js';
2
+ export declare function createClient(): Promise<MetabaseClient>;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAG5C,wBAAsB,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC,CAY5D"}
package/dist/auth.js ADDED
@@ -0,0 +1,13 @@
1
+ import { MetabaseClient } from './client.js';
2
+ import { loadConfig } from './config.js';
3
+ export async function createClient() {
4
+ const config = loadConfig();
5
+ if (config.apiKey) {
6
+ return MetabaseClient.withApiKey(config.url, config.apiKey);
7
+ }
8
+ if (config.sessionToken) {
9
+ return MetabaseClient.withSession(config.url, config.sessionToken);
10
+ }
11
+ return MetabaseClient.login(config.url, config.username, config.password);
12
+ }
13
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAE3B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7D,CAAC;IAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,OAAO,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;IACpE,CAAC;IAED,OAAO,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,QAAS,EAAE,MAAM,CAAC,QAAS,CAAC,CAAA;AAC7E,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { createClient } from './auth.js';
5
+ import { apply } from './commands/apply.js';
6
+ import { pull } from './commands/pull.js';
7
+ import { saveState } from './state.js';
8
+ const program = new Command();
9
+ program
10
+ .name('metabase-iac')
11
+ .description('Infrastructure as Code for Metabase')
12
+ .version('0.1.0');
13
+ program
14
+ .command('pull')
15
+ .description('Import all collections, questions, dashboards from Metabase into local files')
16
+ .option('-o, --output <dir>', 'Output directory', '.')
17
+ .option('--import-base <path>', 'Import path for types in generated files', 'metabase-iac/types')
18
+ .action(async (opts) => {
19
+ try {
20
+ const client = await createClient();
21
+ console.log(chalk.bold('Pulling from Metabase...\n'));
22
+ const state = await pull(client, opts.output, opts.importBase);
23
+ await saveState(state);
24
+ console.log(chalk.green(`\nPull complete! ${state.length} resource(s) saved to ${opts.output}/`));
25
+ }
26
+ catch (err) {
27
+ console.error(chalk.red(`Error: ${err.message}`));
28
+ process.exit(1);
29
+ }
30
+ });
31
+ program
32
+ .command('plan')
33
+ .description('Show what changes would be applied (dry run)')
34
+ .option('-d, --dir <dir>', 'Resources directory', '.')
35
+ .action(async (opts) => {
36
+ try {
37
+ const client = await createClient();
38
+ await apply(client, true, opts.dir);
39
+ }
40
+ catch (err) {
41
+ console.error(chalk.red(`Error: ${err.message}`));
42
+ process.exit(1);
43
+ }
44
+ });
45
+ program
46
+ .command('apply')
47
+ .description('Apply resource definitions to Metabase')
48
+ .option('-d, --dir <dir>', 'Resources directory', '.')
49
+ .action(async (opts) => {
50
+ try {
51
+ const client = await createClient();
52
+ await apply(client, false, opts.dir);
53
+ }
54
+ catch (err) {
55
+ console.error(chalk.red(`Error: ${err.message}`));
56
+ process.exit(1);
57
+ }
58
+ });
59
+ program
60
+ .command('databases')
61
+ .description('List available databases in Metabase')
62
+ .action(async () => {
63
+ try {
64
+ const client = await createClient();
65
+ const dbs = await client.getDatabases();
66
+ console.log(chalk.bold('Databases:\n'));
67
+ for (const db of dbs) {
68
+ console.log(` ${chalk.cyan(db.id.toString().padStart(3))} ${db.name} (${db.engine})`);
69
+ }
70
+ }
71
+ catch (err) {
72
+ console.error(chalk.red(`Error: ${err.message}`));
73
+ process.exit(1);
74
+ }
75
+ });
76
+ program
77
+ .command('collections')
78
+ .description('List available collections in Metabase')
79
+ .action(async () => {
80
+ try {
81
+ const client = await createClient();
82
+ const collections = await client.getCollections();
83
+ console.log(chalk.bold('Collections:\n'));
84
+ for (const c of collections) {
85
+ if (c.archived)
86
+ continue;
87
+ const parent = c.parent_id ? ` (parent: ${c.parent_id})` : '';
88
+ console.log(` ${chalk.cyan(c.id.toString().padStart(3))} ${c.name}${chalk.gray(parent)}`);
89
+ }
90
+ }
91
+ catch (err) {
92
+ console.error(chalk.red(`Error: ${err.message}`));
93
+ process.exit(1);
94
+ }
95
+ });
96
+ program.parse();
97
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { MetabaseCard, MetabaseCollection, MetabaseDashboard, MetabaseDatabase } from './types.js';
2
+ export declare class MetabaseClient {
3
+ private baseUrl;
4
+ private headers;
5
+ constructor(baseUrl: string, auth: {
6
+ sessionToken?: string;
7
+ apiKey?: string;
8
+ });
9
+ static login(baseUrl: string, username: string, password: string): Promise<MetabaseClient>;
10
+ static withApiKey(baseUrl: string, apiKey: string): MetabaseClient;
11
+ static withSession(baseUrl: string, sessionToken: string): MetabaseClient;
12
+ getCollections(): Promise<MetabaseCollection[]>;
13
+ createCollection(data: {
14
+ name: string;
15
+ description?: string;
16
+ parent_id?: number | null;
17
+ }): Promise<MetabaseCollection>;
18
+ updateCollection(id: number, data: {
19
+ name?: string;
20
+ description?: string;
21
+ parent_id?: number | null;
22
+ }): Promise<MetabaseCollection>;
23
+ getCards(): Promise<MetabaseCard[]>;
24
+ getCard(id: number): Promise<MetabaseCard>;
25
+ createCard(data: Record<string, unknown>): Promise<MetabaseCard>;
26
+ updateCard(id: number, data: Record<string, unknown>): Promise<MetabaseCard>;
27
+ getDashboards(): Promise<MetabaseDashboard[]>;
28
+ getDashboard(id: number): Promise<MetabaseDashboard>;
29
+ createDashboard(data: Record<string, unknown>): Promise<MetabaseDashboard>;
30
+ updateDashboard(id: number, data: Record<string, unknown>): Promise<MetabaseDashboard>;
31
+ addCardToDashboard(dashboardId: number, data: Record<string, unknown>): Promise<unknown>;
32
+ updateDashboardCards(dashboardId: number, cards: Record<string, unknown>[]): Promise<unknown>;
33
+ getDatabases(): Promise<MetabaseDatabase[]>;
34
+ getDatabaseMetadata(id: number): Promise<MetabaseDatabase>;
35
+ get<T = any>(path: string): Promise<T>;
36
+ private post;
37
+ private put;
38
+ }
39
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;WAWhE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAiBhG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc;IAIlE,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,cAAc;IAMnE,cAAc,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAI/C,gBAAgB,CAAC,IAAI,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAC1B,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIzB,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACvE,OAAO,CAAC,kBAAkB,CAAC;IAMxB,QAAQ,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAInC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAIhE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAM5E,aAAa,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAI7C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIpD,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAI1E,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAItF,kBAAkB,CACtB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC;IAIb,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC/B,OAAO,CAAC,OAAO,CAAC;IAMb,YAAY,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAK3C,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAM1D,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;YAS9B,IAAI;YAaJ,GAAG;CAYlB"}
package/dist/client.js ADDED
@@ -0,0 +1,118 @@
1
+ export class MetabaseClient {
2
+ baseUrl;
3
+ headers;
4
+ constructor(baseUrl, auth) {
5
+ this.baseUrl = baseUrl.replace(/\/$/, '');
6
+ this.headers = { 'Content-Type': 'application/json' };
7
+ if (auth.apiKey) {
8
+ this.headers['x-api-key'] = auth.apiKey;
9
+ }
10
+ else if (auth.sessionToken) {
11
+ this.headers['X-Metabase-Session'] = auth.sessionToken;
12
+ }
13
+ }
14
+ static async login(baseUrl, username, password) {
15
+ const url = `${baseUrl.replace(/\/$/, '')}/api/session`;
16
+ const res = await fetch(url, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify({ username, password }),
20
+ });
21
+ if (!res.ok) {
22
+ const text = await res.text();
23
+ throw new Error(`Login failed (${res.status}): ${text}`);
24
+ }
25
+ const { id } = (await res.json());
26
+ return new MetabaseClient(baseUrl, { sessionToken: id });
27
+ }
28
+ static withApiKey(baseUrl, apiKey) {
29
+ return new MetabaseClient(baseUrl, { apiKey });
30
+ }
31
+ static withSession(baseUrl, sessionToken) {
32
+ return new MetabaseClient(baseUrl, { sessionToken });
33
+ }
34
+ // ─── Collections ───
35
+ async getCollections() {
36
+ return this.get('/api/collection');
37
+ }
38
+ async createCollection(data) {
39
+ return this.post('/api/collection', data);
40
+ }
41
+ async updateCollection(id, data) {
42
+ return this.put(`/api/collection/${id}`, data);
43
+ }
44
+ // ─── Cards (Questions) ───
45
+ async getCards() {
46
+ return this.get('/api/card');
47
+ }
48
+ async getCard(id) {
49
+ return this.get(`/api/card/${id}`);
50
+ }
51
+ async createCard(data) {
52
+ return this.post('/api/card', data);
53
+ }
54
+ async updateCard(id, data) {
55
+ return this.put(`/api/card/${id}`, data);
56
+ }
57
+ // ─── Dashboards ───
58
+ async getDashboards() {
59
+ return this.get('/api/dashboard');
60
+ }
61
+ async getDashboard(id) {
62
+ return this.get(`/api/dashboard/${id}`);
63
+ }
64
+ async createDashboard(data) {
65
+ return this.post('/api/dashboard', data);
66
+ }
67
+ async updateDashboard(id, data) {
68
+ return this.put(`/api/dashboard/${id}`, data);
69
+ }
70
+ async addCardToDashboard(dashboardId, data) {
71
+ return this.post(`/api/dashboard/${dashboardId}/cards`, data);
72
+ }
73
+ async updateDashboardCards(dashboardId, cards) {
74
+ return this.put(`/api/dashboard/${dashboardId}/cards`, { cards });
75
+ }
76
+ // ─── Databases ───
77
+ async getDatabases() {
78
+ const result = await this.get('/api/database');
79
+ return result.data;
80
+ }
81
+ async getDatabaseMetadata(id) {
82
+ return this.get(`/api/database/${id}/metadata`);
83
+ }
84
+ // ─── HTTP helpers ───
85
+ async get(path) {
86
+ const res = await fetch(`${this.baseUrl}${path}`, { headers: this.headers });
87
+ if (!res.ok) {
88
+ const text = await res.text();
89
+ throw new Error(`GET ${path} failed (${res.status}): ${text}`);
90
+ }
91
+ return res.json();
92
+ }
93
+ async post(path, body) {
94
+ const res = await fetch(`${this.baseUrl}${path}`, {
95
+ method: 'POST',
96
+ headers: this.headers,
97
+ body: JSON.stringify(body),
98
+ });
99
+ if (!res.ok) {
100
+ const text = await res.text();
101
+ throw new Error(`POST ${path} failed (${res.status}): ${text}`);
102
+ }
103
+ return res.json();
104
+ }
105
+ async put(path, body) {
106
+ const res = await fetch(`${this.baseUrl}${path}`, {
107
+ method: 'PUT',
108
+ headers: this.headers,
109
+ body: JSON.stringify(body),
110
+ });
111
+ if (!res.ok) {
112
+ const text = await res.text();
113
+ throw new Error(`PUT ${path} failed (${res.status}): ${text}`);
114
+ }
115
+ return res.json();
116
+ }
117
+ }
118
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { MetabaseClient } from '../client.js';
2
+ export declare function apply(client: MetabaseClient, dryRun: boolean, resourceDir?: string): Promise<void>;
3
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAOlD,wBAAsB,KAAK,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAE,MAAY,iBAiC7F"}
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk';
2
+ import { Resolver } from '../resolver.js';
3
+ import { loadResources } from '../loader.js';
4
+ import { loadState, saveState } from '../state.js';
5
+ import { buildPlan, applyActions } from '../reconciler.js';
6
+ import { printPlan } from './plan.js';
7
+ export async function apply(client, dryRun, resourceDir = '.') {
8
+ console.log('Loading resources...');
9
+ const resources = await loadResources(resourceDir);
10
+ if (resources.length === 0) {
11
+ console.log(chalk.yellow('No resources found. Expected collections/, questions/, dashboards/ subdirectories.'));
12
+ return;
13
+ }
14
+ console.log(`Found ${resources.length} resource(s)`);
15
+ console.log('Initializing resolver...');
16
+ const resolver = new Resolver();
17
+ await resolver.init(client);
18
+ const state = await loadState();
19
+ const actions = buildPlan(resources, state);
20
+ printPlan(actions);
21
+ if (dryRun) {
22
+ console.log(chalk.cyan('Dry run — no changes applied.'));
23
+ return;
24
+ }
25
+ if (actions.length === 0)
26
+ return;
27
+ console.log(chalk.bold('Applying changes...'));
28
+ const newState = await applyActions(client, resolver, actions, state);
29
+ await saveState(newState);
30
+ console.log(chalk.green(`\nDone! Applied ${actions.length} change(s).`));
31
+ }
32
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,MAAsB,EAAE,MAAe,EAAE,cAAsB,GAAG;IAC5F,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;IACnC,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAA;IAElD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,oFAAoF,CAAC,CAAC,CAAA;QAC/G,OAAM;IACR,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,SAAS,SAAS,CAAC,MAAM,cAAc,CAAC,CAAA;IAEpD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAA;IAC/B,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAE3B,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAA;IAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAE3C,SAAS,CAAC,OAAO,CAAC,CAAA;IAElB,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAA;QACxD,OAAM;IACR,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAEhC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAA;IAE9C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IACrE,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAA;IAEzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,OAAO,CAAC,MAAM,aAAa,CAAC,CAAC,CAAA;AAC1E,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Action } from '../reconciler.js';
2
+ export declare function printPlan(actions: Action[]): void;
3
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAsB1C"}
@@ -0,0 +1,20 @@
1
+ import chalk from 'chalk';
2
+ export function printPlan(actions) {
3
+ if (actions.length === 0) {
4
+ console.log(chalk.green('No changes needed. Everything is up to date.'));
5
+ return;
6
+ }
7
+ console.log(chalk.bold(`\nPlan: ${actions.length} action(s)\n`));
8
+ for (const action of actions) {
9
+ const icon = action.type === 'create' ? chalk.green('+') : chalk.yellow('~');
10
+ const verb = action.type === 'create' ? chalk.green('create') : chalk.yellow('update');
11
+ const name = action.resource.name;
12
+ const kind = action.resource.kind;
13
+ console.log(` ${icon} ${verb} ${kind}: ${chalk.bold(name)}`);
14
+ if (action.existingId) {
15
+ console.log(chalk.gray(` (id: ${action.existingId})`));
16
+ }
17
+ }
18
+ console.log();
19
+ }
20
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,MAAM,UAAU,SAAS,CAAC,OAAiB;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC,CAAA;QACxE,OAAM;IACR,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,MAAM,cAAc,CAAC,CAAC,CAAA;IAEhE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACtF,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QAEjC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAE7D,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { MetabaseClient } from '../client.js';
2
+ import type { StateFile } from '../types.js';
3
+ export declare function pull(client: MetabaseClient, outputDir?: string, importBase?: string): Promise<StateFile>;
4
+ //# sourceMappingURL=pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,KAAK,EAIV,SAAS,EACV,MAAM,aAAa,CAAA;AAoBpB,wBAAsB,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAqH9G"}