ddlforge 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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/dist/bin/ddlforge.d.ts +6 -0
  4. package/dist/bin/ddlforge.d.ts.map +1 -0
  5. package/dist/bin/ddlforge.js +14 -0
  6. package/dist/bin/ddlforge.js.map +1 -0
  7. package/dist/src/cli.d.ts +21 -0
  8. package/dist/src/cli.d.ts.map +1 -0
  9. package/dist/src/cli.js +262 -0
  10. package/dist/src/cli.js.map +1 -0
  11. package/dist/src/engine/analyzer.d.ts +30 -0
  12. package/dist/src/engine/analyzer.d.ts.map +1 -0
  13. package/dist/src/engine/analyzer.js +76 -0
  14. package/dist/src/engine/analyzer.js.map +1 -0
  15. package/dist/src/engine/locks.d.ts +26 -0
  16. package/dist/src/engine/locks.d.ts.map +1 -0
  17. package/dist/src/engine/locks.js +136 -0
  18. package/dist/src/engine/locks.js.map +1 -0
  19. package/dist/src/index.d.ts +12 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +17 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/lexer/sqlTokenizer.d.ts +40 -0
  24. package/dist/src/lexer/sqlTokenizer.d.ts.map +1 -0
  25. package/dist/src/lexer/sqlTokenizer.js +494 -0
  26. package/dist/src/lexer/sqlTokenizer.js.map +1 -0
  27. package/dist/src/lexer/tokens.d.ts +35 -0
  28. package/dist/src/lexer/tokens.d.ts.map +1 -0
  29. package/dist/src/lexer/tokens.js +81 -0
  30. package/dist/src/lexer/tokens.js.map +1 -0
  31. package/dist/src/reporters/index.d.ts +7 -0
  32. package/dist/src/reporters/index.d.ts.map +1 -0
  33. package/dist/src/reporters/index.js +7 -0
  34. package/dist/src/reporters/index.js.map +1 -0
  35. package/dist/src/reporters/json.d.ts +20 -0
  36. package/dist/src/reporters/json.d.ts.map +1 -0
  37. package/dist/src/reporters/json.js +35 -0
  38. package/dist/src/reporters/json.js.map +1 -0
  39. package/dist/src/reporters/markdown.d.ts +6 -0
  40. package/dist/src/reporters/markdown.d.ts.map +1 -0
  41. package/dist/src/reporters/markdown.js +70 -0
  42. package/dist/src/reporters/markdown.js.map +1 -0
  43. package/dist/src/reporters/terminal.d.ts +10 -0
  44. package/dist/src/reporters/terminal.d.ts.map +1 -0
  45. package/dist/src/reporters/terminal.js +109 -0
  46. package/dist/src/reporters/terminal.js.map +1 -0
  47. package/dist/src/rules/addColumnNotNull.d.ts +9 -0
  48. package/dist/src/rules/addColumnNotNull.d.ts.map +1 -0
  49. package/dist/src/rules/addColumnNotNull.js +131 -0
  50. package/dist/src/rules/addColumnNotNull.js.map +1 -0
  51. package/dist/src/rules/foreignKeyNotValid.d.ts +9 -0
  52. package/dist/src/rules/foreignKeyNotValid.d.ts.map +1 -0
  53. package/dist/src/rules/foreignKeyNotValid.js +119 -0
  54. package/dist/src/rules/foreignKeyNotValid.js.map +1 -0
  55. package/dist/src/rules/index.d.ts +14 -0
  56. package/dist/src/rules/index.d.ts.map +1 -0
  57. package/dist/src/rules/index.js +28 -0
  58. package/dist/src/rules/index.js.map +1 -0
  59. package/dist/src/rules/indexConcurrently.d.ts +9 -0
  60. package/dist/src/rules/indexConcurrently.d.ts.map +1 -0
  61. package/dist/src/rules/indexConcurrently.js +92 -0
  62. package/dist/src/rules/indexConcurrently.js.map +1 -0
  63. package/dist/src/rules/prismaRenameDropAdd.d.ts +11 -0
  64. package/dist/src/rules/prismaRenameDropAdd.d.ts.map +1 -0
  65. package/dist/src/rules/prismaRenameDropAdd.js +144 -0
  66. package/dist/src/rules/prismaRenameDropAdd.js.map +1 -0
  67. package/dist/src/rules/setNotNullFullScan.d.ts +9 -0
  68. package/dist/src/rules/setNotNullFullScan.d.ts.map +1 -0
  69. package/dist/src/rules/setNotNullFullScan.js +93 -0
  70. package/dist/src/rules/setNotNullFullScan.js.map +1 -0
  71. package/dist/src/rules/transactionTrap.d.ts +9 -0
  72. package/dist/src/rules/transactionTrap.d.ts.map +1 -0
  73. package/dist/src/rules/transactionTrap.js +104 -0
  74. package/dist/src/rules/transactionTrap.js.map +1 -0
  75. package/dist/src/rules/types.d.ts +36 -0
  76. package/dist/src/rules/types.d.ts.map +1 -0
  77. package/dist/src/rules/types.js +5 -0
  78. package/dist/src/rules/types.js.map +1 -0
  79. package/dist/src/rules/unbatchedBackfill.d.ts +9 -0
  80. package/dist/src/rules/unbatchedBackfill.d.ts.map +1 -0
  81. package/dist/src/rules/unbatchedBackfill.js +58 -0
  82. package/dist/src/rules/unbatchedBackfill.js.map +1 -0
  83. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ddlforge contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # 🛡️ ddlforge
