proteum 2.1.9 → 2.2.0

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.
Files changed (79) hide show
  1. package/.codex/environments/environment.toml +11 -0
  2. package/AGENTS.md +25 -11
  3. package/README.md +19 -9
  4. package/agents/project/AGENTS.md +165 -120
  5. package/agents/project/CODING_STYLE.md +1 -1
  6. package/agents/project/app-root/AGENTS.md +16 -0
  7. package/agents/project/client/AGENTS.md +5 -5
  8. package/agents/project/client/pages/AGENTS.md +13 -13
  9. package/agents/project/diagnostics.md +19 -10
  10. package/agents/project/optimizations.md +5 -6
  11. package/agents/project/root/AGENTS.md +295 -0
  12. package/agents/project/server/routes/AGENTS.md +2 -2
  13. package/agents/project/server/services/AGENTS.md +4 -2
  14. package/agents/project/tests/AGENTS.md +2 -2
  15. package/cli/app/index.ts +31 -7
  16. package/cli/commands/configure.ts +226 -0
  17. package/cli/commands/dev.ts +0 -2
  18. package/cli/commands/diagnose.ts +33 -1
  19. package/cli/commands/explain.ts +1 -1
  20. package/cli/commands/migrate.ts +51 -0
  21. package/cli/commands/orient.ts +169 -0
  22. package/cli/commands/perf.ts +8 -1
  23. package/cli/commands/verify.ts +1003 -49
  24. package/cli/compiler/artifacts/manifest.ts +4 -4
  25. package/cli/compiler/artifacts/routing.ts +2 -2
  26. package/cli/compiler/artifacts/services.ts +12 -3
  27. package/cli/compiler/client/index.ts +65 -19
  28. package/cli/compiler/common/files/style.ts +47 -2
  29. package/cli/compiler/common/generatedRouteModules.ts +31 -38
  30. package/cli/compiler/common/index.ts +10 -0
  31. package/cli/compiler/common/proteumManifest.ts +1 -0
  32. package/cli/compiler/server/index.ts +34 -9
  33. package/cli/context.ts +6 -1
  34. package/cli/index.ts +7 -8
  35. package/cli/migrate/pageContract.ts +516 -0
  36. package/cli/paths.ts +47 -6
  37. package/cli/presentation/commands.ts +100 -10
  38. package/cli/presentation/devSession.ts +4 -6
  39. package/cli/presentation/help.ts +2 -2
  40. package/cli/presentation/ink.ts +10 -5
  41. package/cli/presentation/welcome.ts +2 -4
  42. package/cli/runtime/commands.ts +94 -1
  43. package/cli/scaffold/index.ts +2 -2
  44. package/cli/scaffold/templates.ts +4 -2
  45. package/cli/utils/agents.ts +273 -58
  46. package/client/dev/profiler/index.tsx +3 -2
  47. package/client/router.ts +10 -2
  48. package/client/services/router/index.tsx +6 -22
  49. package/common/dev/connect.ts +20 -4
  50. package/common/dev/console.ts +7 -0
  51. package/common/dev/contractsDoctor.ts +354 -0
  52. package/common/dev/diagnostics.ts +10 -7
  53. package/common/dev/inspection.ts +830 -38
  54. package/common/dev/performance.ts +19 -5
  55. package/common/dev/profiler.ts +1 -0
  56. package/common/dev/proteumManifest.ts +5 -4
  57. package/common/dev/requestTrace.ts +12 -1
  58. package/common/router/contracts.ts +8 -11
  59. package/common/router/index.ts +2 -2
  60. package/common/router/pageData.ts +72 -0
  61. package/common/router/register.ts +10 -46
  62. package/common/router/response/page.ts +28 -16
  63. package/docs/dev-sessions.md +8 -4
  64. package/docs/diagnostics.md +77 -11
  65. package/docs/migrate-from-2.1.3.md +388 -0
  66. package/docs/request-tracing.md +25 -6
  67. package/package.json +6 -1
  68. package/scripts/update-codex-agents.ts +2 -2
  69. package/server/app/container/console/index.ts +11 -1
  70. package/server/app/container/trace/index.ts +117 -0
  71. package/server/app/devDiagnostics.ts +1 -1
  72. package/server/app/index.ts +5 -1
  73. package/server/services/auth/index.ts +9 -0
  74. package/server/services/router/index.ts +64 -14
  75. package/server/services/router/request/api.ts +7 -1
  76. package/server/services/router/response/index.ts +8 -28
  77. package/types/global/vendors.d.ts +12 -0
  78. package/types/vendors.d.ts +12 -0
  79. package/common/router/pageSetup.ts +0 -51
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import app from '../../app';
5
5
  import cli from '../..';
6
6
  import { inspectProteumEnv } from '../../../common/env/proteumEnv';
