proteum 2.1.9 → 2.2.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/.codex/environments/environment.toml +11 -0
- package/AGENTS.md +25 -11
- package/README.md +19 -9
- package/agents/project/AGENTS.md +165 -120
- package/agents/project/CODING_STYLE.md +1 -1
- package/agents/project/app-root/AGENTS.md +16 -0
- package/agents/project/client/AGENTS.md +5 -5
- package/agents/project/client/pages/AGENTS.md +13 -13
- package/agents/project/diagnostics.md +19 -10
- package/agents/project/optimizations.md +5 -6
- package/agents/project/root/AGENTS.md +295 -0
- package/agents/project/server/routes/AGENTS.md +2 -2
- package/agents/project/server/services/AGENTS.md +4 -2
- package/agents/project/tests/AGENTS.md +2 -2
- package/cli/app/index.ts +31 -7
- package/cli/commands/configure.ts +226 -0
- package/cli/commands/dev.ts +0 -2
- package/cli/commands/diagnose.ts +33 -1
- package/cli/commands/explain.ts +1 -1
- package/cli/commands/migrate.ts +51 -0
- package/cli/commands/orient.ts +169 -0
- package/cli/commands/perf.ts +8 -1
- package/cli/commands/verify.ts +1003 -49
- package/cli/compiler/artifacts/manifest.ts +4 -4
- package/cli/compiler/artifacts/routing.ts +2 -2
- package/cli/compiler/artifacts/services.ts +12 -3
- package/cli/compiler/client/index.ts +65 -19
- package/cli/compiler/common/files/style.ts +47 -2
- package/cli/compiler/common/generatedRouteModules.ts +31 -38
- package/cli/compiler/common/index.ts +10 -0
- package/cli/compiler/common/proteumManifest.ts +1 -0
- package/cli/compiler/server/index.ts +34 -9
- package/cli/context.ts +6 -1
- package/cli/index.ts +7 -8
- package/cli/migrate/pageContract.ts +516 -0
- package/cli/paths.ts +47 -6
- package/cli/presentation/commands.ts +100 -10
- package/cli/presentation/devSession.ts +4 -6
- package/cli/presentation/help.ts +2 -2
- package/cli/presentation/ink.ts +10 -5
- package/cli/presentation/welcome.ts +2 -4
- package/cli/runtime/commands.ts +94 -1
- package/cli/scaffold/index.ts +2 -2
- package/cli/scaffold/templates.ts +4 -2
- package/cli/utils/agents.ts +273 -58
- package/client/dev/profiler/index.tsx +3 -2
- package/client/router.ts +10 -2
- package/client/services/router/index.tsx +6 -22
- package/common/dev/connect.ts +20 -4
- package/common/dev/console.ts +7 -0
- package/common/dev/contractsDoctor.ts +354 -0
- package/common/dev/diagnostics.ts +10 -7
- package/common/dev/inspection.ts +830 -38
- package/common/dev/performance.ts +19 -5
- package/common/dev/profiler.ts +1 -0
- package/common/dev/proteumManifest.ts +5 -4
- package/common/dev/requestTrace.ts +12 -1
- package/common/router/contracts.ts +8 -11
- package/common/router/index.ts +2 -2
- package/common/router/pageData.ts +72 -0
- package/common/router/register.ts +10 -46
- package/common/router/response/page.ts +28 -16
- package/docs/dev-sessions.md +8 -4
- package/docs/diagnostics.md +77 -11
- package/docs/migrate-from-2.1.3.md +388 -0
- package/docs/request-tracing.md +25 -6
- package/package.json +6 -1
- package/scripts/update-codex-agents.ts +2 -2
- package/server/app/container/console/index.ts +11 -1
- package/server/app/container/trace/index.ts +117 -0
- package/server/app/devDiagnostics.ts +1 -1
- package/server/app/index.ts +5 -1
- package/server/services/auth/index.ts +9 -0
- package/server/services/router/index.ts +64 -14
- package/server/services/router/request/api.ts +7 -1
- package/server/services/router/response/index.ts +8 -28
- package/types/global/vendors.d.ts +12 -0
- package/types/vendors.d.ts +12 -0
- package/common/router/pageSetup.ts +0 -51
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parse } from '@babel/parser';
|
|
4
|
+
import generate from '@babel/generator';
|
|
5
|
+
import traverse, { type Binding, type NodePath } from '@babel/traverse';
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
|
|
8
|
+
import { routeOptionKeys } from '../../common/router/pageData';
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.cjs', '.mjs']);
|
|
11
|
+
const routeOptionKeysSet: ReadonlySet<string> = new Set(routeOptionKeys);
|
|
12
|
+
|
|
13
|
+
export type TPageContractManualFix = {
|
|
14
|
+
filepath: string;
|
|
15
|
+
routeLabel: string;
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type TPageContractMigrationSummary = {
|
|
22
|
+
appRoot: string;
|
|
23
|
+
changedFiles: string[];
|
|
24
|
+
dryRun: boolean;
|
|
25
|
+
manualFixes: TPageContractManualFix[];
|
|
26
|
+
scannedFiles: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type TDataAnalysis =
|
|
30
|
+
| {
|
|
31
|
+
kind: 'ok';
|
|
32
|
+
hasDataAfterMigration: boolean;
|
|
33
|
+
movedOptionProperties: t.ObjectProperty[];
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
kind: 'manual';
|
|
37
|
+
reason: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const findFiles = (dir: string): string[] => {
|
|
41
|
+
if (!fs.existsSync(dir)) return [];
|
|
42
|
+
|
|
43
|
+
const files: string[] = [];
|
|
44
|
+
|
|
45
|
+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
46
|
+
const filepath = path.join(dir, dirent.name);
|
|
47
|
+
|
|
48
|
+
if (dirent.isDirectory()) {
|
|
49
|
+
files.push(...findFiles(filepath));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (dirent.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(filepath))) files.push(filepath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return files;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const isRouterPageCall = (callPath: NodePath<t.CallExpression>) => {
|
|
60
|
+
const calleePath = callPath.get('callee');
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
calleePath.isMemberExpression() &&
|
|
64
|
+
calleePath.get('object').isIdentifier({ name: 'Router' }) &&
|
|
65
|
+
calleePath.get('property').isIdentifier({ name: 'page' })
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const unwrapExpressionPath = (expressionPath: NodePath<t.Expression>): NodePath<t.Expression> => {
|
|
70
|
+
let currentPath = expressionPath;
|
|
71
|
+
|
|
72
|
+
while (true) {
|
|
73
|
+
if (currentPath.isTSAsExpression() || currentPath.isTSTypeAssertion()) {
|
|
74
|
+
currentPath = currentPath.get('expression') as NodePath<t.Expression>;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (currentPath.isTSNonNullExpression() || currentPath.isParenthesizedExpression()) {
|
|
79
|
+
currentPath = currentPath.get('expression') as NodePath<t.Expression>;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return currentPath;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const getObjectPropertyKeyName = (property: t.ObjectProperty) => {
|
|
88
|
+
if (t.isIdentifier(property.key)) return property.key.name;
|
|
89
|
+
if (t.isStringLiteral(property.key)) return property.key.value;
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const normalizeLegacyRouteOptionKey = (key: string) => {
|
|
94
|
+
if (routeOptionKeysSet.has(key)) return key;
|
|
95
|
+
|
|
96
|
+
if (key.startsWith('_')) {
|
|
97
|
+
const normalizedKey = key.slice(1);
|
|
98
|
+
if (routeOptionKeysSet.has(normalizedKey)) return normalizedKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const createRouteOptionProperty = (property: t.ObjectProperty, normalizedKey: string) => {
|
|
105
|
+
const value = t.cloneNode(property.value, true);
|
|
106
|
+
const shorthand = t.isIdentifier(value) && value.name === normalizedKey;
|
|
107
|
+
|
|
108
|
+
return t.objectProperty(t.identifier(normalizedKey), value, false, shorthand);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const getRouteLabel = (pathNode: t.Expression) =>
|
|
112
|
+
(t.isStringLiteral(pathNode) || t.isNumericLiteral(pathNode) ? String(pathNode.value) : generate(pathNode).code).trim();
|
|
113
|
+
|
|
114
|
+
const getRouteLocation = (callPath: NodePath<t.CallExpression>) => ({
|
|
115
|
+
line: callPath.node.loc?.start.line || 1,
|
|
116
|
+
column: callPath.node.loc?.start.column ? callPath.node.loc.start.column + 1 : 1,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const resolveFunctionPathFromBinding = (binding: Binding) => {
|
|
120
|
+
if (binding.path.isFunctionDeclaration()) {
|
|
121
|
+
return binding.path as NodePath<t.FunctionDeclaration>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!binding.path.isVariableDeclarator()) return null;
|
|
125
|
+
|
|
126
|
+
const initPath = binding.path.get('init');
|
|
127
|
+
if (!initPath.node) return null;
|
|
128
|
+
|
|
129
|
+
const unwrappedInitPath = unwrapExpressionPath(initPath as NodePath<t.Expression>);
|
|
130
|
+
if (unwrappedInitPath.isFunctionExpression() || unwrappedInitPath.isArrowFunctionExpression()) return unwrappedInitPath;
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const resolveObjectExpressionFromBinding = (binding: Binding) => {
|
|
136
|
+
if (!binding.path.isVariableDeclarator()) return null;
|
|
137
|
+
|
|
138
|
+
const initPath = binding.path.get('init');
|
|
139
|
+
if (!initPath.node) return null;
|
|
140
|
+
|
|
141
|
+
const unwrappedInitPath = unwrapExpressionPath(initPath as NodePath<t.Expression>);
|
|
142
|
+
if (unwrappedInitPath.isObjectExpression()) return unwrappedInitPath;
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const classifyLegacySecondArg = (expressionPath: NodePath<t.Expression>) => {
|
|
148
|
+
const unwrappedPath = unwrapExpressionPath(expressionPath);
|
|
149
|
+
|
|
150
|
+
if (unwrappedPath.isObjectExpression()) return 'options' as const;
|
|
151
|
+
if (unwrappedPath.isArrowFunctionExpression() || unwrappedPath.isFunctionExpression()) return 'data' as const;
|
|
152
|
+
|
|
153
|
+
if (!unwrappedPath.isIdentifier()) return 'manual' as const;
|
|
154
|
+
|
|
155
|
+
const binding = expressionPath.scope.getBinding(unwrappedPath.node.name);
|
|
156
|
+
if (!binding) return 'manual' as const;
|
|
157
|
+
|
|
158
|
+
if (resolveFunctionPathFromBinding(binding)) return 'data' as const;
|
|
159
|
+
if (resolveObjectExpressionFromBinding(binding)) return 'options' as const;
|
|
160
|
+
|
|
161
|
+
return 'manual' as const;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const findReturnedObjectPath = (
|
|
165
|
+
functionPath: NodePath<t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression>,
|
|
166
|
+
) => {
|
|
167
|
+
if (functionPath.isArrowFunctionExpression() && !functionPath.get('body').isBlockStatement()) {
|
|
168
|
+
const bodyPath = unwrapExpressionPath(functionPath.get('body') as NodePath<t.Expression>);
|
|
169
|
+
if (bodyPath.isObjectExpression()) return { kind: 'ok' as const, objectPath: bodyPath };
|
|
170
|
+
|
|
171
|
+
return { kind: 'manual' as const, reason: 'Data function must directly return an object expression.' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const bodyPath = functionPath.get('body');
|
|
175
|
+
if (!bodyPath.isBlockStatement()) {
|
|
176
|
+
return { kind: 'manual' as const, reason: 'Data function must directly return an object expression.' };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let returnedObjectPath: NodePath<t.ObjectExpression> | null = null;
|
|
180
|
+
|
|
181
|
+
for (const statementPath of bodyPath.get('body')) {
|
|
182
|
+
if (!statementPath.isReturnStatement()) continue;
|
|
183
|
+
|
|
184
|
+
const argumentPath = statementPath.get('argument');
|
|
185
|
+
if (!argumentPath.node) {
|
|
186
|
+
return { kind: 'manual' as const, reason: 'Data function cannot return without an object value.' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const unwrappedArgumentPath = unwrapExpressionPath(argumentPath as NodePath<t.Expression>);
|
|
190
|
+
if (!unwrappedArgumentPath.isObjectExpression()) {
|
|
191
|
+
return { kind: 'manual' as const, reason: 'Data function must directly return an object expression.' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (returnedObjectPath) {
|
|
195
|
+
return { kind: 'manual' as const, reason: 'Data function with multiple direct returns requires a manual rewrite.' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
returnedObjectPath = unwrappedArgumentPath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!returnedObjectPath) {
|
|
202
|
+
return { kind: 'manual' as const, reason: 'Data function must directly return an object expression.' };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { kind: 'ok' as const, objectPath: returnedObjectPath };
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const analyzeFunctionPath = (
|
|
209
|
+
functionPath: NodePath<t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression>,
|
|
210
|
+
analysisCache: Map<string, TDataAnalysis>,
|
|
211
|
+
): TDataAnalysis => {
|
|
212
|
+
const cacheKey = `${functionPath.node.start || 0}:${functionPath.node.end || 0}`;
|
|
213
|
+
const cachedResult = analysisCache.get(cacheKey);
|
|
214
|
+
if (cachedResult) return cachedResult;
|
|
215
|
+
|
|
216
|
+
const objectResult = findReturnedObjectPath(functionPath);
|
|
217
|
+
if (objectResult.kind === 'manual') {
|
|
218
|
+
const result: TDataAnalysis = { kind: 'manual', reason: objectResult.reason };
|
|
219
|
+
analysisCache.set(cacheKey, result);
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const keptProperties: Array<t.ObjectProperty | t.SpreadElement> = [];
|
|
224
|
+
const movedOptionProperties: t.ObjectProperty[] = [];
|
|
225
|
+
|
|
226
|
+
for (const propertyPath of objectResult.objectPath.get('properties')) {
|
|
227
|
+
if (propertyPath.isSpreadElement()) {
|
|
228
|
+
const result: TDataAnalysis = {
|
|
229
|
+
kind: 'manual',
|
|
230
|
+
reason: 'Data function using object spreads requires a manual rewrite.',
|
|
231
|
+
};
|
|
232
|
+
analysisCache.set(cacheKey, result);
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!propertyPath.isObjectProperty() || propertyPath.node.computed) {
|
|
237
|
+
const result: TDataAnalysis = {
|
|
238
|
+
kind: 'manual',
|
|
239
|
+
reason: 'Data function must return a plain object with explicit property keys.',
|
|
240
|
+
};
|
|
241
|
+
analysisCache.set(cacheKey, result);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const propertyKeyName = getObjectPropertyKeyName(propertyPath.node);
|
|
246
|
+
if (!propertyKeyName) {
|
|
247
|
+
const result: TDataAnalysis = {
|
|
248
|
+
kind: 'manual',
|
|
249
|
+
reason: 'Data function must return a plain object with explicit property keys.',
|
|
250
|
+
};
|
|
251
|
+
analysisCache.set(cacheKey, result);
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const normalizedRouteOptionKey = normalizeLegacyRouteOptionKey(propertyKeyName);
|
|
256
|
+
if (normalizedRouteOptionKey) {
|
|
257
|
+
movedOptionProperties.push(createRouteOptionProperty(propertyPath.node, normalizedRouteOptionKey));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
keptProperties.push(t.cloneNode(propertyPath.node, true));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (movedOptionProperties.length > 0) objectResult.objectPath.node.properties = keptProperties;
|
|
265
|
+
|
|
266
|
+
const result: TDataAnalysis = {
|
|
267
|
+
kind: 'ok',
|
|
268
|
+
hasDataAfterMigration: keptProperties.length > 0,
|
|
269
|
+
movedOptionProperties,
|
|
270
|
+
};
|
|
271
|
+
analysisCache.set(cacheKey, result);
|
|
272
|
+
return result;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const analyzeDataExpression = (
|
|
276
|
+
expressionPath: NodePath<t.Expression>,
|
|
277
|
+
analysisCache: Map<string, TDataAnalysis>,
|
|
278
|
+
): TDataAnalysis => {
|
|
279
|
+
const unwrappedPath = unwrapExpressionPath(expressionPath);
|
|
280
|
+
|
|
281
|
+
if (unwrappedPath.isNullLiteral()) {
|
|
282
|
+
return {
|
|
283
|
+
kind: 'ok',
|
|
284
|
+
hasDataAfterMigration: false,
|
|
285
|
+
movedOptionProperties: [],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (unwrappedPath.isArrowFunctionExpression() || unwrappedPath.isFunctionExpression()) {
|
|
290
|
+
return analyzeFunctionPath(unwrappedPath, analysisCache);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!unwrappedPath.isIdentifier()) {
|
|
294
|
+
return {
|
|
295
|
+
kind: 'manual',
|
|
296
|
+
reason: 'Data provider must be a local function expression/reference or null.',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const binding = expressionPath.scope.getBinding(unwrappedPath.node.name);
|
|
301
|
+
if (!binding) {
|
|
302
|
+
return {
|
|
303
|
+
kind: 'manual',
|
|
304
|
+
reason: `Could not resolve local data provider "${unwrappedPath.node.name}".`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const functionPath = resolveFunctionPathFromBinding(binding);
|
|
309
|
+
if (!functionPath) {
|
|
310
|
+
return {
|
|
311
|
+
kind: 'manual',
|
|
312
|
+
reason: `Data provider "${unwrappedPath.node.name}" is not a directly analyzable local function.`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return analyzeFunctionPath(functionPath, analysisCache);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const cloneObjectProperties = (properties: t.ObjectProperty[]) =>
|
|
320
|
+
properties.map((property) => t.cloneNode(property, true));
|
|
321
|
+
|
|
322
|
+
const buildNextOptionsExpression = (optionsNode: t.Expression, movedOptionProperties: t.ObjectProperty[]) => {
|
|
323
|
+
if (movedOptionProperties.length === 0) return t.cloneNode(optionsNode, true);
|
|
324
|
+
|
|
325
|
+
if (t.isObjectExpression(optionsNode) && optionsNode.properties.length === 0) {
|
|
326
|
+
return t.objectExpression(cloneObjectProperties(movedOptionProperties));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return t.objectExpression([
|
|
330
|
+
t.spreadElement(t.cloneNode(optionsNode, true)),
|
|
331
|
+
...cloneObjectProperties(movedOptionProperties),
|
|
332
|
+
]);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const transformFile = (filepath: string) => {
|
|
336
|
+
const source = fs.readFileSync(filepath, 'utf8');
|
|
337
|
+
if (!source.includes('Router.page(')) {
|
|
338
|
+
return {
|
|
339
|
+
changed: false,
|
|
340
|
+
manualFixes: [] as TPageContractManualFix[],
|
|
341
|
+
output: source,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const ast = parse(source, {
|
|
346
|
+
sourceType: 'module',
|
|
347
|
+
errorRecovery: true,
|
|
348
|
+
plugins: ['typescript', 'jsx', 'decorators-legacy', 'classProperties'],
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const routeCalls: NodePath<t.CallExpression>[] = [];
|
|
352
|
+
const manualFixes: TPageContractManualFix[] = [];
|
|
353
|
+
const analysisCache = new Map<string, TDataAnalysis>();
|
|
354
|
+
|
|
355
|
+
traverse(ast, {
|
|
356
|
+
CallExpression(callPath: NodePath<t.CallExpression>) {
|
|
357
|
+
if (!isRouterPageCall(callPath)) return;
|
|
358
|
+
routeCalls.push(callPath);
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
let mutated = false;
|
|
363
|
+
|
|
364
|
+
for (const callPath of routeCalls) {
|
|
365
|
+
const argumentPaths = callPath.get('arguments') as NodePath<t.Expression>[];
|
|
366
|
+
if (argumentPaths.length < 2 || argumentPaths.length > 4) {
|
|
367
|
+
const pathArgument = argumentPaths[0]?.node;
|
|
368
|
+
const routeLabel = pathArgument ? getRouteLabel(pathArgument) : '(unknown route)';
|
|
369
|
+
const location = getRouteLocation(callPath);
|
|
370
|
+
|
|
371
|
+
manualFixes.push({
|
|
372
|
+
filepath,
|
|
373
|
+
routeLabel,
|
|
374
|
+
line: location.line,
|
|
375
|
+
column: location.column,
|
|
376
|
+
reason: 'Unsupported Router.page signature.',
|
|
377
|
+
});
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const pathArgument = argumentPaths[0].node;
|
|
382
|
+
const routeLabel = getRouteLabel(pathArgument);
|
|
383
|
+
const location = getRouteLocation(callPath);
|
|
384
|
+
|
|
385
|
+
let optionsNode: t.Expression;
|
|
386
|
+
let dataPath: NodePath<t.Expression> | null;
|
|
387
|
+
let renderNode: t.Expression;
|
|
388
|
+
const isExplicitSignature = argumentPaths.length === 4;
|
|
389
|
+
|
|
390
|
+
if (argumentPaths.length === 2) {
|
|
391
|
+
optionsNode = t.objectExpression([]);
|
|
392
|
+
dataPath = null;
|
|
393
|
+
renderNode = t.cloneNode(argumentPaths[1].node, true);
|
|
394
|
+
} else if (argumentPaths.length === 3) {
|
|
395
|
+
const secondArgKind = classifyLegacySecondArg(argumentPaths[1]);
|
|
396
|
+
if (secondArgKind === 'manual') {
|
|
397
|
+
manualFixes.push({
|
|
398
|
+
filepath,
|
|
399
|
+
routeLabel,
|
|
400
|
+
line: location.line,
|
|
401
|
+
column: location.column,
|
|
402
|
+
reason: 'Could not classify the legacy second Router.page argument as options or data.',
|
|
403
|
+
});
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (secondArgKind === 'options') {
|
|
408
|
+
optionsNode = t.cloneNode(argumentPaths[1].node, true);
|
|
409
|
+
dataPath = null;
|
|
410
|
+
renderNode = t.cloneNode(argumentPaths[2].node, true);
|
|
411
|
+
} else {
|
|
412
|
+
optionsNode = t.objectExpression([]);
|
|
413
|
+
dataPath = argumentPaths[1];
|
|
414
|
+
renderNode = t.cloneNode(argumentPaths[2].node, true);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
optionsNode = t.cloneNode(argumentPaths[1].node, true);
|
|
418
|
+
dataPath = argumentPaths[2];
|
|
419
|
+
renderNode = t.cloneNode(argumentPaths[3].node, true);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let dataNode: t.Expression = t.nullLiteral();
|
|
423
|
+
let movedOptionProperties: t.ObjectProperty[] = [];
|
|
424
|
+
let shouldRewrite = !isExplicitSignature;
|
|
425
|
+
|
|
426
|
+
if (dataPath) {
|
|
427
|
+
const analysis = analyzeDataExpression(dataPath, analysisCache);
|
|
428
|
+
if (analysis.kind === 'manual') {
|
|
429
|
+
if (isExplicitSignature) continue;
|
|
430
|
+
|
|
431
|
+
manualFixes.push({
|
|
432
|
+
filepath,
|
|
433
|
+
routeLabel,
|
|
434
|
+
line: location.line,
|
|
435
|
+
column: location.column,
|
|
436
|
+
reason: analysis.reason,
|
|
437
|
+
});
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
movedOptionProperties = analysis.movedOptionProperties;
|
|
442
|
+
dataNode = analysis.hasDataAfterMigration ? t.cloneNode(dataPath.node, true) : t.nullLiteral();
|
|
443
|
+
shouldRewrite =
|
|
444
|
+
shouldRewrite ||
|
|
445
|
+
movedOptionProperties.length > 0 ||
|
|
446
|
+
(!analysis.hasDataAfterMigration && !unwrapExpressionPath(dataPath).isNullLiteral());
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (!shouldRewrite) continue;
|
|
450
|
+
|
|
451
|
+
callPath.node.arguments = [
|
|
452
|
+
t.cloneNode(pathArgument, true),
|
|
453
|
+
buildNextOptionsExpression(optionsNode, movedOptionProperties),
|
|
454
|
+
dataNode,
|
|
455
|
+
renderNode,
|
|
456
|
+
];
|
|
457
|
+
mutated = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (manualFixes.length > 0) {
|
|
461
|
+
return {
|
|
462
|
+
changed: false,
|
|
463
|
+
manualFixes,
|
|
464
|
+
output: source,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!mutated) {
|
|
469
|
+
return {
|
|
470
|
+
changed: false,
|
|
471
|
+
manualFixes,
|
|
472
|
+
output: source,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
changed: true,
|
|
478
|
+
manualFixes,
|
|
479
|
+
output: generate(ast, {
|
|
480
|
+
comments: true,
|
|
481
|
+
compact: false,
|
|
482
|
+
retainLines: false,
|
|
483
|
+
}).code,
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export const runPageContractMigration = ({
|
|
488
|
+
appRoot,
|
|
489
|
+
dryRun,
|
|
490
|
+
}: {
|
|
491
|
+
appRoot: string;
|
|
492
|
+
dryRun: boolean;
|
|
493
|
+
}): TPageContractMigrationSummary => {
|
|
494
|
+
const pagesRoot = path.join(appRoot, 'client', 'pages');
|
|
495
|
+
const changedFiles: string[] = [];
|
|
496
|
+
const manualFixes: TPageContractManualFix[] = [];
|
|
497
|
+
const files = findFiles(pagesRoot);
|
|
498
|
+
|
|
499
|
+
for (const filepath of files) {
|
|
500
|
+
const result = transformFile(filepath);
|
|
501
|
+
manualFixes.push(...result.manualFixes);
|
|
502
|
+
|
|
503
|
+
if (!result.changed) continue;
|
|
504
|
+
|
|
505
|
+
changedFiles.push(filepath);
|
|
506
|
+
if (!dryRun) fs.writeFileSync(filepath, result.output);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
appRoot,
|
|
511
|
+
changedFiles,
|
|
512
|
+
dryRun,
|
|
513
|
+
manualFixes,
|
|
514
|
+
scannedFiles: files.length,
|
|
515
|
+
};
|
|
516
|
+
};
|
package/cli/paths.ts
CHANGED
|
@@ -114,6 +114,20 @@ const findVisibleNodeModulesRoot = (startPath: string): string | undefined => {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
const findVisibleNodeModulesRoots = (startPath: string): string[] => {
|
|
118
|
+
const roots: string[] = [];
|
|
119
|
+
let currentPath = path.resolve(startPath);
|
|
120
|
+
|
|
121
|
+
while (true) {
|
|
122
|
+
const candidate = path.join(currentPath, 'node_modules');
|
|
123
|
+
if (fs.existsSync(candidate) && !roots.includes(candidate)) roots.push(candidate);
|
|
124
|
+
|
|
125
|
+
const parentPath = path.dirname(currentPath);
|
|
126
|
+
if (parentPath === currentPath) return roots;
|
|
127
|
+
currentPath = parentPath;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
117
131
|
const findVisiblePackageInstall = (startPath: string, packageName: string): string | undefined => {
|
|
118
132
|
let currentPath = path.resolve(startPath);
|
|
119
133
|
|
|
@@ -127,6 +141,20 @@ const findVisiblePackageInstall = (startPath: string, packageName: string): stri
|
|
|
127
141
|
}
|
|
128
142
|
};
|
|
129
143
|
|
|
144
|
+
const findVisiblePackageInstalls = (startPath: string, packageName: string): string[] => {
|
|
145
|
+
const installs: string[] = [];
|
|
146
|
+
let currentPath = path.resolve(startPath);
|
|
147
|
+
|
|
148
|
+
while (true) {
|
|
149
|
+
const candidate = path.join(currentPath, 'node_modules', packageName);
|
|
150
|
+
if (fs.existsSync(candidate) && !installs.includes(candidate)) installs.push(candidate);
|
|
151
|
+
|
|
152
|
+
const parentPath = path.dirname(currentPath);
|
|
153
|
+
if (parentPath === currentPath) return installs;
|
|
154
|
+
currentPath = parentPath;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
130
158
|
const resolveCoreRoot = (appRoot: string): string => {
|
|
131
159
|
const currentPackageRoot = path.resolve(__dirname, '..');
|
|
132
160
|
const currentBin = path.join(currentPackageRoot, 'cli', 'bin.js');
|
|
@@ -346,9 +374,18 @@ export default class Paths {
|
|
|
346
374
|
return [
|
|
347
375
|
this.framework.activeRoot,
|
|
348
376
|
...(this.framework.installedRoot ? [this.framework.installedRoot] : []),
|
|
377
|
+
...this.getVisiblePackageInstallRoots('proteum'),
|
|
349
378
|
].filter((rootPath, index, list) => list.indexOf(rootPath) === index && fs.existsSync(rootPath));
|
|
350
379
|
}
|
|
351
380
|
|
|
381
|
+
public getVisibleNodeModulesRootsForPath(startPath: string): string[] {
|
|
382
|
+
return findVisibleNodeModulesRoots(startPath);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public getVisiblePackageInstallRoots(packageName: string, startPath = this.appRoot): string[] {
|
|
386
|
+
return findVisiblePackageInstalls(startPath, packageName);
|
|
387
|
+
}
|
|
388
|
+
|
|
352
389
|
public getFrameworkInstallRoot(): string {
|
|
353
390
|
return this.getFrameworkInstallRootForAppRoot(this.appRoot);
|
|
354
391
|
}
|
|
@@ -419,16 +456,20 @@ export default class Paths {
|
|
|
419
456
|
}
|
|
420
457
|
|
|
421
458
|
public resolvePackageRoot(packageName: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
: [this.framework.activeRoot
|
|
459
|
+
const installedRoot = this.framework.installedRoot;
|
|
460
|
+
const searchPaths = (preferApp
|
|
461
|
+
? [this.appRoot, ...(installedRoot ? [installedRoot] : []), this.framework.activeRoot]
|
|
462
|
+
: [this.framework.activeRoot, ...(installedRoot ? [installedRoot] : []), this.appRoot]
|
|
463
|
+
).filter((searchPath, index, list) => list.indexOf(searchPath) === index);
|
|
425
464
|
return path.dirname(resolvePackageJsonPath(packageName, searchPaths));
|
|
426
465
|
}
|
|
427
466
|
|
|
428
467
|
public resolveRequest(request: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
: [this.framework.activeRoot
|
|
468
|
+
const installedRoot = this.framework.installedRoot;
|
|
469
|
+
const searchPaths = (preferApp
|
|
470
|
+
? [this.appRoot, ...(installedRoot ? [installedRoot] : []), this.framework.activeRoot]
|
|
471
|
+
: [this.framework.activeRoot, ...(installedRoot ? [installedRoot] : []), this.appRoot]
|
|
472
|
+
).filter((searchPath, index, list) => list.indexOf(searchPath) === index);
|
|
432
473
|
return require.resolve(request, { paths: searchPaths });
|
|
433
474
|
}
|
|
434
475
|
|