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/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 resolveCoreRoot = (appRoot: string) => {
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 localInstall = path.join(appRoot, 'node_modules', 'proteum');
63
- if (fs.existsSync(localInstall)) return localInstall;
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 constructor(
84
- public appRoot: string,
85
- public coreRoot = resolveCoreRoot(appRoot),
86
- ) {}
87
-
88
- public core = { cli: path.resolve(__dirname, '.'), root: this.coreRoot, pages: this.coreRoot + '/client/pages' };
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
- connectedProjectsCount,
61
+ connectedProjects,
55
62
  }: {
56
63
  appName: string;
57
64
  publicUrl: string;
58
65
  routerPort: number;
59
- connectedProjectsCount?: number;
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
- connectedProjectsCount
79
+ verifiedConnectedProjects.length > 0
72
80
  ? createElement(
73
81
  Text,
74
82
  { dimColor: true },
75
- `Connected projects: ${connectedProjectsCount}`,
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
  }),
@@ -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 createInitFilePlans = (config: TScaffoldInitConfig): TScaffoldFilePlan[] => [
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": "../node_modules/proteum/tsconfig.common.json",
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/*": ["./node_modules/proteum/client/*"],
176
- "@common/*": ["./node_modules/proteum/common/*"],
177
- "@server/*": ["./node_modules/proteum/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": ["./node_modules/preact/compat"],
186
- "react-dom/test-utils": ["./node_modules/preact/test-utils"],
187
- "react-dom": ["./node_modules/preact/compat"],
188
- "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"]
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
- "../node_modules/proteum/types/global",
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": "../node_modules/proteum/tsconfig.common.json",
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/*": ["./node_modules/proteum/client/*"],
213
- "@common/*": ["./node_modules/proteum/common/*"],
214
- "@server/*": ["./node_modules/proteum/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": ["./node_modules/preact/compat"],
223
- "react-dom/test-utils": ["./node_modules/preact/test-utils"],
224
- "react-dom": ["./node_modules/preact/compat"],
225
- "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"]
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
- "../node_modules/proteum/types/global",
247
+ ${JSON.stringify(paths.frameworkTypesGlobal)},
234
248
  "../.proteum/server/services.d.ts",
235
249
  "../server/index.ts"
236
250
  ]
@@ -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 = (name: string) => {
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
  };