red64-cli 0.1.0 → 0.3.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 +1 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/ListScreen.d.ts.map +1 -1
- package/dist/components/screens/ListScreen.js +28 -3
- package/dist/components/screens/ListScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +212 -13
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
- package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
- package/dist/components/ui/ArtifactsSidebar.js +51 -0
- package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
- package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
- package/dist/components/ui/FeatureSidebar.js +1 -1
- package/dist/components/ui/FeatureSidebar.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +3 -3
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# Database Migration Patterns
|
|
2
|
+
|
|
3
|
+
Alembic migration best practices for Python projects with SQLAlchemy.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Reversible by default**: Every migration must have a working downgrade path
|
|
10
|
+
- **Small and atomic**: One logical change per migration file
|
|
11
|
+
- **Zero-downtime aware**: Schema changes must not lock tables or break running code
|
|
12
|
+
- **Schema separate from data**: Never mix DDL and DML in the same migration
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Alembic Setup
|
|
17
|
+
|
|
18
|
+
### Configuration
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# alembic/env.py
|
|
22
|
+
from app.models.base import Base
|
|
23
|
+
from app.config import settings
|
|
24
|
+
|
|
25
|
+
target_metadata = Base.metadata
|
|
26
|
+
|
|
27
|
+
def run_migrations_online():
|
|
28
|
+
connectable = create_engine(settings.database_url)
|
|
29
|
+
with connectable.connect() as connection:
|
|
30
|
+
context.configure(
|
|
31
|
+
connection=connection,
|
|
32
|
+
target_metadata=target_metadata,
|
|
33
|
+
compare_type=True, # Detect column type changes
|
|
34
|
+
compare_server_default=True, # Detect default value changes
|
|
35
|
+
render_as_batch=True, # SQLite compatibility
|
|
36
|
+
)
|
|
37
|
+
with context.begin_transaction():
|
|
38
|
+
context.run_migrations()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Naming Conventions
|
|
42
|
+
|
|
43
|
+
Alembic auto-generates revision IDs. Use descriptive `--message` values:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Schema changes
|
|
47
|
+
uv run alembic revision --autogenerate -m "add_users_table"
|
|
48
|
+
uv run alembic revision --autogenerate -m "add_email_index_to_users"
|
|
49
|
+
uv run alembic revision --autogenerate -m "add_role_column_to_users"
|
|
50
|
+
|
|
51
|
+
# Data migrations (never autogenerate)
|
|
52
|
+
uv run alembic revision -m "backfill_user_display_names"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Pattern**: `{verb}_{noun}[_detail]` -- `add_users_table`, `drop_legacy_flags`, `rename_content_slug`.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Reversible Migrations
|
|
60
|
+
|
|
61
|
+
### Always Implement Downgrade
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
"""add_users_table
|
|
65
|
+
|
|
66
|
+
Revision ID: a1b2c3d4
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def upgrade() -> None:
|
|
70
|
+
op.create_table(
|
|
71
|
+
"users",
|
|
72
|
+
sa.Column("id", sa.Integer(), primary_key=True),
|
|
73
|
+
sa.Column("email", sa.String(255), nullable=False, unique=True),
|
|
74
|
+
sa.Column("name", sa.String(255), nullable=False),
|
|
75
|
+
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
76
|
+
)
|
|
77
|
+
op.create_index("ix_users_email", "users", ["email"])
|
|
78
|
+
|
|
79
|
+
def downgrade() -> None:
|
|
80
|
+
op.drop_index("ix_users_email", table_name="users")
|
|
81
|
+
op.drop_table("users")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Irreversible Migrations
|
|
85
|
+
|
|
86
|
+
When a migration truly cannot be reversed (data loss), make it explicit:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
def downgrade() -> None:
|
|
90
|
+
raise NotImplementedError(
|
|
91
|
+
"This migration drops the legacy_flags column and cannot be reversed. "
|
|
92
|
+
"Restore from backup if needed."
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Small, Atomic Changes
|
|
99
|
+
|
|
100
|
+
### One Concern Per Migration
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# GOOD: Single logical change
|
|
104
|
+
# Migration 1: add_posts_table
|
|
105
|
+
def upgrade() -> None:
|
|
106
|
+
op.create_table("posts", ...)
|
|
107
|
+
|
|
108
|
+
# Migration 2: add_posts_published_at_index
|
|
109
|
+
def upgrade() -> None:
|
|
110
|
+
op.create_index("ix_posts_published_at", "posts", ["published_at"])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# BAD: Multiple unrelated changes
|
|
115
|
+
def upgrade() -> None:
|
|
116
|
+
op.create_table("posts", ...)
|
|
117
|
+
op.add_column("users", sa.Column("bio", sa.Text()))
|
|
118
|
+
op.create_index("ix_posts_published_at", "posts", ["published_at"])
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Zero-Downtime Migrations
|
|
124
|
+
|
|
125
|
+
### Safe Column Addition
|
|
126
|
+
|
|
127
|
+
Adding a nullable column with no default is always safe:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
def upgrade() -> None:
|
|
131
|
+
op.add_column("users", sa.Column("display_name", sa.String(255), nullable=True))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Unsafe Operations and Alternatives
|
|
135
|
+
|
|
136
|
+
| Operation | Problem | Safe Alternative |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| Add NOT NULL column | Locks table, fails on existing rows | Add nullable, backfill, then alter |
|
|
139
|
+
| Drop column | Running code may reference it | Deploy code removal first, then drop |
|
|
140
|
+
| Rename column | Breaks running queries | Add new column, backfill, deploy code, drop old |
|
|
141
|
+
| Add index on large table | Locks table for duration | Use `CREATE INDEX CONCURRENTLY` (PostgreSQL) |
|
|
142
|
+
| Change column type | May require table rewrite | Add new column, backfill, swap |
|
|
143
|
+
|
|
144
|
+
### Multi-Step Column Rename
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Step 1: Add new column (deploy migration)
|
|
148
|
+
def upgrade() -> None:
|
|
149
|
+
op.add_column("users", sa.Column("display_name", sa.String(255), nullable=True))
|
|
150
|
+
|
|
151
|
+
# Step 2: Backfill data (data migration)
|
|
152
|
+
def upgrade() -> None:
|
|
153
|
+
op.execute("UPDATE users SET display_name = name WHERE display_name IS NULL")
|
|
154
|
+
|
|
155
|
+
# Step 3: Deploy code using display_name instead of name
|
|
156
|
+
|
|
157
|
+
# Step 4: Drop old column (after code is deployed)
|
|
158
|
+
def upgrade() -> None:
|
|
159
|
+
op.drop_column("users", "name")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Concurrent Index Creation (PostgreSQL)
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from alembic import op
|
|
166
|
+
|
|
167
|
+
def upgrade() -> None:
|
|
168
|
+
# Must run outside a transaction for CONCURRENTLY
|
|
169
|
+
op.execute("CREATE INDEX CONCURRENTLY ix_posts_user_id ON posts (user_id)")
|
|
170
|
+
|
|
171
|
+
def downgrade() -> None:
|
|
172
|
+
op.drop_index("ix_posts_user_id", table_name="posts")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Add to the migration file:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# Disable transaction wrapping for this migration
|
|
179
|
+
from alembic import context
|
|
180
|
+
context.configure(transaction_per_migration=False)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Schema vs Data Migrations
|
|
186
|
+
|
|
187
|
+
### Schema Migration (DDL)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
"""add_status_column_to_posts
|
|
191
|
+
|
|
192
|
+
Autogenerated: yes
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def upgrade() -> None:
|
|
196
|
+
op.add_column(
|
|
197
|
+
"posts",
|
|
198
|
+
sa.Column("status", sa.String(20), nullable=True, server_default="draft"),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def downgrade() -> None:
|
|
202
|
+
op.drop_column("posts", "status")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Data Migration (DML)
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
"""backfill_post_status
|
|
209
|
+
|
|
210
|
+
Autogenerated: no
|
|
211
|
+
"""
|
|
212
|
+
from sqlalchemy import table, column, String
|
|
213
|
+
|
|
214
|
+
def upgrade() -> None:
|
|
215
|
+
# Use lightweight table references, not ORM models
|
|
216
|
+
posts = table("posts", column("status", String))
|
|
217
|
+
op.execute(
|
|
218
|
+
posts.update()
|
|
219
|
+
.where(posts.c.status.is_(None))
|
|
220
|
+
.values(status="draft")
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def downgrade() -> None:
|
|
224
|
+
# Data backfills are generally not reversible
|
|
225
|
+
pass
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Rule**: Never import ORM models in migrations. Models change over time; migrations must remain stable. Use `sa.table()` / `sa.column()` or raw SQL.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Autogenerate Pitfalls
|
|
233
|
+
|
|
234
|
+
### What Autogenerate Detects
|
|
235
|
+
|
|
236
|
+
| Detected | Not Detected |
|
|
237
|
+
|---|---|
|
|
238
|
+
| Table creation/removal | Table or column renames (shows as drop+create) |
|
|
239
|
+
| Column addition/removal | Changes to constraints names |
|
|
240
|
+
| Column type changes (with `compare_type=True`) | Data migrations |
|
|
241
|
+
| Nullable changes | Custom CHECK constraints (sometimes) |
|
|
242
|
+
| Index creation/removal | PostgreSQL ENUM type changes |
|
|
243
|
+
| Foreign key changes | Trigger or function changes |
|
|
244
|
+
|
|
245
|
+
### Always Review Generated Migrations
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Generate
|
|
249
|
+
uv run alembic revision --autogenerate -m "add_feature_x"
|
|
250
|
+
|
|
251
|
+
# ALWAYS review the generated file before running
|
|
252
|
+
# Check for:
|
|
253
|
+
# 1. Unintended drops (renames detected as drop+create)
|
|
254
|
+
# 2. Missing downgrade operations
|
|
255
|
+
# 3. Correct column types and constraints
|
|
256
|
+
# 4. Index naming consistency
|
|
257
|
+
|
|
258
|
+
# Then run
|
|
259
|
+
uv run alembic upgrade head
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Custom Enum Handling
|
|
263
|
+
|
|
264
|
+
Alembic does not handle PostgreSQL ENUMs well. Manage them manually:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from alembic import op
|
|
268
|
+
import sqlalchemy as sa
|
|
269
|
+
|
|
270
|
+
def upgrade() -> None:
|
|
271
|
+
# Create enum type first
|
|
272
|
+
role_enum = sa.Enum("admin", "member", "viewer", name="user_role")
|
|
273
|
+
role_enum.create(op.get_bind(), checkfirst=True)
|
|
274
|
+
|
|
275
|
+
op.add_column("users", sa.Column("role", role_enum, nullable=True))
|
|
276
|
+
|
|
277
|
+
def downgrade() -> None:
|
|
278
|
+
op.drop_column("users", "role")
|
|
279
|
+
sa.Enum(name="user_role").drop(op.get_bind(), checkfirst=True)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Migration Testing
|
|
285
|
+
|
|
286
|
+
### Test Migrations in CI
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
# tests/test_migrations.py
|
|
290
|
+
import subprocess
|
|
291
|
+
|
|
292
|
+
def test_migrations_up_down():
|
|
293
|
+
"""Verify all migrations can run forward and backward."""
|
|
294
|
+
result = subprocess.run(
|
|
295
|
+
["uv", "run", "alembic", "upgrade", "head"],
|
|
296
|
+
capture_output=True, text=True,
|
|
297
|
+
)
|
|
298
|
+
assert result.returncode == 0, f"Upgrade failed: {result.stderr}"
|
|
299
|
+
|
|
300
|
+
result = subprocess.run(
|
|
301
|
+
["uv", "run", "alembic", "downgrade", "base"],
|
|
302
|
+
capture_output=True, text=True,
|
|
303
|
+
)
|
|
304
|
+
assert result.returncode == 0, f"Downgrade failed: {result.stderr}"
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Test Migration Idempotency
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
def test_no_pending_migrations():
|
|
311
|
+
"""Ensure models and migrations are in sync."""
|
|
312
|
+
result = subprocess.run(
|
|
313
|
+
["uv", "run", "alembic", "check"],
|
|
314
|
+
capture_output=True, text=True,
|
|
315
|
+
)
|
|
316
|
+
assert result.returncode == 0, (
|
|
317
|
+
"Models and migrations are out of sync. "
|
|
318
|
+
"Run: uv run alembic revision --autogenerate -m 'sync_models'"
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Rollback Strategies
|
|
325
|
+
|
|
326
|
+
### Alembic Downgrade
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Roll back one migration
|
|
330
|
+
uv run alembic downgrade -1
|
|
331
|
+
|
|
332
|
+
# Roll back to specific revision
|
|
333
|
+
uv run alembic downgrade a1b2c3d4
|
|
334
|
+
|
|
335
|
+
# Roll back to base (empty database)
|
|
336
|
+
uv run alembic downgrade base
|
|
337
|
+
|
|
338
|
+
# Show current state
|
|
339
|
+
uv run alembic current
|
|
340
|
+
uv run alembic history --verbose
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Production Rollback Procedure
|
|
344
|
+
|
|
345
|
+
1. **Verify**: Check current revision with `alembic current`
|
|
346
|
+
2. **Test**: Run downgrade in staging first
|
|
347
|
+
3. **Downgrade**: Run `alembic downgrade -1` in production
|
|
348
|
+
4. **Deploy**: Roll back application code to match schema
|
|
349
|
+
5. **Verify**: Confirm application health after rollback
|
|
350
|
+
|
|
351
|
+
### When Downgrade Is Not Enough
|
|
352
|
+
|
|
353
|
+
For destructive migrations (dropped columns, deleted data):
|
|
354
|
+
|
|
355
|
+
- Restore from database backup
|
|
356
|
+
- Use point-in-time recovery (PostgreSQL WAL)
|
|
357
|
+
- Never deploy destructive migrations on Friday
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Commands Reference
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# Generate migration from model changes
|
|
365
|
+
uv run alembic revision --autogenerate -m "description"
|
|
366
|
+
|
|
367
|
+
# Create empty migration (for data migrations)
|
|
368
|
+
uv run alembic revision -m "description"
|
|
369
|
+
|
|
370
|
+
# Apply all pending migrations
|
|
371
|
+
uv run alembic upgrade head
|
|
372
|
+
|
|
373
|
+
# Roll back last migration
|
|
374
|
+
uv run alembic downgrade -1
|
|
375
|
+
|
|
376
|
+
# Show migration history
|
|
377
|
+
uv run alembic history --verbose
|
|
378
|
+
|
|
379
|
+
# Show current revision
|
|
380
|
+
uv run alembic current
|
|
381
|
+
|
|
382
|
+
# Check if models and migrations are in sync
|
|
383
|
+
uv run alembic check
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
_Migrations are permanent artifacts. Review every generated file. Never modify a migration after it has been applied in any shared environment._
|