proteum 2.1.9 → 2.2.1

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 (84) hide show
  1. package/.codex/environments/environment.toml +11 -0
  2. package/AGENTS.md +27 -11
  3. package/README.md +30 -11
  4. package/agents/project/AGENTS.md +172 -123
  5. package/agents/project/CODING_STYLE.md +1 -1
  6. package/agents/project/app-root/AGENTS.md +16 -0
  7. package/agents/project/client/AGENTS.md +5 -5
  8. package/agents/project/client/pages/AGENTS.md +13 -13
  9. package/agents/project/diagnostics.md +19 -10
  10. package/agents/project/optimizations.md +5 -6
  11. package/agents/project/root/AGENTS.md +297 -0
  12. package/agents/project/server/routes/AGENTS.md +2 -2
  13. package/agents/project/server/services/AGENTS.md +4 -2
  14. package/agents/project/tests/AGENTS.md +9 -2
  15. package/cli/app/index.ts +31 -7
  16. package/cli/commands/configure.ts +226 -0
  17. package/cli/commands/dev.ts +0 -2
  18. package/cli/commands/diagnose.ts +33 -1
  19. package/cli/commands/explain.ts +1 -1
  20. package/cli/commands/migrate.ts +51 -0
  21. package/cli/commands/orient.ts +169 -0
  22. package/cli/commands/perf.ts +8 -1
  23. package/cli/commands/verify.ts +1003 -49
  24. package/cli/compiler/artifacts/manifest.ts +4 -4
  25. package/cli/compiler/artifacts/routing.ts +2 -2
  26. package/cli/compiler/artifacts/services.ts +12 -3
  27. package/cli/compiler/client/index.ts +65 -19
  28. package/cli/compiler/common/files/style.ts +47 -2
  29. package/cli/compiler/common/generatedRouteModules.ts +31 -38
  30. package/cli/compiler/common/index.ts +10 -0
  31. package/cli/compiler/common/proteumManifest.ts +1 -0
  32. package/cli/compiler/server/index.ts +34 -9
  33. package/cli/context.ts +6 -1
  34. package/cli/index.ts +7 -8
  35. package/cli/migrate/pageContract.ts +516 -0
  36. package/cli/paths.ts +47 -6
  37. package/cli/presentation/commands.ts +100 -10
  38. package/cli/presentation/devSession.ts +4 -6
  39. package/cli/presentation/help.ts +2 -2
  40. package/cli/presentation/ink.ts +10 -5
  41. package/cli/presentation/welcome.ts +2 -4
  42. package/cli/runtime/commands.ts +94 -1
  43. package/cli/scaffold/index.ts +2 -2
  44. package/cli/scaffold/templates.ts +4 -2
  45. package/cli/utils/agents.ts +273 -58
  46. package/client/dev/profiler/index.tsx +3 -2
  47. package/client/router.ts +10 -2
  48. package/client/services/router/index.tsx +6 -22
  49. package/common/dev/connect.ts +20 -4
  50. package/common/dev/console.ts +7 -0
  51. package/common/dev/contractsDoctor.ts +354 -0
  52. package/common/dev/diagnostics.ts +10 -7
  53. package/common/dev/inspection.ts +830 -38
  54. package/common/dev/performance.ts +19 -5
  55. package/common/dev/profiler.ts +1 -0
  56. package/common/dev/proteumManifest.ts +5 -4
  57. package/common/dev/requestTrace.ts +78 -1
  58. package/common/env/proteumEnv.ts +10 -3
  59. package/common/router/contracts.ts +8 -11
  60. package/common/router/index.ts +2 -2
  61. package/common/router/pageData.ts +72 -0
  62. package/common/router/register.ts +10 -46
  63. package/common/router/response/page.ts +28 -16
  64. package/docs/assets/unique-domains-chip.png +0 -0
  65. package/docs/dev-sessions.md +8 -4
  66. package/docs/diagnostics.md +77 -11
  67. package/docs/migrate-from-2.1.3.md +388 -0
  68. package/docs/request-tracing.md +42 -9
  69. package/package.json +6 -1
  70. package/scripts/update-codex-agents.ts +2 -2
  71. package/server/app/container/console/index.ts +11 -1
  72. package/server/app/container/trace/index.ts +370 -72
  73. package/server/app/devDiagnostics.ts +1 -1
  74. package/server/app/index.ts +5 -1
  75. package/server/services/auth/index.ts +9 -0
  76. package/server/services/prisma/index.ts +15 -12
  77. package/server/services/router/http/index.ts +1 -1
  78. package/server/services/router/index.ts +105 -23
  79. package/server/services/router/request/api.ts +7 -1
  80. package/server/services/router/request/index.ts +2 -1
  81. package/server/services/router/response/index.ts +8 -28
  82. package/types/global/vendors.d.ts +12 -0
  83. package/types/vendors.d.ts +12 -0
  84. package/common/router/pageSetup.ts +0 -51
