create-dql-app 0.11.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.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # create-dql-app
2
+
3
+ The fastest way to start a DQL project.
4
+
5
+ ```bash
6
+ npx create-dql-app my-analytics
7
+ cd my-analytics
8
+ npx @duckcodeailabs/dql-cli notebook
9
+ ```
10
+
11
+ Opens a running notebook at <http://localhost:5173> in under 5 minutes on a
12
+ clean machine. No global install required.
13
+
14
+ ## Templates
15
+
16
+ | Template | What you get |
17
+ | --- | --- |
18
+ | `jaffle-shop` *(default)* | DuckDB + the Jaffle Shop dataset + a sample notebook, certified block, and dashboard |
19
+ | `empty` | Just a `cdql.yaml` and project layout — bring your own warehouse |
20
+
21
+ ```bash
22
+ npx create-dql-app finance-reports --template empty
23
+ ```
24
+
25
+ ## Flags
26
+
27
+ | Flag | Default | Meaning |
28
+ | --- | --- | --- |
29
+ | `--template <name>` | `jaffle-shop` | Starter template |
30
+ | `--no-install` | off | Skip downloading the Jaffle Shop seed data |
31
+
32
+ ## Docs
33
+
34
+ - [Quickstart](https://docs.duckcode.ai/get-started/quickstart/)
35
+ - [Concepts](https://docs.duckcode.ai/get-started/concepts/)
36
+ - [Connect your own warehouse](https://docs.duckcode.ai/guides/connect-warehouse/)
37
+
38
+ ## License
39
+
40
+ MIT
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ // create-dql-app — self-contained scaffolder.
3
+ //
4
+ // Contract (from the v1.0 "demo gate"): on a clean machine, `npx
5
+ // create-dql-app` followed by `cd … && npx @duckcodeailabs/dql-cli notebook`
6
+ // produces a running notebook in under 5 minutes.
7
+ //
8
+ // Design: this package writes template files *itself* rather than
9
+ // delegating to `dql init`, so it stays self-contained and installable
10
+ // with zero-peer-dep friction. Templates live under ../templates/ and are
11
+ // copied verbatim.
12
+
13
+ import { spawnSync } from 'node:child_process';
14
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
15
+ import { dirname, join, resolve, basename } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const VERSION = '0.11.0';
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const TEMPLATES_DIR = resolve(__dirname, '..', 'templates');
21
+
22
+ // Tiny ANSI helpers — no dep on chalk/kleur so the bin runs before
23
+ // `npm install` finishes on slow machines.
24
+ const c = {
25
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
26
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
27
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
28
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
29
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
30
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
31
+ };
32
+
33
+ function usage() {
34
+ console.log(`create-dql-app ${VERSION}
35
+
36
+ Usage:
37
+ npx create-dql-app <project-dir> [options]
38
+
39
+ Options:
40
+ --template <name> Starter template: jaffle-shop (default), empty
41
+ --help, -h Show this help
42
+ --version, -v Show version
43
+
44
+ Examples:
45
+ npx create-dql-app my-analytics
46
+ npx create-dql-app finance-reports --template empty
47
+ `);
48
+ }
49
+
50
+ function parseArgs(argv) {
51
+ const args = { dir: null, template: 'jaffle-shop' };
52
+ for (let i = 0; i < argv.length; i++) {
53
+ const a = argv[i];
54
+ if (a === '--help' || a === '-h') { usage(); process.exit(0); }
55
+ if (a === '--version' || a === '-v') { console.log(VERSION); process.exit(0); }
56
+ if (a === '--template') { args.template = argv[++i]; continue; }
57
+ if (a.startsWith('--')) { console.error(`Unknown flag: ${a}`); process.exit(2); }
58
+ if (!args.dir) args.dir = a;
59
+ }
60
+ return args;
61
+ }
62
+
63
+ function isEmptyDir(dir) {
64
+ try { return readdirSync(dir).length === 0; } catch { return true; }
65
+ }
66
+
67
+ function copyDir(src, dst) {
68
+ mkdirSync(dst, { recursive: true });
69
+ for (const entry of readdirSync(src)) {
70
+ const s = join(src, entry);
71
+ const d = join(dst, entry);
72
+ if (statSync(s).isDirectory()) copyDir(s, d);
73
+ else writeFileSync(d, readFileSync(s));
74
+ }
75
+ }
76
+
77
+ function substitute(file, vars) {
78
+ const raw = readFileSync(file, 'utf-8');
79
+ const out = raw.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
80
+ if (out !== raw) writeFileSync(file, out, 'utf-8');
81
+ }
82
+
83
+ function walk(dir) {
84
+ const out = [];
85
+ for (const entry of readdirSync(dir)) {
86
+ const p = join(dir, entry);
87
+ if (statSync(p).isDirectory()) out.push(...walk(p));
88
+ else out.push(p);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ function detectDbtSibling(target) {
94
+ // Walk up 2 levels looking for a dbt_project.yml — common layout is
95
+ // myproject/dql + myproject/dbt, or myproject with dbt + dql as siblings.
96
+ for (const rel of ['..', '../..', '../dbt', '../../dbt']) {
97
+ const probe = resolve(target, rel, 'dbt_project.yml');
98
+ if (existsSync(probe)) return resolve(target, rel);
99
+ }
100
+ return null;
101
+ }
102
+
103
+ async function main() {
104
+ const args = parseArgs(process.argv.slice(2));
105
+ if (!args.dir) {
106
+ console.error('Error: project directory is required.\n');
107
+ usage();
108
+ process.exit(2);
109
+ }
110
+
111
+ const target = resolve(process.cwd(), args.dir);
112
+ const projectName = basename(target);
113
+
114
+ if (existsSync(target) && !isEmptyDir(target)) {
115
+ console.error(c.red(`✗ Target directory "${args.dir}" exists and is not empty.`));
116
+ process.exit(1);
117
+ }
118
+
119
+ const tplDir = join(TEMPLATES_DIR, args.template);
120
+ if (!existsSync(tplDir)) {
121
+ console.error(c.red(`✗ Unknown template: ${args.template}`));
122
+ console.error(` Available: ${readdirSync(TEMPLATES_DIR).join(', ')}`);
123
+ process.exit(1);
124
+ }
125
+
126
+ console.log(c.cyan(`\n⌁ create-dql-app ${VERSION}`));
127
+ console.log(` scaffolding ${c.bold(projectName)} (template: ${c.bold(args.template)})\n`);
128
+
129
+ copyDir(tplDir, target);
130
+
131
+ const dbtSibling = detectDbtSibling(target);
132
+ const vars = {
133
+ PROJECT_NAME: projectName,
134
+ YEAR: String(new Date().getFullYear()),
135
+ DBT_PROJECT_DIR: dbtSibling ? resolve(dbtSibling) : '../my-dbt-project',
136
+ DBT_DETECTED: dbtSibling ? 'true' : 'false',
137
+ };
138
+ for (const f of walk(target)) substitute(f, vars);
139
+
140
+ if (dbtSibling) {
141
+ console.log(c.dim(` detected sibling dbt project at ${dbtSibling}`));
142
+ console.log(c.dim(` wired into cdql.yaml — run 'dql sync dbt' to import\n`));
143
+ }
144
+
145
+ // Best-effort: try to run `git init` so users get a clean first commit.
146
+ const gitResult = spawnSync('git', ['init', '-q'], { cwd: target });
147
+ if (gitResult.status === 0) console.log(c.dim(' initialized git repo'));
148
+
149
+ console.log(`
150
+ ${c.green('✓ Ready.')} Next steps:
151
+
152
+ ${c.bold(`cd ${args.dir}`)}
153
+ ${c.bold('npx @duckcodeailabs/dql-cli notebook')}
154
+
155
+ Your notebook will open at ${c.cyan('http://localhost:5173')}.
156
+
157
+ Docs: ${c.cyan('https://docs.duckcode.ai')}
158
+ Issues: ${c.cyan('https://github.com/duckcode-ai/dql/issues')}
159
+ `);
160
+ }
161
+
162
+ main().catch((e) => {
163
+ console.error(c.red(`\n✗ ${e.message}`));
164
+ process.exit(1);
165
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "create-dql-app",
3
+ "version": "0.11.0",
4
+ "description": "Scaffold a new DQL project. Run with: npx create-dql-app <name>",
5
+ "license": "MIT",
6
+ "author": "DuckCode AI Labs",
7
+ "homepage": "https://docs.duckcode.ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/duckcode-ai/dql.git",
11
+ "directory": "packages/create-dql-app"
12
+ },
13
+ "keywords": ["dql", "dbt", "analytics", "notebook", "semantic-layer", "lineage"],
14
+ "bin": {
15
+ "create-dql-app": "./bin/create-dql-app.mjs"
16
+ },
17
+ "files": [
18
+ "bin",
19
+ "templates",
20
+ "README.md"
21
+ ],
22
+ "type": "module",
23
+ "scripts": {
24
+ "test": "node test/smoke.mjs",
25
+ "build": "echo 'no build step — pure JS'"
26
+ },
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
@@ -0,0 +1,18 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ An empty DQL project, scaffolded by `create-dql-app --template empty`.
4
+
5
+ ## Connect your warehouse
6
+
7
+ Edit `cdql.yaml` — DQL ships 15 drivers out of the box:
8
+ [docs.duckcode.ai/reference/connectors](https://docs.duckcode.ai/reference/connectors/).
9
+
10
+ ```bash
11
+ npx @duckcodeailabs/dql-cli test-connection
12
+ ```
13
+
14
+ ## Start the notebook
15
+
16
+ ```bash
17
+ npx @duckcodeailabs/dql-cli notebook
18
+ ```
@@ -0,0 +1,13 @@
1
+ project:
2
+ name: {{PROJECT_NAME}}
3
+ version: 1
4
+
5
+ connections:
6
+ # Swap this for your warehouse — postgres, snowflake, bigquery, etc.
7
+ # See https://docs.duckcode.ai/reference/connectors/
8
+ default:
9
+ driver: duckdb
10
+ path: ./warehouse.duckdb
11
+
12
+ governance:
13
+ required_fields: [domain, owner, description]
@@ -0,0 +1,17 @@
1
+ // dql-format: 1
2
+
3
+ ---
4
+ type: markdown
5
+ ---
6
+
7
+ # {{PROJECT_NAME}}
8
+
9
+ Welcome to DQL. Start by connecting a warehouse (`cdql.yaml`), then replace
10
+ this cell with your first query.
11
+
12
+ ---
13
+ type: sql
14
+ name: first_query
15
+ ---
16
+
17
+ select 1 as hello;
@@ -0,0 +1,34 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A DQL analytics project, scaffolded by `create-dql-app`.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ npx @duckcodeailabs/dql-cli notebook
9
+ ```
10
+
11
+ Opens the notebook at <http://localhost:5173>.
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ cdql.yaml project config (connections, dbt, governance)
17
+ notebooks/ analytics notebooks (.dql)
18
+ blocks/ certified reusable blocks, grouped by domain
19
+ semantic-layer/ metrics + dimensions authored locally
20
+ dashboards/ compiled static HTML dashboards (git-ignored)
21
+ ```
22
+
23
+ ## Next steps
24
+
25
+ 1. **Run the welcome notebook** — `notebooks/welcome.dql`
26
+ 2. **Connect your warehouse** — [docs.duckcode.ai/guides/connect-warehouse](https://docs.duckcode.ai/guides/connect-warehouse/)
27
+ 3. **Import your dbt project** — [docs.duckcode.ai/guides/import-dbt](https://docs.duckcode.ai/guides/import-dbt/)
28
+ 4. **Author a certified block** — [docs.duckcode.ai/guides/authoring-blocks](https://docs.duckcode.ai/guides/authoring-blocks/)
29
+
30
+ ## Learn
31
+
32
+ - [Quickstart](https://docs.duckcode.ai/get-started/quickstart/)
33
+ - [Concepts](https://docs.duckcode.ai/get-started/concepts/)
34
+ - [CLI reference](https://docs.duckcode.ai/reference/cli/)
@@ -0,0 +1,23 @@
1
+ // dql-format: 1
2
+
3
+ block revenue_by_segment {
4
+ domain: "finance"
5
+ owner: "analytics@{{PROJECT_NAME}}.local"
6
+ tags: ["revenue", "sample"]
7
+ description: "Gross revenue grouped by customer segment."
8
+
9
+ query: |
10
+ select
11
+ c.segment,
12
+ sum(o.order_total) as revenue,
13
+ count(distinct o.customer_id) as customers
14
+ from orders o
15
+ join customers c on c.customer_id = o.customer_id
16
+ group by 1
17
+ order by revenue desc
18
+
19
+ visualization: bar(x: "segment", y: "revenue", title: "Revenue by segment")
20
+
21
+ tests:
22
+ - row_count > 0
23
+ }
@@ -0,0 +1,17 @@
1
+ project:
2
+ name: {{PROJECT_NAME}}
3
+ version: 1
4
+
5
+ connections:
6
+ default:
7
+ driver: duckdb
8
+ path: ./warehouse.duckdb
9
+
10
+ # Remove this block if you aren't using dbt. DQL reads target/manifest.json
11
+ # directly — see https://docs.duckcode.ai/guides/import-dbt/
12
+ dbt:
13
+ projectDir: {{DBT_PROJECT_DIR}}
14
+ manifestPath: target/manifest.json
15
+
16
+ governance:
17
+ required_fields: [domain, owner, description]
@@ -0,0 +1,22 @@
1
+ // dql-format: 1
2
+
3
+ dashboard: {
4
+ title: "{{PROJECT_NAME}} — Overview"
5
+ description: "Revenue overview generated by create-dql-app."
6
+ layout: "grid"
7
+ }
8
+
9
+ ---
10
+ type: markdown
11
+ ---
12
+
13
+ # Overview — {{YEAR}}
14
+
15
+ Compiled with `dql compile dashboards/overview.dql --out build/`. Host the
16
+ resulting HTML anywhere.
17
+
18
+ ---
19
+ type: dql
20
+ ---
21
+
22
+ @block("revenue_by_segment")
@@ -0,0 +1,44 @@
1
+ // dql-format: 1
2
+
3
+ // Welcome to DQL. This notebook runs against a DuckDB-backed Jaffle Shop
4
+ // dataset — the same demo data dbt ships. Press ⌘↵ on each cell.
5
+
6
+ ---
7
+ type: markdown
8
+ ---
9
+
10
+ # {{PROJECT_NAME}}
11
+
12
+ A tour of DQL in four cells. Run them top-to-bottom.
13
+
14
+ ---
15
+ type: sql
16
+ name: orders_overview
17
+ ---
18
+
19
+ select
20
+ count(*) as total_orders,
21
+ count(distinct customer_id) as unique_customers,
22
+ sum(order_total) as lifetime_revenue
23
+ from orders;
24
+
25
+ ---
26
+ type: sql
27
+ name: daily_revenue
28
+ ---
29
+
30
+ select
31
+ order_date,
32
+ sum(order_total) as revenue
33
+ from orders
34
+ group by 1
35
+ order by 1;
36
+
37
+ ---
38
+ type: dql
39
+ name: revenue_by_segment
40
+ ---
41
+
42
+ // Reference a certified block. The block lives in blocks/finance/ and can
43
+ // be reused across notebooks, dashboards, and other blocks.
44
+ @block("revenue_by_segment")
@@ -0,0 +1,14 @@
1
+ dimensions:
2
+ - name: segment
3
+ label: Customer segment
4
+ type: string
5
+ sql: segment
6
+ table: customers
7
+ domain: finance
8
+
9
+ - name: region
10
+ label: Region
11
+ type: string
12
+ sql: region
13
+ table: customers
14
+ domain: finance
@@ -0,0 +1,9 @@
1
+ metrics:
2
+ - name: revenue
3
+ label: Revenue
4
+ description: Gross revenue across all orders
5
+ type: sum
6
+ sql: order_total
7
+ table: orders
8
+ domain: finance
9
+ tags: [revenue, core]