2
+
3
+ > **Ultra-fast, zero-runtime-dependency PostgreSQL migration lock linter & data-loss prevention engine.**
4
+ > Built for Node.js 20+ and TypeScript. Designed for high-traffic Prisma, Drizzle, and raw SQL backends.
5
+
6
+ [![Node.js](https://img.shields.io/badge/Node.js-20%2B-green.svg)](https://nodejs.org)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5%2B-blue.svg)](https://www.typescriptlang.org)
8
+ [![Zero Dependencies](https://img.shields.io/badge/Runtime%20Dependencies-0-success.svg)](package.json)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
+
11
+ ---
12
+
13
+ ## ⚡ Why ddlforge?
14
+
15
+ In PostgreSQL, DDL commands take **table-level locks**. Even if a migration takes only milliseconds to run, if it requests an `ACCESS EXCLUSIVE` or `SHARE` lock while a long-running `SELECT` or transaction is open:
16
+ 1. The migration stalls waiting for the lock.
17
+ 2. **Every single query behind it is queued up**, including basic reads (`SELECT`).
18
+ 3. Connection pools instantly exhaust within seconds, crashing services and taking down production.
19
+
20
+ Furthermore, ORMs like **Prisma Migrate** cannot detect column renames automatically: when you rename a field in `schema.prisma`, Prisma generates a `DROP COLUMN` followed by `ADD COLUMN`, permanently deleting all your production data!
21
+
22
+ `ddlforge` catches these architectural pitfalls in your local pre-commit hooks and CI pipelines before code ever merges to main.
23
+
24
+ ### Design Principles
25
+ - **Zero Runtime Dependencies**: Pure native Node.js standard library (`node:fs`, `node:path`, `node:process`, `node:child_process`). Air-gap safe.
26
+ - **Sub-Second Performance**: Single-pass lexical scanner and linear rule evaluators. Scans 100+ migration files in **<10ms**.
27
+ - **Deterministic Exit Codes**: Exits `0` when clean or advisory-only; exits `1` for any blocking violation (table lock or destructive data loss).
28
+
29
+ ---
30
+
31
+ ## 📊 PostgreSQL Lock Hierarchy Reference
32
+
33
+ | Lock Mode | Acquired By | Conflicted Locks | Blocks Reads? | Blocks Writes? | Risk Level |
34
+ |:---|:---|:---|:---:|:---:|:---:|
35
+ | `ACCESS SHARE` | `SELECT` | `ACCESS EXCLUSIVE` | ❌ | ❌ | Low |
36
+ | `ROW SHARE` | `SELECT FOR UPDATE / FOR SHARE` | `EXCLUSIVE`, `ACCESS EXCLUSIVE` | ❌ | ❌ | Low |
37
+ | `ROW EXCLUSIVE` | `UPDATE`, `DELETE`, `INSERT` | `SHARE`, `SHARE ROW EXCLUSIVE`, `EXCLUSIVE`, `ACCESS EXCLUSIVE` | ❌ | ❌ | Medium |
38
+ | `SHARE UPDATE EXCLUSIVE` | `CREATE INDEX CONCURRENTLY`, `VACUUM`, `ANALYZE`, `VALIDATE CONSTRAINT` | `SHARE UPDATE EXCLUSIVE`, `SHARE`, `SHARE ROW EXCLUSIVE`, `EXCLUSIVE`, `ACCESS EXCLUSIVE` | ❌ | ❌ | **Safe for Concurrency** |
39
+ | `SHARE` | `CREATE INDEX` (non-concurrent) | `ROW EXCLUSIVE`, `SHARE ROW EXCLUSIVE`, `EXCLUSIVE`, `ACCESS EXCLUSIVE` | ❌ | **YES** | 🛑 **BLOCKER** |
40
+ | `SHARE ROW EXCLUSIVE` | `ADD CONSTRAINT FOREIGN KEY` (without `NOT VALID`) | `ROW EXCLUSIVE`, `SHARE UPDATE EXCLUSIVE`, `SHARE`, `SHARE ROW EXCLUSIVE`, `EXCLUSIVE`, `ACCESS EXCLUSIVE` | ❌ | **YES** | 🛑 **BLOCKER** |
41
+ | `EXCLUSIVE` | `REFRESH MATERIALIZED VIEW CONCURRENTLY` | `ROW SHARE`, `ROW EXCLUSIVE`, `SHARE UPDATE EXCLUSIVE`, `SHARE`, `SHARE ROW EXCLUSIVE`, `EXCLUSIVE`, `ACCESS EXCLUSIVE` | Row Share | **YES** | 🛑 **BLOCKER** |
42
+ | `ACCESS EXCLUSIVE` | `ALTER TABLE`, `DROP TABLE`, `TRUNCATE`, `ALTER COLUMN SET NOT NULL` | **ALL LOCK MODES** | **YES (SELECT)** | **YES** | 🚨 **CRITICAL DOWNTIME** |
43
+
44
+ ---
45
+
46
+ ## 룰 The 7 Core Rules & Zero-Downtime Recipes
47
+
48
+ ### 1. `require-concurrent-index` (CREATE INDEX without CONCURRENTLY)
49
+ - **Severity**: `BLOCKER`
50
+ - **Lock**: `SHARE` lock (blocks all `INSERT`, `UPDATE`, `DELETE` operations on table).
51
+ - **Dangerous**:
52
+ ```sql
53
+ CREATE INDEX idx_users_email ON users(email);
54
+ ```
55
+ - **Zero-Downtime Fix**:
56
+ ```sql
57
+ CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
58
+ ```
59
+
60
+ ---
61
+
62
+ ### 2. `concurrent-index-in-transaction` (CONCURRENTLY inside Transaction Trap)
63
+ - **Severity**: `BLOCKER`
64
+ - **Engine Behavior**: PostgreSQL aborts with `ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block`.
65
+ - **Dangerous**:
66
+ ```sql
67
+ BEGIN;
68
+ CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
69
+ COMMIT;
70
+ ```
71
+ - **Prisma Caveat**: Prisma Migrate wraps all migrations in an implicit transaction by default. Running `CREATE INDEX CONCURRENTLY` in Prisma fails unless you add the `-- prisma:no-transaction` directive.
72
+ - **Zero-Downtime Fix**:
73
+ ```sql
74
+ -- prisma:no-transaction
75
+ CREATE INDEX CONCURRENTLY "users_email_idx" ON "users"("email");
76
+ ```
77
+
78
+ ---
79
+
80
+ ### 3. `add-column-not-null-without-default` (ADD COLUMN NOT NULL without DEFAULT)
81
+ - **Severity**: `BLOCKER`
82
+ - **Lock**: `ACCESS EXCLUSIVE` lock.
83
+ - **Dangerous**:
84
+ ```sql
85
+ ALTER TABLE users ADD COLUMN role VARCHAR(50) NOT NULL;
86
+ ```
87
+ - **Why**: On non-empty tables, PostgreSQL immediately aborts with `ERROR: column "role" contains null values` while holding an `ACCESS EXCLUSIVE` table lock.
88
+ - **Zero-Downtime Fix (PG 11+)**:
89
+ Provide a static non-volatile default (metadata-only instant operation):
90
+ ```sql
91
+ ALTER TABLE users ADD COLUMN role VARCHAR(50) DEFAULT 'user' NOT NULL;
92
+ ```
93
+ Or for dynamic/custom defaults, use the multi-step backfill pattern:
94
+ ```sql
95
+ -- Step 1: Add column nullable
96
+ ALTER TABLE users ADD COLUMN role VARCHAR(50);
97
+ -- Step 2: Batched backfill
98
+ -- Step 3: Add CHECK constraint NOT VALID & validate
99
+ ALTER TABLE users ADD CONSTRAINT chk_role_not_null CHECK (role IS NOT NULL) NOT VALID;
100
+ ALTER TABLE users VALIDATE CONSTRAINT chk_role_not_null;
101
+ ```
102
+
103
+ ---
104
+
105
+ ### 4. `foreign-key-missing-not-valid` (ADD CONSTRAINT FOREIGN KEY without NOT VALID)
106
+ - **Severity**: `BLOCKER`
107
+ - **Lock**: `SHARE ROW EXCLUSIVE` on referencing table + `SHARE` on referenced table.
108
+ - **Dangerous**:
109
+ ```sql
110
+ ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);
111
+ ```
112
+ - **Zero-Downtime Fix**:
113
+ Split into two non-blocking steps:
114
+ ```sql
115
+ -- Step 1: Add constraint without scanning rows (instant metadata operation)
116
+ ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID;
117
+
118
+ -- Step 2: Validate concurrently (takes only SHARE UPDATE EXCLUSIVE, reads & writes proceed!)
119
+ ALTER TABLE orders VALIDATE CONSTRAINT fk_user;
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 5. `prisma-silent-rename-data-loss` (Prisma Silent Field Rename)
125
+ - **Severity**: `BLOCKER` (Data-Loss Prevention)
126
+ - **Lock**: `ACCESS EXCLUSIVE` + Table Data Loss.
127
+ - **Dangerous**:
128
+ ```sql
129
+ ALTER TABLE "users" DROP COLUMN "old_name";
130
+ ALTER TABLE "users" ADD COLUMN "new_name" TEXT NOT NULL;
131
+ ```
132
+ - **Why**: Prisma Migrate cannot infer renames from `schema.prisma`. It generates a column drop followed by column creation, destroying all production data.
133
+ - **Zero-Downtime Fix**:
134
+ ```sql
135
+ ALTER TABLE "users" RENAME COLUMN "old_name" TO "new_name";
136
+ ```
137
+ Or use Prisma's `@map("old_name")` in your schema to avoid database column renames.
138
+
139
+ ---
140
+
141
+ ### 6. `set-not-null-full-scan` (ALTER COLUMN SET NOT NULL)
142
+ - **Severity**: `WARNING` (or `BLOCKER` on PG < 12)
143
+ - **Lock**: `ACCESS EXCLUSIVE` lock while scanning entire table for nulls.
144
+ - **Dangerous**:
145
+ ```sql
146
+ ALTER TABLE users ALTER COLUMN email SET NOT NULL;
147
+ ```
148
+ - **Zero-Downtime Fix (PG 12+)**:
149
+ ```sql
150
+ -- 1. Add CHECK constraint NOT VALID (instant)
151
+ ALTER TABLE users ADD CONSTRAINT chk_users_email_not_null CHECK (email IS NOT NULL) NOT VALID;
152
+ -- 2. Validate constraint without blocking writes
153
+ ALTER TABLE users VALIDATE CONSTRAINT chk_users_email_not_null;
154
+ -- 3. Set NOT NULL (Postgres 12+ skips table scan because validated CHECK constraint exists!)
155
+ ALTER TABLE users ALTER COLUMN email SET NOT NULL;
156
+ ```
157
+
158
+ ---
159
+
160
+ ### 7. `unbatched-dml` (Unbatched DML in Migration)
161
+ - **Severity**: `WARNING` (or `BLOCKER` without WHERE)
162
+ - **Lock**: `ROW EXCLUSIVE` lock on touched rows, massive transaction WAL bloat.
163
+ - **Dangerous**:
164
+ ```sql
165
+ UPDATE users SET active = true;
166
+ DELETE FROM sessions;
167
+ ```
168
+ - **Zero-Downtime Fix**:
169
+ Perform large backfills out-of-band using batched background workers. If updating a small static/seed table, exempt with an inline comment:
170
+ ```sql
171
+ -- ddlforge-ignore unbatched-dml
172
+ UPDATE subscription_tiers SET active = true WHERE code = 'PRO';
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 🚀 CLI Installation & Usage
178
+
179
+ ### Running via npx (Zero Install)
180
+ ```bash
181
+ npx ddlforge ./prisma/migrations
182
+ ```
183
+
184
+ ### Local Installation
185
+ ```bash
186
+ npm install --save-dev ddlforge
187
+ ```
188
+
189
+ ### CLI Flags
190
+ ```text
191
+ ddlforge [paths...] [flags]
192
+
193
+ ARGUMENTS:
194
+ paths Target migration files or directories (e.g. ./prisma/migrations, ./drizzle)
195
+
196
+ FLAGS:
197
+ --pg <version> Target PostgreSQL version (default: 16)
198
+ --format <type> Output format: terminal | json | markdown (default: terminal)
199
+ --quiet, -q Suppress advisories/warnings and emit blockers only
200
+ --changed-only Use git diff to lint only staged or branch-modified migration files
201
+ --version, -v Print ddlforge version and exit
202
+ --help, -h Print this help message and exit
203
+ ```
204
+
205
+ ### Examples
206
+ ```bash
207
+ # Lint all Prisma migrations
208
+ npx ddlforge ./prisma/migrations
209
+
210
+ # Lint only modified migration files in current git branch / PR
211
+ npx ddlforge --changed-only
212
+
213
+ # Check Drizzle migrations targeting Postgres 14 with JSON output
214
+ npx ddlforge ./drizzle --pg 14 --format json
215
+
216
+ # CI Mode: Emit blockers only with exit code 1
217
+ npx ddlforge ./migrations --quiet
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 🤖 GitHub Actions Workflow
223
+
224
+ Add this copy-paste workflow to `.github/workflows/migration-lint.yml`:
225
+
226
+ ```yaml
227
+ name: Migration Lock Linter
228
+
229
+ on:
230
+ pull_request:
231
+ paths:
232
+ - '**/migrations/**'
233
+ - '**/*.sql'
234
+
235
+ jobs:
236
+ lint-migrations:
237
+ runs-on: ubuntu-latest
238
+ steps:
239
+ - name: Checkout Code
240
+ uses: actions/checkout@v4
241
+ with:
242
+ fetch-depth: 0
243
+
244
+ - name: Setup Node.js
245
+ uses: actions/setup-node@v4
246
+ with:
247
+ node-version: 20
248
+
249
+ - name: Run ddlforge Lock Linter
250
+ run: |
251
+ npx ddlforge --changed-only --format terminal
252
+ ```
253
+
254
+ ### PR Comment Workflow (with Markdown Formatter)
255
+ ```yaml
256
+ - name: Generate Migration Safety Report
257
+ if: always()
258
+ run: |
259
+ npx ddlforge --changed-only --format markdown > report.md
260
+ cat report.md >> $GITHUB_STEP_SUMMARY
261
+ ```
262
+
263
+ ---
264
+
265
+ ## 💻 Programmatic TypeScript API
266
+
267
+ You can also use `ddlforge` programmatically within your custom deployment scripts:
268
+
269
+ ```typescript
270
+ import { analyzeSql, MigrationAnalyzer, formatTerminal } from 'ddlforge';
271
+
272
+ const sql = `
273
+ CREATE INDEX idx_users_email ON users(email);
274
+ `;
275
+
276
+ const result = analyzeSql(sql, {
277
+ filePath: 'migrations/001_create_index.sql',
278
+ pgVersion: 16,
279
+ });
280
+
281
+ if (result.hasBlockers) {
282
+ console.error(`Found ${result.blockersCount} blocking migration locks!`);
283
+ console.log(formatTerminal([result]));
284
+ process.exit(1);
285
+ }
286
+ ```
287
+
288
+ ---
289
+
290
+ ## ⚙️ Inline Directives
291
+
292
+ You can suppress specific rules per statement using inline comments:
293
+
294
+ ```sql
295
+ -- ddlforge-ignore require-concurrent-index
296
+ CREATE INDEX idx_temp ON temporary_cache(key);
297
+
298
+ -- ddlforge-ignore unbatched-dml
299
+ UPDATE plan_limits SET max_seats = 100 WHERE plan_id = 'enterprise';
300
+ ```
301
+
302
+ For Prisma migrations containing `CONCURRENTLY`:
303
+ ```sql
304
+ -- prisma:no-transaction
305
+ CREATE INDEX CONCURRENTLY "users_email_idx" ON "users"("email");
306
+ ```
307
+
308
+ ---
309
+
310
+ ## 🧪 Testing & Verification
311
+
312
+ Run the native test suite:
313
+ ```bash
314
+ npm test
315
+ ```
316
+
317
+ Performance benchmark:
318
+ ```text
319
+ ✔ analyzes 100 migrations in under 100 milliseconds (9.2ms)
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 📄 License
325
+
326
+ MIT © [ddlforge](https://github.com/ddlforge/ddlforge)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ddlforge - Postgres migration lock linter and data-loss prevention engine
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=ddlforge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddlforge.d.ts","sourceRoot":"","sources":["../../bin/ddlforge.ts"],"names":[],"mappings":";AAEA;;GAEG"}
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ddlforge - Postgres migration lock linter and data-loss prevention engine
4
+ */
5
+ import { runCli } from '../src/cli.js';
6
+ runCli(process.argv.slice(2))
7
+ .then(exitCode => {
8
+ process.exit(exitCode);
9
+ })
10
+ .catch(err => {
11
+ console.error('Fatal ddlforge error:', err);
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=ddlforge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddlforge.js","sourceRoot":"","sources":["../../bin/ddlforge.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KAC1B,IAAI,CAAC,QAAQ,CAAC,EAAE;IACf,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC,CAAC;KACD,KAAK,CAAC,GAAG,CAAC,EAAE;IACX,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ddlforge - Zero-dependency custom CLI argument parser and execution engine
3
+ */
4
+ export interface CliOptions {
5
+ targets: string[];
6
+ pgVersion: number;
7
+ format: 'terminal' | 'json' | 'markdown';
8
+ quiet: boolean;
9
+ changedOnly: boolean;
10
+ help: boolean;
11
+ version: boolean;
12
+ }
13
+ export declare const VERSION = "0.1.0";
14
+ export declare function parseArgs(args: string[]): CliOptions;
15
+ export declare function printHelp(): void;
16
+ /**
17
+ * Finds all .sql files from input paths, directories, or defaults.
18
+ */
19
+ export declare function discoverSqlFiles(targets: string[], changedOnly: boolean, cwd?: string): string[];
20
+ export declare function runCli(argv?: string[]): Promise<number>;
21
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAkFpD;AAED,wBAAgB,SAAS,IAAI,IAAI,CAwBhC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,EAAE,CAwB/G;AAgFD,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDpF"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * ddlforge - Zero-dependency custom CLI argument parser and execution engine
3
+ */
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import { execSync } from 'node:child_process';
7
+ import { MigrationAnalyzer } from './engine/analyzer.js';
8
+ import { formatTerminal } from './reporters/terminal.js';
9
+ import { formatJson } from './reporters/json.js';
10
+ import { formatMarkdown } from './reporters/markdown.js';
11
+ export const VERSION = '0.1.0';
12
+ export function parseArgs(args) {
13
+ const options = {
14
+ targets: [],
15
+ pgVersion: 16,
16
+ format: 'terminal',
17
+ quiet: false,
18
+ changedOnly: false,
19
+ help: false,
20
+ version: false,
21
+ };
22
+ let i = 0;
23
+ while (i < args.length) {
24
+ const arg = args[i];
25
+ if (arg === '--help' || arg === '-h') {
26
+ options.help = true;
27
+ i++;
28
+ continue;
29
+ }
30
+ if (arg === '--version' || arg === '-v') {
31
+ options.version = true;
32
+ i++;
33
+ continue;
34
+ }
35
+ if (arg === '--quiet' || arg === '-q') {
36
+ options.quiet = true;
37
+ i++;
38
+ continue;
39
+ }
40
+ if (arg === '--changed-only') {
41
+ options.changedOnly = true;
42
+ i++;
43
+ continue;
44
+ }
45
+ if (arg === '--pg') {
46
+ i++;
47
+ if (i < args.length) {
48
+ options.pgVersion = parseInt(args[i], 10) || 16;
49
+ }
50
+ i++;
51
+ continue;
52
+ }
53
+ if (arg.startsWith('--pg=')) {
54
+ options.pgVersion = parseInt(arg.slice(5), 10) || 16;
55
+ i++;
56
+ continue;
57
+ }
58
+ if (arg === '--format') {
59
+ i++;
60
+ if (i < args.length) {
61
+ const fmt = args[i].toLowerCase();
62
+ if (fmt === 'json' || fmt === 'markdown' || fmt === 'terminal') {
63
+ options.format = fmt;
64
+ }
65
+ }
66
+ i++;
67
+ continue;
68
+ }
69
+ if (arg.startsWith('--format=')) {
70
+ const fmt = arg.slice(9).toLowerCase();
71
+ if (fmt === 'json' || fmt === 'markdown' || fmt === 'terminal') {
72
+ options.format = fmt;
73
+ }
74
+ i++;
75
+ continue;
76
+ }
77
+ // Positional target
78
+ if (!arg.startsWith('-')) {
79
+ options.targets.push(arg);
80
+ }
81
+ i++;
82
+ }
83
+ return options;
84
+ }
85
+ export function printHelp() {
86
+ console.log(`
87
+ ddlforge v${VERSION} — Ultra-fast, zero-dependency Postgres migration lock linter
88
+
89
+ USAGE:
90
+ ddlforge [paths...] [flags]
91
+
92
+ ARGUMENTS:
93
+ paths Target migration files or directories (e.g. ./prisma/migrations, ./drizzle)
94
+
95
+ FLAGS:
96
+ --pg <version> Target PostgreSQL version (default: 16)
97
+ --format <type> Output format: terminal | json | markdown (default: terminal)
98
+ --quiet, -q Suppress advisories/warnings and emit blockers only
99
+ --changed-only Use git diff to lint only staged or branch-modified migration files
100
+ --version, -v Print ddlforge version and exit
101
+ --help, -h Print this help message and exit
102
+
103
+ EXAMPLES:
104
+ $ ddlforge ./prisma/migrations
105
+ $ ddlforge ./drizzle --pg 14 --format json
106
+ $ ddlforge --changed-only
107
+ $ ddlforge ./migrations/001_add_index.sql
108
+ `);
109
+ }
110
+ /**
111
+ * Finds all .sql files from input paths, directories, or defaults.
112
+ */
113
+ export function discoverSqlFiles(targets, changedOnly, cwd = process.cwd()) {
114
+ if (changedOnly) {
115
+ return getChangedSqlFiles(cwd);
116
+ }
117
+ const filesToScan = [];
118
+ const candidateTargets = targets.length > 0
119
+ ? targets
120
+ : findDefaultMigrationTargets(cwd);
121
+ for (const target of candidateTargets) {
122
+ const fullPath = path.isAbsolute(target) ? target : path.resolve(cwd, target);
123
+ if (!fs.existsSync(fullPath))
124
+ continue;
125
+ const stat = fs.statSync(fullPath);
126
+ if (stat.isDirectory()) {
127
+ collectSqlFromDir(fullPath, filesToScan);
128
+ }
129
+ else if (stat.isFile() && (fullPath.endsWith('.sql') || targets.length > 0)) {
130
+ filesToScan.push(fullPath);
131
+ }
132
+ }
133
+ return filesToScan;
134
+ }
135
+ function findDefaultMigrationTargets(cwd) {
136
+ const commonDirs = [
137
+ 'prisma/migrations',
138
+ 'drizzle',
139
+ 'migrations',
140
+ 'sql',
141
+ ];
142
+ const found = [];
143
+ for (const dir of commonDirs) {
144
+ const p = path.resolve(cwd, dir);
145
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
146
+ found.push(p);
147
+ }
148
+ }
149
+ return found.length > 0 ? found : [cwd];
150
+ }
151
+ function collectSqlFromDir(dir, accumulator) {
152
+ const IGNORED = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.turbo']);
153
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
154
+ for (const entry of entries) {
155
+ if (entry.isDirectory()) {
156
+ if (!IGNORED.has(entry.name)) {
157
+ collectSqlFromDir(path.join(dir, entry.name), accumulator);
158
+ }
159
+ }
160
+ else if (entry.isFile() && entry.name.endsWith('.sql')) {
161
+ accumulator.push(path.join(dir, entry.name));
162
+ }
163
+ }
164
+ }
165
+ function getChangedSqlFiles(cwd) {
166
+ const resultFiles = new Set();
167
+ // 1. Try git diff --name-only HEAD (staged and unstaged against HEAD)
168
+ try {
169
+ const diffHead = execSync('git diff --name-only HEAD', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
170
+ diffHead
171
+ .split(/\r?\n/)
172
+ .map((f) => f.trim())
173
+ .filter((f) => f.endsWith('.sql') && f.length > 0)
174
+ .forEach((f) => {
175
+ const full = path.resolve(cwd, f);
176
+ if (fs.existsSync(full))
177
+ resultFiles.add(full);
178
+ });
179
+ }
180
+ catch {
181
+ // If no commits yet / no HEAD commit
182
+ try {
183
+ const unstaged = execSync('git diff --name-only', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
184
+ unstaged
185
+ .split(/\r?\n/)
186
+ .map((f) => f.trim())
187
+ .filter((f) => f.endsWith('.sql') && f.length > 0)
188
+ .forEach((f) => {
189
+ const full = path.resolve(cwd, f);
190
+ if (fs.existsSync(full))
191
+ resultFiles.add(full);
192
+ });
193
+ }
194
+ catch { }
195
+ try {
196
+ const staged = execSync('git diff --name-only --cached', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
197
+ staged
198
+ .split(/\r?\n/)
199
+ .map((f) => f.trim())
200
+ .filter((f) => f.endsWith('.sql') && f.length > 0)
201
+ .forEach((f) => {
202
+ const full = path.resolve(cwd, f);
203
+ if (fs.existsSync(full))
204
+ resultFiles.add(full);
205
+ });
206
+ }
207
+ catch { }
208
+ }
209
+ return Array.from(resultFiles);
210
+ }
211
+ export async function runCli(argv = process.argv.slice(2)) {
212
+ const options = parseArgs(argv);
213
+ if (options.help) {
214
+ printHelp();
215
+ return 0;
216
+ }
217
+ if (options.version) {
218
+ console.log(`ddlforge v${VERSION}`);
219
+ return 0;
220
+ }
221
+ const files = discoverSqlFiles(options.targets, options.changedOnly);
222
+ if (files.length === 0) {
223
+ if (options.format === 'json') {
224
+ console.log(formatJson([]));
225
+ }
226
+ else if (options.format === 'markdown') {
227
+ console.log(formatMarkdown([]));
228
+ }
229
+ else {
230
+ console.log('No migration .sql files found to analyze.');
231
+ }
232
+ return 0;
233
+ }
234
+ const analyzer = new MigrationAnalyzer();
235
+ const results = [];
236
+ for (const file of files) {
237
+ try {
238
+ const content = fs.readFileSync(file, 'utf-8');
239
+ const res = analyzer.analyze(content, {
240
+ filePath: file,
241
+ pgVersion: options.pgVersion,
242
+ });
243
+ results.push(res);
244
+ }
245
+ catch (err) {
246
+ console.error(`Error reading ${file}: ${err?.message ?? String(err)}`);
247
+ }
248
+ }
249
+ // Format and output results
250
+ if (options.format === 'json') {
251
+ console.log(formatJson(results));
252
+ }
253
+ else if (options.format === 'markdown') {
254
+ console.log(formatMarkdown(results));
255
+ }
256
+ else {
257
+ console.log(formatTerminal(results, { quiet: options.quiet }));
258
+ }
259
+ const hasBlockers = results.some(r => r.hasBlockers);
260
+ return hasBlockers ? 1 : 0;
261
+ }
262
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAkB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAYzD,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,OAAO,GAAe;QAC1B,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,EAAE;QACb,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,KAAK;QACZ,WAAW,EAAE,KAAK;QAClB,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,KAAK;KACf,CAAC;IAEF,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;YACpB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YACvB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACtC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;YACrB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;YAC7B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAC3B,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACnB,CAAC,EAAE,CAAC;YACJ,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACpB,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YAClD,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YACrD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,CAAC,EAAE,CAAC;YACJ,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;gBAClC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;oBAC/D,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC;gBACvB,CAAC;YACH,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBAC/D,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC;YACvB,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,oBAAoB;QACpB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QAED,CAAC,EAAE,CAAC;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,CAAC,GAAG,CAAC;YACF,OAAO;;;;;;;;;;;;;;;;;;;;;CAqBlB,CAAC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAiB,EAAE,WAAoB,EAAE,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE;IACnG,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;QACzC,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAErC,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAS;QAEvC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;YAC9E,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,2BAA2B,CAAC,GAAW;IAC9C,MAAM,UAAU,GAAG;QACjB,mBAAmB;QACnB,SAAS;QACT,YAAY;QACZ,KAAK;KACN,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACrD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAW,EAAE,WAAqB;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtF,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW;IACrC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,sEAAsE;IACtE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,2BAA2B,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxH,QAAQ;aACL,KAAK,CAAC,OAAO,CAAC;aACd,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aAC5B,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aACzD,OAAO,CAAC,CAAC,CAAS,EAAE,EAAE;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAClC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACP,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACnH,QAAQ;iBACL,KAAK,CAAC,OAAO,CAAC;iBACd,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBAC5B,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;iBACzD,OAAO,CAAC,CAAC,CAAS,EAAE,EAAE;gBACrB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAClC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;oBAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;QACP,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,+BAA+B,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1H,MAAM;iBACH,KAAK,CAAC,OAAO,CAAC;iBACd,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBAC5B,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;iBACzD,OAAO,CAAC,CAAC,CAAS,EAAE,EAAE;gBACrB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAClC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;oBAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;QACP,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAI,GAAa,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAErE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,iBAAiB,EAAE,CAAC;IACzC,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE;gBACpC,QAAQ,EAAE,IAAI;gBACd,SAAS,EAAE,OAAO,CAAC,SAAS;aAC7B,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,iBAAiB,IAAI,KAAK,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC"}