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.
- package/.codex/environments/environment.toml +11 -0
- package/AGENTS.md +27 -11
- package/README.md +30 -11
- package/agents/project/AGENTS.md +172 -123
- 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 +297 -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 +9 -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 +78 -1
- package/common/env/proteumEnv.ts +10 -3
- 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/assets/unique-domains-chip.png +0 -0
- 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 +42 -9
- 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 +370 -72
- 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/prisma/index.ts +15 -12
- package/server/services/router/http/index.ts +1 -1
- package/server/services/router/index.ts +105 -23
- package/server/services/router/request/api.ts +7 -1
- package/server/services/router/request/index.ts +2 -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
package/cli/utils/agents.ts
CHANGED
|
@@ -12,17 +12,41 @@ import { logVerbose } from '../runtime/verbose';
|
|
|
12
12
|
----------------------------------*/
|
|
13
13
|
|
|
14
14
|
type TProjectInstructionArgs = { coreRoot: string };
|
|
15
|
-
type
|
|
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
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
const
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
86
|
-
const
|
|
87
|
-
|
|
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
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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,
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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))
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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', '
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
package/common/dev/connect.ts
CHANGED
|
@@ -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 ?
|
|
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
|
});
|
package/common/dev/console.ts
CHANGED
|
@@ -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;
|