proteum 2.1.8 → 2.1.9-2
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 +33 -9
- package/cli/commands/dev.ts +7 -1
- package/cli/compiler/artifacts/commands.ts +20 -5
- package/cli/compiler/client/index.ts +46 -23
- package/cli/compiler/common/index.ts +16 -5
- package/cli/compiler/index.ts +12 -5
- package/cli/compiler/server/index.ts +39 -13
- package/cli/index.ts +6 -0
- package/cli/paths.ts +341 -10
- package/cli/presentation/devSession.ts +22 -9
- package/cli/presentation/ink.ts +10 -5
- package/cli/presentation/welcome.ts +8 -4
- 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 +2 -1
- package/server/app.tsconfig.json +10 -9
- package/server/services/auth/index.ts +9 -0
- 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,88 @@ 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 findVisibleNodeModulesRoots = (startPath: string): string[] => {
|
|
118
|
+
const roots: string[] = [];
|
|
119
|
+
let currentPath = path.resolve(startPath);
|
|
120
|
+
|
|
121
|
+
while (true) {
|
|
122
|
+
const candidate = path.join(currentPath, 'node_modules');
|
|
123
|
+
if (fs.existsSync(candidate) && !roots.includes(candidate)) roots.push(candidate);
|
|
124
|
+
|
|
125
|
+
const parentPath = path.dirname(currentPath);
|
|
126
|
+
if (parentPath === currentPath) return roots;
|
|
127
|
+
currentPath = parentPath;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const findVisiblePackageInstall = (startPath: string, packageName: string): string | undefined => {
|
|
132
|
+
let currentPath = path.resolve(startPath);
|
|
133
|
+
|
|
134
|
+
while (true) {
|
|
135
|
+
const candidate = path.join(currentPath, 'node_modules', packageName);
|
|
136
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
137
|
+
|
|
138
|
+
const parentPath = path.dirname(currentPath);
|
|
139
|
+
if (parentPath === currentPath) return undefined;
|
|
140
|
+
currentPath = parentPath;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const findVisiblePackageInstalls = (startPath: string, packageName: string): string[] => {
|
|
145
|
+
const installs: string[] = [];
|
|
146
|
+
let currentPath = path.resolve(startPath);
|
|
147
|
+
|
|
148
|
+
while (true) {
|
|
149
|
+
const candidate = path.join(currentPath, 'node_modules', packageName);
|
|
150
|
+
if (fs.existsSync(candidate) && !installs.includes(candidate)) installs.push(candidate);
|
|
151
|
+
|
|
152
|
+
const parentPath = path.dirname(currentPath);
|
|
153
|
+
if (parentPath === currentPath) return installs;
|
|
154
|
+
currentPath = parentPath;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const resolveCoreRoot = (appRoot: string): string => {
|
|
55
159
|
const currentPackageRoot = path.resolve(__dirname, '..');
|
|
56
160
|
const currentBin = path.join(currentPackageRoot, 'cli', 'bin.js');
|
|
57
161
|
const invokedScript = process.argv[1] ? safeRealpath(process.argv[1]) : '';
|
|
@@ -59,8 +163,8 @@ const resolveCoreRoot = (appRoot: string) => {
|
|
|
59
163
|
|
|
60
164
|
if (invokedCurrentPackage) return currentPackageRoot;
|
|
61
165
|
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
166
|
+
const installedFrameworkRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
167
|
+
if (installedFrameworkRoot) return installedFrameworkRoot;
|
|
64
168
|
|
|
65
169
|
// When running `npx`/global installs, there may be no local `node_modules/proteum` yet.
|
|
66
170
|
// Fall back to the installed package root (this file lives in `<root>/cli`).
|
|
@@ -69,9 +173,101 @@ const resolveCoreRoot = (appRoot: string) => {
|
|
|
69
173
|
|
|
70
174
|
const normalizeImportPath = (value: string) => value.replace(/\\/g, '/');
|
|
71
175
|
|
|
176
|
+
const resolveAppNodeModulesRoot = (appRoot: string): string => {
|
|
177
|
+
const installedRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
(installedRoot ? path.dirname(installedRoot) : undefined) ||
|
|
181
|
+
findVisibleNodeModulesRoot(appRoot) ||
|
|
182
|
+
path.join(appRoot, 'node_modules')
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const resolveFrameworkInstallRoot = (appRoot: string): string =>
|
|
187
|
+
findVisiblePackageInstall(appRoot, 'proteum') || path.join(resolveAppNodeModulesRoot(appRoot), 'proteum');
|
|
188
|
+
|
|
189
|
+
const resolveFrameworkInstallGraph = (appRoot: string, activeRoot: string): TFrameworkInstallGraph => {
|
|
190
|
+
const installedRoot = findVisiblePackageInstall(appRoot, 'proteum');
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
activeRoot,
|
|
194
|
+
installedRoot,
|
|
195
|
+
appNodeModulesRoot: resolveAppNodeModulesRoot(appRoot),
|
|
196
|
+
frameworkNodeModulesRoot: findVisibleNodeModulesRoot(activeRoot) || path.join(activeRoot, 'node_modules'),
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const resolvePackageJsonPath = (packageName: string, searchPaths: string[]): string =>
|
|
201
|
+
require.resolve(`${packageName}/package.json`, { paths: searchPaths });
|
|
202
|
+
|
|
72
203
|
const filenameToImportName = (value: string) =>
|
|
73
204
|
normalizeImportPath(value).replace(/[^A-Za-z0-9_]+/g, '_');
|
|
74
205
|
|
|
206
|
+
export const resolveFrameworkInstallInfo = ({
|
|
207
|
+
appRoot,
|
|
208
|
+
framework,
|
|
209
|
+
}: {
|
|
210
|
+
appRoot: string;
|
|
211
|
+
framework: TFrameworkInstallGraph;
|
|
212
|
+
}): TFrameworkInstallInfo => {
|
|
213
|
+
const dependencySpec = readPackageDependencySpec(appRoot, 'proteum');
|
|
214
|
+
const installedRoot = framework.installedRoot ? path.resolve(framework.installedRoot) : undefined;
|
|
215
|
+
const normalizedActiveRoot = normalizeImportPath(safeRealpath(framework.activeRoot));
|
|
216
|
+
const installedRootIsSymlink =
|
|
217
|
+
installedRoot !== undefined &&
|
|
218
|
+
(() => {
|
|
219
|
+
try {
|
|
220
|
+
return fs.lstatSync(installedRoot).isSymbolicLink();
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
|
|
226
|
+
if (dependencySpec?.startsWith('file:') || dependencySpec?.startsWith('link:')) {
|
|
227
|
+
return {
|
|
228
|
+
mode: 'path',
|
|
229
|
+
summary: `path (${dependencySpec})`,
|
|
230
|
+
dependencySpec,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (dependencySpec?.startsWith('workspace:')) {
|
|
235
|
+
return {
|
|
236
|
+
mode: 'workspace',
|
|
237
|
+
summary: `workspace (${dependencySpec})`,
|
|
238
|
+
dependencySpec,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (installedRootIsSymlink) {
|
|
243
|
+
return {
|
|
244
|
+
mode: 'npm-link',
|
|
245
|
+
summary: 'npm link',
|
|
246
|
+
dependencySpec,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (dependencySpec) {
|
|
251
|
+
return {
|
|
252
|
+
mode: 'npm',
|
|
253
|
+
summary: `npm (${dependencySpec})`,
|
|
254
|
+
dependencySpec,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!normalizedActiveRoot.includes('/node_modules/')) {
|
|
259
|
+
return {
|
|
260
|
+
mode: 'checkout',
|
|
261
|
+
summary: 'local checkout',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
mode: 'global',
|
|
267
|
+
summary: 'global install',
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
|
|
75
271
|
/*----------------------------------
|
|
76
272
|
- LIB
|
|
77
273
|
----------------------------------*/
|
|
@@ -79,13 +275,15 @@ export default class Paths {
|
|
|
79
275
|
/*----------------------------------
|
|
80
276
|
- LISTE
|
|
81
277
|
----------------------------------*/
|
|
82
|
-
|
|
83
|
-
public
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
) {
|
|
87
|
-
|
|
88
|
-
|
|
278
|
+
public coreRoot: string;
|
|
279
|
+
public framework: TFrameworkInstallGraph;
|
|
280
|
+
public core: { cli: string; root: string; pages: string };
|
|
281
|
+
|
|
282
|
+
public constructor(public appRoot: string, coreRoot = resolveCoreRoot(appRoot)) {
|
|
283
|
+
this.coreRoot = coreRoot;
|
|
284
|
+
this.framework = resolveFrameworkInstallGraph(appRoot, coreRoot);
|
|
285
|
+
this.core = { cli: path.resolve(__dirname, '.'), root: this.coreRoot, pages: this.coreRoot + '/client/pages' };
|
|
286
|
+
}
|
|
89
287
|
|
|
90
288
|
/*----------------------------------
|
|
91
289
|
- EXTRACTION
|
|
@@ -171,4 +369,137 @@ export default class Paths {
|
|
|
171
369
|
//console.log('Applying Aliases ...', aliases.forModuleAlias());
|
|
172
370
|
moduleAlias.addAliases(aliases.forModuleAlias());
|
|
173
371
|
}
|
|
372
|
+
|
|
373
|
+
public getFrameworkRoots(): string[] {
|
|
374
|
+
return [
|
|
375
|
+
this.framework.activeRoot,
|
|
376
|
+
...(this.framework.installedRoot ? [this.framework.installedRoot] : []),
|
|
377
|
+
...this.getVisiblePackageInstallRoots('proteum'),
|
|
378
|
+
].filter((rootPath, index, list) => list.indexOf(rootPath) === index && fs.existsSync(rootPath));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public getVisibleNodeModulesRootsForPath(startPath: string): string[] {
|
|
382
|
+
return findVisibleNodeModulesRoots(startPath);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public getVisiblePackageInstallRoots(packageName: string, startPath = this.appRoot): string[] {
|
|
386
|
+
return findVisiblePackageInstalls(startPath, packageName);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public getFrameworkInstallRoot(): string {
|
|
390
|
+
return this.getFrameworkInstallRootForAppRoot(this.appRoot);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public getFrameworkInstallRootForAppRoot(appRoot: string): string {
|
|
394
|
+
return resolveFrameworkInstallRoot(appRoot);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public getAppNodeModulesRootForAppRoot(appRoot: string): string {
|
|
398
|
+
return resolveAppNodeModulesRoot(appRoot);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
public relativePathFromFile(targetFile: string, absolutePath: string): string {
|
|
402
|
+
return this.relativePathFromDirectory(path.dirname(targetFile), absolutePath);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
public relativePathFromDirectory(targetDirectory: string, absolutePath: string): string {
|
|
406
|
+
const relativePath = normalizeImportPath(path.relative(targetDirectory, absolutePath));
|
|
407
|
+
if (relativePath === '') return '.';
|
|
408
|
+
if (!relativePath.startsWith('.')) return `./${relativePath}`;
|
|
409
|
+
return relativePath;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
public relativeFrameworkPathFrom(targetFile: string, ...segments: string[]): string {
|
|
413
|
+
return this.relativePathFromFile(targetFile, path.join(this.getFrameworkInstallRoot(), ...segments));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
public relativeAppNodeModulesPathFrom(targetFile: string, ...segments: string[]): string {
|
|
417
|
+
return this.relativePathFromFile(targetFile, path.join(this.framework.appNodeModulesRoot, ...segments));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
public relativeFrameworkPathFromDirectory(targetDirectory: string, ...segments: string[]): string {
|
|
421
|
+
return this.relativePathFromDirectory(targetDirectory, path.join(this.getFrameworkInstallRoot(), ...segments));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
public relativeAppNodeModulesPathFromDirectory(targetDirectory: string, ...segments: string[]): string {
|
|
425
|
+
return this.relativePathFromDirectory(targetDirectory, path.join(this.framework.appNodeModulesRoot, ...segments));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
public relativeFrameworkTsconfigPathFrom(targetFile: string): string {
|
|
429
|
+
return this.relativeFrameworkPathFrom(targetFile, 'tsconfig.common.json');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
public relativeFrameworkPathForAppRoot(appRoot: string, targetFile: string, ...segments: string[]): string {
|
|
433
|
+
return this.relativePathFromFile(targetFile, path.join(this.getFrameworkInstallRootForAppRoot(appRoot), ...segments));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
public relativeFrameworkPathFromDirectoryForAppRoot(
|
|
437
|
+
appRoot: string,
|
|
438
|
+
targetDirectory: string,
|
|
439
|
+
...segments: string[]
|
|
440
|
+
): string {
|
|
441
|
+
return this.relativePathFromDirectory(
|
|
442
|
+
targetDirectory,
|
|
443
|
+
path.join(this.getFrameworkInstallRootForAppRoot(appRoot), ...segments),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
public relativeAppNodeModulesPathFromDirectoryForAppRoot(
|
|
448
|
+
appRoot: string,
|
|
449
|
+
targetDirectory: string,
|
|
450
|
+
...segments: string[]
|
|
451
|
+
): string {
|
|
452
|
+
return this.relativePathFromDirectory(
|
|
453
|
+
targetDirectory,
|
|
454
|
+
path.join(this.getAppNodeModulesRootForAppRoot(appRoot), ...segments),
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public resolvePackageRoot(packageName: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
459
|
+
const installedRoot = this.framework.installedRoot;
|
|
460
|
+
const searchPaths = (preferApp
|
|
461
|
+
? [this.appRoot, ...(installedRoot ? [installedRoot] : []), this.framework.activeRoot]
|
|
462
|
+
: [this.framework.activeRoot, ...(installedRoot ? [installedRoot] : []), this.appRoot]
|
|
463
|
+
).filter((searchPath, index, list) => list.indexOf(searchPath) === index);
|
|
464
|
+
return path.dirname(resolvePackageJsonPath(packageName, searchPaths));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
public resolveRequest(request: string, { preferApp = true }: { preferApp?: boolean } = {}): string {
|
|
468
|
+
const installedRoot = this.framework.installedRoot;
|
|
469
|
+
const searchPaths = (preferApp
|
|
470
|
+
? [this.appRoot, ...(installedRoot ? [installedRoot] : []), this.framework.activeRoot]
|
|
471
|
+
: [this.framework.activeRoot, ...(installedRoot ? [installedRoot] : []), this.appRoot]
|
|
472
|
+
).filter((searchPath, index, list) => list.indexOf(searchPath) === index);
|
|
473
|
+
return require.resolve(request, { paths: searchPaths });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
public resolveBinary(
|
|
477
|
+
packageName: string,
|
|
478
|
+
binName = packageName,
|
|
479
|
+
{ preferApp = true }: { preferApp?: boolean } = {},
|
|
480
|
+
): TResolvedPackageBinary {
|
|
481
|
+
const packageRoot = this.resolvePackageRoot(packageName, { preferApp });
|
|
482
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) as {
|
|
483
|
+
bin?: string | Record<string, string>;
|
|
484
|
+
};
|
|
485
|
+
const binField = packageJson.bin;
|
|
486
|
+
const relativeBinPath =
|
|
487
|
+
typeof binField === 'string'
|
|
488
|
+
? binField
|
|
489
|
+
: binField?.[binName] || binField?.[packageName];
|
|
490
|
+
|
|
491
|
+
if (!relativeBinPath) {
|
|
492
|
+
throw new Error(`Unable to resolve binary "${binName}" from package "${packageName}".`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const binPath = path.resolve(packageRoot, relativeBinPath);
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
packageName,
|
|
499
|
+
packageRoot,
|
|
500
|
+
binPath,
|
|
501
|
+
command: process.execPath,
|
|
502
|
+
args: [binPath],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
174
505
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { TServerReadyConnectedProject } from '../../common/dev/serverHotReload';
|
|
3
2
|
import { renderRows } from './layout';
|
|
4
|
-
import { renderInk } from './ink';
|
|
3
|
+
import { CliReact, renderInk } from './ink';
|
|
5
4
|
import { renderWelcomePanel } from './welcome';
|
|
6
5
|
|
|
6
|
+
const formatConnectedProjectLabel = (connectedProject: TServerReadyConnectedProject) =>
|
|
7
|
+
`${connectedProject.namespace} -> ${connectedProject.name}`;
|
|
8
|
+
|
|
7
9
|
export const renderDevSession = async ({
|
|
8
10
|
appName,
|
|
9
11
|
appRoot,
|
|
10
12
|
routerPort,
|
|
11
13
|
devEventPort,
|
|
12
14
|
connectedProjects,
|
|
15
|
+
proteumInstallSummary,
|
|
13
16
|
proteumVersion,
|
|
14
17
|
}: {
|
|
15
18
|
appName: string;
|
|
@@ -17,10 +20,12 @@ export const renderDevSession = async ({
|
|
|
17
20
|
routerPort: number;
|
|
18
21
|
devEventPort: number;
|
|
19
22
|
connectedProjects?: Array<{ namespace: string; urlInternal: string }>;
|
|
23
|
+
proteumInstallSummary?: string;
|
|
20
24
|
proteumVersion: string;
|
|
21
25
|
}) =>
|
|
22
26
|
[
|
|
23
27
|
await renderWelcomePanel({
|
|
28
|
+
installSummary: proteumInstallSummary,
|
|
24
29
|
version: proteumVersion,
|
|
25
30
|
tagline: 'Agent-first SSR compiler and server loop.',
|
|
26
31
|
}),
|
|
@@ -51,15 +56,16 @@ export const renderServerReadyBanner = async ({
|
|
|
51
56
|
appName,
|
|
52
57
|
publicUrl,
|
|
53
58
|
routerPort,
|
|
54
|
-
|
|
59
|
+
connectedProjects,
|
|
55
60
|
}: {
|
|
56
61
|
appName: string;
|
|
57
62
|
publicUrl: string;
|
|
58
63
|
routerPort: number;
|
|
59
|
-
|
|
64
|
+
connectedProjects?: TServerReadyConnectedProject[];
|
|
60
65
|
}) =>
|
|
61
66
|
renderInk(({ Box, Text }) => {
|
|
62
|
-
const createElement =
|
|
67
|
+
const createElement = CliReact.createElement;
|
|
68
|
+
const verifiedConnectedProjects = connectedProjects || [];
|
|
63
69
|
|
|
64
70
|
return createElement(
|
|
65
71
|
Box,
|
|
@@ -68,13 +74,20 @@ export const renderServerReadyBanner = async ({
|
|
|
68
74
|
createElement(Text, { bold: true, color: 'green' }, appName),
|
|
69
75
|
createElement(Text, { bold: true }, publicUrl),
|
|
70
76
|
createElement(Text, { dimColor: true }, 'SSR server is listening for requests and hot reloads.'),
|
|
71
|
-
|
|
77
|
+
verifiedConnectedProjects.length > 0
|
|
72
78
|
? createElement(
|
|
73
79
|
Text,
|
|
74
80
|
{ dimColor: true },
|
|
75
|
-
`Connected
|
|
81
|
+
`Connected apps: ${verifiedConnectedProjects.map((connectedProject) => formatConnectedProjectLabel(connectedProject)).join(', ')}`,
|
|
76
82
|
)
|
|
77
83
|
: null,
|
|
84
|
+
...verifiedConnectedProjects.map((connectedProject) =>
|
|
85
|
+
createElement(
|
|
86
|
+
Text,
|
|
87
|
+
{ key: `connected-ping-${connectedProject.namespace}`, dimColor: true },
|
|
88
|
+
`Ping OK (/ping): ${formatConnectedProjectLabel(connectedProject)}`,
|
|
89
|
+
),
|
|
90
|
+
),
|
|
78
91
|
createElement(Text, { dimColor: true }, `Diagnose /: proteum diagnose / --port ${routerPort}`),
|
|
79
92
|
createElement(Text, { dimColor: true }, `Perf top: proteum perf top --port ${routerPort}`),
|
|
80
93
|
createElement(Text, { dimColor: true }, `Trace latest: proteum trace latest --port ${routerPort}`),
|
|
@@ -83,7 +96,7 @@ export const renderServerReadyBanner = async ({
|
|
|
83
96
|
|
|
84
97
|
export const renderDevShutdownBanner = async () =>
|
|
85
98
|
renderInk(({ Box, Text }) => {
|
|
86
|
-
const createElement =
|
|
99
|
+
const createElement = CliReact.createElement;
|
|
87
100
|
|
|
88
101
|
return createElement(
|
|
89
102
|
Box,
|
package/cli/presentation/ink.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
2
|
|
|
3
3
|
import { importEsm } from '../runtime/importEsm';
|
|
4
4
|
import { getTerminalWidth } from './layout';
|
|
@@ -13,6 +13,9 @@ type TInkRuntime = {
|
|
|
13
13
|
StatusMessage: TInkUiModule['StatusMessage'];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
// Keep the CLI renderer on the exact React instance Ink resolved for this install shape.
|
|
17
|
+
const CliReact = createRequire(require.resolve('ink'))('react') as typeof import('react');
|
|
18
|
+
|
|
16
19
|
let inkRuntimePromise: Promise<TInkRuntime> | undefined;
|
|
17
20
|
|
|
18
21
|
const loadInkRuntime = () => {
|
|
@@ -41,7 +44,7 @@ export const renderInk = async (
|
|
|
41
44
|
|
|
42
45
|
export const renderTitle = async (title: string, subtitle?: string) =>
|
|
43
46
|
renderInk(({ Box, Text }) => {
|
|
44
|
-
const createElement =
|
|
47
|
+
const createElement = CliReact.createElement;
|
|
45
48
|
|
|
46
49
|
return createElement(
|
|
47
50
|
Box,
|
|
@@ -52,18 +55,20 @@ export const renderTitle = async (title: string, subtitle?: string) =>
|
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
export const renderSection = async (title: string, body: string) => {
|
|
55
|
-
const heading = await renderInk(({ Text }) =>
|
|
58
|
+
const heading = await renderInk(({ Text }) => CliReact.createElement(Text, { bold: true }, title));
|
|
56
59
|
return `${heading}\n${body}`;
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export const renderStep = async (label: string, message: string) =>
|
|
60
|
-
renderInk(({ Text }) =>
|
|
63
|
+
renderInk(({ Text }) => CliReact.createElement(Text, { color: 'cyan' }, `${label} ${message}`));
|
|
61
64
|
|
|
62
65
|
const renderStatusMessage = async (variant: 'success' | 'warning' | 'error', message: string) =>
|
|
63
|
-
renderInk(({ StatusMessage }) =>
|
|
66
|
+
renderInk(({ StatusMessage }) => CliReact.createElement(StatusMessage, { variant }, message));
|
|
64
67
|
|
|
65
68
|
export const renderSuccess = (message: string) => renderStatusMessage('success', message);
|
|
66
69
|
|
|
67
70
|
export const renderWarning = (message: string) => renderStatusMessage('warning', message);
|
|
68
71
|
|
|
69
72
|
export const renderDanger = (message: string) => renderStatusMessage('error', message);
|
|
73
|
+
|
|
74
|
+
export { CliReact };
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
const React = require('react') as typeof import('react');
|
|
2
|
-
|
|
3
1
|
import { renderRows } from './layout';
|
|
4
|
-
import { renderInk } from './ink';
|
|
2
|
+
import { CliReact, renderInk } from './ink';
|
|
5
3
|
|
|
6
4
|
const ProteumWordmark = [
|
|
7
5
|
String.raw` ____ ____ ___ _____ _____ _ _ __ __`,
|
|
@@ -18,14 +16,16 @@ export const clearInteractiveConsole = () => {
|
|
|
18
16
|
};
|
|
19
17
|
|
|
20
18
|
export const renderWelcomePanel = async ({
|
|
19
|
+
installSummary,
|
|
21
20
|
version,
|
|
22
21
|
tagline,
|
|
23
22
|
}: {
|
|
23
|
+
installSummary?: string;
|
|
24
24
|
version: string;
|
|
25
25
|
tagline: string;
|
|
26
26
|
}) =>
|
|
27
27
|
renderInk(({ Box, Text }) => {
|
|
28
|
-
const createElement =
|
|
28
|
+
const createElement = CliReact.createElement;
|
|
29
29
|
const wordmark = ProteumWordmark.map((line) =>
|
|
30
30
|
createElement(Text, { key: line, bold: true, color: 'blue' }, line),
|
|
31
31
|
);
|
|
@@ -37,19 +37,23 @@ export const renderWelcomePanel = async ({
|
|
|
37
37
|
createElement(Text, { bold: true, backgroundColor: 'blue', color: 'white' }, ' WELCOME TO '),
|
|
38
38
|
createElement(Box, { flexDirection: 'column' }, ...wordmark),
|
|
39
39
|
versionLabel ? createElement(Text, { bold: true, color: 'blue' }, versionLabel) : null,
|
|
40
|
+
installSummary ? createElement(Text, { dimColor: true }, `Installed via ${installSummary}`) : null,
|
|
40
41
|
createElement(Text, { dimColor: true }, tagline),
|
|
41
42
|
);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
export const renderCliWelcomeBanner = async ({
|
|
45
46
|
command,
|
|
47
|
+
installSummary,
|
|
46
48
|
version,
|
|
47
49
|
}: {
|
|
48
50
|
command: string;
|
|
51
|
+
installSummary?: string;
|
|
49
52
|
version: string;
|
|
50
53
|
}) =>
|
|
51
54
|
[
|
|
52
55
|
await renderWelcomePanel({
|
|
56
|
+
installSummary,
|
|
53
57
|
version,
|
|
54
58
|
tagline: 'Explicit SSR / SEO / TypeScript framework for agent-friendly apps.',
|
|
55
59
|
}),
|
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
|
|