proteum 2.1.7 → 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 +16 -5
- package/README.md +5 -1
- package/agents/project/AGENTS.md +13 -2
- package/agents/project/diagnostics.md +4 -0
- package/agents/project/optimizations.md +1 -0
- package/cli/app/index.ts +7 -7
- package/cli/bin.js +0 -8
- package/cli/commands/build.ts +60 -9
- package/cli/commands/dev.ts +232 -5
- package/cli/compiler/artifacts/commands.ts +20 -5
- package/cli/compiler/client/index.ts +9 -9
- package/cli/compiler/common/bundleAnalysis.ts +56 -1
- 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 +43 -2
- package/cli/paths.ts +300 -10
- package/cli/presentation/commands.ts +30 -4
- package/cli/presentation/devSession.ts +24 -29
- package/cli/presentation/help.ts +4 -0
- package/cli/presentation/welcome.ts +69 -0
- package/cli/runtime/commands.ts +40 -3
- package/cli/runtime/devSessions.ts +337 -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
|
@@ -22,8 +22,15 @@ const readServerTsconfigPaths = () => {
|
|
|
22
22
|
return compilerOptions.paths || {};
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
|
|
25
|
+
const getCommandsTsconfigFilepath = () => path.join(app.paths.root, 'commands', 'tsconfig.json');
|
|
26
|
+
|
|
27
|
+
const getCommandsGlobalTypesPath = (commandsTsconfigFilepath: string) =>
|
|
28
|
+
cli.paths.relativeFrameworkPathFrom(commandsTsconfigFilepath, 'types', 'global');
|
|
29
|
+
|
|
30
|
+
const createCommandsTsconfigContent = () => {
|
|
31
|
+
const commandsTsconfigFilepath = getCommandsTsconfigFilepath();
|
|
32
|
+
|
|
33
|
+
return `${JSON.stringify(
|
|
27
34
|
{
|
|
28
35
|
extends: '../server/tsconfig.json',
|
|
29
36
|
compilerOptions: {
|
|
@@ -35,12 +42,13 @@ const createCommandsTsconfigContent = () =>
|
|
|
35
42
|
'@models/types': ['./.proteum/server/models.ts'],
|
|
36
43
|
},
|
|
37
44
|
},
|
|
38
|
-
include: ['.', '../var/typings',
|
|
45
|
+
include: ['.', '../var/typings', getCommandsGlobalTypesPath(commandsTsconfigFilepath), '../.proteum/server/commands.d.ts'],
|
|
39
46
|
},
|
|
40
47
|
null,
|
|
41
48
|
4,
|
|
42
49
|
)}
|
|
43
50
|
`;
|
|
51
|
+
};
|
|
44
52
|
|
|
45
53
|
const legacyCommandsTsconfigContent = `{
|
|
46
54
|
"extends": "../server/tsconfig.json",
|
|
@@ -105,8 +113,15 @@ const isManagedCommandsTsconfig = (content: string) => {
|
|
|
105
113
|
};
|
|
106
114
|
|
|
107
115
|
if (parsed.extends !== '../server/tsconfig.json') return false;
|
|
108
|
-
if (
|
|
116
|
+
if (!Array.isArray(parsed.include) || parsed.include.length !== 4) return false;
|
|
117
|
+
if (parsed.include[0] !== '.' || parsed.include[1] !== '../var/typings') return false;
|
|
118
|
+
if (parsed.include[3] !== '../.proteum/server/commands.d.ts') return false;
|
|
119
|
+
if (
|
|
120
|
+
parsed.include[2] !== getCommandsGlobalTypesPath(getCommandsTsconfigFilepath()) &&
|
|
121
|
+
!parsed.include[2].includes('node_modules/proteum/types/global')
|
|
122
|
+
) {
|
|
109
123
|
return false;
|
|
124
|
+
}
|
|
110
125
|
|
|
111
126
|
if (parsed.compilerOptions?.baseUrl !== undefined && parsed.compilerOptions.baseUrl !== '..') return false;
|
|
112
127
|
if (parsed.compilerOptions?.rootDir !== undefined && parsed.compilerOptions.rootDir !== '..') return false;
|
|
@@ -119,7 +134,7 @@ const isManagedCommandsTsconfig = (content: string) => {
|
|
|
119
134
|
|
|
120
135
|
const ensureCommandsTsconfig = () => {
|
|
121
136
|
const commandsRoot = path.join(app.paths.root, 'commands');
|
|
122
|
-
const commandsTsconfigFilepath =
|
|
137
|
+
const commandsTsconfigFilepath = getCommandsTsconfigFilepath();
|
|
123
138
|
const nextContent = createCommandsTsconfigContent();
|
|
124
139
|
|
|
125
140
|
if (!fs.existsSync(commandsRoot)) return;
|
|
@@ -21,17 +21,18 @@ import type { App } from '../../app';
|
|
|
21
21
|
|
|
22
22
|
const debug = false;
|
|
23
23
|
const ssrScriptPattern = /\.ssr\.(ts|tsx)$/;
|
|
24
|
-
const normalizedCoreRoot = cli.paths.
|
|
24
|
+
const normalizedCoreRoot = cli.paths.framework.activeRoot.replace(/\\/g, '/');
|
|
25
25
|
const hmrClientEntry = path.join(cli.paths.core.root, 'client', 'dev', 'hmr.ts');
|
|
26
26
|
|
|
27
27
|
const normalizeModulePath = (value?: string) => (value || '').replace(/\\/g, '/');
|
|
28
|
-
const resolveFromAppOrCore = (
|
|
29
|
-
require.resolve(request, { paths: [app.paths.root, cli.paths.core.root] });
|
|
28
|
+
const resolveFromAppOrCore = (_app: App, request: string) => cli.paths.resolveRequest(request);
|
|
30
29
|
const rewriteFrameworkAliasTargets = (app: App, aliases: Record<string, string | string[]>) => {
|
|
31
|
-
const installedCoreRoot =
|
|
32
|
-
|
|
30
|
+
const installedCoreRoot = cli.paths.framework.installedRoot
|
|
31
|
+
? normalizeModulePath(cli.paths.framework.installedRoot)
|
|
32
|
+
: undefined;
|
|
33
|
+
const activeCoreRoot = normalizeModulePath(cli.paths.framework.activeRoot);
|
|
33
34
|
|
|
34
|
-
if (installedCoreRoot === activeCoreRoot) return aliases;
|
|
35
|
+
if (!installedCoreRoot || installedCoreRoot === activeCoreRoot) return aliases;
|
|
35
36
|
|
|
36
37
|
const rewriteCandidate = (candidate: string) =>
|
|
37
38
|
normalizeModulePath(candidate).startsWith(installedCoreRoot + '/')
|
|
@@ -76,8 +77,7 @@ export default function createCompiler(
|
|
|
76
77
|
logVerbose(`Creating compiler for client (${mode}).`);
|
|
77
78
|
const dev = mode === 'dev';
|
|
78
79
|
const outputPath = app.outputPath(outputTarget);
|
|
79
|
-
const
|
|
80
|
-
const frameworkRoots = [cli.paths.core.root, installedCoreRoot];
|
|
80
|
+
const frameworkRoots = cli.paths.getFrameworkRoots();
|
|
81
81
|
const transpileModuleDirectories = app.transpileModuleDirectories;
|
|
82
82
|
|
|
83
83
|
const commonConfig = createCommonConfig(app, 'client', mode, outputTarget);
|
|
@@ -93,7 +93,7 @@ export default function createCompiler(
|
|
|
93
93
|
);*/
|
|
94
94
|
|
|
95
95
|
// Convert tsconfig paths into bundler aliases.
|
|
96
|
-
const { aliases } = app.aliases.client.forWebpack({ modulesPath:
|
|
96
|
+
const { aliases } = app.aliases.client.forWebpack({ modulesPath: cli.paths.framework.appNodeModulesRoot });
|
|
97
97
|
const resolvedAliases = rewriteFrameworkAliasTargets(app, aliases);
|
|
98
98
|
|
|
99
99
|
// We're not supposed in any case to import server libs from client
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import type { RspackPluginInstance } from '@rspack/core';
|
|
4
|
+
import { UsageError } from 'clipanion';
|
|
4
5
|
|
|
5
6
|
import cli from '../..';
|
|
6
7
|
import type { App } from '../../app';
|
|
@@ -8,8 +9,56 @@ import type { App } from '../../app';
|
|
|
8
9
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
|
9
10
|
|
|
10
11
|
export type TBundleAnalysisReportPaths = { reportPath: string; statsPath: string };
|
|
12
|
+
export type TBundleAnalysisMode = 'server' | 'static';
|
|
13
|
+
type TBundleAnalysisServerUrlArgs = {
|
|
14
|
+
listenHost: string;
|
|
15
|
+
listenPort: number | 'auto';
|
|
16
|
+
boundAddress?: string | { address?: string; port?: number } | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const defaultAnalyzerHost = '127.0.0.1';
|
|
20
|
+
const defaultAnalyzerPort = 8888;
|
|
21
|
+
let latestClientBundleAnalysisServerUrl: string | undefined;
|
|
11
22
|
|
|
12
23
|
export const isBundleAnalysisEnabled = () => cli.args.analyze === true;
|
|
24
|
+
export const isBundleAnalysisServerEnabled = () => cli.args.analyzeServe === true;
|
|
25
|
+
export const getBundleAnalysisMode = (): TBundleAnalysisMode => (isBundleAnalysisServerEnabled() ? 'server' : 'static');
|
|
26
|
+
|
|
27
|
+
const hasCliStringArg = (name: string) => typeof cli.args[name] === 'string' && (cli.args[name] as string).trim().length > 0;
|
|
28
|
+
|
|
29
|
+
export const hasBundleAnalysisServerOverrides = () => hasCliStringArg('analyzeHost') || hasCliStringArg('analyzePort');
|
|
30
|
+
|
|
31
|
+
export const getBundleAnalysisServerHost = () =>
|
|
32
|
+
hasCliStringArg('analyzeHost') ? String(cli.args.analyzeHost).trim() : defaultAnalyzerHost;
|
|
33
|
+
|
|
34
|
+
export const getBundleAnalysisServerPort = (): number | 'auto' => {
|
|
35
|
+
const rawPort = hasCliStringArg('analyzePort') ? String(cli.args.analyzePort).trim() : '';
|
|
36
|
+
if (!rawPort) return defaultAnalyzerPort;
|
|
37
|
+
if (rawPort === 'auto') return 'auto';
|
|
38
|
+
|
|
39
|
+
const parsedPort = Number.parseInt(rawPort, 10);
|
|
40
|
+
if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
|
|
41
|
+
throw new UsageError(`Invalid analyzer port "${rawPort}". Use a number between 1 and 65535, or \`auto\`.`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return parsedPort;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const createBundleAnalysisServerUrl = ({ listenHost, listenPort, boundAddress }: TBundleAnalysisServerUrlArgs) => {
|
|
48
|
+
const port =
|
|
49
|
+
typeof boundAddress === 'object' && boundAddress !== null && typeof boundAddress.port === 'number'
|
|
50
|
+
? boundAddress.port
|
|
51
|
+
: listenPort;
|
|
52
|
+
const url = `http://${listenHost}:${port}`;
|
|
53
|
+
latestClientBundleAnalysisServerUrl = url;
|
|
54
|
+
return url;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const consumeClientBundleAnalysisServerUrl = () => {
|
|
58
|
+
const url = latestClientBundleAnalysisServerUrl;
|
|
59
|
+
latestClientBundleAnalysisServerUrl = undefined;
|
|
60
|
+
return url;
|
|
61
|
+
};
|
|
13
62
|
|
|
14
63
|
export const getClientBundleAnalysisReportPaths = (
|
|
15
64
|
app: App,
|
|
@@ -23,19 +72,25 @@ export const getClientBundleAnalysisReportPaths = (
|
|
|
23
72
|
export const createClientBundleAnalysisPlugins = (app: App, outputTarget: 'dev' | 'bin'): RspackPluginInstance[] => {
|
|
24
73
|
if (!isBundleAnalysisEnabled()) return [];
|
|
25
74
|
|
|
75
|
+
latestClientBundleAnalysisServerUrl = undefined;
|
|
76
|
+
|
|
26
77
|
const { reportPath, statsPath } = getClientBundleAnalysisReportPaths(app, outputTarget);
|
|
78
|
+
const analyzerMode = getBundleAnalysisMode();
|
|
27
79
|
|
|
28
80
|
fs.ensureDirSync(path.dirname(reportPath));
|
|
29
81
|
|
|
30
82
|
return [
|
|
31
83
|
new BundleAnalyzerPlugin({
|
|
32
|
-
analyzerMode
|
|
84
|
+
analyzerMode,
|
|
85
|
+
analyzerHost: getBundleAnalysisServerHost(),
|
|
86
|
+
analyzerPort: getBundleAnalysisServerPort(),
|
|
33
87
|
openAnalyzer: false,
|
|
34
88
|
defaultSizes: 'parsed',
|
|
35
89
|
reportFilename: reportPath,
|
|
36
90
|
generateStatsFile: true,
|
|
37
91
|
statsFilename: statsPath,
|
|
38
92
|
logLevel: 'info',
|
|
93
|
+
analyzerUrl: createBundleAnalysisServerUrl,
|
|
39
94
|
}),
|
|
40
95
|
];
|
|
41
96
|
};
|
|
@@ -45,6 +45,11 @@ 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 loaderModuleRoots = [
|
|
49
|
+
cli.paths.framework.appNodeModulesRoot,
|
|
50
|
+
cli.paths.framework.frameworkNodeModulesRoot,
|
|
51
|
+
path.join(cli.paths.core.cli, 'node_modules'),
|
|
52
|
+
].filter((moduleRoot, index, list) => list.indexOf(moduleRoot) === index);
|
|
48
53
|
const config: Configuration = {
|
|
49
54
|
// Project root
|
|
50
55
|
context: app.paths.root,
|
|
@@ -55,11 +60,7 @@ export default function createCommonConfig(
|
|
|
55
60
|
// Support both install modes:
|
|
56
61
|
// - npm i: loaders are often hoisted in app/node_modules
|
|
57
62
|
// - npm link: loaders often live in framework/node_modules
|
|
58
|
-
modules:
|
|
59
|
-
app.paths.root + '/node_modules',
|
|
60
|
-
cli.paths.core.root + '/node_modules',
|
|
61
|
-
cli.paths.core.cli + '/node_modules',
|
|
62
|
-
],
|
|
63
|
+
modules: loaderModuleRoots,
|
|
63
64
|
mainFields: ['loader', 'main'],
|
|
64
65
|
},
|
|
65
66
|
|
package/cli/compiler/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { rspack, type Compiler as RspackCompiler } from '@rspack/core';
|
|
|
9
9
|
|
|
10
10
|
// Core
|
|
11
11
|
import app from '../app';
|
|
12
|
+
import cli from '..';
|
|
12
13
|
import createServerConfig from './server';
|
|
13
14
|
import createClientConfig from './client';
|
|
14
15
|
import { TCompileMode, TCompileOutputTarget } from './common';
|
|
@@ -62,15 +63,21 @@ export default class Compiler {
|
|
|
62
63
|
- Including React, so VSCode shows that JSX is missing
|
|
63
64
|
*/
|
|
64
65
|
public fixNpmLinkIssues() {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const installedFrameworkRoot = cli.paths.framework.installedRoot;
|
|
67
|
+
|
|
68
|
+
if (!installedFrameworkRoot || !fs.existsSync(installedFrameworkRoot)) {
|
|
69
|
+
return logVerbose("Not fixing npm link issues because the app can't see an installed Proteum package.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!fs.lstatSync(installedFrameworkRoot).isSymbolicLink()) {
|
|
73
|
+
return logVerbose("Not fixing npm link issues because Proteum wasn't installed with npm link.");
|
|
74
|
+
}
|
|
68
75
|
|
|
69
76
|
this.debug && logVerbose(`Fix NPM link issues ...`);
|
|
70
77
|
const outputPath = app.outputPath(this.outputTarget);
|
|
71
78
|
|
|
72
|
-
const appModules =
|
|
73
|
-
const coreModules =
|
|
79
|
+
const appModules = cli.paths.framework.appNodeModulesRoot;
|
|
80
|
+
const coreModules = cli.paths.framework.frameworkNodeModulesRoot;
|
|
74
81
|
|
|
75
82
|
// When the 5htp package is installed from npm link,
|
|
76
83
|
// Modules are installed locally and not glbally as with with the 5htp package from NPM.
|
|
@@ -51,10 +51,12 @@ const getDevGeneratedRuntimeEntries = (app: App) => ({
|
|
|
51
51
|
});
|
|
52
52
|
const normalizeModulePath = (value?: string) => (value || '').replace(/\\/g, '/');
|
|
53
53
|
const rewriteFrameworkAliasTargets = (app: App, aliases: Record<string, string | string[]>) => {
|
|
54
|
-
const installedCoreRoot =
|
|
55
|
-
|
|
54
|
+
const installedCoreRoot = cli.paths.framework.installedRoot
|
|
55
|
+
? normalizeModulePath(cli.paths.framework.installedRoot)
|
|
56
|
+
: undefined;
|
|
57
|
+
const activeCoreRoot = normalizeModulePath(cli.paths.framework.activeRoot);
|
|
56
58
|
|
|
57
|
-
if (installedCoreRoot === activeCoreRoot) return aliases;
|
|
59
|
+
if (!installedCoreRoot || installedCoreRoot === activeCoreRoot) return aliases;
|
|
58
60
|
|
|
59
61
|
const rewriteCandidate = (candidate: string) =>
|
|
60
62
|
normalizeModulePath(candidate).startsWith(installedCoreRoot + '/')
|
|
@@ -80,12 +82,11 @@ export default function createCompiler(
|
|
|
80
82
|
debug && console.info(`Creating compiler for server (${mode}).`);
|
|
81
83
|
const dev = mode === 'dev';
|
|
82
84
|
const outputPath = app.outputPath(outputTarget);
|
|
83
|
-
const
|
|
84
|
-
const frameworkRoots = [cli.paths.core.root, installedCoreRoot];
|
|
85
|
+
const frameworkRoots = cli.paths.getFrameworkRoots();
|
|
85
86
|
const transpileModuleDirectories = app.transpileModuleDirectories;
|
|
86
87
|
|
|
87
88
|
const commonConfig = createCommonConfig(app, 'server', mode, outputTarget);
|
|
88
|
-
const { aliases } = app.aliases.server.forWebpack({ modulesPath:
|
|
89
|
+
const { aliases } = app.aliases.server.forWebpack({ modulesPath: cli.paths.framework.appNodeModulesRoot });
|
|
89
90
|
const resolvedAliases = rewriteFrameworkAliasTargets(app, aliases);
|
|
90
91
|
|
|
91
92
|
// We're not supposed in any case to import client services from server
|
package/cli/index.ts
CHANGED
|
@@ -3,21 +3,62 @@ process.traceDeprecation = true;
|
|
|
3
3
|
import { Cli } from 'clipanion';
|
|
4
4
|
|
|
5
5
|
import cli from './context';
|
|
6
|
+
import { resolveFrameworkInstallInfo } from './paths';
|
|
6
7
|
import { proteumCommandNames } from './presentation/commands';
|
|
7
8
|
import { renderCliOverview, renderCommandHelp, resolveCustomHelpRequest } from './presentation/help';
|
|
9
|
+
import { renderCliWelcomeBanner } from './presentation/welcome';
|
|
8
10
|
import { normalizeHelpArgv, normalizeLegacyArgv } from './runtime/argv';
|
|
9
11
|
import { createCli, registeredCommands } from './runtime/commands';
|
|
10
12
|
|
|
13
|
+
const formatInvocation = (argv: string[]) => ['proteum', ...argv].join(' ').trim();
|
|
14
|
+
|
|
15
|
+
const shouldRenderSharedWelcomeBanner = ({
|
|
16
|
+
argv,
|
|
17
|
+
helpRequestKind,
|
|
18
|
+
}: {
|
|
19
|
+
argv: string[];
|
|
20
|
+
helpRequestKind: 'none' | 'overview' | 'command';
|
|
21
|
+
}) => {
|
|
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;
|
|
29
|
+
|
|
30
|
+
return action === 'list' || action === 'stop';
|
|
31
|
+
};
|
|
32
|
+
|
|
11
33
|
export const runCli = async (argv: string[] = process.argv.slice(2)) => {
|
|
12
34
|
const normalizedArgv = normalizeHelpArgv(normalizeLegacyArgv(argv), proteumCommandNames);
|
|
13
|
-
const
|
|
35
|
+
const version = String(cli.packageJson.version || '');
|
|
36
|
+
const proteumInstall = resolveFrameworkInstallInfo({
|
|
37
|
+
appRoot: cli.paths.appRoot,
|
|
38
|
+
framework: cli.paths.framework,
|
|
39
|
+
});
|
|
40
|
+
const clipanion = createCli(version);
|
|
14
41
|
const initAvailable = true;
|
|
15
42
|
const helpRequest = resolveCustomHelpRequest(normalizedArgv);
|
|
43
|
+
const shouldRenderWelcomeBanner = shouldRenderSharedWelcomeBanner({
|
|
44
|
+
argv: normalizedArgv,
|
|
45
|
+
helpRequestKind: helpRequest.kind,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (shouldRenderWelcomeBanner) {
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`${await renderCliWelcomeBanner({
|
|
51
|
+
command: formatInvocation(normalizedArgv),
|
|
52
|
+
installSummary: proteumInstall.summary,
|
|
53
|
+
version,
|
|
54
|
+
})}\n\n`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
16
57
|
|
|
17
58
|
if (helpRequest.kind === 'overview') {
|
|
18
59
|
process.stdout.write(
|
|
19
60
|
await renderCliOverview({
|
|
20
|
-
version
|
|
61
|
+
version,
|
|
21
62
|
workdir: process.cwd(),
|
|
22
63
|
initAvailable,
|
|
23
64
|
}),
|