@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.
Files changed (59) hide show
  1. package/lib/actions/cmd.analyze.d.ts +3 -0
  2. package/lib/actions/cmd.analyze.d.ts.map +1 -0
  3. package/lib/actions/cmd.analyze.js +125 -0
  4. package/lib/actions/cmd.analyze.js.map +1 -0
  5. package/lib/actions/cmd.build.d.ts.map +1 -0
  6. package/lib/{common → actions}/cmd.build.js +2 -2
  7. package/lib/actions/cmd.build.js.map +1 -0
  8. package/lib/actions/cmd.dot.d.ts +2 -0
  9. package/lib/actions/cmd.dot.d.ts.map +1 -0
  10. package/lib/actions/cmd.dot.js +101 -0
  11. package/lib/actions/cmd.dot.js.map +1 -0
  12. package/lib/actions/cmd.list.d.ts.map +1 -0
  13. package/lib/actions/cmd.list.js +38 -0
  14. package/lib/actions/cmd.list.js.map +1 -0
  15. package/lib/actions/cmd.watch.d.ts.map +1 -0
  16. package/lib/{common → actions}/cmd.watch.js +15 -10
  17. package/lib/actions/cmd.watch.js.map +1 -0
  18. package/lib/bin.d.ts +1 -1
  19. package/lib/bin.d.ts.map +1 -1
  20. package/lib/bin.js +28 -6
  21. package/lib/bin.js.map +1 -1
  22. package/lib/common/args.d.ts +0 -1
  23. package/lib/common/args.d.ts.map +1 -1
  24. package/lib/common/args.js +3 -9
  25. package/lib/common/args.js.map +1 -1
  26. package/lib/common/string-collect.d.ts +7 -0
  27. package/lib/common/string-collect.d.ts.map +1 -0
  28. package/lib/common/string-collect.js +19 -0
  29. package/lib/common/string-collect.js.map +1 -0
  30. package/lib/common/user-interactive.d.ts +11 -1
  31. package/lib/common/user-interactive.d.ts.map +1 -1
  32. package/lib/common/user-interactive.js +143 -4
  33. package/lib/common/user-interactive.js.map +1 -1
  34. package/lib/common/workspace.d.ts +26 -6
  35. package/lib/common/workspace.d.ts.map +1 -1
  36. package/lib/common/workspace.js +46 -14
  37. package/lib/common/workspace.js.map +1 -1
  38. package/package.json +13 -12
  39. package/src/actions/cmd.analyze.ts +151 -0
  40. package/src/{common → actions}/cmd.build.ts +2 -2
  41. package/src/actions/cmd.dot.ts +106 -0
  42. package/src/actions/cmd.list.ts +41 -0
  43. package/src/{common → actions}/cmd.watch.ts +17 -11
  44. package/src/bin.ts +29 -6
  45. package/src/common/args.ts +3 -10
  46. package/src/common/string-collect.ts +20 -0
  47. package/src/common/user-interactive.ts +160 -4
  48. package/src/common/workspace.ts +63 -16
  49. package/lib/common/cmd.build.d.ts.map +0 -1
  50. package/lib/common/cmd.build.js.map +0 -1
  51. package/lib/common/cmd.list.d.ts.map +0 -1
  52. package/lib/common/cmd.list.js +0 -16
  53. package/lib/common/cmd.list.js.map +0 -1
  54. package/lib/common/cmd.watch.d.ts.map +0 -1
  55. package/lib/common/cmd.watch.js.map +0 -1
  56. package/src/common/cmd.list.ts +0 -19
  57. /package/lib/{common → actions}/cmd.build.d.ts +0 -0
  58. /package/lib/{common → actions}/cmd.list.d.ts +0 -0
  59. /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 './args.js';
7
- import { startUi } from './user-interactive.js';
8
- import { createMonorepoObject } from './workspace.js';
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
- registerGlobalLifecycle(repo);
31
+ const userControl = startUi(repo);
30
32
 
31
- startUi(repo);
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 (!repo.disposed && term) {
87
- setTitle('监视');
88
- terminal.reset();
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 { currentCommand, debugMode, helpMode, printUsage, verboseMode } from './common/args.js';
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
- process.exit(0);
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('./common/cmd.build.js');
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('./common/cmd.watch.js');
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('./common/cmd.list.js');
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
  }
@@ -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 { logger } from '@idlebox/logger';
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
- export async function startUi(_repo: IPnpmMonoRepo) {
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
- logger.error`NotImpl: (this is not an error) Exiting...`;
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
  }
@@ -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 './args.js';
9
+ import { currentCommand } from '../bin.js';
10
+ import { StringCollect } from './string-collect.js';
10
11
 
11
- export async function createMonorepoObject() {
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
- private readonly workersManager: WorkersManager;
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
- protected readonly workspace: MonorepoWorkspace,
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 exec = this.makeExecuter(this.mode === ModeKind.Watch, project);
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${e.output ?? 'no output from process'}`);
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: number = 0, short = false) {
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(0, short) + r;
299
+ r = this.dump({ short }) + r;
253
300
  } else {
254
- r += this.dump(0, short);
301
+ r += this.dump({ short });
255
302
  }
256
303
  console.error(r);
257
304
  }