7
- import { reservedRouteSetupKeys, routeSetupOptionKeys } from '../../../common/router/pageSetup';
7
+ import { reservedRouteOptionKeys, routeOptionKeys } from '../../../common/router/pageData';
8
8
  import { getProjectInstructionGitignoreEntries } from '../../utils/agents';
9
9
  import {
10
10
  TProteumManifest,
@@ -303,7 +303,7 @@ export const writeCurrentProteumManifest = ({
303
303
  });
304
304
 
305
305
  const manifest: TProteumManifest = {
306
- version: 9,
306
+ version: 10,
307
307
  app: {
308
308
  root: normalizeAbsolutePath(app.paths.root),
309
309
  coreRoot: normalizeAbsolutePath(cli.paths.core.root),
@@ -338,8 +338,8 @@ export const writeCurrentProteumManifest = ({
338
338
  },
339
339
  },
340
340
  conventions: {
341
- routeSetupOptionKeys: [...routeSetupOptionKeys],
342
- reservedRouteSetupKeys: [...reservedRouteSetupKeys],
341
+ routeOptionKeys: [...routeOptionKeys],
342
+ reservedRouteOptionKeys: [...reservedRouteOptionKeys],
343
343
  },
344
344
  env: {
345
345
  source: 'process.env',
@@ -59,7 +59,7 @@ const buildClientRouteManifestEntry = (filepath: string): TProteumManifestRoute
59
59
  invalidOptionKeys: definition.invalidOptionKeys,
60
60
  reservedOptionKeys: definition.reservedOptionKeys,
61
61
  optionsRaw: definition.optionsRaw,
62
- hasSetup: definition.hasSetup,
62
+ hasData: definition.hasData,
63
63
  chunkId: pageChunk.chunkId,
64
64
  chunkFilepath: normalizePath(pageChunk.filepath),
65
65
  scope: 'app',
@@ -83,7 +83,7 @@ const buildServerRouteManifestEntries = (filepath: string) =>
83
83
  invalidOptionKeys: definition.invalidOptionKeys,
84
84
  reservedOptionKeys: definition.reservedOptionKeys,
85
85
  optionsRaw: definition.optionsRaw,
86
- hasSetup: definition.hasSetup,
86
+ hasData: definition.hasData,
87
87
  scope: 'app',
88
88
  }));
89
89
 
@@ -791,15 +791,24 @@ import type ${appClassIdentifier}Client from '@/client/index';
791
791
  export type ClientContext = ${appClassIdentifier}Client["Router"]["context"];
792
792
 
793
793
  type GlobalClientContextStore = typeof globalThis & {
794
- __proteumClientContexts?: Record<string, React.Context<ClientContext>>;
794
+ __proteumClientContexts?: Record<string, React.Context<ClientContext | undefined>>;
795
795
  };
796
796
 
797
797
  const globalClientContextStore = globalThis as GlobalClientContextStore;
798
798
  const clientContexts = (globalClientContextStore.__proteumClientContexts ??= {});
799
799
 
800
800
  export const ReactClientContext =
801
- clientContexts['${appClassIdentifier}'] ?? (clientContexts['${appClassIdentifier}'] = React.createContext<ClientContext>({} as ClientContext));
802
- export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext);`,
801
+ clientContexts['${appClassIdentifier}'] ?? (clientContexts['${appClassIdentifier}'] = React.createContext<ClientContext | undefined>(undefined));
802
+ export default (): ClientContext => {
803
+ const context = React.useContext<ClientContext | undefined>(ReactClientContext);
804
+ if (context) return context;
805
+
806
+ throw new Error(
807
+ 'Proteum router context hook was called outside the App provider. This is a framework contract failure. ' +
808
+ 'Likely fix: move the hook back under Router.page render ownership or pass the required values explicitly. ' +
809
+ 'Re-check both SSR and client navigation after the fix.',
810
+ );
811
+ };`,
803
812
  );
804
813
 
