agl 1.0.17 → 21.0.2
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/.smartconfig.json +55 -0
- package/changelog.md +1026 -0
- package/cli.js +14 -0
- package/dist_serve/bundle.js +44778 -0
- package/dist_serve/index.html +13 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.authmodels.d.ts +126 -0
- package/dist_ts/classes.authmodels.js +1422 -0
- package/dist_ts/classes.authstore.d.ts +160 -0
- package/dist_ts/classes.authstore.js +2483 -0
- package/dist_ts/classes.browserresourcehost.d.ts +85 -0
- package/dist_ts/classes.browserresourcehost.js +912 -0
- package/dist_ts/classes.cli.d.ts +21 -0
- package/dist_ts/classes.cli.js +1082 -0
- package/dist_ts/classes.config.d.ts +23 -0
- package/dist_ts/classes.config.js +267 -0
- package/dist_ts/classes.controller.d.ts +523 -0
- package/dist_ts/classes.controller.js +10611 -0
- package/dist_ts/classes.controllermanagement.d.ts +32 -0
- package/dist_ts/classes.controllermanagement.js +222 -0
- package/dist_ts/classes.draftmanager.d.ts +30 -0
- package/dist_ts/classes.draftmanager.js +179 -0
- package/dist_ts/classes.embeddeddb.d.ts +30 -0
- package/dist_ts/classes.embeddeddb.js +159 -0
- package/dist_ts/classes.flexframedtransport.d.ts +56 -0
- package/dist_ts/classes.flexframedtransport.js +275 -0
- package/dist_ts/classes.flexmodels.private.d.ts +231 -0
- package/dist_ts/classes.flexmodels.private.js +985 -0
- package/dist_ts/classes.flexmodels.public.d.ts +207 -0
- package/dist_ts/classes.flexmodels.public.js +885 -0
- package/dist_ts/classes.flexprojectionreader.d.ts +89 -0
- package/dist_ts/classes.flexprojectionreader.js +580 -0
- package/dist_ts/classes.flexservice.d.ts +196 -0
- package/dist_ts/classes.flexservice.js +3704 -0
- package/dist_ts/classes.flexstore.d.ts +174 -0
- package/dist_ts/classes.flexstore.js +3400 -0
- package/dist_ts/classes.flexsupervisor.d.ts +105 -0
- package/dist_ts/classes.flexsupervisor.js +1191 -0
- package/dist_ts/classes.gitreversion.d.ts +431 -0
- package/dist_ts/classes.gitreversion.js +10592 -0
- package/dist_ts/classes.harnessoperationmanager.d.ts +113 -0
- package/dist_ts/classes.harnessoperationmanager.js +602 -0
- package/dist_ts/classes.opencodeclient.d.ts +146 -0
- package/dist_ts/classes.opencodeclient.js +3003 -0
- package/dist_ts/classes.opencodesupervisor.d.ts +60 -0
- package/dist_ts/classes.opencodesupervisor.js +1003 -0
- package/dist_ts/classes.passkeymanager.d.ts +34 -0
- package/dist_ts/classes.passkeymanager.js +456 -0
- package/dist_ts/classes.processinspection.d.ts +50 -0
- package/dist_ts/classes.processinspection.js +484 -0
- package/dist_ts/classes.resourcecoordinator.d.ts +64 -0
- package/dist_ts/classes.resourcecoordinator.js +294 -0
- package/dist_ts/classes.sessionintelligencereader.d.ts +13 -0
- package/dist_ts/classes.sessionintelligencereader.js +40 -0
- package/dist_ts/classes.systemmetricssampler.d.ts +53 -0
- package/dist_ts/classes.systemmetricssampler.js +168 -0
- package/dist_ts/classes.terminalmanager.d.ts +95 -0
- package/dist_ts/classes.terminalmanager.js +981 -0
- package/dist_ts/classes.terminalresourcehost.d.ts +20 -0
- package/dist_ts/classes.terminalresourcehost.js +84 -0
- package/dist_ts/classes.upgradecoordinator.d.ts +244 -0
- package/dist_ts/classes.upgradecoordinator.js +2155 -0
- package/dist_ts/classes.upgradetransaction.d.ts +92 -0
- package/dist_ts/classes.upgradetransaction.js +2125 -0
- package/dist_ts/classes.uploadmanager.d.ts +24 -0
- package/dist_ts/classes.uploadmanager.js +210 -0
- package/dist_ts/classes.validation.d.ts +91 -0
- package/dist_ts/classes.validation.js +1087 -0
- package/dist_ts/flexharness.child.d.ts +1 -0
- package/dist_ts/flexharness.child.js +28 -0
- package/dist_ts/functions.canonicaldirectory.d.ts +21 -0
- package/dist_ts/functions.canonicaldirectory.js +144 -0
- package/dist_ts/functions.controllerdataroot.d.ts +15 -0
- package/dist_ts/functions.controllerdataroot.js +76 -0
- package/dist_ts/functions.embeddeddb.d.ts +2 -0
- package/dist_ts/functions.embeddeddb.js +21 -0
- package/dist_ts/functions.flexcredentialstore.d.ts +16 -0
- package/dist_ts/functions.flexcredentialstore.js +93 -0
- package/dist_ts/functions.opencodeauth.d.ts +3 -0
- package/dist_ts/functions.opencodeauth.js +31 -0
- package/dist_ts/functions.runtimeenvironment.d.ts +2 -0
- package/dist_ts/functions.runtimeenvironment.js +79 -0
- package/dist_ts/functions.upgradefollower.d.ts +3 -0
- package/dist_ts/functions.upgradefollower.js +95 -0
- package/dist_ts/index.d.ts +24 -0
- package/dist_ts/index.js +25 -0
- package/dist_ts/interfaces.auth.d.ts +181 -0
- package/dist_ts/interfaces.auth.js +82 -0
- package/dist_ts/interfaces.config.d.ts +34 -0
- package/dist_ts/interfaces.config.js +2 -0
- package/dist_ts/interfaces.flexipc.d.ts +939 -0
- package/dist_ts/interfaces.flexipc.js +1492 -0
- package/dist_ts/interfaces.gitreversion.d.ts +246 -0
- package/dist_ts/interfaces.gitreversion.js +2 -0
- package/dist_ts/interfaces.lifecycle.d.ts +37 -0
- package/dist_ts/interfaces.lifecycle.js +2 -0
- package/dist_ts/interfaces.opencode.d.ts +101 -0
- package/dist_ts/interfaces.opencode.js +3 -0
- package/dist_ts/interfaces.projects.d.ts +156 -0
- package/dist_ts/interfaces.projects.js +5 -0
- package/dist_ts/plugins.d.ts +36 -0
- package/dist_ts/plugins.js +40 -0
- package/dist_ts_interfaces/index.d.ts +2 -0
- package/dist_ts_interfaces/index.js +3 -0
- package/dist_ts_interfaces/interfaces.d.ts +747 -0
- package/dist_ts_interfaces/interfaces.js +47 -0
- package/dist_ts_interfaces/plugins.d.ts +4 -0
- package/dist_ts_interfaces/plugins.js +3 -0
- package/dist_ts_interfaces/requests.d.ts +806 -0
- package/dist_ts_interfaces/requests.js +2 -0
- package/dist_ts_migration/classes.documentmigrationrunner.d.ts +8 -0
- package/dist_ts_migration/classes.documentmigrationrunner.js +24 -0
- package/dist_ts_migration/classes.flexmigrationrunner.d.ts +11 -0
- package/dist_ts_migration/classes.flexmigrationrunner.js +25 -0
- package/dist_ts_migration/classes.flexpostharnessmigrationrunner.d.ts +10 -0
- package/dist_ts_migration/classes.flexpostharnessmigrationrunner.js +15 -0
- package/dist_ts_migration/classes.migrationrunner.d.ts +8 -0
- package/dist_ts_migration/classes.migrationrunner.js +34 -0
- package/dist_ts_migration/index.d.ts +12 -0
- package/dist_ts_migration/index.js +13 -0
- package/dist_ts_migration/v10_flexharnessv3migration.d.ts +11 -0
- package/dist_ts_migration/v10_flexharnessv3migration.js +23 -0
- package/dist_ts_migration/v13_flexprojectionorder.d.ts +11 -0
- package/dist_ts_migration/v13_flexprojectionorder.js +23 -0
- package/dist_ts_migration/v16_flexprovidercredentials.d.ts +22 -0
- package/dist_ts_migration/v16_flexprovidercredentials.js +149 -0
- package/dist_ts_migration/v18_resourcelayout.d.ts +22 -0
- package/dist_ts_migration/v18_resourcelayout.js +23 -0
- package/dist_ts_migration/v18_resourcelayoutmigration.d.ts +6 -0
- package/dist_ts_migration/v18_resourcelayoutmigration.js +61 -0
- package/dist_ts_migration/v19_flexprojectmanagement.d.ts +11 -0
- package/dist_ts_migration/v19_flexprojectmanagement.js +21 -0
- package/dist_ts_migration/v1_embeddeddatabaselocation.d.ts +28 -0
- package/dist_ts_migration/v1_embeddeddatabaselocation.js +415 -0
- package/dist_ts_migration/v22_projectfilesystemidentitymigration.d.ts +7 -0
- package/dist_ts_migration/v22_projectfilesystemidentitymigration.js +105 -0
- package/dist_ts_migration/v2_controllerdataroot.d.ts +130 -0
- package/dist_ts_migration/v2_controllerdataroot.js +1887 -0
- package/dist_ts_migration/v6_protocoldocumentmigration.d.ts +7 -0
- package/dist_ts_migration/v6_protocoldocumentmigration.js +100 -0
- package/dist_ts_migration/v6_protocoldocuments.d.ts +7 -0
- package/dist_ts_migration/v6_protocoldocuments.js +138 -0
- package/html/index.html +13 -0
- package/{LICENSE → license.md} +2 -2
- package/package.json +86 -48
- package/readme.md +480 -0
- package/readme.plan.md +188 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.authmodels.ts +1245 -0
- package/ts/classes.authstore.ts +3274 -0
- package/ts/classes.browserresourcehost.ts +1205 -0
- package/ts/classes.cli.ts +1254 -0
- package/ts/classes.config.ts +351 -0
- package/ts/classes.controller.ts +14426 -0
- package/ts/classes.controllermanagement.ts +330 -0
- package/ts/classes.draftmanager.ts +232 -0
- package/ts/classes.embeddeddb.ts +166 -0
- package/ts/classes.flexframedtransport.ts +283 -0
- package/ts/classes.flexmodels.private.ts +823 -0
- package/ts/classes.flexmodels.public.ts +732 -0
- package/ts/classes.flexprojectionreader.ts +816 -0
- package/ts/classes.flexservice.ts +4302 -0
- package/ts/classes.flexstore.ts +4565 -0
- package/ts/classes.flexsupervisor.ts +1453 -0
- package/ts/classes.gitreversion.ts +13353 -0
- package/ts/classes.harnessoperationmanager.ts +870 -0
- package/ts/classes.opencodeclient.ts +4110 -0
- package/ts/classes.opencodesupervisor.ts +1280 -0
- package/ts/classes.passkeymanager.ts +570 -0
- package/ts/classes.processinspection.ts +623 -0
- package/ts/classes.resourcecoordinator.ts +436 -0
- package/ts/classes.sessionintelligencereader.ts +52 -0
- package/ts/classes.systemmetricssampler.ts +215 -0
- package/ts/classes.terminalmanager.ts +1207 -0
- package/ts/classes.terminalresourcehost.ts +122 -0
- package/ts/classes.upgradecoordinator.ts +2675 -0
- package/ts/classes.upgradetransaction.ts +2900 -0
- package/ts/classes.uploadmanager.ts +240 -0
- package/ts/classes.validation.ts +1409 -0
- package/ts/flexharness.child.ts +35 -0
- package/ts/functions.canonicaldirectory.ts +213 -0
- package/ts/functions.controllerdataroot.ts +107 -0
- package/ts/functions.embeddeddb.ts +22 -0
- package/ts/functions.flexcredentialstore.ts +128 -0
- package/ts/functions.opencodeauth.ts +34 -0
- package/ts/functions.runtimeenvironment.ts +90 -0
- package/ts/functions.upgradefollower.ts +115 -0
- package/ts/index.ts +36 -6
- package/ts/interfaces.auth.ts +316 -0
- package/ts/interfaces.config.ts +37 -0
- package/ts/interfaces.flexipc.ts +2586 -0
- package/ts/interfaces.gitreversion.ts +336 -0
- package/ts/interfaces.lifecycle.ts +46 -0
- package/ts/interfaces.opencode.ts +153 -0
- package/ts/interfaces.projects.ts +182 -0
- package/ts/plugins.ts +64 -0
- package/ts_interfaces/index.ts +2 -0
- package/ts_interfaces/interfaces.ts +827 -0
- package/ts_interfaces/plugins.ts +15 -0
- package/ts_interfaces/requests.ts +1186 -0
- package/ts_migration/classes.documentmigrationrunner.ts +32 -0
- package/ts_migration/classes.flexmigrationrunner.ts +33 -0
- package/ts_migration/classes.flexpostharnessmigrationrunner.ts +22 -0
- package/ts_migration/classes.migrationrunner.ts +40 -0
- package/ts_migration/index.ts +12 -0
- package/ts_migration/v10_flexharnessv3migration.ts +35 -0
- package/ts_migration/v13_flexprojectionorder.ts +33 -0
- package/ts_migration/v16_flexprovidercredentials.ts +196 -0
- package/ts_migration/v18_resourcelayout.ts +55 -0
- package/ts_migration/v18_resourcelayoutmigration.ts +68 -0
- package/ts_migration/v19_flexprojectmanagement.ts +28 -0
- package/ts_migration/v1_embeddeddatabaselocation.ts +560 -0
- package/ts_migration/v22_projectfilesystemidentitymigration.ts +121 -0
- package/ts_migration/v2_controllerdataroot.ts +2349 -0
- package/ts_migration/v6_protocoldocumentmigration.ts +144 -0
- package/ts_migration/v6_protocoldocuments.ts +198 -0
- package/ts_web/00_commitinfo_data.ts +8 -0
- package/ts_web/classes.controllersocketclient.ts +517 -0
- package/ts_web/classes.sessiondraftsync.ts +390 -0
- package/ts_web/elements.harnesscontrollerapp.ts +11494 -0
- package/ts_web/index.ts +2 -0
- package/ts_web/plugins.ts +31 -0
- package/.gitlab-ci.yml +0 -71
- package/.npmignore +0 -5
- package/README.md +0 -40
- package/dist/cli.js +0 -2
- package/dist/index.d.ts +0 -0
- package/dist/index.js +0 -9
- package/dist/mod_bundle/index.d.ts +0 -1
- package/dist/mod_bundle/index.js +0 -18
- package/dist/mod_bundle/mod.plugins.d.ts +0 -4
- package/dist/mod_bundle/mod.plugins.js +0 -11
- package/dist/mod_install/index.d.ts +0 -4
- package/dist/mod_install/index.js +0 -21
- package/dist/mod_install/mod.plugins.d.ts +0 -1
- package/dist/mod_install/mod.plugins.js +0 -7
- package/dist/mod_standard/index.d.ts +0 -4
- package/dist/mod_standard/index.js +0 -21
- package/dist/mod_standard/mod.browser.d.ts +0 -6
- package/dist/mod_standard/mod.browser.js +0 -14
- package/dist/mod_standard/mod.clean.d.ts +0 -4
- package/dist/mod_standard/mod.clean.js +0 -12
- package/dist/mod_standard/mod.compile.d.ts +0 -4
- package/dist/mod_standard/mod.compile.js +0 -21
- package/dist/mod_standard/mod.plugins.d.ts +0 -8
- package/dist/mod_standard/mod.plugins.js +0 -19
- package/dist/mod_standard/mod.test.d.ts +0 -4
- package/dist/mod_standard/mod.test.js +0 -12
- package/dist/plr.cli.d.ts +0 -3
- package/dist/plr.cli.js +0 -57
- package/dist/plr.config.d.ts +0 -11
- package/dist/plr.config.js +0 -26
- package/dist/plr.mods.d.ts +0 -17
- package/dist/plr.mods.js +0 -17
- package/dist/plr.paths.d.ts +0 -19
- package/dist/plr.paths.js +0 -26
- package/dist/plr.plugins.d.ts +0 -8
- package/dist/plr.plugins.js +0 -16
- package/npmextra.json +0 -10
- package/ts/mod_bundle/index.ts +0 -20
- package/ts/mod_bundle/mod.plugins.ts +0 -9
- package/ts/mod_install/index.ts +0 -20
- package/ts/mod_install/mod.plugins.ts +0 -1
- package/ts/mod_standard/index.ts +0 -23
- package/ts/mod_standard/mod.browser.ts +0 -17
- package/ts/mod_standard/mod.clean.ts +0 -13
- package/ts/mod_standard/mod.compile.ts +0 -23
- package/ts/mod_standard/mod.plugins.ts +0 -17
- package/ts/mod_standard/mod.test.ts +0 -13
- package/ts/plr.cli.ts +0 -60
- package/ts/plr.config.ts +0 -36
- package/ts/plr.mods.ts +0 -25
- package/ts/plr.paths.ts +0 -29
- package/ts/plr.plugins.ts +0 -17
- package/tslint.json +0 -3
- package/yarn.lock +0 -3464
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { controllerPackageName } from '../ts_interfaces/index.js';
|
|
3
|
+
import { defaultControllerPort } from './classes.config.js';
|
|
4
|
+
|
|
5
|
+
export interface IControllerProcessIdentity {
|
|
6
|
+
pid: number;
|
|
7
|
+
processGroupId: number;
|
|
8
|
+
processGroupLeader: boolean;
|
|
9
|
+
arguments: string[];
|
|
10
|
+
argumentsTruncated?: boolean;
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IControllerProcessCandidate {
|
|
15
|
+
identity: IControllerProcessIdentity;
|
|
16
|
+
command: '__serve' | 'foreground';
|
|
17
|
+
port: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IControllerDataWriterProcessCandidate {
|
|
21
|
+
kind: 'controller' | 'temp-password' | 'flex-child';
|
|
22
|
+
identity: IControllerProcessIdentity;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const maximumProcessIdentities = 16_384;
|
|
26
|
+
const processIdentityBatchSize = 128;
|
|
27
|
+
const maximumProcessArguments = 512;
|
|
28
|
+
const maximumProcessArgumentBytes = 256 * 1024;
|
|
29
|
+
const maximumProcessCommandBytes = maximumProcessArgumentBytes + maximumProcessArguments;
|
|
30
|
+
const maximumReconstructedPathArguments = 32;
|
|
31
|
+
const maximumDataWriterCliPaths = 8;
|
|
32
|
+
const maximumDataWriterCandidates = 1_024;
|
|
33
|
+
const currentRuntimeName = String(controllerPackageName) === 'agl' ? 'AGL' : 'hcon';
|
|
34
|
+
const installedControllerPackagePaths = [
|
|
35
|
+
plugins.path.join('node_modules', '@modelprofile.com', 'harness-controller'),
|
|
36
|
+
plugins.path.join('node_modules', 'agl'),
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
const boundedProcessArgumentsPrefix = (argumentsArg: readonly string[]): string[] => {
|
|
40
|
+
const bounded: string[] = [];
|
|
41
|
+
let totalBytes = 0;
|
|
42
|
+
for (const argument of argumentsArg) {
|
|
43
|
+
const argumentBytes = Buffer.byteLength(argument, 'utf8');
|
|
44
|
+
if (
|
|
45
|
+
bounded.length >= maximumProcessArguments
|
|
46
|
+
|| totalBytes + argumentBytes > maximumProcessArgumentBytes
|
|
47
|
+
) break;
|
|
48
|
+
bounded.push(argument);
|
|
49
|
+
totalBytes += argumentBytes;
|
|
50
|
+
}
|
|
51
|
+
return bounded;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const assertPid = (pidArg: number): number => {
|
|
55
|
+
if (!Number.isSafeInteger(pidArg) || pidArg < 2) {
|
|
56
|
+
throw new Error('Controller PID is invalid.');
|
|
57
|
+
}
|
|
58
|
+
return pidArg;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const execFile = async (
|
|
62
|
+
fileArg: string,
|
|
63
|
+
argsArg: string[],
|
|
64
|
+
optionsArg: { detached?: boolean; timeoutMs?: number; maxBufferBytes?: number } = {},
|
|
65
|
+
): Promise<string> => {
|
|
66
|
+
return await new Promise<string>((resolve, reject) => {
|
|
67
|
+
plugins.childProcess.execFile(
|
|
68
|
+
fileArg,
|
|
69
|
+
argsArg,
|
|
70
|
+
{
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
maxBuffer: optionsArg.maxBufferBytes ?? 128 * 1024,
|
|
73
|
+
timeout: optionsArg.timeoutMs ?? 2_000,
|
|
74
|
+
killSignal: 'SIGKILL',
|
|
75
|
+
...(optionsArg.detached === undefined ? {} : { detached: optionsArg.detached }),
|
|
76
|
+
},
|
|
77
|
+
(errorArg, stdoutArg) => {
|
|
78
|
+
if (errorArg) {
|
|
79
|
+
reject(errorArg);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resolve(stdoutArg);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const readBoundedLinuxCommandArguments = async (
|
|
89
|
+
pidArg: number,
|
|
90
|
+
): Promise<{ arguments: string[]; truncated: boolean }> => {
|
|
91
|
+
// Read only the accepted argv budget plus one byte so oversized processes
|
|
92
|
+
// cannot make a system-wide inventory retain unbounded command data.
|
|
93
|
+
const handle = await plugins.fs.promises.open(
|
|
94
|
+
`/proc/${pidArg}/cmdline`,
|
|
95
|
+
plugins.fs.constants.O_RDONLY,
|
|
96
|
+
);
|
|
97
|
+
try {
|
|
98
|
+
const buffer = Buffer.allocUnsafe(maximumProcessCommandBytes + 1);
|
|
99
|
+
let bytesRead = 0;
|
|
100
|
+
while (bytesRead < buffer.length) {
|
|
101
|
+
const read = await handle.read(buffer, bytesRead, buffer.length - bytesRead, null);
|
|
102
|
+
if (read.bytesRead === 0) break;
|
|
103
|
+
bytesRead += read.bytesRead;
|
|
104
|
+
}
|
|
105
|
+
const commandLength = Math.min(bytesRead, maximumProcessCommandBytes);
|
|
106
|
+
const argumentsResult: string[] = [];
|
|
107
|
+
let argumentStart = 0;
|
|
108
|
+
let argumentCountExceeded = false;
|
|
109
|
+
for (let index = 0; index < commandLength; index++) {
|
|
110
|
+
if (buffer[index] !== 0) continue;
|
|
111
|
+
if (argumentsResult.length >= maximumProcessArguments) {
|
|
112
|
+
argumentCountExceeded = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (index > argumentStart) {
|
|
116
|
+
argumentsResult.push(buffer.subarray(argumentStart, index).toString('utf8'));
|
|
117
|
+
}
|
|
118
|
+
argumentStart = index + 1;
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
bytesRead <= maximumProcessCommandBytes
|
|
122
|
+
&& !argumentCountExceeded
|
|
123
|
+
&& argumentStart < commandLength
|
|
124
|
+
&& argumentsResult.length < maximumProcessArguments
|
|
125
|
+
) {
|
|
126
|
+
argumentsResult.push(buffer.subarray(argumentStart, commandLength).toString('utf8'));
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
arguments: argumentsResult,
|
|
130
|
+
truncated: bytesRead > maximumProcessCommandBytes || argumentCountExceeded,
|
|
131
|
+
};
|
|
132
|
+
} finally {
|
|
133
|
+
await handle.close();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const readLinuxIdentity = async (pidArg: number): Promise<IControllerProcessIdentity> => {
|
|
138
|
+
const [statBuffer, command] = await Promise.all([
|
|
139
|
+
plugins.fs.promises.readFile(`/proc/${pidArg}/stat`),
|
|
140
|
+
readBoundedLinuxCommandArguments(pidArg),
|
|
141
|
+
]);
|
|
142
|
+
const stat = statBuffer.toString('utf8');
|
|
143
|
+
const statSuffixIndex = stat.lastIndexOf(') ');
|
|
144
|
+
if (statSuffixIndex < 0) throw new Error('Unable to parse controller process metadata.');
|
|
145
|
+
const statFields = stat.slice(statSuffixIndex + 2).trim().split(/\s+/);
|
|
146
|
+
const processGroupId = Number(statFields[2]);
|
|
147
|
+
const startTime = statFields[19];
|
|
148
|
+
if (!Number.isSafeInteger(processGroupId) || !startTime) {
|
|
149
|
+
throw new Error('Unable to parse controller process identity.');
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
pid: pidArg,
|
|
153
|
+
processGroupId,
|
|
154
|
+
processGroupLeader: processGroupId === pidArg,
|
|
155
|
+
arguments: command.arguments,
|
|
156
|
+
...(command.truncated ? { argumentsTruncated: true } : {}),
|
|
157
|
+
fingerprint: `linux:${pidArg}:${startTime}`,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const readLinuxProcessGroupId = async (pidArg: number): Promise<number | undefined> => {
|
|
162
|
+
try {
|
|
163
|
+
const stat = await plugins.fs.promises.readFile(`/proc/${pidArg}/stat`, 'utf8');
|
|
164
|
+
const statSuffixIndex = stat.lastIndexOf(') ');
|
|
165
|
+
if (statSuffixIndex < 0) return undefined;
|
|
166
|
+
const statFields = stat.slice(statSuffixIndex + 2).trim().split(/\s+/);
|
|
167
|
+
const processGroupId = Number(statFields[2]);
|
|
168
|
+
return Number.isSafeInteger(processGroupId) ? processGroupId : undefined;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
171
|
+
if (code === 'ENOENT' || code === 'ESRCH' || code === 'EACCES') return undefined;
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const readPosixIdentity = async (pidArg: number): Promise<IControllerProcessIdentity> => {
|
|
177
|
+
const output = await execFile('ps', [
|
|
178
|
+
'-ww',
|
|
179
|
+
'-p',
|
|
180
|
+
String(pidArg),
|
|
181
|
+
'-o',
|
|
182
|
+
'pgid=',
|
|
183
|
+
'-o',
|
|
184
|
+
'lstart=',
|
|
185
|
+
'-o',
|
|
186
|
+
'command=',
|
|
187
|
+
], { maxBufferBytes: 8 * 1024 * 1024 });
|
|
188
|
+
const line = output.trim();
|
|
189
|
+
const match = /^\s*(\d+)\s+(.{24})\s+(.+)$/.exec(line);
|
|
190
|
+
if (!match) throw new Error('Unable to parse controller process metadata.');
|
|
191
|
+
const processGroupId = Number(match[1]);
|
|
192
|
+
return {
|
|
193
|
+
pid: pidArg,
|
|
194
|
+
processGroupId,
|
|
195
|
+
processGroupLeader: processGroupId === pidArg,
|
|
196
|
+
arguments: parsePosixCommandArguments(match[3]),
|
|
197
|
+
fingerprint: `posix:${pidArg}:${match[2].trim()}`,
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const parsePosixCommandArguments = (commandArg: string): string[] => {
|
|
202
|
+
return (commandArg.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map((argumentArg) => {
|
|
203
|
+
const quoted = (
|
|
204
|
+
(argumentArg.startsWith('"') && argumentArg.endsWith('"'))
|
|
205
|
+
|| (argumentArg.startsWith("'") && argumentArg.endsWith("'"))
|
|
206
|
+
);
|
|
207
|
+
return quoted ? argumentArg.slice(1, -1) : argumentArg;
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const readControllerProcessIdentity = async (
|
|
212
|
+
pidArg: number,
|
|
213
|
+
): Promise<IControllerProcessIdentity | null> => {
|
|
214
|
+
const pid = assertPid(pidArg);
|
|
215
|
+
try {
|
|
216
|
+
if (process.platform === 'linux') return await readLinuxIdentity(pid);
|
|
217
|
+
if (process.platform !== 'win32') return await readPosixIdentity(pid);
|
|
218
|
+
return null;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
221
|
+
if (code === 'ENOENT' || code === 'ESRCH') return null;
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const readProcessGroupMemberPids = async (
|
|
227
|
+
processGroupIdArg: number,
|
|
228
|
+
): Promise<number[]> => {
|
|
229
|
+
const processGroupId = assertPid(processGroupIdArg);
|
|
230
|
+
if (process.platform === 'linux') {
|
|
231
|
+
const entries = await plugins.fs.promises.readdir('/proc');
|
|
232
|
+
const processIds = entries
|
|
233
|
+
.filter((entryArg) => /^[1-9][0-9]*$/.test(entryArg))
|
|
234
|
+
.map((entryArg) => Number(entryArg))
|
|
235
|
+
.filter((processIdArg) => processIdArg >= 2);
|
|
236
|
+
const groupIds = await Promise.all(
|
|
237
|
+
processIds.map(async (processIdArg) => ({
|
|
238
|
+
processId: processIdArg,
|
|
239
|
+
processGroupId: await readLinuxProcessGroupId(processIdArg),
|
|
240
|
+
})),
|
|
241
|
+
);
|
|
242
|
+
return groupIds
|
|
243
|
+
.filter((entryArg) => entryArg.processGroupId === processGroupId)
|
|
244
|
+
.map((entryArg) => entryArg.processId)
|
|
245
|
+
.sort((leftArg, rightArg) => leftArg - rightArg);
|
|
246
|
+
}
|
|
247
|
+
if (process.platform !== 'win32') {
|
|
248
|
+
// A detached helper gets its own process group, so macOS `ps -ax` cannot
|
|
249
|
+
// report the probe itself as a member of the controller group being read.
|
|
250
|
+
const output = await execFile(
|
|
251
|
+
'ps',
|
|
252
|
+
['-ax', '-o', 'pid=', '-o', 'pgid='],
|
|
253
|
+
{ detached: true },
|
|
254
|
+
);
|
|
255
|
+
return output
|
|
256
|
+
.split(/\r?\n/)
|
|
257
|
+
.map((lineArg) => /^\s*(\d+)\s+(\d+)\s*$/.exec(lineArg))
|
|
258
|
+
.filter((matchArg): matchArg is RegExpExecArray => matchArg !== null)
|
|
259
|
+
.filter((matchArg) => Number(matchArg[2]) === processGroupId)
|
|
260
|
+
.map((matchArg) => Number(matchArg[1]))
|
|
261
|
+
.sort((leftArg, rightArg) => leftArg - rightArg);
|
|
262
|
+
}
|
|
263
|
+
return [];
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const findCliArgumentIndex = async (
|
|
267
|
+
pidArg: number,
|
|
268
|
+
argumentsArg: string[],
|
|
269
|
+
cliPathArg: string,
|
|
270
|
+
allowMissingAbsolutePathArg = false,
|
|
271
|
+
): Promise<number> => {
|
|
272
|
+
const boundedArguments = boundedProcessArgumentsPrefix(argumentsArg);
|
|
273
|
+
const directIndex = boundedArguments.indexOf(cliPathArg);
|
|
274
|
+
if (directIndex >= 0) return directIndex;
|
|
275
|
+
// A controller launched with a relative path (`node cli.js foreground`) is
|
|
276
|
+
// still the same file; resolve candidate tokens against the process's own
|
|
277
|
+
// working directory before declaring the identity unverifiable.
|
|
278
|
+
let processCwd: string | undefined;
|
|
279
|
+
if (process.platform === 'linux') {
|
|
280
|
+
try {
|
|
281
|
+
processCwd = await plugins.fs.promises.readlink(`/proc/${pidArg}/cwd`);
|
|
282
|
+
} catch {
|
|
283
|
+
processCwd = undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const cliBasename = plugins.path.basename(cliPathArg);
|
|
287
|
+
const normalizedCliPath = plugins.path.normalize(cliPathArg);
|
|
288
|
+
for (let endIndex = 1; endIndex <= boundedArguments.length; endIndex++) {
|
|
289
|
+
const minimumStartIndex = Math.max(0, endIndex - maximumReconstructedPathArguments);
|
|
290
|
+
for (let startIndex = endIndex - 1; startIndex >= minimumStartIndex; startIndex--) {
|
|
291
|
+
const candidate = boundedArguments.slice(startIndex, endIndex).join(' ');
|
|
292
|
+
if (plugins.path.basename(candidate) !== cliBasename) continue;
|
|
293
|
+
try {
|
|
294
|
+
const resolved = await plugins.fs.promises.realpath(
|
|
295
|
+
plugins.path.isAbsolute(candidate)
|
|
296
|
+
? candidate
|
|
297
|
+
: plugins.path.resolve(processCwd ?? '', candidate),
|
|
298
|
+
);
|
|
299
|
+
if (resolved === cliPathArg) return endIndex - 1;
|
|
300
|
+
} catch {
|
|
301
|
+
// pnpm may remove A's package while its worker still owns the upgrade
|
|
302
|
+
// lock. Only that trusted caller may use an exact normalized old path.
|
|
303
|
+
if (
|
|
304
|
+
allowMissingAbsolutePathArg
|
|
305
|
+
&& plugins.path.isAbsolute(candidate)
|
|
306
|
+
&& plugins.path.normalize(candidate) === normalizedCliPath
|
|
307
|
+
) return endIndex - 1;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return -1;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export const processIdentityHasFileArgument = async (
|
|
316
|
+
identityArg: IControllerProcessIdentity,
|
|
317
|
+
filePathArg: string,
|
|
318
|
+
optionsArg: { allowMissingAbsolutePath?: boolean } = {},
|
|
319
|
+
): Promise<boolean> => await findCliArgumentIndex(
|
|
320
|
+
identityArg.pid,
|
|
321
|
+
identityArg.arguments,
|
|
322
|
+
filePathArg,
|
|
323
|
+
optionsArg.allowMissingAbsolutePath === true,
|
|
324
|
+
) >= 0;
|
|
325
|
+
|
|
326
|
+
const findInstalledControllerPackageFileArgumentIndex = (
|
|
327
|
+
argumentsArg: readonly string[],
|
|
328
|
+
packageRelativePathArg: string,
|
|
329
|
+
): number => {
|
|
330
|
+
const boundedArguments = boundedProcessArgumentsPrefix(argumentsArg);
|
|
331
|
+
const expectedSuffixes = installedControllerPackagePaths.map((packagePathArg) => (
|
|
332
|
+
`${plugins.path.sep}${plugins.path.join(packagePathArg, packageRelativePathArg)}`
|
|
333
|
+
));
|
|
334
|
+
const expectedBasename = plugins.path.basename(packageRelativePathArg);
|
|
335
|
+
for (let endIndex = 1; endIndex <= boundedArguments.length; endIndex++) {
|
|
336
|
+
const minimumStartIndex = Math.max(0, endIndex - maximumReconstructedPathArguments);
|
|
337
|
+
for (let startIndex = endIndex - 1; startIndex >= minimumStartIndex; startIndex--) {
|
|
338
|
+
const candidate = boundedArguments.slice(startIndex, endIndex).join(' ');
|
|
339
|
+
if (
|
|
340
|
+
plugins.path.basename(candidate) === expectedBasename
|
|
341
|
+
&& plugins.path.isAbsolute(candidate)
|
|
342
|
+
&& expectedSuffixes.some((suffixArg) => (
|
|
343
|
+
plugins.path.normalize(candidate).endsWith(suffixArg)
|
|
344
|
+
))
|
|
345
|
+
) return endIndex - 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return -1;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const processIdentityHasInstalledControllerCommand = (
|
|
352
|
+
identityArg: IControllerProcessIdentity,
|
|
353
|
+
commandArg: '__serve' | 'foreground' | 'temp-password',
|
|
354
|
+
): boolean => {
|
|
355
|
+
const cliIndex = findInstalledControllerPackageFileArgumentIndex(
|
|
356
|
+
identityArg.arguments,
|
|
357
|
+
'cli.js',
|
|
358
|
+
);
|
|
359
|
+
return cliIndex >= 0 && identityArg.arguments[cliIndex + 1] === commandArg;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const readEffectiveArgvPort = (argumentsArg: string[]): number => {
|
|
363
|
+
let effectivePort = defaultControllerPort;
|
|
364
|
+
const boundedArguments = boundedProcessArgumentsPrefix(argumentsArg);
|
|
365
|
+
for (let index = 0; index < boundedArguments.length; index++) {
|
|
366
|
+
if (boundedArguments[index] === '--port') {
|
|
367
|
+
effectivePort = Number(boundedArguments[index + 1]);
|
|
368
|
+
} else if (boundedArguments[index].startsWith('--port=')) {
|
|
369
|
+
effectivePort = Number(boundedArguments[index].slice('--port='.length));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return effectivePort;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
export const processIdentityHasCliCommand = async (
|
|
376
|
+
identityArg: IControllerProcessIdentity,
|
|
377
|
+
cliPathArg: string,
|
|
378
|
+
commandArg: '__serve' | '__upgrade-worker' | 'upgrade' | 'start' | 'foreground' | 'temp-password',
|
|
379
|
+
optionsArg: { allowMissingAbsolutePath?: boolean } = {},
|
|
380
|
+
): Promise<boolean> => {
|
|
381
|
+
const cliIndex = await findCliArgumentIndex(
|
|
382
|
+
identityArg.pid,
|
|
383
|
+
identityArg.arguments,
|
|
384
|
+
cliPathArg,
|
|
385
|
+
optionsArg.allowMissingAbsolutePath === true,
|
|
386
|
+
);
|
|
387
|
+
return cliIndex >= 0 && identityArg.arguments[cliIndex + 1] === commandArg;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const hasExpectedArguments = async (
|
|
391
|
+
identityArg: IControllerProcessIdentity,
|
|
392
|
+
cliPathArg: string,
|
|
393
|
+
portArg: number,
|
|
394
|
+
commandArg: '__serve' | 'foreground',
|
|
395
|
+
): Promise<boolean> => {
|
|
396
|
+
return await processIdentityHasCliCommand(identityArg, cliPathArg, commandArg)
|
|
397
|
+
&& readEffectiveArgvPort(identityArg.arguments) === portArg;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const visitProcessIdentities = async (
|
|
401
|
+
visitorArg: (identityArg: IControllerProcessIdentity) => Promise<void>,
|
|
402
|
+
): Promise<void> => {
|
|
403
|
+
if (process.platform === 'linux') {
|
|
404
|
+
const entries = await plugins.fs.promises.readdir('/proc');
|
|
405
|
+
const processIds = entries
|
|
406
|
+
.filter((entryArg) => /^[1-9][0-9]*$/.test(entryArg))
|
|
407
|
+
.map((entryArg) => Number(entryArg))
|
|
408
|
+
.filter((processIdArg) => processIdArg >= 2)
|
|
409
|
+
.sort((leftArg, rightArg) => leftArg - rightArg);
|
|
410
|
+
if (processIds.length > maximumProcessIdentities) {
|
|
411
|
+
throw new Error('Controller data-writer process inventory exceeds its scan bound.');
|
|
412
|
+
}
|
|
413
|
+
for (let index = 0; index < processIds.length; index += processIdentityBatchSize) {
|
|
414
|
+
const batch = await Promise.all(
|
|
415
|
+
processIds.slice(index, index + processIdentityBatchSize).map(async (processIdArg) => {
|
|
416
|
+
try {
|
|
417
|
+
return await readControllerProcessIdentity(processIdArg);
|
|
418
|
+
} catch (errorArg) {
|
|
419
|
+
const code = (errorArg as NodeJS.ErrnoException).code;
|
|
420
|
+
if (code === 'EACCES' || code === 'EPERM') return null;
|
|
421
|
+
throw errorArg;
|
|
422
|
+
}
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
425
|
+
for (const identity of batch) {
|
|
426
|
+
if (identity) await visitorArg(identity);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (process.platform === 'win32') return;
|
|
432
|
+
const output = await execFile('ps', [
|
|
433
|
+
'-ax',
|
|
434
|
+
'-ww',
|
|
435
|
+
'-o',
|
|
436
|
+
'pid=',
|
|
437
|
+
'-o',
|
|
438
|
+
'pgid=',
|
|
439
|
+
'-o',
|
|
440
|
+
'lstart=',
|
|
441
|
+
'-o',
|
|
442
|
+
'command=',
|
|
443
|
+
], { maxBufferBytes: 8 * 1024 * 1024 });
|
|
444
|
+
let identityCount = 0;
|
|
445
|
+
for (const lineArg of output.split(/\r?\n/)) {
|
|
446
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.{24})\s+(.+)$/.exec(lineArg);
|
|
447
|
+
if (!match) continue;
|
|
448
|
+
const pid = Number(match[1]);
|
|
449
|
+
const processGroupId = Number(match[2]);
|
|
450
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(processGroupId)) continue;
|
|
451
|
+
identityCount += 1;
|
|
452
|
+
if (identityCount > maximumProcessIdentities) {
|
|
453
|
+
throw new Error('Controller data-writer process inventory exceeds its scan bound.');
|
|
454
|
+
}
|
|
455
|
+
await visitorArg({
|
|
456
|
+
pid,
|
|
457
|
+
processGroupId,
|
|
458
|
+
processGroupLeader: processGroupId === pid,
|
|
459
|
+
arguments: parsePosixCommandArguments(match[4]),
|
|
460
|
+
fingerprint: `posix:${pid}:${match[3].trim()}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const compactProcessIdentity = (
|
|
466
|
+
identityArg: IControllerProcessIdentity,
|
|
467
|
+
): IControllerProcessIdentity => ({
|
|
468
|
+
...identityArg,
|
|
469
|
+
arguments: [],
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
export const listControllerProcessesForCli = async (
|
|
473
|
+
cliPathArg: string,
|
|
474
|
+
): Promise<IControllerProcessCandidate[]> => {
|
|
475
|
+
const candidates: IControllerProcessCandidate[] = [];
|
|
476
|
+
await visitProcessIdentities(async (identity) => {
|
|
477
|
+
for (const command of ['__serve', 'foreground'] as const) {
|
|
478
|
+
if (!await processIdentityHasCliCommand(identity, cliPathArg, command)) continue;
|
|
479
|
+
const port = readEffectiveArgvPort(identity.arguments);
|
|
480
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
481
|
+
throw new Error(`An ${currentRuntimeName} process has an invalid port argument (PID ${identity.pid}).`);
|
|
482
|
+
}
|
|
483
|
+
candidates.push({ identity: compactProcessIdentity(identity), command, port });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
return candidates.sort((leftArg, rightArg) => leftArg.identity.pid - rightArg.identity.pid);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export const listControllerDataWriterProcessesForCli = async (
|
|
490
|
+
cliPathArg: string,
|
|
491
|
+
optionsArg: { includeInstalledPackagePaths?: boolean } = {},
|
|
492
|
+
): Promise<IControllerDataWriterProcessCandidate[]> =>
|
|
493
|
+
await listControllerDataWriterProcessesForCliPaths([cliPathArg], optionsArg);
|
|
494
|
+
|
|
495
|
+
export const listControllerDataWriterProcessesForCliPaths = async (
|
|
496
|
+
cliPathsArg: readonly string[],
|
|
497
|
+
optionsArg: { includeInstalledPackagePaths?: boolean } = {},
|
|
498
|
+
): Promise<IControllerDataWriterProcessCandidate[]> => {
|
|
499
|
+
const cliPaths = [...new Set(cliPathsArg)];
|
|
500
|
+
if (
|
|
501
|
+
cliPaths.length === 0
|
|
502
|
+
|| cliPaths.length > maximumDataWriterCliPaths
|
|
503
|
+
|| cliPaths.some((cliPathArg) => !plugins.path.isAbsolute(cliPathArg))
|
|
504
|
+
) throw new Error('Controller data-writer CLI path inventory is invalid.');
|
|
505
|
+
const flexChildPaths = cliPaths.map((cliPathArg) => plugins.path.join(
|
|
506
|
+
plugins.path.dirname(cliPathArg),
|
|
507
|
+
'dist_ts',
|
|
508
|
+
'flexharness.child.js',
|
|
509
|
+
));
|
|
510
|
+
const candidates: IControllerDataWriterProcessCandidate[] = [];
|
|
511
|
+
await visitProcessIdentities(async (identity) => {
|
|
512
|
+
let matched = false;
|
|
513
|
+
for (const cliPath of cliPaths) {
|
|
514
|
+
if (
|
|
515
|
+
await processIdentityHasCliCommand(identity, cliPath, '__serve')
|
|
516
|
+
|| await processIdentityHasCliCommand(identity, cliPath, 'foreground')
|
|
517
|
+
) {
|
|
518
|
+
matched = true;
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (
|
|
523
|
+
!matched
|
|
524
|
+
&& optionsArg.includeInstalledPackagePaths === true
|
|
525
|
+
&& (processIdentityHasInstalledControllerCommand(identity, '__serve')
|
|
526
|
+
|| processIdentityHasInstalledControllerCommand(identity, 'foreground'))
|
|
527
|
+
) matched = true;
|
|
528
|
+
if (matched) {
|
|
529
|
+
if (candidates.length >= maximumDataWriterCandidates) {
|
|
530
|
+
throw new Error('Controller data-writer candidate inventory exceeds its scan bound.');
|
|
531
|
+
}
|
|
532
|
+
candidates.push({ kind: 'controller', identity: compactProcessIdentity(identity) });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const cliPath of cliPaths) {
|
|
536
|
+
if (await processIdentityHasCliCommand(identity, cliPath, 'temp-password')) {
|
|
537
|
+
matched = true;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (
|
|
542
|
+
!matched
|
|
543
|
+
&& optionsArg.includeInstalledPackagePaths === true
|
|
544
|
+
&& processIdentityHasInstalledControllerCommand(identity, 'temp-password')
|
|
545
|
+
) matched = true;
|
|
546
|
+
if (matched) {
|
|
547
|
+
if (candidates.length >= maximumDataWriterCandidates) {
|
|
548
|
+
throw new Error('Controller data-writer candidate inventory exceeds its scan bound.');
|
|
549
|
+
}
|
|
550
|
+
candidates.push({ kind: 'temp-password', identity: compactProcessIdentity(identity) });
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
for (const flexChildPath of flexChildPaths) {
|
|
554
|
+
if (await processIdentityHasFileArgument(identity, flexChildPath)) {
|
|
555
|
+
matched = true;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (
|
|
560
|
+
!matched
|
|
561
|
+
&& optionsArg.includeInstalledPackagePaths === true
|
|
562
|
+
&& findInstalledControllerPackageFileArgumentIndex(
|
|
563
|
+
identity.arguments,
|
|
564
|
+
plugins.path.join('dist_ts', 'flexharness.child.js'),
|
|
565
|
+
) >= 0
|
|
566
|
+
) matched = true;
|
|
567
|
+
if (matched) {
|
|
568
|
+
if (candidates.length >= maximumDataWriterCandidates) {
|
|
569
|
+
throw new Error('Controller data-writer candidate inventory exceeds its scan bound.');
|
|
570
|
+
}
|
|
571
|
+
candidates.push({ kind: 'flex-child', identity: compactProcessIdentity(identity) });
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
return candidates.sort((leftArg, rightArg) => leftArg.identity.pid - rightArg.identity.pid);
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
export const inspectControllerProcess = async (optionsArg: {
|
|
578
|
+
pid: number;
|
|
579
|
+
cliPath: string;
|
|
580
|
+
port: number;
|
|
581
|
+
expectedProcessGroupId?: number;
|
|
582
|
+
expectedFingerprint?: string;
|
|
583
|
+
command: '__serve' | 'foreground';
|
|
584
|
+
}): Promise<IControllerProcessIdentity | null> => {
|
|
585
|
+
const identity = await readControllerProcessIdentity(optionsArg.pid);
|
|
586
|
+
if (!identity) return null;
|
|
587
|
+
if (!await hasExpectedArguments(identity, optionsArg.cliPath, optionsArg.port, optionsArg.command)) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
if (
|
|
591
|
+
optionsArg.expectedProcessGroupId !== undefined
|
|
592
|
+
&& identity.processGroupId !== optionsArg.expectedProcessGroupId
|
|
593
|
+
) return null;
|
|
594
|
+
if (
|
|
595
|
+
optionsArg.expectedFingerprint !== undefined
|
|
596
|
+
&& identity.fingerprint !== optionsArg.expectedFingerprint
|
|
597
|
+
) return null;
|
|
598
|
+
return identity;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
export const signalVerifiedControllerProcess = async (optionsArg: {
|
|
602
|
+
pid: number;
|
|
603
|
+
cliPath: string;
|
|
604
|
+
port: number;
|
|
605
|
+
expectedProcessGroupId: number;
|
|
606
|
+
expectedFingerprint: string;
|
|
607
|
+
signal: NodeJS.Signals;
|
|
608
|
+
signalProcessGroup: boolean;
|
|
609
|
+
command: '__serve' | 'foreground';
|
|
610
|
+
}): Promise<void> => {
|
|
611
|
+
const identity = await inspectControllerProcess(optionsArg);
|
|
612
|
+
if (!identity) {
|
|
613
|
+
throw new Error('Refusing to signal a process whose controller identity cannot be verified.');
|
|
614
|
+
}
|
|
615
|
+
if (optionsArg.signalProcessGroup) {
|
|
616
|
+
if (!identity.processGroupLeader) {
|
|
617
|
+
throw new Error('Refusing to signal a process group whose controller is not its leader.');
|
|
618
|
+
}
|
|
619
|
+
process.kill(-identity.processGroupId, optionsArg.signal);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
process.kill(identity.pid, optionsArg.signal);
|
|
623
|
+
};
|