@@ -12,17 +12,41 @@ import { logVerbose } from '../runtime/verbose';
12
12
  ----------------------------------*/
13
13
 
14
14
  type TProjectInstructionArgs = { coreRoot: string };
15
- type TEnsureProjectAgentSymlinksArgs = { appRoot: string; coreRoot: string };
15
+ type TConfigureProjectAgentSymlinksArgs = {
16
+ appRoot: string;
17
+ coreRoot: string;
18
+ dryRun?: boolean;
19
+ monorepoRoot?: string;
20
+ overwriteBlockedPaths?: string[];
21
+ };
16
22
 
17
23
  type TAgentLinkDefinition = { projectPath: string; sourcePath: string; ensureParentDir?: boolean };
18
24
 
25
+ type TEnsureSymlinksResult = {
26
+ blocked: string[];
27
+ created: string[];
28
+ overwritten: string[];
29
+ skipped: string[];
30
+ updated: string[];
31
+ };
32
+
33
+ export type TConfigureProjectAgentSymlinksResult = {
34
+ appRoot: string;
35
+ blocked: string[];
36
+ created: string[];
37
+ monorepoRoot?: string;
38
+ mode: 'monorepo' | 'standalone';
39
+ overwritten: string[];
40
+ skipped: string[];
41
+ updated: string[];
42
+ updatedGitignores: string[];
43
+ };
44
+
19
45
  /*----------------------------------
20
46
  - CONSTANTS
21
47
  ----------------------------------*/
22
48
 