805
814
  writeIfChanged(
@@ -21,23 +21,40 @@ import type { App } from '../../app';
21
21
 
22
22
  const debug = false;
23
23
  const ssrScriptPattern = /\.ssr\.(ts|tsx)$/;
24
- const normalizedCoreRoot = cli.paths.framework.activeRoot.replace(/\\/g, '/');
25
- const hmrClientEntry = path.join(cli.paths.core.root, 'client', 'dev', 'hmr.ts');
26
-
27
24
  const normalizeModulePath = (value?: string) => (value || '').replace(/\\/g, '/');
28
- const resolveFromAppOrCore = (_app: App, request: string) => cli.paths.resolveRequest(request);
29
- const rewriteFrameworkAliasTargets = (app: App, aliases: Record<string, string | string[]>) => {
25
+ const getFrameworkSourceRoot = () => {
30
26
  const installedCoreRoot = cli.paths.framework.installedRoot
31
27
  ? normalizeModulePath(cli.paths.framework.installedRoot)
32
28
  : undefined;
33
29
  const activeCoreRoot = normalizeModulePath(cli.paths.framework.activeRoot);
34
30
 
35
- if (!installedCoreRoot || installedCoreRoot === activeCoreRoot) return aliases;
31
+ if (installedCoreRoot && activeCoreRoot.includes('/node_modules/')) {
32
+ return installedCoreRoot;
33
+ }
34
+
35
+ return activeCoreRoot;
36
+ };
37
+
38
+ const resolveFromAppOrCore = (_app: App, request: string) => cli.paths.resolveRequest(request);
39
+ const rewriteFrameworkAliasTargets = (aliases: Record<string, string | string[]>) => {
40
+ const visibleFrameworkRoots = [
41
+ ...cli.paths.getVisiblePackageInstallRoots('proteum'),
42
+ cli.paths.framework.installedRoot,
43
+ cli.paths.framework.activeRoot,
44
+ ]
45
+ .filter((rootPath): rootPath is string => typeof rootPath === 'string' && rootPath !== '')
46
+ .map((rootPath) => normalizeModulePath(rootPath))
47
+ .filter((rootPath, index, list) => list.indexOf(rootPath) === index);
48
+ const frameworkSourceRoot = getFrameworkSourceRoot();
36
49
 
37
50
  const rewriteCandidate = (candidate: string) =>
38
- normalizeModulePath(candidate).startsWith(installedCoreRoot + '/')
39
- ? activeCoreRoot + normalizeModulePath(candidate).substring(installedCoreRoot.length)
40
- : candidate;
51
+ visibleFrameworkRoots.reduce((nextCandidate, rootPath) => {
52
+ const normalizedCandidate = normalizeModulePath(nextCandidate);
53
+
54
+ return normalizedCandidate.startsWith(rootPath + '/')
55
+ ? frameworkSourceRoot + normalizedCandidate.substring(rootPath.length)
56
+ : nextCandidate;
57
+ }, candidate);
41
58
 
42
59
  return Object.fromEntries(
43
60
  Object.entries(aliases).map(([alias, value]) => [
@@ -62,8 +79,29 @@ const isExternalVendorModule = (module: Module) => {
62
79
 
63
80
  const isCoreSourceModule = (module: Module) => {
64
81
  const modulePath = getModulePath(module);
82
+ const frameworkSourceRoot = getFrameworkSourceRoot();
83
+
84
+ return modulePath.startsWith(frameworkSourceRoot + '/') || modulePath.includes('/node_modules/proteum/');
85
+ };
86
+ const resolveLightningCssTargets = (app: App) => {
87
+ const browserslistConfig = app.packageJson.browserslist;
88
+
89
+ if (typeof browserslistConfig === 'string') return browserslistConfig;
90
+
91
+ if (Array.isArray(browserslistConfig) && browserslistConfig.every((target) => typeof target === 'string'))
92
+ return browserslistConfig;
93
+
94
+ if (!browserslistConfig || typeof browserslistConfig !== 'object') return undefined;
65
95
 
66
- return modulePath.startsWith(normalizedCoreRoot + '/') || modulePath.includes('/node_modules/proteum/');
96
+ for (const env of ['production', 'defaults']) {
97
+ const targets = browserslistConfig[env];
98
+
99
+ if (typeof targets === 'string') return targets;
100
+
101
+ if (Array.isArray(targets) && targets.every((target) => typeof target === 'string')) return targets;
102
+ }
103
+
104
+ return undefined;
67
105
  };
68
106
 
69
107
  /*----------------------------------
@@ -77,8 +115,13 @@ export default function createCompiler(
77
115
  logVerbose(`Creating compiler for client (${mode}).`);
78
116
  const dev = mode === 'dev';
79
117
  const outputPath = app.outputPath(outputTarget);
80
- const frameworkRoots = cli.paths.getFrameworkRoots();
118
+ const frameworkSourceRoot = getFrameworkSourceRoot();
119
+ const frameworkRoots = [frameworkSourceRoot, ...cli.paths.getFrameworkRoots()].filter(
120
+ (rootPath, index, list) => list.indexOf(rootPath) === index,
121
+ );
81
122
  const transpileModuleDirectories = app.transpileModuleDirectories;
123
+ const lightningCssTargets = resolveLightningCssTargets(app);
124
+ const hmrClientEntry = path.join(frameworkSourceRoot, 'client', 'dev', 'hmr.ts');
82
125
 
83
126
  const commonConfig = createCommonConfig(app, 'client', mode, outputTarget);
84
127
 
@@ -94,13 +137,14 @@ export default function createCompiler(
94
137
 
95
138
  // Convert tsconfig paths into bundler aliases.
96
139
  const { aliases } = app.aliases.client.forWebpack({ modulesPath: cli.paths.framework.appNodeModulesRoot });
97
- const resolvedAliases = rewriteFrameworkAliasTargets(app, aliases);
140
+ const resolvedAliases = rewriteFrameworkAliasTargets(aliases);
98
141
 
99
142
  // We're not supposed in any case to import server libs from client
100
143
  delete resolvedAliases['@server'];
101
144
  delete resolvedAliases['@/server'];
102
145
  const rspackAliases = toRspackAliases(resolvedAliases);
103
- rspackAliases['@/client/router$'] = cli.paths.core.root + '/client/router.ts';
146
+ rspackAliases['proteum'] = frameworkSourceRoot;
147
+ rspackAliases['@/client/router$'] = frameworkSourceRoot + '/client/router.ts';
104
148
  rspackAliases['preact/jsx-runtime$'] = resolveFromAppOrCore(app, 'preact/jsx-runtime');
105
149
  rspackAliases['react/jsx-runtime$'] = resolveFromAppOrCore(app, 'preact/jsx-runtime');
106
150
  rspackAliases['react/jsx-dev-runtime$'] = resolveFromAppOrCore(app, 'preact/jsx-dev-runtime');
@@ -113,8 +157,8 @@ export default function createCompiler(
113
157
  target: 'web',
114
158
  entry: {
115
159
  client: dev
116
- ? [hmrClientEntry, cli.paths.core.root + '/client/index.ts']
117
- : [cli.paths.core.root + '/client/index.ts'],
160
+ ? [hmrClientEntry, frameworkSourceRoot + '/client/index.ts']
161
+ : [frameworkSourceRoot + '/client/index.ts'],
118
162
  },
119
163
 
120
164
  output: {
@@ -168,7 +212,7 @@ export default function createCompiler(
168
212
  ...transpileModuleDirectories,
169
213
  ],
170
214
  loader: path.join(
171
- cli.paths.core.root,
215
+ frameworkSourceRoot,
172
216
  'cli',
173
217
  'compiler',
174
218
  'common',
@@ -229,11 +273,11 @@ export default function createCompiler(
229
273
  : [
230
274
  new rspack.NormalModuleReplacementPlugin(
231
275
  /^@client\/dev\/profiler$/,
232
- cli.paths.core.root + '/client/dev/profiler/noop.tsx',
276
+ frameworkSourceRoot + '/client/dev/profiler/noop.tsx',
233
277
  ),
234
278
  new rspack.NormalModuleReplacementPlugin(
235
279
  /^@client\/dev\/profiler\/runtime$/,
236
- cli.paths.core.root + '/client/dev/profiler/runtime.noop.ts',
280
+ frameworkSourceRoot + '/client/dev/profiler/runtime.noop.ts',
237
281
  ),
238
282
  ]),
239
283
 
@@ -297,7 +341,9 @@ export default function createCompiler(
297
341
  removeAvailableModules: true,
298
342
  minimizer: [
299
343
  new rspack.SwcJsMinimizerRspackPlugin({}),
300
- new rspack.LightningCssMinimizerRspackPlugin({}),
344
+ new rspack.LightningCssMinimizerRspackPlugin({
345
+ ...(lightningCssTargets ? { minimizerOptions: { targets: lightningCssTargets } } : {}),
346
+ }),
301
347
  ],
302
348
  nodeEnv: 'production',
303
349
  sideEffects: true,
@@ -2,7 +2,7 @@ import path from 'path';
2
2
 
3
3
  // Plugons
4
4
  import { rspack } from '@rspack/core';
5
- import type { Root } from 'postcss';
5
+ import type { Declaration, Root } from 'postcss';
6
6
 
7
7
  import type { App } from '../../../app';
8
8
 
@@ -11,6 +11,17 @@ const normalizePath = (value: string) => path.resolve(value).replace(/\\/g, '/')
11
11
  const isPathInsideDirectory = (filepath: string, directory: string) =>
12
12
  filepath === directory || filepath.startsWith(directory + '/');
13
13
 
14
+ const VENDOR_PROPERTY_PREFIXES = ['-webkit-', '-moz-', '-ms-', '-o-'] as const;
15
+ const getVendorlessProperty = (property: string) => {
16
+ for (const prefix of VENDOR_PROPERTY_PREFIXES) {
17
+ if (property.startsWith(prefix)) return property.slice(prefix.length);
18
+ }
19
+
20
+ return property;
21
+ };
22
+ const isVendorPrefixedProperty = (property: string) =>
23
+ VENDOR_PROPERTY_PREFIXES.some((prefix) => property.startsWith(prefix));
24
+
14
25
  const createTailwindTranspileSourcesPlugin = (app: App) => {
15
26
  const appRoot = normalizePath(app.paths.root);
16
27
  const transpileSourceDirectories = Array.from(
@@ -50,9 +61,42 @@ const createTailwindTranspileSourcesPlugin = (app: App) => {
50
61
  };
51
62
  };
52
63
 
64
+ const createVendorPropertyOrderPlugin = () => {
65
+ return {
66
+ postcssPlugin: 'proteum-normalize-vendor-property-order',
67
+ Once(root: Root) {
68
+ root.walkRules((rule) => {
69
+ const declarations = (rule.nodes || []).filter((node): node is Declaration => node.type === 'decl');
70
+
71
+ for (const declaration of declarations) {
72
+ if (isVendorPrefixedProperty(declaration.prop)) continue;
73
+
74
+ const property = declaration.prop;
75
+ let nextNode = declaration.next();
76
+
77
+ // LightningCSS canonicalizes property aliases based on source order.
78
+ // Keep prefixed declarations before the standard property so target-aware
79
+ // minification preserves the right output for both old and modern browsers.
80
+ while (nextNode && nextNode.type === 'decl' && getVendorlessProperty(nextNode.prop) === property) {
81
+ const currentNextNode = nextNode;
82
+ nextNode = currentNextNode.next();
83
+
84
+ if (!isVendorPrefixedProperty(currentNextNode.prop)) continue;
85
+
86
+ const reorderedDeclaration = currentNextNode.clone();
87
+ currentNextNode.remove();
88
+ rule.insertBefore(declaration, reorderedDeclaration);
89
+ }
90
+ }
91
+ });
92
+ },
93
+ };
94
+ };
95
+
53
96
  module.exports = (app: App, dev: boolean, _client: boolean) => {
54
97
  const enableSourceMaps = dev;
55
98
  const tailwindTranspileSourcesPlugin = createTailwindTranspileSourcesPlugin(app);
99
+ const vendorPropertyOrderPlugin = createVendorPropertyOrderPlugin();
56
100
 
57
101
  return [
58
102
  // Keep CSS delivery identical in dev and prod: extract files so SSR links stylesheets in both modes.
@@ -86,9 +130,10 @@ module.exports = (app: App, dev: boolean, _client: boolean) => {
86
130
  // process is launched from another working directory (e.g. Docker).
87
131
  base: app.paths.root,
88
132
 
89
- // Avoid double-minifying: Webpack already runs CssMinimizerPlugin in prod.
133
+ // Avoid double-minifying: Rspack already runs LightningCssMinimizerRspackPlugin in prod.
90
134
  optimize: false,
91
135
  }),
136
+ vendorPropertyOrderPlugin,
92
137
  ///* Tailwind V3 */require('tailwindcss'),
93
138
  require('autoprefixer'),
94
139
  ],
@@ -2,7 +2,7 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import ts from 'typescript';
4
4
 
5
- import { getRouteSetupOptionKey } from '../../../common/router/pageSetup';
5
+ import { getRouteOptionKey } from '../../../common/router/pageData';
6
6
  import writeIfChanged from '../writeIfChanged';
7
7
 
8
8
  type TRouteSide = 'client' | 'server';
@@ -33,7 +33,7 @@ export type TIndexedRouteDefinition = {
33
33
  invalidOptionKeys: string[];
34
34
  reservedOptionKeys: string[];
35
35
  optionsRaw?: string;
36
- hasSetup: boolean;
36
+ hasData: boolean;
37
37
  };
38
38
 
39
39
  type TGeneratedClientRouteModuleOptions = { chunkId: string };
@@ -206,7 +206,7 @@ const getRouteOptionMetadata = (node: ts.ObjectLiteralExpression | undefined) =>
206
206
 
207
207
  for (const optionKey of optionKeys) {
208
208
  try {
209
- const normalizedOptionKey = getRouteSetupOptionKey(optionKey);
209
+ const normalizedOptionKey = getRouteOptionKey(optionKey);
210
210
 
211
211
  if (normalizedOptionKey) {
212
212
  normalizedOptionKeys.push(normalizedOptionKey);
@@ -438,29 +438,33 @@ const buildDestructuring = (importedServices: TImportedService[]) => {
438
438
  const getClientRouteSignature = (sourceFile: ts.SourceFile, definition: TRouteDefinition) => {
439
439
  const [, ...routeArgs] = [...definition.args];
440
440
 
441
- if (routeArgs.length === 1) {
442
- return { hasSetup: false, renderArg: routeArgs[0] };
443
- }
444
-
445
- if (routeArgs.length === 2) {
446
- if (ts.isObjectLiteralExpression(routeArgs[0])) {
447
- return { hasSetup: false, optionsArg: routeArgs[0], renderArg: routeArgs[1] };
441
+ if (definition.methodName === 'error') {
442
+ if (routeArgs.length === 2) {
443
+ return {
444
+ hasData: false,
445
+ optionsExpression: routeArgs[0],
446
+ optionsArg: ts.isObjectLiteralExpression(routeArgs[0]) ? routeArgs[0] : undefined,
447
+ renderArg: routeArgs[1],
448
+ };
448
449
  }
449
450
 
450
- return { hasSetup: true, setupArg: routeArgs[0], renderArg: routeArgs[1] };
451
+ throw new Error(
452
+ `Unsupported client error route signature in ${sourceFile.fileName}. Expected Router.error(code, options, render).`,
453
+ );
451
454
  }
452
455
 
453
- if (routeArgs.length === 3 && ts.isObjectLiteralExpression(routeArgs[0])) {
456
+ if (routeArgs.length === 3) {
454
457
  return {
455
- hasSetup: true,
456
- optionsArg: routeArgs[0],
457
- setupArg: routeArgs[1],
458
+ hasData: routeArgs[1].kind !== ts.SyntaxKind.NullKeyword,
459
+ optionsExpression: routeArgs[0],
460
+ optionsArg: ts.isObjectLiteralExpression(routeArgs[0]) ? routeArgs[0] : undefined,
461
+ dataArg: routeArgs[1],
458
462
  renderArg: routeArgs[2],
459
463
  };
460
464
  }
461
465
 
462
466
  throw new Error(
463
- `Unsupported client route signature in ${sourceFile.fileName}. Expected Router.page/error with 2-4 arguments.`,
467
+ `Unsupported client page route signature in ${sourceFile.fileName}. Expected Router.page(path, options, data, render).`,
464
468
  );
465
469
  };
466
470
 
@@ -469,30 +473,23 @@ const buildClientRegisterArgs = (
469
473
  definition: TRouteDefinition,
470
474
  clientRoute: TGeneratedClientRouteModuleOptions,
471
475
  ) => {
472
- const { optionsArg, setupArg, renderArg } = getClientRouteSignature(sourceFile, definition);
476
+ const { optionsExpression, renderArg } = getClientRouteSignature(sourceFile, definition);
473
477
  const sourceLocation = getNodeLocation(sourceFile, definition.callExpression);
474
478
  const injectedOptions = buildInjectedRouteMetadata(sourceFile.fileName, sourceLocation, [
475
479
  `id: ${JSON.stringify(clientRoute.chunkId)}`,
476
480
  ]);
477
481
 
478
- if (!optionsArg && !setupArg) {
479
- return [injectedOptions, getNodeText(sourceFile, renderArg)];
480
- }
481
-
482
- if (optionsArg && !setupArg) {
482
+ if (definition.methodName === 'error') {
483
483
  return [
484
- `{ ...(${getNodeText(sourceFile, optionsArg)}), ...${injectedOptions} }`,
484
+ `{ ...(${getNodeText(sourceFile, optionsExpression)}), ...${injectedOptions} }`,
485
485
  getNodeText(sourceFile, renderArg),
486
486
  ];
487
487
  }
488
488
 
489
- if (!optionsArg && setupArg) {
490
- return [injectedOptions, getNodeText(sourceFile, setupArg), getNodeText(sourceFile, renderArg)];
491
- }
492
-
489
+ const { dataArg } = getClientRouteSignature(sourceFile, definition);
493
490
  return [
494
- `{ ...(${getNodeText(sourceFile, optionsArg!)}), ...${injectedOptions} }`,
495
- getNodeText(sourceFile, setupArg!),
491
+ `{ ...(${getNodeText(sourceFile, optionsExpression)}), ...${injectedOptions} }`,
492
+ getNodeText(sourceFile, dataArg!),
496
493
  getNodeText(sourceFile, renderArg),
497
494
  ];
498
495
  };
@@ -604,10 +601,8 @@ export const indexRouteDefinitions = ({ side, sourceFilepath }: { side: TRouteSi
604
601
  normalizedOptionKeys: optionMetadata.normalizedOptionKeys,
605
602
  invalidOptionKeys: optionMetadata.invalidOptionKeys,
606
603
  reservedOptionKeys: optionMetadata.reservedOptionKeys,
607
- optionsRaw: clientSignature.optionsArg
608
- ? getNodeText(sourceFile, clientSignature.optionsArg)
609
- : undefined,
610
- hasSetup: clientSignature.hasSetup,
604
+ optionsRaw: getNodeText(sourceFile, clientSignature.optionsExpression),
605
+ hasData: false,
611
606
  }
612
607
  : {
613
608
  methodName: definition.methodName,
@@ -627,10 +622,8 @@ export const indexRouteDefinitions = ({ side, sourceFilepath }: { side: TRouteSi
627
622
  normalizedOptionKeys: optionMetadata.normalizedOptionKeys,
628
623
  invalidOptionKeys: optionMetadata.invalidOptionKeys,
629
624
  reservedOptionKeys: optionMetadata.reservedOptionKeys,
630
- optionsRaw: clientSignature.optionsArg
631
- ? getNodeText(sourceFile, clientSignature.optionsArg)
632
- : undefined,
633
- hasSetup: clientSignature.hasSetup,
625
+ optionsRaw: getNodeText(sourceFile, clientSignature.optionsExpression),
626
+ hasData: clientSignature.hasData,
634
627
  };
635
628
  }
636
629
 
@@ -659,7 +652,7 @@ export const indexRouteDefinitions = ({ side, sourceFilepath }: { side: TRouteSi
659
652
  invalidOptionKeys: optionMetadata.invalidOptionKeys,
660
653
  reservedOptionKeys: optionMetadata.reservedOptionKeys,
661
654
  optionsRaw: optionsArg ? getNodeText(sourceFile, optionsArg) : undefined,
662
- hasSetup: false,
655
+ hasData: false,
663
656
  };
664
657
  });
665
658
  };
@@ -45,7 +45,17 @@ export default function createCommonConfig(
45
45
  ): Configuration {
46
46
  const dev = mode === 'dev';
47
47
  const enableFilesystemCache = dev ? cli.args.cache !== false : cli.args.cache === true;
48
+ const frameworkPackageRoots = [cli.paths.framework.installedRoot, cli.paths.framework.activeRoot].filter(
49
+ (rootPath, index, list): rootPath is string => typeof rootPath === 'string' && list.indexOf(rootPath) === index,
50
+ );
51
+ const visibleNodeModulesRoots = [
52
+ ...cli.paths.getVisibleNodeModulesRootsForPath(app.paths.root),
53
+ ...frameworkPackageRoots.flatMap((rootPath) => cli.paths.getVisibleNodeModulesRootsForPath(rootPath)),
54
+ ...cli.paths.getVisibleNodeModulesRootsForPath(cli.paths.core.cli),
55
+ ].filter((moduleRoot, index, list) => list.indexOf(moduleRoot) === index);
48
56
  const loaderModuleRoots = [
57
+ ...visibleNodeModulesRoots,
58
+ ...frameworkPackageRoots.map((rootPath) => path.join(rootPath, 'node_modules')),
49
59
  cli.paths.framework.appNodeModulesRoot,
50
60
  cli.paths.framework.frameworkNodeModulesRoot,
51
61
  path.join(cli.paths.core.cli, 'node_modules'),
@@ -7,6 +7,7 @@ import type { TProteumManifest } from '@common/dev/proteumManifest';
7
7
  export type {
8
8
  TProteumManifest,
9
9
  TProteumManifestCommand,
10
+ TProteumManifestConnectedProject,
10
11
  TProteumManifestController,
11
12
  TProteumManifestDiagnostic,
12
13
  TProteumManifestDiagnosticLevel,
@@ -3,6 +3,7 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Npm
6
+ import path from 'path';
6
7
  import { type Configuration } from '@rspack/core';
7
8
 
8
9
  // Core
@@ -50,18 +51,38 @@ const getDevGeneratedRuntimeEntries = (app: App) => ({
50
51
  __proteum_dev_controllers: [app.paths.server.generated + '/controllers.ts'],
51
52
  });
52
53
  const normalizeModulePath = (value?: string) => (value || '').replace(/\\/g, '/');
53
- const rewriteFrameworkAliasTargets = (app: App, aliases: Record<string, string | string[]>) => {
54
+ const getFrameworkSourceRoot = () => {
54
55
  const installedCoreRoot = cli.paths.framework.installedRoot
55
56
  ? normalizeModulePath(cli.paths.framework.installedRoot)
56
57
  : undefined;
57
58
  const activeCoreRoot = normalizeModulePath(cli.paths.framework.activeRoot);
58
59
 
59
- if (!installedCoreRoot || installedCoreRoot === activeCoreRoot) return aliases;
60
+ if (installedCoreRoot && activeCoreRoot.includes('/node_modules/')) {
61
+ return installedCoreRoot;
62
+ }
63
+
64
+ return activeCoreRoot;
65
+ };
66
+
67
+ const rewriteFrameworkAliasTargets = (aliases: Record<string, string | string[]>) => {
68
+ const visibleFrameworkRoots = [
69
+ ...cli.paths.getVisiblePackageInstallRoots('proteum'),
70
+ cli.paths.framework.installedRoot,
71
+ cli.paths.framework.activeRoot,
72
+ ]
73
+ .filter((rootPath): rootPath is string => typeof rootPath === 'string' && rootPath !== '')
74
+ .map((rootPath) => normalizeModulePath(rootPath))
75
+ .filter((rootPath, index, list) => list.indexOf(rootPath) === index);
76
+ const frameworkSourceRoot = getFrameworkSourceRoot();
60
77
 
61
78
  const rewriteCandidate = (candidate: string) =>
62
- normalizeModulePath(candidate).startsWith(installedCoreRoot + '/')
63
- ? activeCoreRoot + normalizeModulePath(candidate).substring(installedCoreRoot.length)
64
- : candidate;
79
+ visibleFrameworkRoots.reduce((nextCandidate, rootPath) => {
80
+ const normalizedCandidate = normalizeModulePath(nextCandidate);
81
+
82
+ return normalizedCandidate.startsWith(rootPath + '/')
83
+ ? frameworkSourceRoot + normalizedCandidate.substring(rootPath.length)
84
+ : nextCandidate;
85
+ }, candidate);
65
86
 
66
87
  return Object.fromEntries(
67
88
  Object.entries(aliases).map(([alias, value]) => [
@@ -82,18 +103,22 @@ export default function createCompiler(
82
103
  debug && console.info(`Creating compiler for server (${mode}).`);
83
104
  const dev = mode === 'dev';
84
105
  const outputPath = app.outputPath(outputTarget);
85
- const frameworkRoots = cli.paths.getFrameworkRoots();
106
+ const frameworkSourceRoot = getFrameworkSourceRoot();
107
+ const frameworkRoots = [frameworkSourceRoot, ...cli.paths.getFrameworkRoots()].filter(
108
+ (rootPath, index, list) => list.indexOf(rootPath) === index,
109
+ );
86
110
  const transpileModuleDirectories = app.transpileModuleDirectories;
87
111
 
88
112
  const commonConfig = createCommonConfig(app, 'server', mode, outputTarget);
89
113
  const { aliases } = app.aliases.server.forWebpack({ modulesPath: cli.paths.framework.appNodeModulesRoot });
90
- const resolvedAliases = rewriteFrameworkAliasTargets(app, aliases);
114
+ const resolvedAliases = rewriteFrameworkAliasTargets(aliases);
91
115
 
92
116
  // We're not supposed in any case to import client services from server
93
117
  delete resolvedAliases['@client/services'];
94
118
  delete resolvedAliases['@/client/services'];
95
119
  const rspackAliases = toRspackAliases(resolvedAliases);
96
- rspackAliases['@/client/router$'] = cli.paths.core.root + '/client/router.ts';
120
+ rspackAliases['proteum'] = frameworkSourceRoot;
121
+ rspackAliases['@/client/router$'] = frameworkSourceRoot + '/client/router.ts';
97
122
 
98
123
  debug &&
99
124
  console.log(
@@ -108,7 +133,7 @@ export default function createCompiler(
108
133
  name: 'server',
109
134
  target: 'node',
110
135
  entry: {
111
- server: [cli.paths.coreRoot + '/server/index.ts'],
136
+ server: [path.join(frameworkSourceRoot, 'server', 'index.ts')],
112
137
  ...(dev ? getDevGeneratedRuntimeEntries(app) : {}),
113
138
  },
114
139
 
package/cli/context.ts CHANGED
@@ -24,7 +24,12 @@ export class CLIContext {
24
24
  }
25
25
 
26
26
  public setArgs(args: TArgsObject = {}) {
27
- this.args = { workdir: process.cwd(), ...args };
27
+ const workdir =
28
+ typeof args.workdir === 'string' && args.workdir.trim().length > 0 ? args.workdir.trim() : process.cwd();
29
+
30
+ this.args = { ...args, workdir };
31
+ this.paths = new Paths(workdir, this.paths.core.root);
32
+ this.paths.applyAliases();
28
33
  this.verbose = this.args.verbose === true;
29
34
  this.debug = this.verbose;
30
35
  }
package/cli/index.ts CHANGED
@@ -12,6 +12,8 @@ import { createCli, registeredCommands } from './runtime/commands';
12
12
 
13
13
  const formatInvocation = (argv: string[]) => ['proteum', ...argv].join(' ').trim();
14
14
 
15
+ const sharedWelcomeBannerCommands = new Set(['build', 'dev']);
16
+
15
17
  const shouldRenderSharedWelcomeBanner = ({
16
18
  argv,
17
19
  helpRequestKind,
@@ -19,15 +21,12 @@ const shouldRenderSharedWelcomeBanner = ({
19
21
  argv: string[];
20
22
  helpRequestKind: 'none' | 'overview' | 'command';
21
23
  }) => {
22
- if (helpRequestKind !== 'none') return true;
23
- if (argv[0] !== 'dev') return true;
24
-
25
- if (argv.length === 1) return false;
26
-
27
- const action = argv[1];
28
- if (action.startsWith('-')) return false;
24
+ if (helpRequestKind !== 'none') return false;
25
+ if (argv.length !== 1) return false;
29
26
 
30
- return action === 'list' || action === 'stop';
27
+ const command = argv[0];
28
+ if (!command || !sharedWelcomeBannerCommands.has(command)) return false;
29
+ return true;
31
30
  };
32
31
 
33
32
  export const runCli = async (argv: string[] = process.argv.slice(2)) => {