proteum 2.1.2 → 2.1.6
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/AGENTS.md +22 -14
- package/README.md +112 -17
- package/agents/project/AGENTS.md +188 -25
- package/agents/project/CODING_STYLE.md +1 -0
- package/agents/project/client/AGENTS.md +13 -8
- package/agents/project/client/pages/AGENTS.md +17 -9
- package/agents/project/diagnostics.md +52 -0
- package/agents/project/optimizations.md +48 -0
- package/agents/project/server/routes/AGENTS.md +9 -6
- package/agents/project/server/services/AGENTS.md +10 -6
- package/agents/project/tests/AGENTS.md +11 -5
- package/cli/app/config.ts +13 -14
- package/cli/app/index.ts +58 -0
- package/cli/commands/command.ts +8 -0
- package/cli/commands/connect.ts +45 -0
- package/cli/commands/dev.ts +26 -11
- package/cli/commands/diagnose.ts +286 -0
- package/cli/commands/doctor.ts +18 -5
- package/cli/commands/explain.ts +25 -0
- package/cli/commands/perf.ts +243 -0
- package/cli/commands/session.ts +254 -0
- package/cli/commands/sessionLocalRunner.js +188 -0
- package/cli/commands/trace.ts +17 -1
- package/cli/commands/verify.ts +281 -0
- package/cli/compiler/artifacts/connectedProjects.ts +453 -0
- package/cli/compiler/artifacts/controllers.ts +198 -49
- package/cli/compiler/artifacts/discovery.ts +0 -34
- package/cli/compiler/artifacts/manifest.ts +90 -6
- package/cli/compiler/artifacts/routing.ts +2 -2
- package/cli/compiler/artifacts/services.ts +277 -130
- package/cli/compiler/client/index.ts +3 -0
- package/cli/compiler/common/files/style.ts +52 -0
- package/cli/compiler/common/generatedRouteModules.ts +34 -5
- package/cli/compiler/common/scripts.ts +11 -5
- package/cli/compiler/index.ts +2 -1
- package/cli/compiler/server/index.ts +3 -0
- package/cli/presentation/commands.ts +136 -7
- package/cli/presentation/devSession.ts +32 -7
- package/cli/runtime/commands.ts +193 -6
- package/cli/scaffold/index.ts +14 -25
- package/cli/scaffold/templates.ts +41 -27
- package/cli/utils/agents.ts +4 -2
- package/cli/utils/keyboard.ts +8 -0
- package/client/dev/profiler/ApexChart.tsx +66 -0
- package/client/dev/profiler/index.tsx +2798 -417
- package/client/dev/profiler/runtime.noop.ts +12 -0
- package/client/dev/profiler/runtime.ts +195 -4
- package/client/services/router/request/api.ts +6 -1
- package/common/applicationConfig.ts +173 -0
- package/common/applicationConfigLoader.ts +102 -0
- package/common/connectedProjects.ts +113 -0
- package/common/dev/connect.ts +267 -0
- package/common/dev/console.ts +31 -0
- package/common/dev/contractsDoctor.ts +128 -0
- package/common/dev/diagnostics.ts +59 -15
- package/common/dev/inspection.ts +491 -0
- package/common/dev/performance.ts +809 -0
- package/common/dev/profiler.ts +3 -0
- package/common/dev/proteumManifest.ts +31 -6
- package/common/dev/requestTrace.ts +56 -1
- package/common/dev/session.ts +24 -0
- package/common/env/proteumEnv.ts +176 -50
- package/common/router/index.ts +1 -0
- package/common/router/request/api.ts +2 -0
- package/config.ts +5 -0
- package/docs/dev-commands.md +5 -1
- package/docs/dev-sessions.md +90 -0
- package/docs/diagnostics.md +74 -11
- package/docs/request-tracing.md +50 -3
- package/package.json +1 -1
- package/server/app/container/config.ts +16 -87
- package/server/app/container/console/index.ts +42 -8
- package/server/app/container/index.ts +3 -1
- package/server/app/container/trace/index.ts +153 -0
- package/server/app/devDiagnostics.ts +138 -0
- package/server/app/index.ts +18 -8
- package/server/app/service/container.ts +0 -12
- package/server/app/service/index.ts +0 -2
- package/server/services/prisma/index.ts +121 -4
- package/server/services/router/http/index.ts +352 -0
- package/server/services/router/index.ts +50 -47
- package/server/services/router/request/api.ts +160 -19
- package/server/services/router/request/index.ts +8 -0
- package/server/services/router/response/index.ts +24 -1
- package/server/services/router/response/page/document.tsx +5 -0
- package/server/services/router/response/page/index.tsx +10 -0
- package/agents/framework/AGENTS.md +0 -177
- package/server/services/auth/router/service.json +0 -6
- package/server/services/auth/service.json +0 -6
- package/server/services/cron/service.json +0 -6
- package/server/services/disks/drivers/local/service.json +0 -6
- package/server/services/disks/drivers/s3/service.json +0 -6
- package/server/services/disks/service.json +0 -6
- package/server/services/fetch/service.json +0 -7
- package/server/services/prisma/service.json +0 -6
- package/server/services/router/service.json +0 -6
- package/server/services/schema/router/service.json +0 -6
- package/server/services/schema/service.json +0 -6
- package/server/services/security/encrypt/aes/service.json +0 -6
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const connectedProjectContractVersion = 1 as const;
|
|
2
|
+
export const connectedProjectHealthPath = '/api/__proteum/connected/ping';
|
|
3
|
+
export const connectedProjectProxyPathPrefix = '/api/__proteum/connected';
|
|
4
|
+
|
|
5
|
+
export const connectedProjectSourceKinds = ['file', 'github'] as const;
|
|
6
|
+
|
|
7
|
+
export type TConnectedProjectSourceKind = (typeof connectedProjectSourceKinds)[number];
|
|
8
|
+
|
|
9
|
+
export type TConnectedProjectTypingMode = 'local-typed' | 'runtime-only';
|
|
10
|
+
|
|
11
|
+
export type TConnectedProjectConfig = {
|
|
12
|
+
source?: string;
|
|
13
|
+
urlInternal?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type TConnectedProjectsConfig = Record<string, TConnectedProjectConfig>;
|
|
17
|
+
|
|
18
|
+
export type TConnectedProjectContractController = {
|
|
19
|
+
className: string;
|
|
20
|
+
methodName: string;
|
|
21
|
+
routeBasePath: string;
|
|
22
|
+
routePath: string;
|
|
23
|
+
httpPath: string;
|
|
24
|
+
clientAccessor: string;
|
|
25
|
+
hasInput: boolean;
|
|
26
|
+
inputCallsCount: number;
|
|
27
|
+
importPath: string;
|
|
28
|
+
relativeFilepath: string;
|
|
29
|
+
sourceLocation: { line: number; column: number };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TConnectedProjectContract = {
|
|
33
|
+
version: typeof connectedProjectContractVersion;
|
|
34
|
+
packageName?: string;
|
|
35
|
+
identity: {
|
|
36
|
+
name: string;
|
|
37
|
+
identifier: string;
|
|
38
|
+
};
|
|
39
|
+
controllers: TConnectedProjectContractController[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type TConnectedProjectEnvConfig = {
|
|
43
|
+
namespace: string;
|
|
44
|
+
urlInternal: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type TConnectedFetcherTarget = {
|
|
48
|
+
namespace: string;
|
|
49
|
+
controllerAccessor: string;
|
|
50
|
+
httpPath: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const normalizeNamespace = (value: string) => value.trim();
|
|
54
|
+
|
|
55
|
+
export const normalizeConnectedProjectsConfig = (value: unknown): TConnectedProjectsConfig => {
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
57
|
+
|
|
58
|
+
const output: TConnectedProjectsConfig = {};
|
|
59
|
+
|
|
60
|
+
for (const [rawNamespace, rawConfig] of Object.entries(value)) {
|
|
61
|
+
const namespace = normalizeNamespace(rawNamespace);
|
|
62
|
+
if (!namespace) continue;
|
|
63
|
+
if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) continue;
|
|
64
|
+
|
|
65
|
+
if ('source' in rawConfig && rawConfig.source !== undefined && rawConfig.source !== null && typeof rawConfig.source !== 'string') {
|
|
66
|
+
throw new Error(`Invalid connect.${namespace}.source. Expected a string.`);
|
|
67
|
+
}
|
|
68
|
+
if ('urlInternal' in rawConfig && rawConfig.urlInternal !== undefined && rawConfig.urlInternal !== null && typeof rawConfig.urlInternal !== 'string') {
|
|
69
|
+
throw new Error(`Invalid connect.${namespace}.urlInternal. Expected a string.`);
|
|
70
|
+
}
|
|
71
|
+
const source =
|
|
72
|
+
'source' in rawConfig && rawConfig.source !== undefined && rawConfig.source !== null
|
|
73
|
+
? String(rawConfig.source).trim() || undefined
|
|
74
|
+
: undefined;
|
|
75
|
+
const urlInternal =
|
|
76
|
+
'urlInternal' in rawConfig && rawConfig.urlInternal !== undefined && rawConfig.urlInternal !== null
|
|
77
|
+
? String(rawConfig.urlInternal).trim() || undefined
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
output[namespace] = {
|
|
81
|
+
...(source ? { source } : {}),
|
|
82
|
+
...(urlInternal ? { urlInternal } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return output;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const getConnectedProjectSlug = (namespace: string) =>
|
|
90
|
+
normalizeNamespace(namespace)
|
|
91
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
92
|
+
.replace(/[^A-Za-z0-9]+/g, '_')
|
|
93
|
+
.replace(/^_+|_+$/g, '')
|
|
94
|
+
.toUpperCase();
|
|
95
|
+
|
|
96
|
+
export const buildConnectedProjectProxyPath = (namespace: string, httpPath: string) => {
|
|
97
|
+
const normalizedHttpPath = httpPath.startsWith('/') ? httpPath : `/${httpPath}`;
|
|
98
|
+
return `${connectedProjectProxyPathPrefix}/${encodeURIComponent(normalizeNamespace(namespace))}${normalizedHttpPath}`;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const parseConnectedProjectProxyPath = (pathname: string) => {
|
|
102
|
+
if (!pathname.startsWith(`${connectedProjectProxyPathPrefix}/`)) return undefined;
|
|
103
|
+
|
|
104
|
+
const remainder = pathname.slice(connectedProjectProxyPathPrefix.length + 1);
|
|
105
|
+
const separatorIndex = remainder.indexOf('/');
|
|
106
|
+
if (separatorIndex === -1) return undefined;
|
|
107
|
+
|
|
108
|
+
const namespace = decodeURIComponent(remainder.slice(0, separatorIndex)).trim();
|
|
109
|
+
const httpPath = `/${remainder.slice(separatorIndex + 1)}`;
|
|
110
|
+
if (!namespace || httpPath === '/') return undefined;
|
|
111
|
+
|
|
112
|
+
return { namespace, httpPath };
|
|
113
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatManifestFilepath,
|
|
5
|
+
formatManifestLocation,
|
|
6
|
+
renderHumanBlock,
|
|
7
|
+
type THumanTextBlock,
|
|
8
|
+
} from './diagnostics';
|
|
9
|
+
import type {
|
|
10
|
+
TProteumManifest,
|
|
11
|
+
TProteumManifestController,
|
|
12
|
+
TProteumManifestDiagnostic,
|
|
13
|
+
} from './proteumManifest';
|
|
14
|
+
|
|
15
|
+
export type TConnectProjectController = {
|
|
16
|
+
clientAccessor: string;
|
|
17
|
+
filepath: string;
|
|
18
|
+
hasInput: boolean;
|
|
19
|
+
httpPath: string;
|
|
20
|
+
methodName: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type TConnectProjectReport = {
|
|
24
|
+
namespace: string;
|
|
25
|
+
identityIdentifier?: string;
|
|
26
|
+
identityName?: string;
|
|
27
|
+
sourceKind?: string;
|
|
28
|
+
sourceValue?: string;
|
|
29
|
+
sourceConfigured: boolean;
|
|
30
|
+
cachedContractFilepath?: string;
|
|
31
|
+
cachedContractExists: boolean;
|
|
32
|
+
typingMode?: string;
|
|
33
|
+
urlInternalConfigured: boolean;
|
|
34
|
+
urlInternal?: string;
|
|
35
|
+
controllerCount: number;
|
|
36
|
+
controllers?: TConnectProjectController[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type TConnectResponse = {
|
|
40
|
+
app: {
|
|
41
|
+
identifier: string;
|
|
42
|
+
name: string;
|
|
43
|
+
root: string;
|
|
44
|
+
};
|
|
45
|
+
summary: {
|
|
46
|
+
connectedProjects: number;
|
|
47
|
+
errors: number;
|
|
48
|
+
importedControllers: number;
|
|
49
|
+
strictFailed: boolean;
|
|
50
|
+
warnings: number;
|
|
51
|
+
};
|
|
52
|
+
projects: TConnectProjectReport[];
|
|
53
|
+
diagnostics: TProteumManifestDiagnostic[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createDiagnostic = ({
|
|
57
|
+
code,
|
|
58
|
+
filepath,
|
|
59
|
+
level = 'error',
|
|
60
|
+
message,
|
|
61
|
+
}: {
|
|
62
|
+
code: string;
|
|
63
|
+
filepath: string;
|
|
64
|
+
level?: TProteumManifestDiagnostic['level'];
|
|
65
|
+
message: string;
|
|
66
|
+
}): TProteumManifestDiagnostic => ({
|
|
67
|
+
code,
|
|
68
|
+
filepath,
|
|
69
|
+
level,
|
|
70
|
+
message,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const sortDiagnostics = (diagnostics: TProteumManifestDiagnostic[]) =>
|
|
74
|
+
[...diagnostics].sort((left, right) => {
|
|
75
|
+
if (left.level !== right.level) return left.level === 'error' ? -1 : 1;
|
|
76
|
+
if (left.filepath !== right.filepath) return left.filepath.localeCompare(right.filepath);
|
|
77
|
+
return left.code.localeCompare(right.code);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const toControllerSummary = (controller: TProteumManifestController): TConnectProjectController => ({
|
|
81
|
+
clientAccessor: controller.clientAccessor,
|
|
82
|
+
filepath: controller.filepath,
|
|
83
|
+
hasInput: controller.hasInput,
|
|
84
|
+
httpPath: controller.httpPath,
|
|
85
|
+
methodName: controller.methodName,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const formatControllerItem = (manifest: TProteumManifest, controller: TConnectProjectController) =>
|
|
89
|
+
`${controller.clientAccessor} -> POST ${controller.httpPath} input=${controller.hasInput ? 'yes' : 'no'} source=${formatManifestFilepath(manifest, controller.filepath)}#${controller.methodName}`;
|
|
90
|
+
|
|
91
|
+
const formatProjectItem = (manifest: TProteumManifest, project: TConnectProjectReport) =>
|
|
92
|
+
`${project.namespace} -> ${project.identityIdentifier || project.identityName || 'unknown'} controllers=${project.controllerCount} sourceConfigured=${project.sourceConfigured ? 'yes' : 'no'} internal=${project.urlInternal || 'missing'} configured=${project.urlInternalConfigured ? 'yes' : 'no'}${project.sourceKind ? ` source=${project.sourceKind}` : ''}${project.sourceValue ? ` sourceValue=${project.sourceValue}` : ''}${project.typingMode ? ` typing=${project.typingMode}` : ''}${project.cachedContractFilepath ? ` contract=${formatManifestFilepath(manifest, project.cachedContractFilepath)}` : ''}${project.cachedContractFilepath ? ` cache=${project.cachedContractExists ? 'present' : 'missing'}` : ''}`;
|
|
93
|
+
|
|
94
|
+
export const buildConnectResponse = (
|
|
95
|
+
manifest: TProteumManifest,
|
|
96
|
+
options: { includeControllers?: boolean; strict?: boolean } = {},
|
|
97
|
+
): TConnectResponse => {
|
|
98
|
+
const diagnostics: TProteumManifestDiagnostic[] = [];
|
|
99
|
+
const projects: TConnectProjectReport[] = manifest.connectedProjects.map((project) => {
|
|
100
|
+
const controllers = manifest.controllers
|
|
101
|
+
.filter((controller) => controller.connectedProjectNamespace === project.namespace)
|
|
102
|
+
.sort((left, right) => left.clientAccessor.localeCompare(right.clientAccessor));
|
|
103
|
+
const sourceConfigured = typeof project.sourceValue === 'string' && project.sourceValue.trim() !== '';
|
|
104
|
+
const urlInternalConfigured = typeof project.urlInternal === 'string' && project.urlInternal.trim() !== '';
|
|
105
|
+
const cachedContractExists = project.cachedContractFilepath ? fs.existsSync(project.cachedContractFilepath) : false;
|
|
106
|
+
|
|
107
|
+
if (!sourceConfigured) {
|
|
108
|
+
diagnostics.push(
|
|
109
|
+
createDiagnostic({
|
|
110
|
+
code: 'connect.source-missing',
|
|
111
|
+
filepath: manifest.app.setupFilepath,
|
|
112
|
+
message: `Connected project "${project.namespace}" requires connect.${project.namespace}.source in proteum.config.ts during generation.`,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (sourceConfigured && !project.sourceKind) {
|
|
118
|
+
diagnostics.push(
|
|
119
|
+
createDiagnostic({
|
|
120
|
+
code: 'connect.contract-unresolved',
|
|
121
|
+
filepath: manifest.app.setupFilepath,
|
|
122
|
+
message: `Connected project "${project.namespace}" does not have a resolved contract source in the manifest.`,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!project.cachedContractFilepath) {
|
|
128
|
+
diagnostics.push(
|
|
129
|
+
createDiagnostic({
|
|
130
|
+
code: 'connect.contract-cache-missing',
|
|
131
|
+
filepath: manifest.app.setupFilepath,
|
|
132
|
+
message: `Connected project "${project.namespace}" has no cached contract filepath in the manifest.`,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
} else if (!cachedContractExists) {
|
|
136
|
+
diagnostics.push(
|
|
137
|
+
createDiagnostic({
|
|
138
|
+
code: 'connect.contract-cache-missing-on-disk',
|
|
139
|
+
filepath: project.cachedContractFilepath,
|
|
140
|
+
message: `Cached connected contract "${project.cachedContractFilepath}" is missing from disk.`,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!urlInternalConfigured) {
|
|
146
|
+
diagnostics.push(
|
|
147
|
+
createDiagnostic({
|
|
148
|
+
code: 'connect.url-internal-missing',
|
|
149
|
+
filepath: manifest.app.setupFilepath,
|
|
150
|
+
message: `Connected project "${project.namespace}" requires connect.${project.namespace}.urlInternal in proteum.config.ts for runtime calls.`,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (project.controllerCount === 0) {
|
|
156
|
+
diagnostics.push(
|
|
157
|
+
createDiagnostic({
|
|
158
|
+
code: 'connect.controllers-empty',
|
|
159
|
+
filepath: manifest.app.setupFilepath,
|
|
160
|
+
level: 'warning',
|
|
161
|
+
message: `Connected project "${project.namespace}" imported zero controllers.`,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (project.sourceKind === 'file' && project.typingMode !== 'local-typed') {
|
|
167
|
+
diagnostics.push(
|
|
168
|
+
createDiagnostic({
|
|
169
|
+
code: 'connect.typing-mode-unexpected',
|
|
170
|
+
filepath: manifest.app.setupFilepath,
|
|
171
|
+
level: 'warning',
|
|
172
|
+
message: `Connected project "${project.namespace}" uses a file source but typing mode is "${project.typingMode || 'unknown'}".`,
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (project.sourceKind && project.sourceKind !== 'file' && project.typingMode === 'local-typed') {
|
|
178
|
+
diagnostics.push(
|
|
179
|
+
createDiagnostic({
|
|
180
|
+
code: 'connect.typing-mode-unexpected',
|
|
181
|
+
filepath: manifest.app.setupFilepath,
|
|
182
|
+
level: 'warning',
|
|
183
|
+
message: `Connected project "${project.namespace}" is non-local but typing mode is "${project.typingMode}".`,
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
namespace: project.namespace,
|
|
190
|
+
identityIdentifier: project.identityIdentifier,
|
|
191
|
+
identityName: project.identityName,
|
|
192
|
+
sourceKind: project.sourceKind,
|
|
193
|
+
sourceValue: project.sourceValue,
|
|
194
|
+
sourceConfigured,
|
|
195
|
+
cachedContractFilepath: project.cachedContractFilepath,
|
|
196
|
+
cachedContractExists,
|
|
197
|
+
typingMode: project.typingMode,
|
|
198
|
+
urlInternalConfigured,
|
|
199
|
+
urlInternal: project.urlInternal,
|
|
200
|
+
controllerCount: project.controllerCount,
|
|
201
|
+
...(options.includeControllers === true ? { controllers: controllers.map(toControllerSummary) } : {}),
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const sortedDiagnostics = sortDiagnostics(diagnostics);
|
|
206
|
+
const errors = sortedDiagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
|
|
207
|
+
const warnings = sortedDiagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
app: {
|
|
211
|
+
identifier: manifest.app.identity.identifier,
|
|
212
|
+
name: manifest.app.identity.name,
|
|
213
|
+
root: manifest.app.root,
|
|
214
|
+
},
|
|
215
|
+
summary: {
|
|
216
|
+
connectedProjects: projects.length,
|
|
217
|
+
errors,
|
|
218
|
+
importedControllers: projects.reduce((count, project) => count + project.controllerCount, 0),
|
|
219
|
+
strictFailed: options.strict === true && sortedDiagnostics.length > 0,
|
|
220
|
+
warnings,
|
|
221
|
+
},
|
|
222
|
+
projects,
|
|
223
|
+
diagnostics: sortedDiagnostics,
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const renderConnectHuman = (manifest: TProteumManifest, response: TConnectResponse) => {
|
|
228
|
+
const blocks: THumanTextBlock[] = [
|
|
229
|
+
{
|
|
230
|
+
title: 'Proteum Connect',
|
|
231
|
+
items: [
|
|
232
|
+
`app=${response.app.name} (${response.app.identifier})`,
|
|
233
|
+
`root=${formatManifestFilepath(manifest, response.app.root)}`,
|
|
234
|
+
`connectedProjects=${response.summary.connectedProjects}`,
|
|
235
|
+
`importedControllers=${response.summary.importedControllers}`,
|
|
236
|
+
`diagnostics=${response.summary.errors} errors, ${response.summary.warnings} warnings`,
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
title: 'Connected Projects',
|
|
241
|
+
items: response.projects.map((project) => formatProjectItem(manifest, project)),
|
|
242
|
+
empty: 'No connected projects are configured.',
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const controllerItems = response.projects.flatMap((project) =>
|
|
247
|
+
(project.controllers || []).map((controller) => formatControllerItem(manifest, controller)),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (controllerItems.length > 0) {
|
|
251
|
+
blocks.push({
|
|
252
|
+
title: 'Imported Controllers',
|
|
253
|
+
items: controllerItems,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
blocks.push({
|
|
258
|
+
title: 'Diagnostics',
|
|
259
|
+
items: response.diagnostics.map(
|
|
260
|
+
(diagnostic) =>
|
|
261
|
+
`[${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}`,
|
|
262
|
+
),
|
|
263
|
+
empty: 'No connect diagnostics were found.',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return blocks.map((block) => renderHumanBlock(block)).join('\n\n');
|
|
267
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- TYPES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
import type { TTraceCallOrigin, TTraceSqlQueryKind } from './requestTrace';
|
|
6
|
+
|
|
7
|
+
export type TDevConsoleLogLevel = 'silly' | 'log' | 'info' | 'warn' | 'error';
|
|
8
|
+
|
|
9
|
+
export type TDevConsoleLogChannel = {
|
|
10
|
+
channelType: 'cron' | 'master' | 'request' | 'socket';
|
|
11
|
+
channelId?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
user?: string;
|
|
15
|
+
traceCallId?: string;
|
|
16
|
+
traceCallOrigin?: TTraceCallOrigin;
|
|
17
|
+
traceCallLabel?: string;
|
|
18
|
+
traceCallFetcherId?: string;
|
|
19
|
+
prismaOperations?: Array<{ kind: TTraceSqlQueryKind; model?: string; operation: string }>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type TDevConsoleLogEntry = {
|
|
23
|
+
channel: TDevConsoleLogChannel;
|
|
24
|
+
level: TDevConsoleLogLevel;
|
|
25
|
+
text: string;
|
|
26
|
+
time: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TDevConsoleLogsResponse = {
|
|
30
|
+
logs: TDevConsoleLogEntry[];
|
|
31
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import type { TDoctorResponse } from './diagnostics';
|
|
5
|
+
import type {
|
|
6
|
+
TProteumManifest,
|
|
7
|
+
TProteumManifestDiagnostic,
|
|
8
|
+
} from './proteumManifest';
|
|
9
|
+
|
|
10
|
+
const buildGeneratedArtifactList = (manifest: TProteumManifest) => {
|
|
11
|
+
const appRoot = manifest.app.root;
|
|
12
|
+
const clientRouteModulesRoot = path.join(appRoot, '.proteum', 'client', 'route-modules');
|
|
13
|
+
const serverRouteModulesRoot = path.join(appRoot, '.proteum', 'server', 'route-modules');
|
|
14
|
+
const generated = new Set<string>([
|
|
15
|
+
path.join(appRoot, 'proteum.connected.json'),
|
|
16
|
+
path.join(appRoot, '.proteum', 'manifest.json'),
|
|
17
|
+
path.join(appRoot, '.proteum', 'proteum.connected.d.ts'),
|
|
18
|
+
path.join(appRoot, '.proteum', 'client', 'context.ts'),
|
|
19
|
+
path.join(appRoot, '.proteum', 'client', 'controllers.ts'),
|
|
20
|
+
path.join(appRoot, '.proteum', 'client', 'layouts.ts'),
|
|
21
|
+
path.join(appRoot, '.proteum', 'client', 'models.ts'),
|
|
22
|
+
path.join(appRoot, '.proteum', 'client', 'routes.ts'),
|
|
23
|
+
path.join(appRoot, '.proteum', 'client', 'services.d.ts'),
|
|
24
|
+
path.join(appRoot, '.proteum', 'common', 'controllers.ts'),
|
|
25
|
+
path.join(appRoot, '.proteum', 'common', 'models.ts'),
|
|
26
|
+
path.join(appRoot, '.proteum', 'common', 'services.d.ts'),
|
|
27
|
+
path.join(appRoot, '.proteum', 'server', 'commands.app.d.ts'),
|
|
28
|
+
path.join(appRoot, '.proteum', 'server', 'commands.d.ts'),
|
|
29
|
+
path.join(appRoot, '.proteum', 'server', 'commands.ts'),
|
|
30
|
+
path.join(appRoot, '.proteum', 'server', 'controllers.ts'),
|
|
31
|
+
path.join(appRoot, '.proteum', 'server', 'models.ts'),
|
|
32
|
+
path.join(appRoot, '.proteum', 'server', 'routes.ts'),
|
|
33
|
+
path.join(appRoot, '.proteum', 'server', 'services.d.ts'),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
for (const route of manifest.routes.client) {
|
|
37
|
+
const sourceExtension = path.extname(route.filepath);
|
|
38
|
+
const chunkFilepath = route.chunkFilepath
|
|
39
|
+
? `${route.chunkFilepath}${sourceExtension && route.chunkFilepath.endsWith(sourceExtension) ? '' : sourceExtension}`
|
|
40
|
+
: undefined;
|
|
41
|
+
|
|
42
|
+
if (chunkFilepath) generated.add(path.join(clientRouteModulesRoot, chunkFilepath));
|
|
43
|
+
|
|
44
|
+
const relativeSource = path.relative(appRoot, route.filepath);
|
|
45
|
+
generated.add(path.join(serverRouteModulesRoot, relativeSource));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const route of manifest.routes.server) {
|
|
49
|
+
const relativeSource = path.relative(appRoot, route.filepath);
|
|
50
|
+
generated.add(path.join(serverRouteModulesRoot, relativeSource));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [...generated];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createContractDiagnostic = ({
|
|
57
|
+
code,
|
|
58
|
+
filepath,
|
|
59
|
+
level = 'error',
|
|
60
|
+
message,
|
|
61
|
+
relatedFilepaths,
|
|
62
|
+
}: {
|
|
63
|
+
code: string;
|
|
64
|
+
filepath: string;
|
|
65
|
+
level?: TProteumManifestDiagnostic['level'];
|
|
66
|
+
message: string;
|
|
67
|
+
relatedFilepaths?: string[];
|
|
68
|
+
}): TProteumManifestDiagnostic => ({
|
|
69
|
+
code,
|
|
70
|
+
filepath,
|
|
71
|
+
level,
|
|
72
|
+
message,
|
|
73
|
+
...(relatedFilepaths && relatedFilepaths.length > 0 ? { relatedFilepaths } : {}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const buildContractsDoctorResponse = (manifest: TProteumManifest, strict = false): TDoctorResponse => {
|
|
77
|
+
const diagnostics: TProteumManifestDiagnostic[] = [];
|
|
78
|
+
const sourceFilepaths = new Set<string>([
|
|
79
|
+
manifest.app.identityFilepath,
|
|
80
|
+
manifest.app.setupFilepath,
|
|
81
|
+
...manifest.controllers.map((controller) => controller.filepath),
|
|
82
|
+
...manifest.connectedProjects.flatMap((connectedProject) =>
|
|
83
|
+
connectedProject.cachedContractFilepath ? [connectedProject.cachedContractFilepath] : [],
|
|
84
|
+
),
|
|
85
|
+
...manifest.commands.map((command) => command.filepath),
|
|
86
|
+
...manifest.routes.client.map((route) => route.filepath),
|
|
87
|
+
...manifest.routes.server.map((route) => route.filepath),
|
|
88
|
+
...manifest.layouts.map((layout) => layout.filepath),
|
|
89
|
+
...manifest.services.app.flatMap((service) => (service.sourceFilepath ? [service.sourceFilepath] : [])),
|
|
90
|
+
...manifest.services.routerPlugins.flatMap((service) => (service.sourceFilepath ? [service.sourceFilepath] : [])),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
for (const filepath of sourceFilepaths) {
|
|
94
|
+
if (fs.existsSync(filepath)) continue;
|
|
95
|
+
|
|
96
|
+
diagnostics.push(
|
|
97
|
+
createContractDiagnostic({
|
|
98
|
+
code: 'contract.source-missing',
|
|
99
|
+
filepath,
|
|
100
|
+
message: `Referenced source file "${filepath}" is missing from disk.`,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const filepath of buildGeneratedArtifactList(manifest)) {
|
|
106
|
+
if (fs.existsSync(filepath)) continue;
|
|
107
|
+
|
|
108
|
+
diagnostics.push(
|
|
109
|
+
createContractDiagnostic({
|
|
110
|
+
code: 'contract.generated-missing',
|
|
111
|
+
filepath,
|
|
112
|
+
message: `Expected generated artifact "${filepath}" is missing.`,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.level === 'error');
|
|
118
|
+
const warnings = diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
diagnostics,
|
|
122
|
+
summary: {
|
|
123
|
+
errors: errors.length,
|
|
124
|
+
strictFailed: strict === true && diagnostics.length > 0,
|
|
125
|
+
warnings: warnings.length,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
};
|