Repository navigation
Expand file tree
/
Copy pathpatch_backend_api.py
More file actions
192 lines (163 loc) · 6.58 KB
/
Copy pathpatch_backend_api.py
File metadata and controls
192 lines (163 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/usr/bin/env python3
"""One-time backend API patch script.
This script exists to apply a specific set of route-handler source files that were
written during an earlier migration/recovery effort. It is not invoked by any
`package.json` script and it is not part of the normal development or CI workflow.
Danger:
Running this script without care will overwrite entire route files with the
embedded (now potentially stale) content. On a post-migration codebase this
can silently revert real fixes.
Safety guards:
- By default the script runs in dry-run mode and prints a unified diff for each
target file. Nothing is written to disk.
- Writing requires the explicit `--force` flag.
- A `--dry-run` flag is also accepted for explicitness.
Usage:
python scripts/patch_backend_api.py # dry-run (default)
python scripts/patch_backend_api.py --dry-run
python scripts/patch_backend_api.py --force # writes files to disk
python scripts/patch_backend_api.py --force --file src/app/api/marketplace/listings/route.ts
"""
from __future__ import annotations
import argparse
import difflib
import sys
import textwrap
from pathlib import Path
REPLACEMENTS: dict[str, str] = {
"src/app/api/marketplace/listings/route.ts": '''import { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { withApiHandler } from '@/lib/backend/withApiHandler';
import { getMarketplaceListings } from '@/lib/backend/marketplace';
export const GET = withApiHandler(async (_req: NextRequest) => {
const listings = await getMarketplaceListings();
return NextResponse.json({ listings });
});
''',
"src/app/api/marketplace/listings/[id]/route.ts": '''import { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { withApiHandler } from '@/lib/backend/withApiHandler';
import { getMarketplaceListing } from '@/lib/backend/marketplace';
export const GET = withApiHandler(async (_req: NextRequest, { params }: { params: { id: string } }) => {
const listing = await getMarketplaceListing(params.id);
if (!listing) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json({ listing });
});
''',
"src/app/api/commitments/route.ts": '''import { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { withApiHandler } from '@/lib/backend/withApiHandler';
import { listCommitments, createCommitment } from '@/lib/backend/commitments';
export const GET = withApiHandler(async (_req: NextRequest) => {
const commitments = await listCommitments();
return NextResponse.json({ commitments });
});
export const POST = withApiHandler(async (req: NextRequest) => {
const body = await req.json();
const commitment = await createCommitment(body);
return NextResponse.json({ commitment }, { status: 201 });
});
''',
"src/app/api/commitments/[id]/route.ts": '''
import { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { withApiHandler } from '@/lib/backend/withApiHandler';
import { getCommitment } from '@/lib/backend/commitments';
export const GET = withApiHandler(async (_req: NextRequest, { params }: { params: { id: string } }) => {
const commitment = await getCommitment(params.id);
if (!commitment) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json({ commitment });
});
''',
}
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
'Apply the one-time backend API route-handler patch. '
'Defaults to a dry run that prints a diff.'
),
epilog=(
'This script is intended for targeted migration or recovery work only. '
'It overwrites entire route files with embedded content and should '
'not be run against a post-migration codebase without reviewing '
'the diff first.'
),
)
parser.add_argument(
'--force',
action='store_true',
help='Write files to disk. Without this flag the script only prints a diff.',
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Explicitly request a dry run (the default).',
)
parser.add_argument(
'--file',
action='append',
default=[],
metavar='PATH',
help='Limit the patch to the given repository-relative file path.',
)
return parser
def _resolve_targets(repo_root: Path, selected: list[str]) -> dict[str, str]:
if not selected:
return dict(REPLACEMENTS)
unknown = [path for path in selected if path not in REPLACEMENTS]
if unknown:
joined = ', '.join(unknown)
raise SystemExit(f'Unknown target file(s): {joined}')
return {path: REPLACEMENTS[path] for path in selected}
def _print_diff(rel_path: str, current: str, desired: str) -> bool:
if current == desired:
print(f'--- {rel_path}: already up to date')
return False
diff = difflib.unified_diff(
current.splitlines(keepends=True),
desired.splitlines(keepends=True),
fromfile=f,'a/{rel_path}',
tofile=f,'b/{rel_path}',
)
print(''.join(diff))
return True
def main(argv: list[str] | None = None) -> int:
args = _build_parser().parse_args(argv)
repo_root = Path(__file__).resolve().parent.parent
targets = _resolve_targets(repo_root, args.file)
write_mode = bool(args.force) and not bool(args.dry_run)
if args.force and args.dry_run:
print('Note: --force and --dry-run were both passed; dry run wins.')
mode = 'write' if write_mode else 'dry-run'
print(f'[patch_backend_api] mode={mode} targets={len(targets)}')
print('-' * 72)
changed = 0
for rel_path, desired in sorted(targets.items()):
abs_path = repo_root / rel_path
current = abs_path.read_text(encoding='utf-8') if abs_path.exists() else ''
differs = _print_diff(rel_path, current, desired)
if not differs:
continue
changed += 1
if write_mode:
abs_path.parent.mkdir(parents=True, exist_ok=True)
abs_path.write_text(desired, encoding='utf-8')
print(f'[write] {rel_path}')
print('-' * 72)
if write_mode:
print(f'Applied {changed} file change(s).')
else:
print(
textwrap.fill(
f'Dry run: would update {changed} file(s). '
'Re-run with --force to write these changes to disk.',
width=72,
)
)
return 0
if __name__ == '__main__':
raise SystemExit(main())