@kennethsolomon/shipkit 1.0.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 +321 -0
- package/bin/shipkit.js +146 -0
- package/commands/sk/brainstorm.md +63 -0
- package/commands/sk/branch.md +35 -0
- package/commands/sk/config.md +96 -0
- package/commands/sk/execute-plan.md +85 -0
- package/commands/sk/features.md +238 -0
- package/commands/sk/finish-feature.md +154 -0
- package/commands/sk/help.md +103 -0
- package/commands/sk/hotfix.md +61 -0
- package/commands/sk/plan.md +30 -0
- package/commands/sk/release.md +72 -0
- package/commands/sk/security-check.md +188 -0
- package/commands/sk/set-profile.md +71 -0
- package/commands/sk/status.md +25 -0
- package/commands/sk/update-task.md +35 -0
- package/commands/sk/write-plan.md +72 -0
- package/package.json +23 -0
- package/skills/sk:accessibility/LICENSE.txt +177 -0
- package/skills/sk:accessibility/SKILL.md +150 -0
- package/skills/sk:api-design/LICENSE.txt +177 -0
- package/skills/sk:api-design/SKILL.md +158 -0
- package/skills/sk:brainstorming/SKILL.md +124 -0
- package/skills/sk:debug/SKILL.md +252 -0
- package/skills/sk:debug/debug_conductor.py +177 -0
- package/skills/sk:debug/lib/__init__.py +1 -0
- package/skills/sk:debug/lib/bug_gatherer.py +55 -0
- package/skills/sk:debug/lib/context_reader.py +139 -0
- package/skills/sk:debug/lib/findings_writer.py +76 -0
- package/skills/sk:debug/lib/lessons_writer.py +165 -0
- package/skills/sk:debug/lib/step_runner.py +326 -0
- package/skills/sk:features/SKILL.md +238 -0
- package/skills/sk:frontend-design/LICENSE.txt +177 -0
- package/skills/sk:frontend-design/SKILL.md +191 -0
- package/skills/sk:laravel-init/SKILL.md +37 -0
- package/skills/sk:laravel-new/SKILL.md +68 -0
- package/skills/sk:lint/SKILL.md +113 -0
- package/skills/sk:perf/LICENSE.txt +177 -0
- package/skills/sk:perf/SKILL.md +188 -0
- package/skills/sk:release/SKILL.md +113 -0
- package/skills/sk:release/references/android-checklist.md +269 -0
- package/skills/sk:release/references/ios-checklist.md +339 -0
- package/skills/sk:release/release.sh +378 -0
- package/skills/sk:review/SKILL.md +346 -0
- package/skills/sk:review/references/security-checklist.md +223 -0
- package/skills/sk:schema-migrate/SKILL.md +125 -0
- package/skills/sk:schema-migrate/orms/drizzle.md +546 -0
- package/skills/sk:schema-migrate/orms/laravel.md +367 -0
- package/skills/sk:schema-migrate/orms/prisma.md +357 -0
- package/skills/sk:schema-migrate/orms/rails.md +351 -0
- package/skills/sk:schema-migrate/orms/sqlalchemy.md +385 -0
- package/skills/sk:schema-migrate/references/detection.md +110 -0
- package/skills/sk:setup-claude/SKILL.md +365 -0
- package/skills/sk:setup-claude/references/detection.md +6 -0
- package/skills/sk:setup-claude/references/templates.md +11 -0
- package/skills/sk:setup-claude/scripts/apply_setup_claude.py +443 -0
- package/skills/sk:setup-claude/scripts/detect_arch_changes.py +437 -0
- package/skills/sk:setup-claude/templates/.claude/docs/arch-changelog-guide.md.template +6 -0
- package/skills/sk:setup-claude/templates/.claude/docs/changelog-guide.md.template +12 -0
- package/skills/sk:setup-claude/templates/CHANGELOG.md.template +21 -0
- package/skills/sk:setup-claude/templates/CLAUDE.md.template +299 -0
- package/skills/sk:setup-claude/templates/arch-changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +57 -0
- package/skills/sk:setup-claude/templates/commands/features.md.template +238 -0
- package/skills/sk:setup-claude/templates/commands/finish-feature.md.template +155 -0
- package/skills/sk:setup-claude/templates/commands/plan.md.template +30 -0
- package/skills/sk:setup-claude/templates/commands/re-setup.md.template +38 -0
- package/skills/sk:setup-claude/templates/commands/release.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/security-check.md.template +172 -0
- package/skills/sk:setup-claude/templates/commands/status.md.template +17 -0
- package/skills/sk:setup-claude/templates/commands/write-plan.md.template +34 -0
- package/skills/sk:setup-claude/templates/finish-feature.md.template +3 -0
- package/skills/sk:setup-claude/templates/plan.md.template +3 -0
- package/skills/sk:setup-claude/templates/status.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks/findings.md.template +19 -0
- package/skills/sk:setup-claude/templates/tasks/lessons.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/progress.md.template +20 -0
- package/skills/sk:setup-claude/templates/tasks/security-findings.md.template +5 -0
- package/skills/sk:setup-claude/templates/tasks/todo.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/workflow-status.md.template +31 -0
- package/skills/sk:setup-claude/templates/tasks-findings.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-lessons.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-progress.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-todo.md.template +3 -0
- package/skills/sk:setup-claude/tests/test_apply_setup_claude.py +193 -0
- package/skills/sk:setup-optimizer/SKILL.md +184 -0
- package/skills/sk:setup-optimizer/lib/__init__.py +24 -0
- package/skills/sk:setup-optimizer/lib/detect.py +205 -0
- package/skills/sk:setup-optimizer/lib/discover.py +221 -0
- package/skills/sk:setup-optimizer/lib/enrich.py +163 -0
- package/skills/sk:setup-optimizer/lib/merge.py +277 -0
- package/skills/sk:setup-optimizer/lib/sidecar.py +129 -0
- package/skills/sk:setup-optimizer/optimize_claude.py +174 -0
- package/skills/sk:setup-optimizer/templates/CLAUDE.md.template +105 -0
- package/skills/sk:skill-creator/LICENSE.txt +202 -0
- package/skills/sk:skill-creator/SKILL.md +479 -0
- package/skills/sk:skill-creator/agents/analyzer.md +274 -0
- package/skills/sk:skill-creator/agents/comparator.md +202 -0
- package/skills/sk:skill-creator/agents/grader.md +223 -0
- package/skills/sk:skill-creator/assets/eval_review.html +146 -0
- package/skills/sk:skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/sk:skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/sk:skill-creator/references/schemas.md +430 -0
- package/skills/sk:skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/sk:skill-creator/scripts/generate_report.py +326 -0
- package/skills/sk:skill-creator/scripts/improve_description.py +248 -0
- package/skills/sk:skill-creator/scripts/package_skill.py +136 -0
- package/skills/sk:skill-creator/scripts/quick_validate.py +103 -0
- package/skills/sk:skill-creator/scripts/run_eval.py +310 -0
- package/skills/sk:skill-creator/scripts/run_loop.py +332 -0
- package/skills/sk:skill-creator/scripts/utils.py +47 -0
- package/skills/sk:smart-commit/SKILL.md +175 -0
- package/skills/sk:test/SKILL.md +171 -0
- package/skills/sk:write-tests/SKILL.md +195 -0
- package/skills/sk:write-tests/references/patterns.md +209 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# /schema-migrate — Drizzle ORM Analysis (Phases 2–5)
|
|
2
|
+
|
|
3
|
+
> This file is loaded by `SKILL.md` when Drizzle ORM is detected.
|
|
4
|
+
> Execute Phase 2 through Phase 5 below, then return the final report.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Phase 2: Classify Changes
|
|
9
|
+
|
|
10
|
+
Parse `git diff HEAD -- [schema_file]` and classify each change:
|
|
11
|
+
|
|
12
|
+
### Files to Scan (read in parallel)
|
|
13
|
+
|
|
14
|
+
1. **`drizzle.config.ts` / `drizzle.config.js`**
|
|
15
|
+
- Extract: `dialect` (sqlite | postgres | mysql), `schema`, `out`, `dbCredentials`
|
|
16
|
+
- Detect Supabase: `dialect === 'postgres'` + `dbCredentials.url` contains `supabase` or `supabaseUrl` is set
|
|
17
|
+
|
|
18
|
+
2. **Schema file** (from config or default `src/db/schema.ts`)
|
|
19
|
+
- Parse all `export const [table] = ...` definitions
|
|
20
|
+
- Track: table names, columns, constraints, indexes, FKs, defaults
|
|
21
|
+
|
|
22
|
+
3. **`drizzle/meta/_journal.json`**
|
|
23
|
+
- List migration entries + timestamps
|
|
24
|
+
- Track: last migration name and date
|
|
25
|
+
|
|
26
|
+
4. **`drizzle/meta/` latest snapshot** (e.g., `0000_colorful_nebula.json`)
|
|
27
|
+
- Last recorded schema state from migrations
|
|
28
|
+
- Used to detect "out of sync" condition (pushed changes without `db:generate`)
|
|
29
|
+
|
|
30
|
+
5. **`package.json`** scripts
|
|
31
|
+
- Detect: `db:push`, `db:migrate`, `db:generate` commands
|
|
32
|
+
- Detect: `supabase push`, `supabase migration` scripts (if Supabase)
|
|
33
|
+
|
|
34
|
+
6. **`git diff HEAD -- [schema_file]`**
|
|
35
|
+
- Uncommitted schema changes (what's pending)
|
|
36
|
+
|
|
37
|
+
7. **`git log --oneline -5 -- [schema_file]`**
|
|
38
|
+
- Recent schema commit history
|
|
39
|
+
|
|
40
|
+
8. **`supabase/config.toml`** (if Supabase project detected)
|
|
41
|
+
- Project ID, region, API settings
|
|
42
|
+
- Check if local migrations exist (`supabase/migrations/`)
|
|
43
|
+
|
|
44
|
+
9. **Schema version on remote** (Supabase only)
|
|
45
|
+
- If Supabase: fetch schema introspection from `{project_url}/rest/v1/?apikey={key}`
|
|
46
|
+
- Compare local schema against remote to detect sync issues
|
|
47
|
+
|
|
48
|
+
### Risk Matrix
|
|
49
|
+
|
|
50
|
+
| Change Type | Risk | SQLite | Postgres | MySQL | Supabase | Notes |
|
|
51
|
+
|---|---|---|---|---|---|---|
|
|
52
|
+
| Add new table | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Unless FK targets are missing |
|
|
53
|
+
| Add nullable column | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Standard additive change |
|
|
54
|
+
| Add index | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Non-breaking |
|
|
55
|
+
| Add comment | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Metadata only |
|
|
56
|
+
| Change default value | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Only affects new rows |
|
|
57
|
+
| Remove `.notNull()` | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Relaxing constraint |
|
|
58
|
+
| Add `.primaryKey()` to new col | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | New table schema only |
|
|
59
|
+
| Add NOT NULL col + default | 🟡 Careful | ✅ | ✅ | ✅ | ✅ | Existing rows get default |
|
|
60
|
+
| Add unique constraint | 🟡 Careful | ✅ | ✅ | ✅ | ✅ | Fails if duplicates exist |
|
|
61
|
+
| Add FK to existing col | 🟡 Careful | ⚠️ Table recreation | ✅ | ⚠️ Online DDL | ✅ | Check for orphan rows first |
|
|
62
|
+
| Add/change CASCADE behavior | 🟡 Careful | ⚠️ Table recreation | ✅ | ✅ | ✅ | Behavioral change, data safety |
|
|
63
|
+
| Add check constraint | 🟡 Careful | ⚠️ Table recreation | ✅ | ✅ | ✅ | SQLite requires table recreation |
|
|
64
|
+
| Add RLS policy (Supabase only) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Auth-dependent | Review security implications |
|
|
65
|
+
| Add NOT NULL col (no default) | 🔴 Breaking | ❌ | ❌ | ❌ | ❌ | Fails on non-empty tables |
|
|
66
|
+
| Rename column | 🔴 Breaking | ❌ → data loss | ✅ | ✅ | ✅ | Drizzle sees as drop+add |
|
|
67
|
+
| Change column type | 🔴 Breaking | ⚠️ Table recreation | ⚠️ May fail | ⚠️ May fail | ⚠️ May fail | Cast failures, data loss risk |
|
|
68
|
+
| Drop column | 🔴 Breaking | ⚠️ Data loss | ❌ Data loss | ❌ Data loss | ❌ Data loss | Check usages in code first |
|
|
69
|
+
| Drop table | 🔴 Breaking | ❌ Data loss | ❌ Data loss | ❌ Data loss | ❌ Data loss | Check usages in code first |
|
|
70
|
+
| Add edge function (Supabase) | 🟢 Safe | N/A | N/A | N/A | ✅ | Deployment via Supabase CLI |
|
|
71
|
+
| Modify RLS policy (Supabase) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Auth-dependent | Test auth flows after change |
|
|
72
|
+
| Enable realtime (Supabase) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Performance | Large tables may impact realtime |
|
|
73
|
+
|
|
74
|
+
### Also Detect
|
|
75
|
+
|
|
76
|
+
- ⚠️ **Migration snapshot out of sync**: Latest snapshot JSON doesn't match schema file
|
|
77
|
+
- Indicates: schema changes have been pushed without `db:generate`
|
|
78
|
+
|
|
79
|
+
- ⚠️ **Uncommitted schema changes**: Diff includes non-schema files (e.g., seed.ts modified)
|
|
80
|
+
|
|
81
|
+
- ⚠️ **Supabase sync issue**: Remote schema differs from local schema
|
|
82
|
+
- Run `supabase db pull` to sync
|
|
83
|
+
|
|
84
|
+
- 🔴 **RLS policies in schema without auth context**: Supabase security issue
|
|
85
|
+
- Policies must reference `auth.uid()` correctly or deny all access
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Phase 3: Present Risk Report
|
|
90
|
+
|
|
91
|
+
### Report Format
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
95
|
+
/schema-migrate — Schema Change Analysis
|
|
96
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
97
|
+
|
|
98
|
+
ORM: Drizzle ORM
|
|
99
|
+
Dialect: PostgreSQL (Supabase)
|
|
100
|
+
Project: my-project (region: us-east-1)
|
|
101
|
+
Workflow: db:migrate (versioned migrations)
|
|
102
|
+
Schema: src/db/schema.ts
|
|
103
|
+
Migrations dir: drizzle/
|
|
104
|
+
Last migration: 2025-02-28 (0003_add_profiles_table.sql)
|
|
105
|
+
Supabase local: supabase/migrations/
|
|
106
|
+
|
|
107
|
+
Status:
|
|
108
|
+
📁 Migration snapshot: IN SYNC
|
|
109
|
+
📝 Schema file: COMMITTED
|
|
110
|
+
🌐 Remote schema: IN SYNC with local
|
|
111
|
+
✅ All systems ready for push
|
|
112
|
+
|
|
113
|
+
Detected Changes (since last commit):
|
|
114
|
+
──────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
🟢 SAFE (2 changes)
|
|
117
|
+
• jobs: Added nullable column `applied_notes` (text)
|
|
118
|
+
• jobs: Added index on `status`
|
|
119
|
+
|
|
120
|
+
🟡 CAREFUL (1 change — review before pushing)
|
|
121
|
+
• profile: Added `email text NOT NULL DEFAULT ''`
|
|
122
|
+
↳ All existing rows will get empty string value
|
|
123
|
+
↳ Is '' an acceptable initial value? Consider nullable first.
|
|
124
|
+
↳ Supabase: Run seed/trigger to populate meaningful defaults
|
|
125
|
+
|
|
126
|
+
🔴 BREAKING (1 change — stop and read migration plan)
|
|
127
|
+
• jobs: Column `job_type` changed text → integer
|
|
128
|
+
↳ PostgreSQL can auto-cast, but verify casts don't lose data
|
|
129
|
+
↳ See safe migration path below
|
|
130
|
+
↳ Supabase: Test on staging env first
|
|
131
|
+
|
|
132
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Dialect-Specific Notes
|
|
136
|
+
|
|
137
|
+
#### SQLite
|
|
138
|
+
- 🚨 **No ALTER COLUMN TYPE** — must recreate table
|
|
139
|
+
- 🚨 **No ADD FOREIGN KEY to existing column** — must recreate table
|
|
140
|
+
- ✅ Table recreation is safe, but slower for large tables
|
|
141
|
+
- ✅ Foreign key constraints enabled by default (Drizzle handles this)
|
|
142
|
+
|
|
143
|
+
#### PostgreSQL
|
|
144
|
+
- ✅ Full `ALTER TABLE` support — changes are online
|
|
145
|
+
- ✅ Type casts usually safe (explicit `CAST` syntax supported)
|
|
146
|
+
- ⚠️ Long-running `ALTER` may lock table briefly
|
|
147
|
+
- ✅ Migrations are versioned and reversible
|
|
148
|
+
|
|
149
|
+
#### MySQL
|
|
150
|
+
- ⚠️ Partial `ALTER TABLE` — some operations trigger table recreation
|
|
151
|
+
- ⚠️ Type casts may fail silently (use `CAST` explicitly)
|
|
152
|
+
- ⚠️ Foreign key constraints must be managed carefully
|
|
153
|
+
- ✅ Online DDL available in MySQL 8.0+
|
|
154
|
+
|
|
155
|
+
#### Supabase (PostgreSQL + managed features)
|
|
156
|
+
- ✅ All PostgreSQL features available
|
|
157
|
+
- ⚠️ RLS policies require careful auth context review
|
|
158
|
+
- ⚠️ Realtime subscriptions on large tables may impact performance
|
|
159
|
+
- ✅ Remote schema introspection available via Supabase API
|
|
160
|
+
- ✅ Local migrations sync with `supabase push` / `supabase pull`
|
|
161
|
+
- ⚠️ Edge functions deployed via `supabase functions deploy`
|
|
162
|
+
- ✅ Can test changes locally before pushing to production
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Phase 4: Per-Change Migration Plans
|
|
167
|
+
|
|
168
|
+
### Scenario: NOT NULL Column Without Default
|
|
169
|
+
|
|
170
|
+
#### Safe Path (db:push / PostgreSQL / Supabase)
|
|
171
|
+
```
|
|
172
|
+
Option A (nullable first — most conservative):
|
|
173
|
+
1. Schema: text('col') [no .notNull()]
|
|
174
|
+
2. db:push
|
|
175
|
+
3. Data: backfill values with SQL or migration script
|
|
176
|
+
4. Schema: text('col').notNull()
|
|
177
|
+
5. db:push
|
|
178
|
+
|
|
179
|
+
Option B (with default — immediate):
|
|
180
|
+
1. Schema: text('col').notNull().default('')
|
|
181
|
+
2. db:push
|
|
182
|
+
3. All existing rows get '' (or your default)
|
|
183
|
+
⚠️ Verify '' is acceptable for your use case
|
|
184
|
+
|
|
185
|
+
Option C (pre-populate — scripted):
|
|
186
|
+
1. Create a migration script that backfills values
|
|
187
|
+
2. Apply script
|
|
188
|
+
3. Schema: text('col').notNull()
|
|
189
|
+
4. db:push
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Supabase-Specific Notes
|
|
193
|
+
- Use `supabase migration new [name]` to create custom migration with backfill logic
|
|
194
|
+
- Test on staging env: `supabase push --linked` (push to remote staging)
|
|
195
|
+
- Then promote to production
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### Scenario: Column Type Change (PostgreSQL / Supabase)
|
|
200
|
+
|
|
201
|
+
#### Safe Path (PostgreSQL can auto-cast)
|
|
202
|
+
```
|
|
203
|
+
PostgreSQL/Supabase:
|
|
204
|
+
1. Schema: Change column type from text to integer
|
|
205
|
+
2. db:push (generates migration)
|
|
206
|
+
3. Review generated SQL — PostgreSQL will attempt CAST
|
|
207
|
+
4. If migration succeeds: done ✅
|
|
208
|
+
5. If migration fails: review data for non-numeric values, fix, retry
|
|
209
|
+
|
|
210
|
+
Supabase:
|
|
211
|
+
1. Test on local/staging first:
|
|
212
|
+
supabase migration new change_type_to_integer
|
|
213
|
+
[Edit migration to include: ALTER TABLE ... ALTER COLUMN ... USING CAST(...) ]
|
|
214
|
+
2. Test locally: supabase db reset (or supabase push --linked)
|
|
215
|
+
3. Verify data integrity
|
|
216
|
+
4. Promote to production: supabase push
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### SQLite (Requires Table Recreation)
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
SQLite (zero data loss — table recreation):
|
|
223
|
+
1. Add new column with new type: col_new: integer('col_new')
|
|
224
|
+
2. db:push → column added, existing data untouched
|
|
225
|
+
3. Write conversion script: UPDATE table SET col_new = CAST(col AS INTEGER)
|
|
226
|
+
4. Remove original column from schema: delete text('col')
|
|
227
|
+
5. db:push → original column dropped
|
|
228
|
+
6. Rename in schema: col_new → col
|
|
229
|
+
7. db:push → final shape
|
|
230
|
+
|
|
231
|
+
Note: Step 3 may need custom conversion logic for non-trivial casts
|
|
232
|
+
(e.g., "full-time" → cannot cast to integer → must map manually)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### Scenario: Rename Column
|
|
238
|
+
|
|
239
|
+
#### Safe Path (All Dialects via Drizzle)
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Problem: Drizzle sees rename as DROP old + ADD new → data loss!
|
|
243
|
+
|
|
244
|
+
Safe path (add column, backfill, drop old):
|
|
245
|
+
1. Schema: Add new_name: text('new_name') [nullable]
|
|
246
|
+
2. db:push
|
|
247
|
+
3. Backfill: UPDATE table SET new_name = old_name
|
|
248
|
+
4. Remove old_name from schema
|
|
249
|
+
5. db:push → old column drops, data preserved ✅
|
|
250
|
+
6. (Optional) Rename in schema: new_name → desired_name
|
|
251
|
+
7. db:push
|
|
252
|
+
|
|
253
|
+
Supabase note:
|
|
254
|
+
- Wrap steps 1-3 in a single migration: supabase migration new rename_column
|
|
255
|
+
- Test on staging: supabase db reset
|
|
256
|
+
- Then push to production
|
|
257
|
+
|
|
258
|
+
Alternative for Postgres/Supabase (direct migration):
|
|
259
|
+
- Create manual migration file with: ALTER TABLE table RENAME COLUMN old TO new
|
|
260
|
+
- BUT: also update Drizzle schema to match, or next db:generate will conflict
|
|
261
|
+
- Not recommended — add/backfill/drop is safer and Drizzle-friendly
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### Scenario: Unique Constraint on Existing Data
|
|
267
|
+
|
|
268
|
+
#### Pre-Check Before Pushing
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
SQL to check for duplicates:
|
|
272
|
+
SELECT [col], COUNT(*) FROM [table]
|
|
273
|
+
GROUP BY [col] HAVING COUNT(*) > 1;
|
|
274
|
+
|
|
275
|
+
If duplicates found:
|
|
276
|
+
Option A: Remove duplicates (keep latest/earliest)
|
|
277
|
+
Option B: Add unique constraint only to new data (not retroactive)
|
|
278
|
+
Option C: Use nullable column (allow NULL to bypass unique)
|
|
279
|
+
|
|
280
|
+
After duplicates removed:
|
|
281
|
+
Schema: text('col').unique()
|
|
282
|
+
db:push → constraint added
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Supabase-Specific
|
|
286
|
+
- If using managed auth, check `auth.users` table for unique email — already enforced
|
|
287
|
+
- For custom tables, test constraint on staging first
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### Scenario: Add Foreign Key to Existing Column (Supabase / PostgreSQL)
|
|
292
|
+
|
|
293
|
+
#### Pre-Check for Orphan Rows
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
SQL to check for orphan rows:
|
|
297
|
+
SELECT COUNT(*) FROM [table]
|
|
298
|
+
WHERE [col] NOT IN (SELECT id FROM [referenced_table]);
|
|
299
|
+
|
|
300
|
+
If orphans found:
|
|
301
|
+
Option A: Delete orphan rows (data loss — review first)
|
|
302
|
+
Option B: Set orphans to NULL (if col is nullable)
|
|
303
|
+
Option C: Update orphans to valid reference
|
|
304
|
+
Option D: Don't add FK constraint (allow data quality drift)
|
|
305
|
+
|
|
306
|
+
After orphans cleaned:
|
|
307
|
+
Schema: int('col').references(() => other_table.id)
|
|
308
|
+
db:push → FK constraint added
|
|
309
|
+
|
|
310
|
+
Supabase:
|
|
311
|
+
- RLS policies must permit the JOIN for app code
|
|
312
|
+
- Test ON DELETE / ON UPDATE behavior (CASCADE, SET NULL, RESTRICT)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### SQLite (Requires Table Recreation)
|
|
316
|
+
```
|
|
317
|
+
SQLite cannot add FK to existing column directly.
|
|
318
|
+
Follow same orphan-check process, then:
|
|
319
|
+
1. Schema: int('col').references(() => other_table.id)
|
|
320
|
+
2. db:push → Drizzle recreates table with FK
|
|
321
|
+
3. All data preserved ✅
|
|
322
|
+
|
|
323
|
+
⚠️ Large tables will be slow during recreation (single-threaded)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Scenario: Add/Change CASCADE Behavior (Supabase / PostgreSQL)
|
|
329
|
+
|
|
330
|
+
#### Before Changing
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
Review current ON DELETE / ON UPDATE clauses:
|
|
334
|
+
- CASCADE: delete child rows when parent deleted
|
|
335
|
+
- SET NULL: set foreign key to NULL
|
|
336
|
+
- RESTRICT: prevent parent deletion if children exist
|
|
337
|
+
- NO ACTION: (default) similar to RESTRICT
|
|
338
|
+
|
|
339
|
+
Behavioral change example:
|
|
340
|
+
Before: ON DELETE RESTRICT (prevent deletion if children exist)
|
|
341
|
+
After: ON DELETE CASCADE (delete all children too)
|
|
342
|
+
|
|
343
|
+
Impact: If parent record deleted, child records silently deleted
|
|
344
|
+
|
|
345
|
+
Safety checklist:
|
|
346
|
+
✅ Is cascade the intended behavior?
|
|
347
|
+
✅ Are there dependent triggers/views that rely on old behavior?
|
|
348
|
+
✅ Will data loss be acceptable?
|
|
349
|
+
✅ Test on staging: attempt parent deletion, verify children deleted
|
|
350
|
+
|
|
351
|
+
After approval:
|
|
352
|
+
Schema: .references(() => parent.id, { onDelete: 'cascade' })
|
|
353
|
+
db:push / supabase push
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
### Scenario: Enable Realtime Subscriptions (Supabase)
|
|
359
|
+
|
|
360
|
+
#### Performance Considerations
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
⚠️ Realtime subscriptions on large tables (1M+ rows) may:
|
|
364
|
+
- Increase latency on INSERT/UPDATE/DELETE
|
|
365
|
+
- Consume more server resources
|
|
366
|
+
- Send unnecessary updates to many clients
|
|
367
|
+
|
|
368
|
+
Before enabling:
|
|
369
|
+
1. Estimate table size and update frequency
|
|
370
|
+
2. Test on staging with realistic load
|
|
371
|
+
3. Consider filtering subscriptions (WHERE clauses)
|
|
372
|
+
4. Use `realtimeEnabled: true` selectively on tables that benefit
|
|
373
|
+
|
|
374
|
+
Schema example:
|
|
375
|
+
export const jobs = pgTable('jobs', { ... }, (table) => ({
|
|
376
|
+
realtimeEnabled: true,
|
|
377
|
+
}))
|
|
378
|
+
|
|
379
|
+
Supabase dashboard:
|
|
380
|
+
- Verify in Supabase project: Database > Replication > Enable for [table]
|
|
381
|
+
- Confirm clients can subscribe: .on('*', callback)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### Scenario: Add RLS Policy (Supabase)
|
|
387
|
+
|
|
388
|
+
#### Auth Context Review
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
⚠️ RLS policies require careful auth setup. If policy is wrong, table is inaccessible.
|
|
392
|
+
|
|
393
|
+
Policy template:
|
|
394
|
+
CREATE POLICY "Users can read own jobs"
|
|
395
|
+
ON jobs FOR SELECT
|
|
396
|
+
USING (auth.uid() = user_id);
|
|
397
|
+
|
|
398
|
+
Before adding policy:
|
|
399
|
+
✅ Is `user_id` column present and populated?
|
|
400
|
+
✅ Is `auth.uid()` available (Supabase Auth enabled)?
|
|
401
|
+
✅ Will SELECT/INSERT/UPDATE/DELETE still work for expected users?
|
|
402
|
+
✅ Are service roles (admin) exempted?
|
|
403
|
+
|
|
404
|
+
Test on staging:
|
|
405
|
+
1. Set up test user in Supabase Auth
|
|
406
|
+
2. Query table as test user (use user's JWT token)
|
|
407
|
+
3. Verify policy allows expected rows, blocks others
|
|
408
|
+
4. Retry as different user — verify isolation
|
|
409
|
+
5. Retry as service role — verify bypass
|
|
410
|
+
|
|
411
|
+
If policy blocks all access:
|
|
412
|
+
- Disable policy temporarily: ALTER POLICY ... DISABLE
|
|
413
|
+
- Review policy logic
|
|
414
|
+
- Re-enable after fix
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Phase 5: Command Recommendations
|
|
420
|
+
|
|
421
|
+
### SQLite (db:push workflow)
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
# 1. View pending changes
|
|
425
|
+
git diff HEAD -- src/db/schema.ts
|
|
426
|
+
|
|
427
|
+
# 2. Review with /schema-migrate
|
|
428
|
+
/schema-migrate
|
|
429
|
+
|
|
430
|
+
# 3. If safe changes only:
|
|
431
|
+
npm run db:push
|
|
432
|
+
|
|
433
|
+
# 4. After pushing, update snapshot
|
|
434
|
+
npm run db:generate
|
|
435
|
+
git add drizzle/
|
|
436
|
+
git commit -m "chore(schema): sync migration snapshot"
|
|
437
|
+
|
|
438
|
+
# 5. If breaking changes:
|
|
439
|
+
# — Fix schema first (follow per-change migration plan)
|
|
440
|
+
# — Then db:push when ready
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### PostgreSQL (db:migrate workflow)
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
# 1. Review changes
|
|
447
|
+
/schema-migrate
|
|
448
|
+
|
|
449
|
+
# 2. Generate migration from schema diff
|
|
450
|
+
npm run db:generate
|
|
451
|
+
|
|
452
|
+
# 3. Review generated SQL
|
|
453
|
+
cat drizzle/[latest-file].sql
|
|
454
|
+
|
|
455
|
+
# 4. Test migration locally (if possible)
|
|
456
|
+
npm run db:migrate
|
|
457
|
+
|
|
458
|
+
# 5. If migration succeeds:
|
|
459
|
+
git add src/db/schema.ts drizzle/
|
|
460
|
+
git commit -m "feat(schema): [describe change]"
|
|
461
|
+
|
|
462
|
+
# 6. Deploy to production (via CI/CD or manual migration)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### MySQL (db:migrate workflow + online DDL)
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# 1. Check MySQL version (8.0+ for online DDL)
|
|
469
|
+
mysql --version
|
|
470
|
+
|
|
471
|
+
# 2. Review changes
|
|
472
|
+
/schema-migrate
|
|
473
|
+
|
|
474
|
+
# 3. For large tables, enable online DDL (MySQL 8.0+)
|
|
475
|
+
# — Drizzle will use ALGORITHM=INSTANT where possible
|
|
476
|
+
# — Some operations still require table rebuild
|
|
477
|
+
|
|
478
|
+
npm run db:generate
|
|
479
|
+
|
|
480
|
+
# 4. Review and test
|
|
481
|
+
cat drizzle/[latest-file].sql
|
|
482
|
+
npm run db:migrate
|
|
483
|
+
|
|
484
|
+
# 5. Commit
|
|
485
|
+
git add src/db/schema.ts drizzle/
|
|
486
|
+
git commit -m "feat(schema): [describe change]"
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Supabase (db:migrate + supabase CLI workflow)
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# 1. Review changes
|
|
493
|
+
/schema-migrate
|
|
494
|
+
|
|
495
|
+
# 2. Generate migration from schema diff
|
|
496
|
+
npm run db:generate
|
|
497
|
+
|
|
498
|
+
# 3. Review generated migration SQL
|
|
499
|
+
cat drizzle/[latest-file].sql
|
|
500
|
+
|
|
501
|
+
# 4. Create Supabase migration with same changes
|
|
502
|
+
supabase migration new [describe_change]
|
|
503
|
+
# Edit supabase/migrations/[timestamp]_[describe_change].sql
|
|
504
|
+
# Copy SQL from drizzle/[latest-file].sql
|
|
505
|
+
|
|
506
|
+
# 5. Test on local environment
|
|
507
|
+
supabase start
|
|
508
|
+
supabase db reset # or supabase push (to local)
|
|
509
|
+
|
|
510
|
+
# 6. Test on linked staging environment
|
|
511
|
+
supabase link --project-ref=[staging-project]
|
|
512
|
+
supabase push --linked
|
|
513
|
+
|
|
514
|
+
# 7. Verify on staging (Supabase dashboard or API tests)
|
|
515
|
+
|
|
516
|
+
# 8. Promote to production
|
|
517
|
+
supabase link --project-ref=[prod-project]
|
|
518
|
+
supabase push --linked
|
|
519
|
+
|
|
520
|
+
# 9. Commit both schema and migration
|
|
521
|
+
git add src/db/schema.ts drizzle/ supabase/migrations/
|
|
522
|
+
git commit -m "feat(schema): [describe change]"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### Supabase-Specific Commands
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
# Pull remote schema changes (if someone edited via dashboard)
|
|
529
|
+
supabase db pull
|
|
530
|
+
|
|
531
|
+
# Create a custom migration (e.g., backfill data)
|
|
532
|
+
supabase migration new backfill_user_emails
|
|
533
|
+
|
|
534
|
+
# Test migrations locally
|
|
535
|
+
supabase db reset
|
|
536
|
+
supabase db push # or supabase push --local
|
|
537
|
+
|
|
538
|
+
# Check status
|
|
539
|
+
supabase status
|
|
540
|
+
|
|
541
|
+
# Reset to clean state
|
|
542
|
+
supabase db reset
|
|
543
|
+
|
|
544
|
+
# View migration history
|
|
545
|
+
ls supabase/migrations/
|
|
546
|
+
```
|