@mpis/monorepo 0.0.18 → 0.0.20
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/lib/actions/cmd.analyze.d.ts +3 -0
- package/lib/actions/cmd.analyze.d.ts.map +1 -0
- package/lib/actions/cmd.analyze.js +125 -0
- package/lib/actions/cmd.analyze.js.map +1 -0
- package/lib/actions/cmd.build.d.ts.map +1 -0
- package/lib/{common → actions}/cmd.build.js +2 -2
- package/lib/actions/cmd.build.js.map +1 -0
- package/lib/actions/cmd.dot.d.ts +2 -0
- package/lib/actions/cmd.dot.d.ts.map +1 -0
- package/lib/actions/cmd.dot.js +101 -0
- package/lib/actions/cmd.dot.js.map +1 -0
- package/lib/actions/cmd.list.d.ts.map +1 -0
- package/lib/actions/cmd.list.js +38 -0
- package/lib/actions/cmd.list.js.map +1 -0
- package/lib/actions/cmd.watch.d.ts.map +1 -0
- package/lib/{common → actions}/cmd.watch.js +15 -10
- package/lib/actions/cmd.watch.js.map +1 -0
- package/lib/bin.d.ts +1 -1
- package/lib/bin.d.ts.map +1 -1
- package/lib/bin.js +28 -6
- package/lib/bin.js.map +1 -1
- package/lib/common/args.d.ts +0 -1
- package/lib/common/args.d.ts.map +1 -1
- package/lib/common/args.js +3 -9
- package/lib/common/args.js.map +1 -1
- package/lib/common/string-collect.d.ts +7 -0
- package/lib/common/string-collect.d.ts.map +1 -0
- package/lib/common/string-collect.js +19 -0
- package/lib/common/string-collect.js.map +1 -0
- package/lib/common/user-interactive.d.ts +11 -1
- package/lib/common/user-interactive.d.ts.map +1 -1
- package/lib/common/user-interactive.js +143 -4
- package/lib/common/user-interactive.js.map +1 -1
- package/lib/common/workspace.d.ts +26 -6
- package/lib/common/workspace.d.ts.map +1 -1
- package/lib/common/workspace.js +46 -14
- package/lib/common/workspace.js.map +1 -1
- package/package.json +13 -12
- package/src/actions/cmd.analyze.ts +151 -0
- package/src/{common → actions}/cmd.build.ts +2 -2
- package/src/actions/cmd.dot.ts +106 -0
- package/src/actions/cmd.list.ts +41 -0
- package/src/{common → actions}/cmd.watch.ts +17 -11
- package/src/bin.ts +29 -6
- package/src/common/args.ts +3 -10
- package/src/common/string-collect.ts +20 -0
- package/src/common/user-interactive.ts +160 -4
- package/src/common/workspace.ts +63 -16
- package/lib/common/cmd.build.d.ts.map +0 -1
- package/lib/common/cmd.build.js.map +0 -1
- package/lib/common/cmd.list.d.ts.map +0 -1
- package/lib/common/cmd.list.js +0 -16
- package/lib/common/cmd.list.js.map +0 -1
- package/lib/common/cmd.watch.d.ts.map +0 -1
- package/lib/common/cmd.watch.js.map +0 -1
- package/src/common/cmd.list.ts +0 -19
- /package/lib/{common → actions}/cmd.build.d.ts +0 -0
- /package/lib/{common → actions}/cmd.list.d.ts +0 -0
- /package/lib/{common → actions}/cmd.watch.d.ts +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createWorkspace } from '@build-script/monorepo-lib';
|
|
2
|
+
import { argv } from '@idlebox/args/default';
|
|
3
|
+
import { logger } from '@idlebox/logger';
|
|
4
|
+
import { shutdown } from '@idlebox/node';
|
|
5
|
+
|
|
6
|
+
type NamePair = readonly [string, string];
|
|
7
|
+
const outerScope: string[] = [];
|
|
8
|
+
const scopes = new Map<string, string[]>();
|
|
9
|
+
|
|
10
|
+
export async function runDot() {
|
|
11
|
+
const reverse = argv.flag(['--reverse', '-r']) > 0;
|
|
12
|
+
const prodOnly = argv.flag(['--prod', '-P']) > 0;
|
|
13
|
+
const devOnly = argv.flag(['--dev', '-D']) > 0;
|
|
14
|
+
|
|
15
|
+
// const packages = argv.range(0)
|
|
16
|
+
|
|
17
|
+
if (argv.unused().length) {
|
|
18
|
+
logger.error`Unknown arguments: ${argv.unused().join(', ')}`;
|
|
19
|
+
return shutdown(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const workspace = await createWorkspace();
|
|
23
|
+
await workspace.decoupleDependencies();
|
|
24
|
+
|
|
25
|
+
const projects = await workspace.listPackages();
|
|
26
|
+
logger.debug`workspace: ${projects.length} packages.`;
|
|
27
|
+
for (const project of projects) {
|
|
28
|
+
if (!project.name) continue;
|
|
29
|
+
if (ignoreName(project.name)) continue;
|
|
30
|
+
|
|
31
|
+
const [scopeName, nodeName] = split(project.name);
|
|
32
|
+
|
|
33
|
+
const all_deps = new Set<string>();
|
|
34
|
+
if (!devOnly) {
|
|
35
|
+
for (const [dep, version] of Object.entries(project.packageJson.dependencies ?? {})) {
|
|
36
|
+
if (!version.startsWith('workspace:') || ignoreName(dep)) continue;
|
|
37
|
+
all_deps.add(dep);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!prodOnly) {
|
|
41
|
+
for (const [dep, version] of Object.entries(project.packageJson.devDependencies ?? {})) {
|
|
42
|
+
if (!version.startsWith('workspace:') || ignoreName(dep)) continue;
|
|
43
|
+
all_deps.add(dep);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
scope(scopeName).push(` "${nodeName}" [label="${nodeName}\\n依赖: ${osize(project.packageJson.devDependencies)}+${osize(project.packageJson.dependencies)}"];`);
|
|
48
|
+
for (const dep of all_deps) {
|
|
49
|
+
const styles = [];
|
|
50
|
+
if (Object.hasOwn(project.packageJson.devDependencies ?? {}, dep)) {
|
|
51
|
+
styles.push('style=dashed', 'color=darkorange3');
|
|
52
|
+
} else {
|
|
53
|
+
styles.push('color=darkorchid3');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (reverse) {
|
|
57
|
+
connect(split(dep), split(project.name), styles);
|
|
58
|
+
} else {
|
|
59
|
+
connect(split(project.name), split(dep), styles);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const output: string[] = ['digraph Tree {', 'splines=ortho;', 'node [shape=rect,style=filled,fillcolor=darkolivegreen3];'];
|
|
65
|
+
for (const lines of scopes.values()) {
|
|
66
|
+
output.push(...lines, '}');
|
|
67
|
+
}
|
|
68
|
+
output.push(...outerScope);
|
|
69
|
+
output.push('}');
|
|
70
|
+
|
|
71
|
+
console.log(output.join('\n'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function split(name: string): NamePair {
|
|
75
|
+
name = name.replace(/-/g, '_');
|
|
76
|
+
if (name.startsWith('@')) {
|
|
77
|
+
const n = name.split('/');
|
|
78
|
+
return [n[0].slice(1), n[1]] as const;
|
|
79
|
+
} else {
|
|
80
|
+
return ['standalone', name] as const;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function scope(scopeName: string) {
|
|
84
|
+
const arr = scopes.get(scopeName);
|
|
85
|
+
if (arr) return arr;
|
|
86
|
+
const newArr: string[] = [`subgraph "cluster_${scopeName}" {`, `fillcolor=cornsilk;`, `style=filled;`, `label = "Scope @${scopeName}";`];
|
|
87
|
+
scopes.set(scopeName, newArr);
|
|
88
|
+
return newArr;
|
|
89
|
+
}
|
|
90
|
+
function connect(from: NamePair, to: NamePair, styles: string[] = []) {
|
|
91
|
+
if (from[0] === to[0]) {
|
|
92
|
+
scope(from[0]).push(` "${from[1]}" -> "${to[1]}" [${styles.join(',')}];`);
|
|
93
|
+
} else {
|
|
94
|
+
styles.push(`ltail="cluster_${from[0]}"`, `lhead="cluster_${to[0]}"`);
|
|
95
|
+
outerScope.push(` "${from[1]}" -> "${to[1]}" [${styles.join(',')}];`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ignoreName(_name: string) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function osize(objects: object) {
|
|
104
|
+
if (!objects) return 0;
|
|
105
|
+
return Object.keys(objects).length;
|
|
106
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { argv } from '@idlebox/args/default';
|
|
2
|
+
import { logger } from '@idlebox/logger';
|
|
3
|
+
import { shutdown } from '@idlebox/node';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { createMonorepoObject } from '../common/workspace.js';
|
|
6
|
+
|
|
7
|
+
export async function runList() {
|
|
8
|
+
const reverse = argv.flag(['--reverse', '-r']) > 0;
|
|
9
|
+
const noPager = argv.flag(['--no-pager']) > 0 || !process.stdout.isTTY;
|
|
10
|
+
const depth = parseInt(argv.single(['--depth']) || '0', 10);
|
|
11
|
+
if (argv.unused().length) {
|
|
12
|
+
logger.error`Unknown arguments: ${argv.unused().join(', ')}`;
|
|
13
|
+
return shutdown(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const repo = await createMonorepoObject();
|
|
17
|
+
repo._finalize();
|
|
18
|
+
|
|
19
|
+
const text = repo.dump({
|
|
20
|
+
depth: depth,
|
|
21
|
+
reverse: reverse,
|
|
22
|
+
short: false,
|
|
23
|
+
summary: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (noPager) {
|
|
27
|
+
console.log(text);
|
|
28
|
+
} else {
|
|
29
|
+
const less = spawnLess();
|
|
30
|
+
less.stdin.end(text);
|
|
31
|
+
await new Promise((resolve) => {
|
|
32
|
+
less.on('exit', resolve);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function spawnLess() {
|
|
38
|
+
return spawn('less', ['-r'], {
|
|
39
|
+
stdio: ['pipe', process.stdout, process.stderr],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -3,9 +3,9 @@ import { InterruptError, prettyPrintError, registerGlobalLifecycle, type IDispos
|
|
|
3
3
|
import { logger } from '@idlebox/logger';
|
|
4
4
|
import { registerNodejsGlobalTypedErrorHandlerWithInheritance, shutdown } from '@idlebox/node';
|
|
5
5
|
import { terminal, type ITitleControl } from '@idlebox/terminal-control';
|
|
6
|
-
import { debugMode } from '
|
|
7
|
-
import { startUi } from '
|
|
8
|
-
import { createMonorepoObject } from '
|
|
6
|
+
import { debugMode } from '../common/args.js';
|
|
7
|
+
import { startUi } from '../common/user-interactive.js';
|
|
8
|
+
import { createMonorepoObject } from '../common/workspace.js';
|
|
9
9
|
|
|
10
10
|
let titleControl: ITitleControl | undefined;
|
|
11
11
|
function setTitle(title: string) {
|
|
@@ -17,18 +17,20 @@ function setTitle(title: string) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export async function runWatch() {
|
|
20
|
+
const debugChild = argv.flag('--child-verbose') > 0;
|
|
21
|
+
|
|
20
22
|
if (argv.unused().length) {
|
|
21
23
|
logger.error`Unknown arguments: ${argv.unused().join(', ')}`;
|
|
22
24
|
return shutdown(1);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
let statePrinterDisposable: IDisposable | undefined;
|
|
26
|
-
const repo = await createMonorepoObject();
|
|
28
|
+
const repo = await createMonorepoObject({ debugChildren: debugChild });
|
|
27
29
|
const term = process.stderr.isTTY && !debugMode;
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
const userControl = startUi(repo);
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
registerGlobalLifecycle(repo);
|
|
32
34
|
|
|
33
35
|
registerNodejsGlobalTypedErrorHandlerWithInheritance(InterruptError, () => {
|
|
34
36
|
console.error(' -- Interrupted.');
|
|
@@ -46,6 +48,8 @@ export async function runWatch() {
|
|
|
46
48
|
|
|
47
49
|
if (!debugMode) {
|
|
48
50
|
statePrinterDisposable = repo.onStateChange(() => {
|
|
51
|
+
if (userControl.pause) return;
|
|
52
|
+
|
|
49
53
|
if (!repo.disposed && term) {
|
|
50
54
|
terminal.reset();
|
|
51
55
|
const p = repo.getProgress();
|
|
@@ -73,7 +77,7 @@ export async function runWatch() {
|
|
|
73
77
|
if (term) setTitle('启动');
|
|
74
78
|
await repo.startup();
|
|
75
79
|
} catch (e: any) {
|
|
76
|
-
if (debugMode) {
|
|
80
|
+
if (debugMode && !userControl.pause) {
|
|
77
81
|
if (!repo.disposed && term) {
|
|
78
82
|
terminal.reset();
|
|
79
83
|
}
|
|
@@ -83,9 +87,11 @@ export async function runWatch() {
|
|
|
83
87
|
shutdown(1);
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
if (!
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
if (!userControl.pause) {
|
|
91
|
+
if (!repo.disposed && term) {
|
|
92
|
+
setTitle('监视');
|
|
93
|
+
terminal.reset();
|
|
94
|
+
}
|
|
95
|
+
repo.printScreen();
|
|
89
96
|
}
|
|
90
|
-
repo.printScreen();
|
|
91
97
|
}
|
package/src/bin.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { argv } from '@idlebox/args/default';
|
|
1
2
|
import { humanDate, registerGlobalLifecycle, toDisposable } from '@idlebox/common';
|
|
2
3
|
import { createRootLogger, EnableLogLevel, logger } from '@idlebox/logger';
|
|
3
|
-
import { debuggerBreakUserEntrypoint, registerNodejsExitHandler, setExitCodeIfNot } from '@idlebox/node';
|
|
4
|
-
import {
|
|
4
|
+
import { debuggerBreakUserEntrypoint, registerNodejsExitHandler, setExitCodeIfNot, shutdown } from '@idlebox/node';
|
|
5
|
+
import { debugMode, helpMode, printUsage, verboseMode } from './common/args.js';
|
|
5
6
|
|
|
6
7
|
debuggerBreakUserEntrypoint();
|
|
7
8
|
registerNodejsExitHandler();
|
|
@@ -16,9 +17,17 @@ createRootLogger('', level);
|
|
|
16
17
|
|
|
17
18
|
if (helpMode) {
|
|
18
19
|
printUsage();
|
|
19
|
-
|
|
20
|
+
shutdown(0);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
const cmd = argv.command(['build', 'watch', 'clean', 'list', 'ls', 'dot', 'analyze']);
|
|
24
|
+
if (!cmd) {
|
|
25
|
+
printUsage();
|
|
26
|
+
logger.error`No command specified`;
|
|
27
|
+
shutdown(1);
|
|
28
|
+
}
|
|
29
|
+
export const currentCommand = cmd.value;
|
|
30
|
+
|
|
22
31
|
const start = Date.now();
|
|
23
32
|
registerGlobalLifecycle(
|
|
24
33
|
toDisposable(() => {
|
|
@@ -32,14 +41,14 @@ logger.log`working directory: ${process.cwd()}`;
|
|
|
32
41
|
switch (currentCommand) {
|
|
33
42
|
case 'build':
|
|
34
43
|
{
|
|
35
|
-
const { runBuild } = await import('./
|
|
44
|
+
const { runBuild } = await import('./actions/cmd.build.js');
|
|
36
45
|
await runBuild();
|
|
37
46
|
setExitCodeIfNot(0);
|
|
38
47
|
}
|
|
39
48
|
break;
|
|
40
49
|
case 'watch':
|
|
41
50
|
{
|
|
42
|
-
const { runWatch } = await import('./
|
|
51
|
+
const { runWatch } = await import('./actions/cmd.watch.js');
|
|
43
52
|
await runWatch();
|
|
44
53
|
setExitCodeIfNot(0);
|
|
45
54
|
}
|
|
@@ -47,13 +56,27 @@ switch (currentCommand) {
|
|
|
47
56
|
case 'list':
|
|
48
57
|
case 'ls':
|
|
49
58
|
{
|
|
50
|
-
const { runList } = await import('./
|
|
59
|
+
const { runList } = await import('./actions/cmd.list.js');
|
|
51
60
|
await runList();
|
|
52
61
|
setExitCodeIfNot(0);
|
|
53
62
|
}
|
|
54
63
|
break;
|
|
55
64
|
case 'clean':
|
|
56
65
|
throw new Error('The "clean" command is not implemented yet.');
|
|
66
|
+
case 'dot':
|
|
67
|
+
{
|
|
68
|
+
const { runDot } = await import('./actions/cmd.dot.js');
|
|
69
|
+
await runDot();
|
|
70
|
+
setExitCodeIfNot(0);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'analyze':
|
|
74
|
+
{
|
|
75
|
+
const { runAnalyze } = await import('./actions/cmd.analyze.js');
|
|
76
|
+
await runAnalyze(cmd);
|
|
77
|
+
setExitCodeIfNot(0);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
57
80
|
default:
|
|
58
81
|
throw new Error(`impossible: invalid command`);
|
|
59
82
|
}
|
package/src/common/args.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { argv } from '@idlebox/args/default';
|
|
2
|
-
import { logger } from '@idlebox/logger';
|
|
3
2
|
|
|
4
3
|
export function printUsage() {
|
|
5
4
|
console.log('Usage: build-manager <command>');
|
|
@@ -8,17 +7,11 @@ export function printUsage() {
|
|
|
8
7
|
console.log(' build run build');
|
|
9
8
|
console.log(' watch start watch mode');
|
|
10
9
|
console.log(' clean cleanup all projects');
|
|
11
|
-
console.log(' list/ls [--depth=0] list projects');
|
|
10
|
+
console.log(' list/ls [--depth=0] [--reverse] list projects');
|
|
11
|
+
console.log(' dot [--reverse] generate dot graph');
|
|
12
|
+
console.log(' analyze <package name> analyze package dependencies');
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const verboseMode = argv.flag(['-d', '--debug']) > 1;
|
|
15
16
|
export const debugMode = argv.flag(['-d', '--debug']) > 0;
|
|
16
17
|
export const helpMode = argv.flag(['-h', '--help']) > 0;
|
|
17
|
-
|
|
18
|
-
const cmd = argv.command(['build', 'watch', 'clean', 'list', 'ls']);
|
|
19
|
-
if (!cmd) {
|
|
20
|
-
printUsage();
|
|
21
|
-
logger.fatal`No command specified.`;
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
export const currentCommand = cmd.value;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class StringCollect<T> {
|
|
2
|
+
private readonly buffers = new Map<T, string[]>();
|
|
3
|
+
|
|
4
|
+
append(id: T, str: string) {
|
|
5
|
+
const list = this.buffers.get(id);
|
|
6
|
+
if (!list) {
|
|
7
|
+
this.buffers.set(id, [str]);
|
|
8
|
+
} else {
|
|
9
|
+
list.push(str);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get(id: T) {
|
|
14
|
+
return this.buffers.get(id)?.join('') ?? '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
clear(id: T) {
|
|
18
|
+
this.buffers.set(id, []);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,11 +1,167 @@
|
|
|
1
|
-
import { registerGlobalLifecycle, toDisposable } from '@idlebox/common';
|
|
2
|
-
import {
|
|
1
|
+
import { convertCaughtError, InterruptError, prettyPrintError, registerGlobalLifecycle, toDisposable } from '@idlebox/common';
|
|
2
|
+
import { createLogger, EnableLogLevel } from '@idlebox/logger';
|
|
3
|
+
import { createInterface, type Interface } from 'node:readline/promises';
|
|
4
|
+
import { inspect } from 'node:util';
|
|
3
5
|
import type { IPnpmMonoRepo } from './workspace.js';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const logger = createLogger('user', true);
|
|
8
|
+
logger.enable(EnableLogLevel.verbose);
|
|
9
|
+
|
|
10
|
+
class UserControl {
|
|
11
|
+
private _pause = false;
|
|
12
|
+
private _quit = false;
|
|
13
|
+
|
|
14
|
+
public setPause(pause: boolean) {
|
|
15
|
+
this._pause = pause;
|
|
16
|
+
}
|
|
17
|
+
public togglePause() {
|
|
18
|
+
this._pause = !this._pause;
|
|
19
|
+
logger.info`${this._pause ? '暂停' : '恢复'}状态输出`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get pause() {
|
|
23
|
+
return this._pause;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get quit() {
|
|
27
|
+
return this._quit;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public setQuit() {
|
|
31
|
+
this._quit = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function startUi(repo: IPnpmMonoRepo) {
|
|
36
|
+
const controller = new UserControl();
|
|
37
|
+
if (!process.stdin.isTTY) {
|
|
38
|
+
logger.info`非交互式环境,跳过用户界面`;
|
|
39
|
+
return controller;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rl = createInterface(process.stdin, process.stderr);
|
|
43
|
+
|
|
6
44
|
registerGlobalLifecycle(
|
|
7
45
|
toDisposable(async () => {
|
|
8
|
-
|
|
46
|
+
controller.setQuit();
|
|
47
|
+
rl.close();
|
|
9
48
|
}),
|
|
10
49
|
);
|
|
50
|
+
|
|
51
|
+
messageLoop(repo, controller, rl).catch((e) => {
|
|
52
|
+
if (controller.quit) return;
|
|
53
|
+
if (e.code === 'ABORT_ERR') {
|
|
54
|
+
throw new InterruptError('SIGINT');
|
|
55
|
+
}
|
|
56
|
+
prettyPrintError('命令接口错误', e);
|
|
57
|
+
});
|
|
58
|
+
return controller;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function messageLoop(repo: IPnpmMonoRepo, controller: UserControl, rl: Interface) {
|
|
62
|
+
rl.setPrompt('> ');
|
|
63
|
+
while (!controller.quit) {
|
|
64
|
+
const line = await rl.question(`${helpText()}\n> `);
|
|
65
|
+
|
|
66
|
+
const [cmd, ...args] = line.trim().split(/\s+/g);
|
|
67
|
+
|
|
68
|
+
let found = false;
|
|
69
|
+
for (const [name, fn] of Object.entries(commands)) {
|
|
70
|
+
if (name.startsWith(cmd)) {
|
|
71
|
+
try {
|
|
72
|
+
await fn(repo, controller, args);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
prettyPrintError(`命令执行错误: ${name}`, convertCaughtError(e));
|
|
75
|
+
}
|
|
76
|
+
found = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!found) {
|
|
81
|
+
console.error(`未知命令: ${cmd}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function helpText() {
|
|
87
|
+
return '*** 帮助信息';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type CommandFunction = (repo: IPnpmMonoRepo, controller: UserControl, args: string[]) => void;
|
|
91
|
+
|
|
92
|
+
const commands: Record<string, CommandFunction> = {
|
|
93
|
+
help() {
|
|
94
|
+
console.error(helpText());
|
|
95
|
+
},
|
|
96
|
+
status(repo) {
|
|
97
|
+
repo.printScreen();
|
|
98
|
+
},
|
|
99
|
+
pause(_repo, controller) {
|
|
100
|
+
controller.togglePause();
|
|
101
|
+
},
|
|
102
|
+
async dump(repo, controller, [what]: string[]) {
|
|
103
|
+
const pkg = await singleProject(repo, what);
|
|
104
|
+
if (!pkg) return;
|
|
105
|
+
|
|
106
|
+
const worker = repo._debugWorker(pkg);
|
|
107
|
+
Object.assign(globalThis, { dumpWorker: worker });
|
|
108
|
+
|
|
109
|
+
console.error(inspect(worker, { customInspect: false, colors: true }));
|
|
110
|
+
|
|
111
|
+
logger.info`已将项目 ${pkg.name} 的 worker 对象暴露为全局变量 dumpWorker`;
|
|
112
|
+
controller.setPause(true);
|
|
113
|
+
|
|
114
|
+
// biome-ignore lint/suspicious/noDebugger: it's for debugging
|
|
115
|
+
debugger;
|
|
116
|
+
},
|
|
117
|
+
async print(repo, _controller, [what]: string[]) {
|
|
118
|
+
const pkg = await singleProject(repo, what);
|
|
119
|
+
if (!pkg) return;
|
|
120
|
+
|
|
121
|
+
const output = repo._debugGetOutput(pkg);
|
|
122
|
+
if (output) {
|
|
123
|
+
console.error('\n\n');
|
|
124
|
+
console.error(output);
|
|
125
|
+
console.error('\n\n');
|
|
126
|
+
} else {
|
|
127
|
+
logger.error`项目 ${pkg.name} 没有输出`;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
async errors(repo, _controller, [what]: string[]) {
|
|
131
|
+
const errMap = repo.getErrors();
|
|
132
|
+
const pkg = await singleProject(repo, what);
|
|
133
|
+
if (!pkg) return;
|
|
134
|
+
|
|
135
|
+
const err = errMap.get(pkg);
|
|
136
|
+
if (err) {
|
|
137
|
+
console.error('\n\n');
|
|
138
|
+
console.error(err);
|
|
139
|
+
console.error('\n\n');
|
|
140
|
+
} else {
|
|
141
|
+
logger.success`项目 ${pkg.name} 没有错误`;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
async function singleProject(repo: IPnpmMonoRepo, name: string) {
|
|
147
|
+
if (!name) {
|
|
148
|
+
logger.error`需要指定项目`;
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const exactMatch = await repo.workspace.getPackage(name);
|
|
153
|
+
if (exactMatch) {
|
|
154
|
+
return exactMatch;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const packages = await repo.workspace.listPackages();
|
|
158
|
+
const pkgs = packages.filter((p) => p.name?.includes(name));
|
|
159
|
+
if (pkgs.length === 0) {
|
|
160
|
+
logger.error`没有找到匹配的项目: ${name}`;
|
|
161
|
+
return null;
|
|
162
|
+
} else if (pkgs.length > 1) {
|
|
163
|
+
logger.error`找到多个匹配的项目list<${pkgs.map((e) => e.name)}>`;
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return pkgs[0];
|
|
11
167
|
}
|
package/src/common/workspace.ts
CHANGED
|
@@ -6,13 +6,18 @@ import { CompileError, ModeKind, ProcessIPCClient, WorkerClientState, WorkersMan
|
|
|
6
6
|
import { RigConfig, type IRigConfig } from '@rushstack/rig-package';
|
|
7
7
|
import { dirname, resolve } from 'node:path';
|
|
8
8
|
import { split as splitCmd } from 'split-cmd';
|
|
9
|
-
import { currentCommand } from '
|
|
9
|
+
import { currentCommand } from '../bin.js';
|
|
10
|
+
import { StringCollect } from './string-collect.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
interface IOptions {
|
|
13
|
+
readonly debugChildren?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function createMonorepoObject(options?: IOptions) {
|
|
12
17
|
const workspace = await createWorkspace();
|
|
13
18
|
logger.debug`workspace: long<${workspace.root}>`;
|
|
14
19
|
const repo = new PnpmMonoRepo(logger, workspace);
|
|
15
|
-
await repo.initialize();
|
|
20
|
+
await repo.initialize(options);
|
|
16
21
|
return repo;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -20,11 +25,19 @@ export type IPnpmMonoRepo = PnpmMonoRepo;
|
|
|
20
25
|
const colorReg = /\x1B\[[0-9;]+?m/g;
|
|
21
26
|
const unclosedColorReg = /\x1B\[[^m]*$/g;
|
|
22
27
|
|
|
28
|
+
interface IDumpOptions {
|
|
29
|
+
readonly depth?: number;
|
|
30
|
+
readonly summary?: boolean;
|
|
31
|
+
readonly short?: boolean;
|
|
32
|
+
readonly reverse?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
const firstEmptyLine = /^\s*\n/;
|
|
24
36
|
class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
25
|
-
|
|
37
|
+
public readonly workersManager: WorkersManager;
|
|
26
38
|
private readonly pathvar: PathArray;
|
|
27
39
|
private readonly errorMessages = new Map<IPackageInfo, string>();
|
|
40
|
+
private readonly debugOutputs = new StringCollect<IPackageInfo>();
|
|
28
41
|
private readonly rigConfig = new Map<IPackageInfo, IRigConfig>(); // TODO: 太重了
|
|
29
42
|
|
|
30
43
|
private readonly _onStateChange = this._register(new Emitter<void>());
|
|
@@ -35,7 +48,7 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
35
48
|
|
|
36
49
|
constructor(
|
|
37
50
|
public readonly logger: IMyLogger,
|
|
38
|
-
|
|
51
|
+
public readonly workspace: MonorepoWorkspace,
|
|
39
52
|
) {
|
|
40
53
|
super('PnpmMonoRepo');
|
|
41
54
|
|
|
@@ -50,14 +63,20 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
50
63
|
this.workersManager = new WorkersManager(this.mode, logger);
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
async initialize() {
|
|
66
|
+
async initialize({ debugChildren = false }: IOptions = {}) {
|
|
54
67
|
await this.workspace.decoupleDependencies();
|
|
55
68
|
const projects = await this.workspace.listPackages();
|
|
56
69
|
logger.debug`workspace: ${projects.length} packages.`;
|
|
57
70
|
for (const project of projects) {
|
|
58
71
|
if (!project.packageJson.name) continue;
|
|
59
72
|
|
|
60
|
-
const
|
|
73
|
+
const addEnvs: Record<string, string> = {};
|
|
74
|
+
if (debugChildren) {
|
|
75
|
+
addEnvs['DEBUG_LEVEL'] = 'verbose';
|
|
76
|
+
addEnvs['DEBUG'] = '* -executer:* -dispose:*';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const exec = this.makeExecuter(this.mode === ModeKind.Watch, project, addEnvs);
|
|
61
80
|
if (exec) {
|
|
62
81
|
const all_deps = new Set<string>([...project.devDependencies, ...project.dependencies]);
|
|
63
82
|
this.workersManager.addWorker(exec, Array.from(all_deps));
|
|
@@ -78,7 +97,7 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
78
97
|
await graph.startup();
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
private makeExecuter(watchMode: boolean, project: IPackageInfo): undefined | ProcessIPCClient {
|
|
100
|
+
private makeExecuter(watchMode: boolean, project: IPackageInfo, addEnvs: Record<string, string> = {}): undefined | ProcessIPCClient {
|
|
82
101
|
if (project.packageJson.scripts?.watch === undefined) {
|
|
83
102
|
this.logger.fatal`project ${project.packageJson.name} does not have a "watch" script. If it doesn't need, specify a empty string.`;
|
|
84
103
|
}
|
|
@@ -98,7 +117,7 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
98
117
|
}
|
|
99
118
|
const logger = this.logger.extend(project.name);
|
|
100
119
|
|
|
101
|
-
const env: Record<string, string> = {};
|
|
120
|
+
const env: Record<string, string> = { ...addEnvs };
|
|
102
121
|
env[isWindows ? 'Path' : 'PATH'] = this.forkPath(project).toString();
|
|
103
122
|
|
|
104
123
|
const exec = new ProcessIPCClient(project.packageJson.name, cmds, project.absolute, env, logger); // TODO: env add Path
|
|
@@ -107,22 +126,29 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
exec.onFailure((e) => {
|
|
129
|
+
const output = (e as CompileError).output ?? '## onFailure: no output from process ##';
|
|
130
|
+
this.debugOutputs.append(project, output);
|
|
110
131
|
if (e instanceof CompileError) {
|
|
111
|
-
this.errorMessages.set(project, `${e.message}\n${
|
|
132
|
+
this.errorMessages.set(project, `${e.message}\n${output}`);
|
|
112
133
|
} else {
|
|
113
134
|
this.errorMessages.set(project, e.stack || e.message);
|
|
114
135
|
}
|
|
115
136
|
this._onStateChange.fireNoError();
|
|
116
137
|
});
|
|
117
|
-
exec.onSuccess(() => {
|
|
138
|
+
exec.onSuccess((e) => {
|
|
139
|
+
const output = e.output ?? '## onSuccess: no output from process ##';
|
|
140
|
+
this.debugOutputs.append(project, output);
|
|
141
|
+
|
|
118
142
|
this.errorMessages.delete(project);
|
|
119
143
|
this._onStateChange.fireNoError();
|
|
120
144
|
});
|
|
121
145
|
exec.onStart(() => {
|
|
146
|
+
this.debugOutputs.clear(project);
|
|
122
147
|
this.errorMessages.delete(project);
|
|
123
148
|
this._onStateChange.fireNoError();
|
|
124
149
|
});
|
|
125
150
|
exec.onTerminate(() => {
|
|
151
|
+
this.debugOutputs.append(project, '## onTerminate: process terminated ##');
|
|
126
152
|
try {
|
|
127
153
|
this._onStateChange.fireNoError();
|
|
128
154
|
} catch (e) {
|
|
@@ -150,6 +176,25 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
150
176
|
});
|
|
151
177
|
}
|
|
152
178
|
|
|
179
|
+
/**
|
|
180
|
+
* @internal
|
|
181
|
+
*/
|
|
182
|
+
_debugWorker(project: IPackageInfo) {
|
|
183
|
+
return this.packageToWorker.get(project);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
_debugGetOutput(project: IPackageInfo): string | undefined {
|
|
190
|
+
const worker = this.packageToWorker.get(project);
|
|
191
|
+
return worker?.outputStream.toString();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getErrors(): ReadonlyMap<IPackageInfo, string> {
|
|
195
|
+
return this.errorMessages;
|
|
196
|
+
}
|
|
197
|
+
|
|
153
198
|
formatErrors() {
|
|
154
199
|
if (this.errorMessages.size === 0) return '';
|
|
155
200
|
let output = '';
|
|
@@ -223,7 +268,7 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
223
268
|
return Math.floor((100 * completed) / count);
|
|
224
269
|
}
|
|
225
270
|
|
|
226
|
-
dump(depth
|
|
271
|
+
dump({ depth = 0, short = false, summary = true, reverse = false }: IDumpOptions = {}) {
|
|
227
272
|
const graph = this.workersManager.finalize();
|
|
228
273
|
let graphTxt: string;
|
|
229
274
|
if (depth <= 0) {
|
|
@@ -235,23 +280,25 @@ class PnpmMonoRepo extends EnhancedAsyncDisposable {
|
|
|
235
280
|
result.push(node.customInspect());
|
|
236
281
|
}
|
|
237
282
|
}
|
|
238
|
-
result.push(graph.debugFormatSummary());
|
|
283
|
+
if (summary) result.push(graph.debugFormatSummary());
|
|
239
284
|
return result.join('\n');
|
|
240
285
|
} else {
|
|
241
286
|
graphTxt = graph.debugFormatList();
|
|
242
287
|
}
|
|
243
288
|
} else {
|
|
244
|
-
graphTxt = graph.debugFormatGraph(depth);
|
|
289
|
+
graphTxt = graph.debugFormatGraph(depth, reverse);
|
|
245
290
|
}
|
|
291
|
+
if (!summary) return graphTxt;
|
|
292
|
+
|
|
246
293
|
return `${graphTxt}\n${graph.debugFormatSummary()}`;
|
|
247
294
|
}
|
|
248
295
|
|
|
249
296
|
printScreen(short = false, listAbove = false) {
|
|
250
297
|
let r = this.formatErrors();
|
|
251
298
|
if (listAbove) {
|
|
252
|
-
r = this.dump(
|
|
299
|
+
r = this.dump({ short }) + r;
|
|
253
300
|
} else {
|
|
254
|
-
r += this.dump(
|
|
301
|
+
r += this.dump({ short });
|
|
255
302
|
}
|
|
256
303
|
console.error(r);
|
|
257
304
|
}
|