proteum 2.1.7 → 2.1.9-1
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 +33 -9
- 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 +46 -23
- package/cli/compiler/common/bundleAnalysis.ts +56 -1
- 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 +43 -2
- package/cli/paths.ts +341 -10
- package/cli/presentation/commands.ts +30 -4
- package/cli/presentation/devSession.ts +27 -34
- package/cli/presentation/help.ts +4 -0
- package/cli/presentation/ink.ts +10 -5
- package/cli/presentation/welcome.ts +67 -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 +2 -1
- package/server/app.tsconfig.json +10 -9
- package/server/services/router/http/index.ts +72 -10
|
@@ -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,16 +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
|
|
54
|
-
const installedCoreRoot =
|
|
55
|
-
|
|
54
|
+
const getFrameworkSourceRoot = () => {
|
|
55
|
+
const installedCoreRoot = cli.paths.framework.installedRoot
|
|
56
|
+
? normalizeModulePath(cli.paths.framework.installedRoot)
|
|
57
|
+
: undefined;
|
|
58
|
+
const activeCoreRoot = normalizeModulePath(cli.paths.framework.activeRoot);
|
|
56
59
|
|
|
57
|
-
if (installedCoreRoot
|
|
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();
|
|
58
77
|
|
|
59
78
|
const rewriteCandidate = (candidate: string) =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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);
|
|
63
86
|
|
|
64
87
|
return Object.fromEntries(
|
|
65
88
|
Object.entries(aliases).map(([alias, value]) => [
|
|
@@ -80,19 +103,22 @@ export default function createCompiler(
|
|
|
80
103
|
debug && console.info(`Creating compiler for server (${mode}).`);
|
|
81
104
|
const dev = mode === 'dev';
|
|
82
105
|
const outputPath = app.outputPath(outputTarget);
|
|
83
|
-
const
|
|
84
|
-
const frameworkRoots = [cli.paths.
|
|
106
|
+
const frameworkSourceRoot = getFrameworkSourceRoot();
|
|
107
|
+
const frameworkRoots = [frameworkSourceRoot, ...cli.paths.getFrameworkRoots()].filter(
|
|
108
|
+
(rootPath, index, list) => list.indexOf(rootPath) === index,
|
|
109
|
+
);
|
|
85
110
|
const transpileModuleDirectories = app.transpileModuleDirectories;
|
|
86
111
|
|
|
87
112
|
const commonConfig = createCommonConfig(app, 'server', mode, outputTarget);
|
|
88
|
-
const { aliases } = app.aliases.server.forWebpack({ modulesPath:
|
|
89
|
-
const resolvedAliases = rewriteFrameworkAliasTargets(
|
|
113
|
+
const { aliases } = app.aliases.server.forWebpack({ modulesPath: cli.paths.framework.appNodeModulesRoot });
|
|
114
|
+
const resolvedAliases = rewriteFrameworkAliasTargets(aliases);
|
|
90
115
|
|
|
91
116
|
// We're not supposed in any case to import client services from server
|
|
92
117
|
delete resolvedAliases['@client/services'];
|
|
93
118
|
delete resolvedAliases['@/client/services'];
|
|
94
119
|
const rspackAliases = toRspackAliases(resolvedAliases);
|
|
95
|
-
rspackAliases['
|
|
120
|
+
rspackAliases['proteum'] = frameworkSourceRoot;
|
|
121
|
+
rspackAliases['@/client/router$'] = frameworkSourceRoot + '/client/router.ts';
|
|
96
122
|
|
|
97
123
|
debug &&
|
|
98
124
|
console.log(
|
|
@@ -107,7 +133,7 @@ export default function createCompiler(
|
|
|
107
133
|
name: 'server',
|
|
108
134
|
target: 'node',
|
|
109
135
|
entry: {
|
|
110
|
-
server: [
|
|
136
|
+
server: [path.join(frameworkSourceRoot, 'server', 'index.ts')],
|
|
111
137
|
...(dev ? getDevGeneratedRuntimeEntries(app) : {}),
|
|
112
138
|
},
|
|
113
139
|
|
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
|
}),
|
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
|
}
|
|
@@ -110,18 +110,37 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
110
110
|
name: 'dev',
|
|
111
111
|
category: 'Daily workflow',
|
|
112
112
|
summary: 'Start the local compiler, SSR server, and hot reload loop.',
|
|
113
|
-
usage: 'proteum dev [--port <port>] [--cache|--no-cache]',
|
|
113
|
+
usage: 'proteum dev [list|stop] [--port <port>] [--session-file <path>] [--replace-existing] [--all] [--stale] [--json] [--cache|--no-cache]',
|
|
114
114
|
bestFor:
|
|
115
115
|
'Day-to-day app work. This is the main entrypoint used by the current reference apps during local development.',
|
|
116
116
|
examples: [
|
|
117
117
|
{ description: 'Start the app on its configured router port', command: 'proteum dev' },
|
|
118
|
-
{ description: '
|
|
118
|
+
{ description: 'Replace the tracked dev session on another port', command: 'proteum dev --port 3101 --replace-existing' },
|
|
119
|
+
{
|
|
120
|
+
description: 'Start a tracked dev session with an explicit session file for an agent task',
|
|
121
|
+
command: 'proteum dev --port 3101 --session-file var/run/proteum/dev/agents/task.json --replace-existing',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
description: 'List tracked Proteum dev sessions as JSON',
|
|
125
|
+
command: 'proteum dev list --json',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
description: 'Stop every stale tracked dev session for the current app',
|
|
129
|
+
command: 'proteum dev stop --all --stale',
|
|
130
|
+
},
|
|
119
131
|
{
|
|
120
132
|
description: 'Disable the filesystem cache while debugging compiler state',
|
|
121
133
|
command: 'proteum dev --no-cache',
|
|
122
134
|
},
|
|
123
135
|
],
|
|
124
|
-
notes: [
|
|
136
|
+
notes: [
|
|
137
|
+
'Proteum writes a machine-readable dev session file under `var/run/proteum/dev/<port>.json` by default; override it with `--session-file` when an agent needs a stable path.',
|
|
138
|
+
'Use `--replace-existing` when retries should stop the previously tracked matching session before starting a new one.',
|
|
139
|
+
'`proteum dev list` inspects tracked sessions for the current app root. Add `--stale` to show only orphaned or dead sessions.',
|
|
140
|
+
'`proteum dev stop` targets the current session file by default. Add `--all` to stop every tracked session for the current app root.',
|
|
141
|
+
'`proteum dev` clears the interactive terminal once at startup, then shows `CTRL+R` reload and `CTRL+C` shutdown hotkeys in the session banner.',
|
|
142
|
+
'Legacy single-dash long options remain supported, for example `proteum dev -port 3001`.',
|
|
143
|
+
],
|
|
125
144
|
status: 'stable',
|
|
126
145
|
},
|
|
127
146
|
refresh: {
|
|
@@ -141,7 +160,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
141
160
|
name: 'build',
|
|
142
161
|
category: 'Daily workflow',
|
|
143
162
|
summary: 'Build the application.',
|
|
144
|
-
usage: 'proteum build [--prod] [--strict] [--cache] [--analyze] [--port <port>]',
|
|
163
|
+
usage: 'proteum build [--prod] [--strict] [--cache] [--analyze] [--analyze-serve] [--analyze-host <host>] [--analyze-port <port|auto>] [--port <port>]',
|
|
145
164
|
bestFor: 'CI, release builds, and local verification of the production server and client output.',
|
|
146
165
|
examples: [
|
|
147
166
|
{ description: 'Run the normal production build', command: 'proteum build --prod' },
|
|
@@ -150,10 +169,17 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
150
169
|
command: 'proteum build --prod --strict',
|
|
151
170
|
},
|
|
152
171
|
{ description: 'Generate bundle analysis artifacts', command: 'proteum build --prod --analyze' },
|
|
172
|
+
{
|
|
173
|
+
description: 'Serve the bundle analysis at a local URL and let the OS choose the port',
|
|
174
|
+
command: 'proteum build --prod --analyze --analyze-serve --analyze-port auto',
|
|
175
|
+
},
|
|
153
176
|
{ description: 'Reuse the filesystem cache during builds', command: 'proteum build --prod --cache' },
|
|
154
177
|
],
|
|
155
178
|
notes: [
|
|
156
179
|
'Legacy positional booleans remain supported, for example `proteum build prod strict analyze`.',
|
|
180
|
+
'`--analyze` alone emits `bin/bundle-analysis/client.html` and `client-stats.json`.',
|
|
181
|
+
'`--analyze-serve` switches the analyzer to HTTP server mode and keeps the process open until you stop it.',
|
|
182
|
+
'`--analyze-host` and `--analyze-port` require `--analyze-serve`; use `auto` to let the OS assign a free port.',
|
|
157
183
|
'Use `--strict` when the build must refresh generated typings and fail on any TypeScript error before compilation starts.',
|
|
158
184
|
'The production output is emitted under `bin/`.',
|
|
159
185
|
],
|