proteum 2.1.8 → 2.1.9
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 +11 -5
- package/README.md +1 -1
- package/agents/project/AGENTS.md +7 -2
- package/agents/project/diagnostics.md +1 -1
- package/cli/app/index.ts +7 -7
- package/cli/commands/dev.ts +7 -1
- package/cli/compiler/artifacts/commands.ts +20 -5
- package/cli/compiler/client/index.ts +9 -9
- package/cli/compiler/common/index.ts +6 -5
- package/cli/compiler/index.ts +12 -5
- package/cli/compiler/server/index.ts +7 -6
- package/cli/index.ts +6 -0
- package/cli/paths.ts +300 -10
- package/cli/presentation/devSession.ts +19 -4
- package/cli/presentation/welcome.ts +6 -0
- package/cli/scaffold/index.ts +27 -4
- package/cli/scaffold/templates.ts +34 -20
- package/cli/utils/check.ts +5 -11
- package/client/app/index.ts +17 -2
- package/client/app.tsconfig.json +11 -10
- package/common/connectedProjects.ts +7 -0
- package/common/dev/serverHotReload.ts +22 -1
- package/package.json +1 -1
- package/server/app.tsconfig.json +10 -9
- package/server/services/router/http/index.ts +72 -10
package/cli/paths.ts
CHANGED
|
@@ -35,6 +35,29 @@ export type TPathInfos = {
|
|
|
35
35
|
isIndex: boolean;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
export type TResolvedPackageBinary = {
|
|
39
|
+
packageName: string;
|
|
40
|
+
packageRoot: string;
|
|
41
|
+
binPath: string;
|
|
42
|
+
command: string;
|
|
43
|
+
args: string[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type TFrameworkInstallGraph = {
|
|
47
|
+
activeRoot: string;
|
|
48
|
+
installedRoot?: string;
|
|
49
|
+
appNodeModulesRoot: string;
|
|
50
|
+
frameworkNodeModulesRoot: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type TFrameworkInstallMode = 'npm' | 'npm-link' | 'path' | 'workspace' | 'global' | 'checkout';
|
|
54
|
+
|
|
55
|
+
export type TFrameworkInstallInfo = {
|
|
56
|
+
mode: TFrameworkInstallMode;
|
|
57
|
+
summary: string;
|
|
58
|
+
dependencySpec?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
38
61
|
/*----------------------------------
|
|
39
62
|
- CONFIG
|
|
40
63
|
----------------------------------*/
|
|
@@ -51,7 +74,60 @@ const safeRealpath = (filepath: string) => {
|
|
|
51
74
|
}
|
|
52
75
|
};
|
|
53
76
|
|
|
54
|
-
const
|
|
77
|
+
const readPackageJson = (filepath: string) => {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(fs.readFileSync(filepath, 'utf8')) as Record<string, unknown>;
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const readPackageDependencySpec = (appRoot: string, packageName: string) => {
|
|
86
|
+
const packageJson = readPackageJson(path.join(appRoot, 'package.json'));
|
|
87
|
+
if (!packageJson) return undefined;
|
|
88
|
+
|
|
89
|
+
const dependencySections = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const;
|
|
90
|
+
|
|
91
|
+
for (const section of dependencySections) {
|
|
92
|
+
const dependencies = packageJson[section];
|
|
93
|
+
if (!dependencies || typeof dependencies !== 'object' || Array.isArray(dependencies)) continue;
|
|
94
|
+
|
|
95
|
+
const dependencySpec = (dependencies as Record<string, unknown>)[packageName];
|
|
96
|
+
if (typeof dependencySpec !== 'string' || dependencySpec.trim() === '') continue;
|
|
97
|
+
|
|
98
|
+
return dependencySpec.trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return undefined;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const findVisibleNodeModulesRoot = (startPath: string): string | undefined => {
|
|
105
|
+
let currentPath = path.resolve(startPath);
|
|
106
|
+
|
|
107
|
+
while (true) {
|
|
108
|
+
const candidate = path.join(currentPath, 'node_modules');
|
|
109
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
110
|
+
|
|
111
|
+
const parentPath = path.dirname(currentPath);
|
|
112
|
+
if (parentPath === currentPath) return undefined;
|
|
113
|
+
currentPath = parentPath;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const findVisiblePackageInstall = (startPath: string, packageName: string): string | undefined => {
|
|
118
|
+
let currentPath = path.resolve(startPath);
|
|
119
|
+
|
|
120
|
+
while (true) {
|
|
121
|
+
const candidate = path.join(currentPath, 'node_modules', packageName);
|
|
122
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
123
|
+
|
|
124
|
+
const parentPath = path.dirname(currentPath);
|
|
125
|
+
if (parentPath === currentPath) return undefined;
|
|
126
|
+
currentPath = parentPath;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const resolveCoreRoot = (appRoot: string): string => {
|
|
55
131
|
const currentPackageRoot = path.resolve(__dirname, '..');
|
|
56
132
|
const currentBin = path.join(currentPackageRoot, 'cli', 'bin.js');
|
|
57
133
|
const invokedScript = process.argv[1] ? safeRealpath(process.argv[1]) : '';
|
|
@@ -59,8 +135,8 @@ const resolveCoreRoot = (appRoot: string) => {
|
|
|
59
135
|
|
|
60
136
|
if (invokedCurrentPackage) return currentPackageRoot;
|
|
61
137
|
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
138
|
+
const installedFrameworkRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
139
|
+
if (installedFrameworkRoot) return installedFrameworkRoot;
|
|
64
140
|
|
|
65
141
|
// When running `npx`/global installs, there may be no local `node_modules/proteum` yet.
|
|
66
142
|
// Fall back to the installed package root (this file lives in `<root>/cli`).
|
|
@@ -69,9 +145,101 @@ const resolveCoreRoot = (appRoot: string) => {
|
|
|
69
145
|
|
|
70
146
|
const normalizeImportPath = (value: string) => value.replace(/\\/g, '/');
|
|
71
147
|
|
|
148
|
+
const resolveAppNodeModulesRoot = (appRoot: string): string => {
|
|
149
|
+
const installedRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
(installedRoot ? path.dirname(installedRoot) : undefined) ||
|
|
153
|
+
findVisibleNodeModulesRoot(appRoot) ||
|
|
154
|
+
path.join(appRoot, 'node_modules')
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const resolveFrameworkInstallRoot = (appRoot: string): string =>
|
|
159
|
+
findVisiblePackageInstall(appRoot, 'proteum') || path.join(resolveAppNodeModulesRoot(appRoot), 'proteum');
|
|
160
|
+
|
|
161
|
+
const resolveFrameworkInstallGraph = (appRoot: string, activeRoot: string): TFrameworkInstallGraph => {
|
|
162
|
+
const installedRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
activeRoot,
|
|
166
|
+
installedRoot,
|
|
167
|
+
appNodeModulesRoot: resolveAppNodeModulesRoot(appRoot),
|
|
168
|
+
frameworkNodeModulesRoot: findVisibleNodeModulesRoot(activeRoot) || path.join(activeRoot, 'node_modules'),
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const resolvePackageJsonPath = (packageName: string, searchPaths: string[]): string =>
|
|
173
|
+
require.resolve(`${packageName}/package.json`, { paths: searchPaths });
|
|
174
|
+
|
|
72
175
|
const filenameToImportName = (value: string) =>
|
|
73
176
|
normalizeImportPath(value).replace(/[^A-Za-z0-9_]+/g, '_');
|
|
74
177
|
|
|
178
|
+
export const resolveFrameworkInstallInfo = ({
|
|
179
|
+
appRoot,
|
|
180
|
+
framework,
|
|
181
|
+
}: {
|
|
182
|
+
appRoot: string;
|
|
183
|
+
framework: TFrameworkInstallGraph;
|
|
184
|
+
}): TFrameworkInstallInfo => {
|
|
185
|
+
const dependencySpec = readPackageDependencySpec(appRoot, 'proteum');
|
|
186
|
+
const installedRoot = framework.installedRoot ? path.resolve(framework.installedRoot) : undefined;
|
|
187
|
+
const normalizedActiveRoot = normalizeImportPath(safeRealpath(framework.activeRoot));
|
|
188
|
+
const installedRootIsSymlink =
|
|
189
|
+
installedRoot !== undefined &&
|
|
190
|
+
(() => {
|
|
191
|
+
try {
|
|
192
|
+
return fs.lstatSync(installedRoot).isSymbolicLink();
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
})();
|
|
197
|
+
|
|
198
|
+
if (dependencySpec?.startsWith('file:') || dependencySpec?.startsWith('link:')) {
|
|
199
|
+
return {
|
|
200
|
+
mode: 'path',
|
|
201
|
+
summary: `path (${dependencySpec})`,
|
|
202
|
+
dependencySpec,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (dependencySpec?.startsWith('workspace:')) {
|
|
207
|
+
return {
|
|
208
|
+
mode: 'workspace',
|
|
209
|
+
summary: `workspace (${dependencySpec})`,
|
|
210
|
+
dependencySpec,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (installedRootIsSymlink) {
|
|
215
|
+
return {
|
|
216
|
+
mode: 'npm-link',
|
|
217
|
+
summary: 'npm link',
|
|
218
|
+
dependencySpec,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (dependencySpec) {
|
|
223
|
+
return {
|
|
224
|
+
mode: 'npm',
|
|
225
|
+
summary: `npm (${dependencySpec})`,
|
|
226
|
+
dependencySpec,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!normalizedActiveRoot.includes('/node_modules/')) {
|
|
231
|
+
return {
|
|
232
|
+
mode: 'checkout',
|
|
233
|
+
summary: 'local checkout',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
mode: 'global',
|
|
239
|
+
summary: 'global install',
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
75
243
|
/*----------------------------------
|
|
76
244
|
- LIB
|
|
77
245
|
----------------------------------*/
|
|
@@ -79,13 +247,15 @@ export default class Paths {
|
|
|
79
247
|
/*----------------------------------
|
|
80
248
|
- LISTE
|
|
81
249
|
----------------------------------*/
|
|
82
|
-
|
|
83
|
-
public
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
) {
|
|
87
|
-
|
|
88
|
-
|
|
250
|
+
public coreRoot: string;
|
|
251
|
+
public framework: TFrameworkInstallGraph;
|
|
252
|
+
public core: { cli: string; root: string; pages: string };
|
|
253
|
+
|
|
254
|
+
public constructor(public appRoot: string, coreRoot = resolveCoreRoot(appRoot)) {
|
|
255
|
+
this.coreRoot = coreRoot;
|
|
256
|
+
this.framework = resolveFrameworkInstallGraph(appRoot, coreRoot);
|
|
257
|
+
this.core = { cli: path.resolve(__dirname, '.'), root: this.coreRoot, pages: this.coreRoot + '/client/pages' };
|
|
258
|
+
}
|
|
89
259
|
|
|
90
260
|
/*----------------------------------
|
|
91
261
|
- EXTRACTION
|
|
@@ -171,4 +341,124 @@ export default class Paths {
|
|
|
171
341
|
//console.log('Applying Aliases ...', aliases.forModuleAlias());
|
|
172
342
|
moduleAlias.addAliases(aliases.forModuleAlias());
|
|
173
343
|
}
|
|
344
|
+
|
|
345
|
+
public getFrameworkRoots(): string[] {
|
|
346
|
+
return [
|
|
347
|
+
this.framework.activeRoot,
|
|
348
|
+
...(this.framework.installedRoot ? [this.framework.installedRoot] : []),
|
|
349
|
+
].filter((rootPath, index, list) => list.indexOf(rootPath) === index && fs.existsSync(rootPath));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public getFrameworkInstallRoot(): string {
|
|
353
|
+
return this.getFrameworkInstallRootForAppRoot(this.appRoot);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
public getFrameworkInstallRootForAppRoot(appRoot: string): string {
|
|
357
|
+
return resolveFrameworkInstallRoot(appRoot);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public getAppNodeModulesRootForAppRoot(appRoot: string): string {
|
|
361
|
+
return resolveAppNodeModulesRoot(appRoot);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public relativePathFromFile(targetFile: string, absolutePath: string): string {
|
|
365
|
+
return this.relativePathFromDirectory(path.dirname(targetFile), absolutePath);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public relativePathFromDirectory(targetDirectory: string, absolutePath: string): string {
|
|
369
|
+
const relativePath = normalizeImportPath(path.relative(targetDirectory, absolutePath));
|
|
370
|
+
if (relativePath === '') return '.';
|
|
371
|
+
if (!relativePath.startsWith('.')) return `./${relativePath}`;
|
|
372
|
+
return relativePath;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public relativeFrameworkPathFrom(targetFile: string, ...segments: string[]): string {
|
|
376
|
+
return this.relativePathFromFile(targetFile, path.join(this.getFrameworkInstallRoot(), ...segments));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public relativeAppNodeModulesPathFrom(targetFile: string, ...segments: string[]): string {
|
|
380
|
+
return this.relativePathFromFile(targetFile, path.join(this.framework.appNodeModulesRoot, ...segments));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public relativeFrameworkPathFromDirectory(targetDirectory: string, ...segments: string[]): string {
|
|
384
|
+
return this.relativePathFromDirectory(targetDirectory, path.join(this.getFrameworkInstallRoot(), ...segments));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public relativeAppNodeModulesPathFromDirectory(targetDirectory: string, ...segments: string[]): string {
|
|
388
|
+
return this.relativePathFromDirectory(targetDirectory, path.join(this.framework.appNodeModulesRoot, ...segments));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
public relativeFrameworkTsconfigPathFrom(targetFile: string): string {
|
|
392
|
+
return this.relativeFrameworkPathFrom(targetFile, 'tsconfig.common.json');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
public relativeFrameworkPathForAppRoot(appRoot: string, targetFile: string, ...segments: string[]): string {
|
|
396
|
+
return this.relativePathFromFile(targetFile, path.join(this.getFrameworkInstallRootForAppRoot(appRoot), ...segments));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
public relativeFrameworkPathFromDirectoryForAppRoot(
|
|
400
|
+
appRoot: string,
|
|
401
|
+
targetDirectory: string,
|
|
402
|
+
...segments: string[]
|
|
403
|
+
): string {
|
|
404
|
+
return this.relativePathFromDirectory(
|
|
405
|
+
targetDirectory,
|
|
406
|
+
path.join(this.getFrameworkInstallRootForAppRoot(appRoot), ...segments),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public relativeAppNodeModulesPathFromDirectoryForAppRoot(
|
|
411
|
+
appRoot: string,
|
|
412
|
+
targetDirectory: string,
|
|
413
|
+
...segments: string[]
|
|
414
|
+
): string {
|
|
415
|
+
return this.relativePathFromDirectory(
|
|
416
|
+
targetDirectory,
|
|
417
|
+
path.join(this.getAppNodeModulesRootForAppRoot(appRoot), ...segments),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public resolvePackageRoot(packageName: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
422
|
+
const searchPaths = preferApp
|
|
423
|
+
? [this.appRoot, this.framework.activeRoot]
|
|
424
|
+
: [this.framework.activeRoot, this.appRoot];
|
|
425
|
+
return path.dirname(resolvePackageJsonPath(packageName, searchPaths));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
public resolveRequest(request: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
429
|
+
const searchPaths = preferApp
|
|
430
|
+
? [this.appRoot, this.framework.activeRoot]
|
|
431
|
+
: [this.framework.activeRoot, this.appRoot];
|
|
432
|
+
return require.resolve(request, { paths: searchPaths });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public resolveBinary(
|
|
436
|
+
packageName: string,
|
|
437
|
+
binName = packageName,
|
|
438
|
+
{ preferApp = true }: { preferApp?: boolean } = {},
|
|
439
|
+
): TResolvedPackageBinary {
|
|
440
|
+
const packageRoot = this.resolvePackageRoot(packageName, { preferApp });
|
|
441
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) as {
|
|
442
|
+
bin?: string | Record<string, string>;
|
|
443
|
+
};
|
|
444
|
+
const binField = packageJson.bin;
|
|
445
|
+
const relativeBinPath =
|
|
446
|
+
typeof binField === 'string'
|
|
447
|
+
? binField
|
|
448
|
+
: binField?.[binName] || binField?.[packageName];
|
|
449
|
+
|
|
450
|
+
if (!relativeBinPath) {
|
|
451
|
+
throw new Error(`Unable to resolve binary "${binName}" from package "${packageName}".`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const binPath = path.resolve(packageRoot, relativeBinPath);
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
packageName,
|
|
458
|
+
packageRoot,
|
|
459
|
+
binPath,
|
|
460
|
+
command: process.execPath,
|
|
461
|
+
args: [binPath],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
174
464
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
const React = require('react') as typeof import('react');
|
|
2
2
|
|
|
3
|
+
import type { TServerReadyConnectedProject } from '../../common/dev/serverHotReload';
|
|
3
4
|
import { renderRows } from './layout';
|
|
4
5
|
import { renderInk } from './ink';
|
|
5
6
|
import { renderWelcomePanel } from './welcome';
|
|
6
7
|
|
|
8
|
+
const formatConnectedProjectLabel = (connectedProject: TServerReadyConnectedProject) =>
|
|
9
|
+
`${connectedProject.namespace} -> ${connectedProject.name}`;
|
|
10
|
+
|
|
7
11
|
export const renderDevSession = async ({
|
|
8
12
|
appName,
|
|
9
13
|
appRoot,
|
|
10
14
|
routerPort,
|
|
11
15
|
devEventPort,
|
|
12
16
|
connectedProjects,
|
|
17
|
+
proteumInstallSummary,
|
|
13
18
|
proteumVersion,
|
|
14
19
|
}: {
|
|
15
20
|
appName: string;
|
|
@@ -17,10 +22,12 @@ export const renderDevSession = async ({
|
|
|
17
22
|
routerPort: number;
|
|
18
23
|
devEventPort: number;
|
|
19
24
|
connectedProjects?: Array<{ namespace: string; urlInternal: string }>;
|
|
25
|
+
proteumInstallSummary?: string;
|
|
20
26
|
proteumVersion: string;
|
|
21
27
|
}) =>
|
|
22
28
|
[
|
|
23
29
|
await renderWelcomePanel({
|
|
30
|
+
installSummary: proteumInstallSummary,
|
|
24
31
|
version: proteumVersion,
|
|
25
32
|
tagline: 'Agent-first SSR compiler and server loop.',
|
|
26
33
|
}),
|
|
@@ -51,15 +58,16 @@ export const renderServerReadyBanner = async ({
|
|
|
51
58
|
appName,
|
|
52
59
|
publicUrl,
|
|
53
60
|
routerPort,
|
|
54
|
-
|
|
61
|
+
connectedProjects,
|
|
55
62
|
}: {
|
|
56
63
|
appName: string;
|
|
57
64
|
publicUrl: string;
|
|
58
65
|
routerPort: number;
|
|
59
|
-
|
|
66
|
+
connectedProjects?: TServerReadyConnectedProject[];
|
|
60
67
|
}) =>
|
|
61
68
|
renderInk(({ Box, Text }) => {
|
|
62
69
|
const createElement = React.createElement;
|
|
70
|
+
const verifiedConnectedProjects = connectedProjects || [];
|
|
63
71
|
|
|
64
72
|
return createElement(
|
|
65
73
|
Box,
|
|
@@ -68,13 +76,20 @@ export const renderServerReadyBanner = async ({
|
|
|
68
76
|
createElement(Text, { bold: true, color: 'green' }, appName),
|
|
69
77
|
createElement(Text, { bold: true }, publicUrl),
|
|
70
78
|
createElement(Text, { dimColor: true }, 'SSR server is listening for requests and hot reloads.'),
|
|
71
|
-
|
|
79
|
+
verifiedConnectedProjects.length > 0
|
|
72
80
|
? createElement(
|
|
73
81
|
Text,
|
|
74
82
|
{ dimColor: true },
|
|
75
|
-
`Connected
|
|
83
|
+
`Connected apps: ${verifiedConnectedProjects.map((connectedProject) => formatConnectedProjectLabel(connectedProject)).join(', ')}`,
|
|
76
84
|
)
|
|
77
85
|
: null,
|
|
86
|
+
...verifiedConnectedProjects.map((connectedProject) =>
|
|
87
|
+
createElement(
|
|
88
|
+
Text,
|
|
89
|
+
{ key: `connected-ping-${connectedProject.namespace}`, dimColor: true },
|
|
90
|
+
`Ping OK: ${formatConnectedProjectLabel(connectedProject)} responded to /api/__proteum/connected/ping`,
|
|
91
|
+
),
|
|
92
|
+
),
|
|
78
93
|
createElement(Text, { dimColor: true }, `Diagnose /: proteum diagnose / --port ${routerPort}`),
|
|
79
94
|
createElement(Text, { dimColor: true }, `Perf top: proteum perf top --port ${routerPort}`),
|
|
80
95
|
createElement(Text, { dimColor: true }, `Trace latest: proteum trace latest --port ${routerPort}`),
|
|
@@ -18,9 +18,11 @@ export const clearInteractiveConsole = () => {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const renderWelcomePanel = async ({
|
|
21
|
+
installSummary,
|
|
21
22
|
version,
|
|
22
23
|
tagline,
|
|
23
24
|
}: {
|
|
25
|
+
installSummary?: string;
|
|
24
26
|
version: string;
|
|
25
27
|
tagline: string;
|
|
26
28
|
}) =>
|
|
@@ -37,19 +39,23 @@ export const renderWelcomePanel = async ({
|
|
|
37
39
|
createElement(Text, { bold: true, backgroundColor: 'blue', color: 'white' }, ' WELCOME TO '),
|
|
38
40
|
createElement(Box, { flexDirection: 'column' }, ...wordmark),
|
|
39
41
|
versionLabel ? createElement(Text, { bold: true, color: 'blue' }, versionLabel) : null,
|
|
42
|
+
installSummary ? createElement(Text, { dimColor: true }, `Installed via ${installSummary}`) : null,
|
|
40
43
|
createElement(Text, { dimColor: true }, tagline),
|
|
41
44
|
);
|
|
42
45
|
});
|
|
43
46
|
|
|
44
47
|
export const renderCliWelcomeBanner = async ({
|
|
45
48
|
command,
|
|
49
|
+
installSummary,
|
|
46
50
|
version,
|
|
47
51
|
}: {
|
|
48
52
|
command: string;
|
|
53
|
+
installSummary?: string;
|
|
49
54
|
version: string;
|
|
50
55
|
}) =>
|
|
51
56
|
[
|
|
52
57
|
await renderWelcomePanel({
|
|
58
|
+
installSummary,
|
|
53
59
|
version,
|
|
54
60
|
tagline: 'Explicit SSR / SEO / TypeScript framework for agent-friendly apps.',
|
|
55
61
|
}),
|
package/cli/scaffold/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
createServiceConfigTemplate,
|
|
27
27
|
createServiceTemplate,
|
|
28
28
|
} from './templates';
|
|
29
|
+
import type { TTsconfigTemplatePaths } from './templates';
|
|
29
30
|
import type { TScaffoldFilePlan, TScaffoldInitConfig, TScaffoldKind, TScaffoldResult } from './types';
|
|
30
31
|
|
|
31
32
|
type TCreatePlan = {
|
|
@@ -602,7 +603,29 @@ const assertInitTarget = ({ appRoot }: { appRoot: string }) => {
|
|
|
602
603
|
);
|
|
603
604
|
};
|
|
604
605
|
|
|
605
|
-
const
|
|
606
|
+
const createAppTsconfigTemplatePaths = ({
|
|
607
|
+
appRoot,
|
|
608
|
+
side,
|
|
609
|
+
}: {
|
|
610
|
+
appRoot: string;
|
|
611
|
+
side: 'client' | 'server';
|
|
612
|
+
}): TTsconfigTemplatePaths => {
|
|
613
|
+
const tsconfigFilepath = path.join(appRoot, side, 'tsconfig.json');
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
frameworkTsconfig: cli.paths.relativeFrameworkPathForAppRoot(appRoot, tsconfigFilepath, 'tsconfig.common.json'),
|
|
617
|
+
frameworkClient: cli.paths.relativeFrameworkPathFromDirectoryForAppRoot(appRoot, appRoot, 'client', '*'),
|
|
618
|
+
frameworkCommon: cli.paths.relativeFrameworkPathFromDirectoryForAppRoot(appRoot, appRoot, 'common', '*'),
|
|
619
|
+
frameworkServer: cli.paths.relativeFrameworkPathFromDirectoryForAppRoot(appRoot, appRoot, 'server', '*'),
|
|
620
|
+
frameworkTypesGlobal: cli.paths.relativeFrameworkPathForAppRoot(appRoot, tsconfigFilepath, 'types', 'global'),
|
|
621
|
+
preactCompat: cli.paths.relativeAppNodeModulesPathFromDirectoryForAppRoot(appRoot, appRoot, 'preact', 'compat'),
|
|
622
|
+
preactCompatClient: cli.paths.relativeAppNodeModulesPathFromDirectoryForAppRoot(appRoot, appRoot, 'preact', 'compat', 'client'),
|
|
623
|
+
preactTestUtils: cli.paths.relativeAppNodeModulesPathFromDirectoryForAppRoot(appRoot, appRoot, 'preact', 'test-utils'),
|
|
624
|
+
preactJsxRuntime: cli.paths.relativeAppNodeModulesPathFromDirectoryForAppRoot(appRoot, appRoot, 'preact', 'jsx-runtime'),
|
|
625
|
+
};
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const createInitFilePlans = ({ appRoot, config }: { appRoot: string; config: TScaffoldInitConfig }): TScaffoldFilePlan[] => [
|
|
606
629
|
{
|
|
607
630
|
relativePath: 'package.json',
|
|
608
631
|
content: createPackageJsonTemplate({
|
|
@@ -643,11 +666,11 @@ const createInitFilePlans = (config: TScaffoldInitConfig): TScaffoldFilePlan[] =
|
|
|
643
666
|
},
|
|
644
667
|
{
|
|
645
668
|
relativePath: path.join('client', 'tsconfig.json'),
|
|
646
|
-
content: createClientTsconfigTemplate(),
|
|
669
|
+
content: createClientTsconfigTemplate(createAppTsconfigTemplatePaths({ appRoot, side: 'client' })),
|
|
647
670
|
},
|
|
648
671
|
{
|
|
649
672
|
relativePath: path.join('server', 'tsconfig.json'),
|
|
650
|
-
content: createServerTsconfigTemplate(),
|
|
673
|
+
content: createServerTsconfigTemplate(createAppTsconfigTemplatePaths({ appRoot, side: 'server' })),
|
|
651
674
|
},
|
|
652
675
|
{
|
|
653
676
|
relativePath: path.join('server', 'config', 'app.ts'),
|
|
@@ -675,7 +698,7 @@ export const runInitScaffold = async () => {
|
|
|
675
698
|
assertInitTarget({ appRoot });
|
|
676
699
|
|
|
677
700
|
const result = createEmptyResult({ dryRun: isDryRun() });
|
|
678
|
-
const filePlans = createInitFilePlans(config);
|
|
701
|
+
const filePlans = createInitFilePlans({ appRoot, config });
|
|
679
702
|
|
|
680
703
|
maybeWriteFilePlans({ rootDir: appRoot, filePlans, result });
|
|
681
704
|
|
|
@@ -2,6 +2,18 @@ import type { TScaffoldInitConfig, TScaffoldResult } from './types';
|
|
|
2
2
|
|
|
3
3
|
const renderJson = (value: unknown) => JSON.stringify(value, null, 4);
|
|
4
4
|
|
|
5
|
+
export type TTsconfigTemplatePaths = {
|
|
6
|
+
frameworkTsconfig: string;
|
|
7
|
+
frameworkClient: string;
|
|
8
|
+
frameworkCommon: string;
|
|
9
|
+
frameworkServer: string;
|
|
10
|
+
frameworkTypesGlobal: string;
|
|
11
|
+
preactCompat: string;
|
|
12
|
+
preactCompatClient: string;
|
|
13
|
+
preactTestUtils: string;
|
|
14
|
+
preactJsxRuntime: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
5
17
|
export const createPageTemplate = ({
|
|
6
18
|
routePath,
|
|
7
19
|
heading,
|
|
@@ -162,8 +174,8 @@ export default class ${appIdentifier} extends Application {
|
|
|
162
174
|
}
|
|
163
175
|
`;
|
|
164
176
|
|
|
165
|
-
export const createClientTsconfigTemplate = () => `{
|
|
166
|
-
"extends":
|
|
177
|
+
export const createClientTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `{
|
|
178
|
+
"extends": ${JSON.stringify(paths.frameworkTsconfig)},
|
|
167
179
|
"compilerOptions": {
|
|
168
180
|
"rootDir": "..",
|
|
169
181
|
"baseUrl": "..",
|
|
@@ -172,9 +184,9 @@ export const createClientTsconfigTemplate = () => `{
|
|
|
172
184
|
"strictBindCallApply": true,
|
|
173
185
|
"useUnknownInCatchVariables": true,
|
|
174
186
|
"paths": {
|
|
175
|
-
"@client/*": [
|
|
176
|
-
"@common/*": [
|
|
177
|
-
"@server/*": [
|
|
187
|
+
"@client/*": [${JSON.stringify(paths.frameworkClient)}],
|
|
188
|
+
"@common/*": [${JSON.stringify(paths.frameworkCommon)}],
|
|
189
|
+
"@server/*": [${JSON.stringify(paths.frameworkServer)}],
|
|
178
190
|
|
|
179
191
|
"@/client/context": ["./.proteum/client/context.ts"],
|
|
180
192
|
"@generated/client/*": ["./.proteum/client/*"],
|
|
@@ -182,24 +194,25 @@ export const createClientTsconfigTemplate = () => `{
|
|
|
182
194
|
"@generated/server/*": ["./.proteum/server/*"],
|
|
183
195
|
"@/*": ["./*"],
|
|
184
196
|
|
|
185
|
-
"react": [
|
|
186
|
-
"react-dom/
|
|
187
|
-
"react-dom": [
|
|
188
|
-
"react
|
|
197
|
+
"react": [${JSON.stringify(paths.preactCompat)}],
|
|
198
|
+
"react-dom/client": [${JSON.stringify(paths.preactCompatClient)}],
|
|
199
|
+
"react-dom/test-utils": [${JSON.stringify(paths.preactTestUtils)}],
|
|
200
|
+
"react-dom": [${JSON.stringify(paths.preactCompat)}],
|
|
201
|
+
"react/jsx-runtime": [${JSON.stringify(paths.preactJsxRuntime)}]
|
|
189
202
|
}
|
|
190
203
|
},
|
|
191
204
|
"include": [
|
|
192
205
|
".",
|
|
193
206
|
"../var/typings",
|
|
194
|
-
|
|
207
|
+
${JSON.stringify(paths.frameworkTypesGlobal)},
|
|
195
208
|
"../.proteum/client/services.d.ts",
|
|
196
209
|
"../server/index.ts"
|
|
197
210
|
]
|
|
198
211
|
}
|
|
199
212
|
`;
|
|
200
213
|
|
|
201
|
-
export const createServerTsconfigTemplate = () => `{
|
|
202
|
-
"extends":
|
|
214
|
+
export const createServerTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `{
|
|
215
|
+
"extends": ${JSON.stringify(paths.frameworkTsconfig)},
|
|
203
216
|
"compilerOptions": {
|
|
204
217
|
"rootDir": "..",
|
|
205
218
|
"baseUrl": "..",
|
|
@@ -209,9 +222,9 @@ export const createServerTsconfigTemplate = () => `{
|
|
|
209
222
|
"useUnknownInCatchVariables": true,
|
|
210
223
|
"moduleSuffixes": [".ssr", ""],
|
|
211
224
|
"paths": {
|
|
212
|
-
"@client/*": [
|
|
213
|
-
"@common/*": [
|
|
214
|
-
"@server/*": [
|
|
225
|
+
"@client/*": [${JSON.stringify(paths.frameworkClient)}],
|
|
226
|
+
"@common/*": [${JSON.stringify(paths.frameworkCommon)}],
|
|
227
|
+
"@server/*": [${JSON.stringify(paths.frameworkServer)}],
|
|
215
228
|
|
|
216
229
|
"@/client/context": ["./.proteum/client/context.ts"],
|
|
217
230
|
"@generated/client/*": ["./.proteum/client/*"],
|
|
@@ -219,10 +232,11 @@ export const createServerTsconfigTemplate = () => `{
|
|
|
219
232
|
"@generated/server/*": ["./.proteum/server/*"],
|
|
220
233
|
"@/*": ["./*"],
|
|
221
234
|
|
|
222
|
-
"react": [
|
|
223
|
-
"react-dom/
|
|
224
|
-
"react-dom": [
|
|
225
|
-
"react
|
|
235
|
+
"react": [${JSON.stringify(paths.preactCompat)}],
|
|
236
|
+
"react-dom/client": [${JSON.stringify(paths.preactCompatClient)}],
|
|
237
|
+
"react-dom/test-utils": [${JSON.stringify(paths.preactTestUtils)}],
|
|
238
|
+
"react-dom": [${JSON.stringify(paths.preactCompat)}],
|
|
239
|
+
"react/jsx-runtime": [${JSON.stringify(paths.preactJsxRuntime)}]
|
|
226
240
|
}
|
|
227
241
|
},
|
|
228
242
|
"include": [
|
|
@@ -230,7 +244,7 @@ export const createServerTsconfigTemplate = () => `{
|
|
|
230
244
|
"../identity.config.ts",
|
|
231
245
|
"../proteum.config.ts",
|
|
232
246
|
"../var/typings",
|
|
233
|
-
|
|
247
|
+
${JSON.stringify(paths.frameworkTypesGlobal)},
|
|
234
248
|
"../.proteum/server/services.d.ts",
|
|
235
249
|
"../server/index.ts"
|
|
236
250
|
]
|
package/cli/utils/check.ts
CHANGED
|
@@ -8,13 +8,7 @@ import { runProcess } from './runProcess';
|
|
|
8
8
|
const tsconfigPaths = ['client/tsconfig.json', 'server/tsconfig.json', 'commands/tsconfig.json'];
|
|
9
9
|
const eslintConfigPaths = ['eslint.config.mjs', 'eslint.config.js', 'eslint.config.cjs'];
|
|
10
10
|
|
|
11
|
-
const resolveInstalledBinary = (
|
|
12
|
-
const binary = path.join(cli.paths.core.root, 'node_modules', '.bin', name);
|
|
13
|
-
|
|
14
|
-
if (!fs.existsSync(binary)) throw new Error(`Missing required binary "${name}" in Proteum dependencies.`);
|
|
15
|
-
|
|
16
|
-
return binary;
|
|
17
|
-
};
|
|
11
|
+
const resolveInstalledBinary = (packageName: string, binName: string) => cli.paths.resolveBinary(packageName, binName);
|
|
18
12
|
|
|
19
13
|
const resolveExistingAppPaths = (paths: string[]) =>
|
|
20
14
|
paths
|
|
@@ -43,10 +37,10 @@ export const runAppTypecheck = async () => {
|
|
|
43
37
|
if (existingProjects.length === 0)
|
|
44
38
|
throw new Error(`No TypeScript app projects found. Expected one of: ${tsconfigPaths.join(', ')}.`);
|
|
45
39
|
|
|
46
|
-
const tsc = resolveInstalledBinary('tsc');
|
|
40
|
+
const tsc = resolveInstalledBinary('typescript', 'tsc');
|
|
47
41
|
|
|
48
42
|
for (const { relativePath } of existingProjects)
|
|
49
|
-
await runProcess(tsc, ['-p', relativePath, '--noEmit', '--pretty', 'false'], {
|
|
43
|
+
await runProcess(tsc.command, [...tsc.args, '-p', relativePath, '--noEmit', '--pretty', 'false'], {
|
|
50
44
|
cwd: cli.paths.appRoot,
|
|
51
45
|
env: getTypecheckEnv(),
|
|
52
46
|
});
|
|
@@ -62,10 +56,10 @@ export const runAppLint = async ({ fix = false } = {}) => {
|
|
|
62
56
|
.join(', ')}.`,
|
|
63
57
|
);
|
|
64
58
|
|
|
65
|
-
const eslint = resolveInstalledBinary('eslint');
|
|
59
|
+
const eslint = resolveInstalledBinary('eslint', 'eslint');
|
|
66
60
|
const args = ['.', '--config', config.absolutePath, '--no-config-lookup'];
|
|
67
61
|
|
|
68
62
|
if (fix) args.push('--fix');
|
|
69
63
|
|
|
70
|
-
await runProcess(eslint, args, { cwd: cli.paths.appRoot });
|
|
64
|
+
await runProcess(eslint.command, [...eslint.args, ...args], { cwd: cli.paths.appRoot });
|
|
71
65
|
};
|