23
- // Project-local instruction entrypoints mapped to their canonical shipped source files.
24
- const projectAgentLinkDefinitions: TAgentLinkDefinition[] = [
25
- { projectPath: 'AGENTS.md', sourcePath: 'AGENTS.md' },
49
+ const sharedAppAgentLinkDefinitions: TAgentLinkDefinition[] = [
26
50
  { projectPath: 'CODING_STYLE.md', sourcePath: 'CODING_STYLE.md' },
27
51
  { projectPath: 'diagnostics.md', sourcePath: 'diagnostics.md' },
28
52
  { projectPath: 'optimizations.md', sourcePath: 'optimizations.md' },
@@ -35,6 +59,21 @@ const projectAgentLinkDefinitions: TAgentLinkDefinition[] = [
35
59
  { projectPath: path.join('server', 'routes', 'AGENTS.md'), sourcePath: path.join('server', 'routes', 'AGENTS.md') },
36
60
  { projectPath: path.join('tests', 'e2e', 'AGENTS.md'), sourcePath: path.join('tests', 'AGENTS.md') },
37
61
  ];
62
+
63
+ const standaloneAppAgentLinkDefinitions: TAgentLinkDefinition[] = [
64
+ { projectPath: 'AGENTS.md', sourcePath: 'AGENTS.md' },
65
+ ...sharedAppAgentLinkDefinitions,
66
+ ];
67
+
68
+ const monorepoAppAgentLinkDefinitions: TAgentLinkDefinition[] = [
69
+ { projectPath: 'AGENTS.md', sourcePath: path.join('app-root', 'AGENTS.md') },
70
+ ...sharedAppAgentLinkDefinitions,
71
+ ];
72
+
73
+ const monorepoRootAgentLinkDefinitions: TAgentLinkDefinition[] = [
74
+ { projectPath: 'AGENTS.md', sourcePath: path.join('root', 'AGENTS.md') },
75
+ ];
76
+
38
77
  const projectInstructionGitignoreBlockStart = '# Proteum-managed instruction symlinks';
39
78
  const projectInstructionGitignoreBlockEnd = '# End Proteum-managed instruction symlinks';
40
79
 
@@ -42,71 +81,130 @@ const projectInstructionGitignoreBlockEnd = '# End Proteum-managed instruction s
42
81
  - PUBLIC API
43
82
  ----------------------------------*/
44
83
 
45
- export function ensureProjectAgentSymlinks({ appRoot, coreRoot }: TEnsureProjectAgentSymlinksArgs) {
46
- ensureSymlinks(appRoot, getProjectAgentLinkDefinitions({ coreRoot }), '[agents]');
47
- ensureSymlinks(appRoot, getProjectSkillLinkDefinitions({ coreRoot }), '[skills]');
48
- ensureProjectInstructionGitignoreEntries({ appRoot, coreRoot });
49
- }
84
+ export function configureProjectAgentSymlinks({
85
+ appRoot,
86
+ coreRoot,
87
+ dryRun = false,
88
+ monorepoRoot,
89
+ overwriteBlockedPaths = [],
90
+ }: TConfigureProjectAgentSymlinksArgs): TConfigureProjectAgentSymlinksResult {
91
+ const normalizedAppRoot = path.resolve(appRoot);
92
+ const normalizedMonorepoRoot = monorepoRoot ? path.resolve(monorepoRoot) : undefined;
93
+ const normalizedOverwriteBlockedPaths = new Set(
94
+ overwriteBlockedPaths.map((blockedPath) => normalizeAbsolutePath(path.resolve(blockedPath))),
95
+ );
96
+ const mode =
97
+ normalizedMonorepoRoot && normalizedMonorepoRoot !== normalizedAppRoot ? ('monorepo' as const) : ('standalone' as const);
98
+ const result: TConfigureProjectAgentSymlinksResult = {
99
+ appRoot: normalizedAppRoot,
100
+ blocked: [],
101
+ created: [],
102
+ mode,
103
+ overwritten: [],
104
+ skipped: [],
105
+ updated: [],
106
+ updatedGitignores: [],
107
+ };
108
+
109
+ if (mode === 'monorepo' && normalizedMonorepoRoot) {
110
+ result.monorepoRoot = normalizedMonorepoRoot;
111
+
112
+ const rootLinks = getRootAgentLinkDefinitions({ coreRoot });
113
+ const rootSymlinks = ensureSymlinks(normalizedMonorepoRoot, rootLinks, '[agents]', path.join(coreRoot, 'agents', 'project'), {
114
+ dryRun,
115
+ overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
116
+ });
117
+ mergeSymlinkResults(result, rootSymlinks, normalizedMonorepoRoot);
118
+
119
+ if (!dryRun && ensureInstructionGitignoreEntries({ rootDir: normalizedMonorepoRoot, linkDefinitions: rootLinks }))
120
+ result.updatedGitignores.push(path.join(normalizedMonorepoRoot, '.gitignore'));
121
+ }
50
122
 
51
- export function getProjectInstructionGitignoreEntries({ coreRoot }: TProjectInstructionArgs) {
52
- const entries = new Set<string>();
123
+ const appLinks = getAppAgentLinkDefinitions({ coreRoot, mode });
124
+ const appSymlinks = ensureSymlinks(normalizedAppRoot, appLinks, '[agents]', path.join(coreRoot, 'agents', 'project'), {
125
+ dryRun,
126
+ overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
127
+ });
128
+ mergeSymlinkResults(result, appSymlinks, normalizedAppRoot);
53
129
 
54
- for (const linkDefinition of [
55
- ...getProjectAgentLinkDefinitions({ coreRoot }),
56
- ...getProjectSkillLinkDefinitions({ coreRoot }),
57
- ]) {
58
- entries.add(`/${normalizeProjectPathForGitignore(linkDefinition.projectPath)}`);
59
- }
130
+ if (!dryRun && ensureInstructionGitignoreEntries({ rootDir: normalizedAppRoot, linkDefinitions: appLinks }))
131
+ result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
60
132
 
61
- return Array.from(entries);
133
+ return result;
134
+ }
135
+
136
+ export function getProjectInstructionGitignoreEntries({ coreRoot }: TProjectInstructionArgs) {
137
+ return Array.from(
138
+ new Set(
139
+ getAppAgentLinkDefinitions({ coreRoot, mode: 'standalone' }).map((linkDefinition) =>
140
+ `/${normalizeProjectPathForGitignore(linkDefinition.projectPath)}`,
141
+ ),
142
+ ),
143
+ );
62
144
  }
63
145
 
64
146
  export function renderProjectInstructionGitignoreBlock({ coreRoot }: TProjectInstructionArgs) {
65
- return [
66
- projectInstructionGitignoreBlockStart,
67
- ...getProjectInstructionGitignoreEntries({ coreRoot }),
68
- projectInstructionGitignoreBlockEnd,
69
- ].join('\n');
147
+ return renderInstructionGitignoreBlock({ linkDefinitions: getAppAgentLinkDefinitions({ coreRoot, mode: 'standalone' }) });
70
148
  }
71
149
 
72
150
  /*----------------------------------
73
151
  - HELPERS
74
152
  ----------------------------------*/
75
153
 
76
- function getProjectAgentLinkDefinitions({ coreRoot }: TProjectInstructionArgs) {
154
+ function getAppAgentLinkDefinitions({
155
+ coreRoot,
156
+ mode,
157
+ }: TProjectInstructionArgs & { mode: 'monorepo' | 'standalone' }) {
77
158
  const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
159
+ const sourceDefinitions = mode === 'monorepo' ? monorepoAppAgentLinkDefinitions : standaloneAppAgentLinkDefinitions;
160
+
161
+ return resolveAgentLinkDefinitions({
162
+ agentSourceRoot,
163
+ linkDefinitions: sourceDefinitions,
164
+ });
165
+ }
166
+
167
+ function getRootAgentLinkDefinitions({ coreRoot }: TProjectInstructionArgs) {
168
+ return resolveAgentLinkDefinitions({
169
+ agentSourceRoot: path.join(coreRoot, 'agents', 'project'),
170
+ linkDefinitions: monorepoRootAgentLinkDefinitions,
171
+ });
172
+ }
78
173
 
79
- return projectAgentLinkDefinitions.map((linkDefinition) => ({
174
+ function resolveAgentLinkDefinitions({
175
+ agentSourceRoot,
176
+ linkDefinitions,
177
+ }: {
178
+ agentSourceRoot: string;
179
+ linkDefinitions: TAgentLinkDefinition[];
180
+ }) {
181
+ return linkDefinitions.map((linkDefinition) => ({
80
182
  ...linkDefinition,
81
183
  sourcePath: path.join(agentSourceRoot, linkDefinition.sourcePath),
82
184
  }));
83
185
  }
84
186
 
85
- function getProjectSkillLinkDefinitions({ coreRoot }: TProjectInstructionArgs) {
86
- const frameworkSkillsRoot = path.join(coreRoot, 'skills');
87
- if (!fs.existsSync(frameworkSkillsRoot)) return [];
187
+ function renderInstructionGitignoreBlock({ linkDefinitions }: { linkDefinitions: TAgentLinkDefinition[] }) {
188
+ const entries = Array.from(
189
+ new Set(linkDefinitions.map((linkDefinition) => `/${normalizeProjectPathForGitignore(linkDefinition.projectPath)}`)),
190
+ );
88
191
 
89
- return fs
90
- .readdirSync(frameworkSkillsRoot, { withFileTypes: true })
91
- .filter((dirent) => dirent.isDirectory())
92
- .sort((left, right) => left.name.localeCompare(right.name))
93
- .map((dirent) => ({
94
- projectPath: path.join('skills', dirent.name),
95
- sourcePath: path.join(frameworkSkillsRoot, dirent.name),
96
- ensureParentDir: true,
97
- }))
98
- .filter((linkDefinition) => pathEntryExists(path.join(linkDefinition.sourcePath, 'SKILL.md')));
192
+ return [projectInstructionGitignoreBlockStart, ...entries, projectInstructionGitignoreBlockEnd].join('\n');
99
193
  }
100
194
 
101
- function ensureProjectInstructionGitignoreEntries({ appRoot, coreRoot }: TEnsureProjectAgentSymlinksArgs) {
102
- const gitignoreFilepath = path.join(appRoot, '.gitignore');
103
- if (!pathEntryExists(gitignoreFilepath)) return;
104
-
105
- const managedEntries = getProjectInstructionGitignoreEntries({ coreRoot });
106
- const managedNormalizedEntries = new Set(managedEntries.map(normalizeGitignoreEntry));
195
+ function ensureInstructionGitignoreEntries({
196
+ rootDir,
197
+ linkDefinitions,
198
+ }: {
199
+ rootDir: string;
200
+ linkDefinitions: TAgentLinkDefinition[];
201
+ }) {
202
+ const gitignoreFilepath = path.join(rootDir, '.gitignore');
203
+ if (!pathEntryExists(gitignoreFilepath)) return false;
204
+
205
+ const managedEntries = new Set(linkDefinitions.map((linkDefinition) => normalizeGitignoreEntry(linkDefinition.projectPath)));
107
206
  const lines = fs.readFileSync(gitignoreFilepath, 'utf8').split(/\r?\n/);
108
207
  const filteredLines: string[] = [];
109
-
110
208
  let insideManagedBlock = false;
111
209
 
112
210
  for (const line of lines) {
@@ -123,41 +221,158 @@ function ensureProjectInstructionGitignoreEntries({ appRoot, coreRoot }: TEnsure
123
221
  }
124
222
 
125
223
  if (insideManagedBlock) continue;
126
- if (shouldSkipLegacyManagedGitignoreLine(line, managedNormalizedEntries)) continue;
224
+ if (shouldSkipLegacyManagedGitignoreLine(line, managedEntries)) continue;
127
225
 
128
226
  filteredLines.push(line);
129
227
  }
130
228
 
131
229
  const baseContent = trimTrailingBlankLines(filteredLines).join('\n');
132
- const managedBlock = renderProjectInstructionGitignoreBlock({ coreRoot });
230
+ const managedBlock = renderInstructionGitignoreBlock({ linkDefinitions });
133
231
  const nextContent = baseContent ? `${baseContent}\n\n${managedBlock}\n` : `${managedBlock}\n`;
134
232
 
135
- if (nextContent === fs.readFileSync(gitignoreFilepath, 'utf8')) return;
233
+ if (nextContent === fs.readFileSync(gitignoreFilepath, 'utf8')) return false;
136
234
 
137
235
  fs.writeFileSync(gitignoreFilepath, nextContent);
138
- logVerbose(`[agents] Updated ${path.relative(appRoot, gitignoreFilepath) || '.gitignore'} with Proteum-managed instruction ignore entries.`);
236
+ logVerbose(`[agents] Updated ${path.relative(rootDir, gitignoreFilepath) || '.gitignore'} with Proteum-managed instruction ignore entries.`);
237
+
238
+ return true;
139
239
  }
140
240
 
141
- function ensureSymlinks(appRoot: string, linkDefinitions: TAgentLinkDefinition[], logPrefix: string) {
241
+ function ensureSymlinks(
242
+ rootDir: string,
243
+ linkDefinitions: TAgentLinkDefinition[],
244
+ logPrefix: string,
245
+ managedSourceRoot: string,
246
+ {
247
+ dryRun,
248
+ overwriteBlockedPaths,
249
+ }: {
250
+ dryRun: boolean;
251
+ overwriteBlockedPaths: Set<string>;
252
+ },
253
+ ): TEnsureSymlinksResult {
254
+ const result: TEnsureSymlinksResult = {
255
+ blocked: [],
256
+ created: [],
257
+ overwritten: [],
258
+ skipped: [],
259
+ updated: [],
260
+ };
261
+
142
262
  for (const linkDefinition of linkDefinitions) {
143
- const projectFilepath = path.join(appRoot, linkDefinition.projectPath);
263
+ const projectFilepath = path.join(rootDir, linkDefinition.projectPath);
144
264
  const projectParentDir = path.dirname(projectFilepath);
265
+ const relativeProjectPath = path.relative(rootDir, projectFilepath) || '.';
145
266
 
146
267
  if (linkDefinition.ensureParentDir) fs.ensureDirSync(projectParentDir);
147
- else if (!fs.existsSync(projectParentDir)) continue;
148
-
149
- if (pathEntryExists(projectFilepath)) continue;
268
+ else if (!fs.existsSync(projectParentDir)) {
269
+ result.skipped.push(relativeProjectPath);
270
+ continue;
271
+ }
150
272
 
151
273
  const sourceFilepath = linkDefinition.sourcePath;
152
- if (!fs.existsSync(sourceFilepath)) {
153
- throw new Error(`Missing project instruction asset: ${sourceFilepath}`);
274
+ if (!fs.existsSync(sourceFilepath)) throw new Error(`Missing project instruction asset: ${sourceFilepath}`);
275
+
276
+ const existingState = inspectExistingPath({
277
+ managedSourceRoot,
278
+ projectFilepath,
279
+ sourceFilepath,
280
+ });
281
+
282
+ if (existingState.kind === 'match') {
283
+ result.skipped.push(relativeProjectPath);
284
+ continue;
285
+ }
286
+
287
+ const normalizedProjectFilepath = normalizeAbsolutePath(projectFilepath);
288
+ if (existingState.kind === 'blocked' && !overwriteBlockedPaths.has(normalizedProjectFilepath)) {
289
+ result.blocked.push(relativeProjectPath);
290
+ continue;
154
291
  }
155
292
 
156
293
  const symlinkTarget = path.relative(projectParentDir, sourceFilepath);
157
- fs.symlinkSync(symlinkTarget, projectFilepath);
158
294
 
159
- logVerbose(`${logPrefix} Created ${path.relative(appRoot, projectFilepath) || '.'} -> ${symlinkTarget}`);
295
+ if (existingState.kind === 'managed-different') {
296
+ if (!dryRun) {
297
+ fs.unlinkSync(projectFilepath);
298
+ fs.symlinkSync(symlinkTarget, projectFilepath);
299
+ }
300
+ result.updated.push(relativeProjectPath);
301
+ logVerbose(`${logPrefix} Updated ${relativeProjectPath} -> ${symlinkTarget}`);
302
+ continue;
303
+ }
304
+
305
+ if (existingState.kind === 'blocked') {
306
+ if (!dryRun) {
307
+ fs.removeSync(projectFilepath);
308
+ fs.symlinkSync(symlinkTarget, projectFilepath);
309
+ }
310
+ result.overwritten.push(relativeProjectPath);
311
+ logVerbose(`${logPrefix} Replaced ${relativeProjectPath} -> ${symlinkTarget}`);
312
+ continue;
313
+ }
314
+
315
+ if (!dryRun) fs.symlinkSync(symlinkTarget, projectFilepath);
316
+ result.created.push(relativeProjectPath);
317
+ logVerbose(`${logPrefix} Created ${relativeProjectPath} -> ${symlinkTarget}`);
160
318
  }
319
+
320
+ return result;
321
+ }
322
+
323
+ function inspectExistingPath({
324
+ managedSourceRoot,
325
+ projectFilepath,
326
+ sourceFilepath,
327
+ }: {
328
+ managedSourceRoot: string;
329
+ projectFilepath: string;
330
+ sourceFilepath: string;
331
+ }) {
332
+ if (!pathEntryExists(projectFilepath)) return { kind: 'missing' as const };
333
+
334
+ const stats = fs.lstatSync(projectFilepath);
335
+ if (!stats.isSymbolicLink()) return { kind: 'blocked' as const };
336
+
337
+ const existingTarget = resolveSymlinkTarget(projectFilepath);
338
+ const normalizedExistingTarget = normalizeAbsolutePath(existingTarget);
339
+ const normalizedSourceFilepath = normalizeAbsolutePath(sourceFilepath);
340
+ const normalizedManagedSourceRoot = normalizeAbsolutePath(managedSourceRoot);
341
+
342
+ if (normalizedExistingTarget === normalizedSourceFilepath) return { kind: 'match' as const };
343
+ if (
344
+ normalizedExistingTarget === normalizedManagedSourceRoot ||
345
+ normalizedExistingTarget.startsWith(`${normalizedManagedSourceRoot}/`)
346
+ )
347
+ return { kind: 'managed-different' as const };
348
+
349
+ return { kind: 'blocked' as const };
350
+ }
351
+
352
+ function resolveSymlinkTarget(projectFilepath: string) {
353
+ const projectParentDir = path.dirname(projectFilepath);
354
+ const rawTarget = fs.readlinkSync(projectFilepath);
355
+ return path.resolve(projectParentDir, rawTarget);
356
+ }
357
+
358
+ function mergeSymlinkResults(
359
+ result: TConfigureProjectAgentSymlinksResult,
360
+ next: TEnsureSymlinksResult,
361
+ rootDir: string,
362
+ ) {
363
+ result.created.push(...next.created.map((entry) => formatResultPath(rootDir, entry)));
364
+ result.overwritten.push(...next.overwritten.map((entry) => formatResultPath(rootDir, entry)));
365
+ result.updated.push(...next.updated.map((entry) => formatResultPath(rootDir, entry)));
366
+ result.skipped.push(...next.skipped.map((entry) => formatResultPath(rootDir, entry)));
367
+ result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
368
+ }
369
+
370
+ function formatResultPath(rootDir: string, relativePath: string) {
371
+ return normalizeProjectPathForGitignore(path.join(rootDir, relativePath));
372
+ }
373
+
374
+ function normalizeAbsolutePath(filepath: string) {
375
+ return filepath.replace(/\\/g, '/');
161
376
  }
162
377
 
163
378
  function normalizeProjectPathForGitignore(projectPath: string) {
@@ -1166,12 +1166,13 @@ const traceEventDepths: Record<TTraceEventType, number> = {
1166
1166
  'resolve.not-found': 1,
1167
1167
  'controller.start': 2,
1168
1168
  'controller.result': 2,
1169
- 'setup.options': 3,
1170
1169
  'context.create': 3,
1171
1170
  'page.data': 3,
1172
1171
  'ssr.payload': 3,
1173
1172
  'render.start': 2,
1174
1173
  'render.end': 2,
1174
+ 'cache.hit': 2,
1175
+ 'cache.write': 2,
1175
1176
  'response.send': 1,
1176
1177
  'request.finish': 0,
1177
1178
  error: 0,
@@ -3799,7 +3800,7 @@ const renderPanel = (panel: TProfilerPanel, session: TProfilerNavigationSession,
3799
3800
  }
3800
3801
 
3801
3802
  if (panel === 'controller') {
3802
- const controllerEvents = findTraceEvents(primaryTrace, ['controller.start', 'controller.result', 'setup.options', 'context.create']);
3803
+ const controllerEvents = findTraceEvents(primaryTrace, ['controller.start', 'controller.result', 'context.create']);
3803
3804
  const controllerFlowChart = buildHorizontalBarChartOptions({
3804
3805
  color: profilerChartTheme.indigo,
3805
3806
  entries: buildCountEntries(controllerEvents.map((event) => event.type.replace(/\./g, ' '))),
package/client/router.ts CHANGED
@@ -2,12 +2,20 @@ import type Router from '@client/services/router';
2
2
 
3
3
  const getRouter = (): Router => {
4
4
  if (typeof window === 'undefined') {
5
- throw new Error(`Client router is not available on the server.`);
5
+ throw new Error(
6
+ 'Proteum client router was accessed during SSR or server execution. This is a framework contract failure. ' +
7
+ 'Likely fix: remove `@/client/router` from server or `.ssr` code and pass request or router data explicitly. ' +
8
+ 'Re-check both SSR and client navigation after the fix.',
9
+ );
6
10
  }
7
11
 
8
12
  const router = (window.app as (Record<string, unknown> & { Router?: Router }) | undefined)?.Router;
9
13
  if (!router) {
10
- throw new Error(`Client router was accessed before the application booted.`);
14
+ throw new Error(
15
+ 'Proteum client router was accessed before the browser app finished booting. This is a framework contract failure. ' +
16
+ 'Likely fix: call the router from code that runs after App mount or from a component under the Proteum router tree. ' +
17
+ 'Re-check both SSR and client navigation after the fix.',
18
+ );
11
19
  }
12
20
 
13
21
  return router;
@@ -28,7 +28,7 @@ import type { TRegisterPageArgs, TSsrUnresolvedRoute } from '@common/router/cont
28
28
  import { getLayout } from '@common/router/layouts';
29
29
  import { getRegisterPageArgs, buildRegex } from '@common/router/register';
30
30
  import { TFetcherList } from '@common/router/request/api';
31
- import type { TFrontRenderer, TPageSetup } from '@common/router/response/page';
31
+ import type { TFrontRenderer, TPageDataProvider } from '@common/router/response/page';
32
32
 
33
33
  import App from '@client/app/component';
34
34
  import type ClientApplication from '@client/app';
@@ -241,32 +241,15 @@ export default class ClientRouter<
241
241
  return currentRoute;
242
242
  }
243
243
 
244
- public page<TProvidedData extends {} = {}>(
245
- path: string,
246
- renderer: TFrontRenderer<TProvidedData>,
247
- ): TClientPageRoute<this>;
248
-
249
- public page<TProvidedData extends {} = {}>(
250
- path: string,
251
- setup: TPageSetup<TProvidedData>,
252
- renderer: TFrontRenderer<TProvidedData>,
253
- ): TClientPageRoute<this>;
254
-
255
- public page<TProvidedData extends {} = {}>(
256
- path: string,
257
- options: Partial<TRouteOptions>,
258
- renderer: TFrontRenderer<TProvidedData>,
259
- ): TClientPageRoute<this>;
260
-
261
244
  public page<TProvidedData extends {} = {}>(
262
245
  path: string,
263
246
  options: Partial<TRouteOptions>,
264
- setup: TPageSetup<TProvidedData>,
247
+ data: TPageDataProvider<TProvidedData> | null,
265
248
  renderer: TFrontRenderer<TProvidedData>,
266
249
  ): TClientPageRoute<this>;
267
250
 
268
251
  public page(...args: TRegisterPageArgs<any, TRouteOptions>): TClientPageRoute<this> {
269
- const { path, options, setup, renderer, layout } = getRegisterPageArgs(...args);
252
+ const { path, options, data, renderer, layout } = getRegisterPageArgs(...args);
270
253
 
271
254
  // Page ids are injected by the generated route wrapper modules.
272
255
  const id = options.id;
@@ -279,7 +262,8 @@ export default class ClientRouter<
279
262
  path,
280
263
  regex,
281
264
  keys,
282
- options: { ...defaultOptions, setup, ...options },
265
+ data,
266
+ options: { ...defaultOptions, ...options },
283
267
  controller: (context) => new ClientPage(route, renderer, context as any, layout),
284
268
  };
285
269
 
@@ -406,7 +390,7 @@ export default class ClientRouter<
406
390
 
407
391
  const response = await this.createResponse(route, request, apiData);
408
392
 
409
- ReactDOM.hydrate(<App context={response.context as AppPropsContext} />, document.body, () => {
393
+ ReactDOM.hydrate(<App context={response.context as unknown as AppPropsContext} />, document.body, () => {
410
394
  console.log(`Render complete`);
411
395
  withProfiler((runtime) => runtime.markInitialHydrated({ chunkId: response.chunkId, title: response.title }));
412
396
 
@@ -1,5 +1,3 @@
1
- import fs from 'fs';
2
-
3
1
  import {
4
2
  formatManifestFilepath,
5
3
  formatManifestLocation,
@@ -53,6 +51,24 @@ export type TConnectResponse = {
53
51
  diagnostics: TProteumManifestDiagnostic[];
54
52
  };
55
53
 
54
+ type TNodeFs = {
55
+ existsSync: (filepath: string) => boolean;
56
+ };
57
+
58
+ let cachedNodeFs: TNodeFs | null | undefined;
59
+
60
+ const getNodeFs = () => {
61
+ if (cachedNodeFs !== undefined) return cachedNodeFs || undefined;
62
+
63
+ try {
64
+ cachedNodeFs = (eval('require')('fs') as TNodeFs) || null;
65
+ } catch (_error) {
66
+ cachedNodeFs = null;
67
+ }
68
+
69
+ return cachedNodeFs || undefined;
70
+ };
71
+
56
72
  const createDiagnostic = ({
57
73
  code,
58
74
  filepath,
@@ -102,7 +118,7 @@ export const buildConnectResponse = (
102
118
  .sort((left, right) => left.clientAccessor.localeCompare(right.clientAccessor));
103
119
  const sourceConfigured = typeof project.sourceValue === 'string' && project.sourceValue.trim() !== '';
104
120
  const urlInternalConfigured = typeof project.urlInternal === 'string' && project.urlInternal.trim() !== '';
105
- const cachedContractExists = project.cachedContractFilepath ? fs.existsSync(project.cachedContractFilepath) : false;
121
+ const cachedContractExists = project.cachedContractFilepath ? getNodeFs()?.existsSync(project.cachedContractFilepath) === true : false;
106
122
 
107
123
  if (!sourceConfigured) {
108
124
  diagnostics.push(
@@ -258,7 +274,7 @@ export const renderConnectHuman = (manifest: TProteumManifest, response: TConnec
258
274
  title: 'Diagnostics',
259
275
  items: response.diagnostics.map(
260
276
  (diagnostic) =>
261
- `[${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}`,
277
+ `[${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${diagnostic.fixHint ? ` fix=${diagnostic.fixHint}` : ''}`,
262
278
  ),
263
279
  empty: 'No connect diagnostics were found.',
264
280
  });
@@ -9,9 +9,16 @@ export type TDevConsoleLogLevel = 'silly' | 'log' | 'info' | 'warn' | 'error';
9
9
  export type TDevConsoleLogChannel = {
10
10
  channelType: 'cron' | 'master' | 'request' | 'socket';
11
11
  channelId?: string;
12
+ silentLogs?: boolean;
12
13
  method?: string;
13
14
  path?: string;
14
15
  user?: string;
16
+ connectedNamespace?: string;
17
+ ownerLabel?: string;
18
+ ownerFilepath?: string;
19
+ serviceLabel?: string;
20
+ cacheKey?: string;
21
+ cachePhase?: string;
15
22
  traceCallId?: string;
16
23
  traceCallOrigin?: TTraceCallOrigin;
17
24
  traceCallLabel?: string;