@guanjinke/artifact-client 1.3.0 → 1.4.0

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 (69) hide show
  1. package/README.md +135 -1
  2. package/dist/cli/config-manager.d.ts +15 -0
  3. package/dist/cli/config-manager.d.ts.map +1 -1
  4. package/dist/cli/config-manager.js +31 -0
  5. package/dist/cli/config-manager.js.map +1 -1
  6. package/dist/cli/environment.d.ts +6 -0
  7. package/dist/cli/environment.d.ts.map +1 -0
  8. package/dist/cli/environment.js +134 -0
  9. package/dist/cli/environment.js.map +1 -0
  10. package/dist/cli/setup.d.ts.map +1 -1
  11. package/dist/cli/setup.js +160 -2
  12. package/dist/cli/setup.js.map +1 -1
  13. package/dist/cli.js +4 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/environment/archive.d.ts +7 -0
  16. package/dist/environment/archive.d.ts.map +1 -0
  17. package/dist/environment/archive.js +132 -0
  18. package/dist/environment/archive.js.map +1 -0
  19. package/dist/environment/builder.d.ts +6 -0
  20. package/dist/environment/builder.d.ts.map +1 -0
  21. package/dist/environment/builder.js +323 -0
  22. package/dist/environment/builder.js.map +1 -0
  23. package/dist/environment/catalog.d.ts +9 -0
  24. package/dist/environment/catalog.d.ts.map +1 -0
  25. package/dist/environment/catalog.js +35 -0
  26. package/dist/environment/catalog.js.map +1 -0
  27. package/dist/environment/config.d.ts +8 -0
  28. package/dist/environment/config.d.ts.map +1 -0
  29. package/dist/environment/config.js +60 -0
  30. package/dist/environment/config.js.map +1 -0
  31. package/dist/environment/manager.d.ts +43 -0
  32. package/dist/environment/manager.d.ts.map +1 -0
  33. package/dist/environment/manager.js +383 -0
  34. package/dist/environment/manager.js.map +1 -0
  35. package/dist/environment/plan.d.ts +10 -0
  36. package/dist/environment/plan.d.ts.map +1 -0
  37. package/dist/environment/plan.js +131 -0
  38. package/dist/environment/plan.js.map +1 -0
  39. package/dist/environment/process.d.ts +94 -0
  40. package/dist/environment/process.d.ts.map +1 -0
  41. package/dist/environment/process.js +381 -0
  42. package/dist/environment/process.js.map +1 -0
  43. package/dist/environment/registry.d.ts +45 -0
  44. package/dist/environment/registry.d.ts.map +1 -0
  45. package/dist/environment/registry.js +157 -0
  46. package/dist/environment/registry.js.map +1 -0
  47. package/dist/environment/root.d.ts +33 -0
  48. package/dist/environment/root.d.ts.map +1 -0
  49. package/dist/environment/root.js +119 -0
  50. package/dist/environment/root.js.map +1 -0
  51. package/dist/environment/types.d.ts +66 -0
  52. package/dist/environment/types.d.ts.map +1 -0
  53. package/dist/environment/types.js +3 -0
  54. package/dist/environment/types.js.map +1 -0
  55. package/dist/environment/update.d.ts +85 -0
  56. package/dist/environment/update.d.ts.map +1 -0
  57. package/dist/environment/update.js +644 -0
  58. package/dist/environment/update.js.map +1 -0
  59. package/dist/environment/validate.d.ts +36 -0
  60. package/dist/environment/validate.d.ts.map +1 -0
  61. package/dist/environment/validate.js +147 -0
  62. package/dist/environment/validate.js.map +1 -0
  63. package/dist/index.d.ts +13 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +29 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/types.d.ts +6 -0
  68. package/dist/types.d.ts.map +1 -1
  69. package/package.json +9 -3
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.validateTarget = validateTarget;
37
+ exports.validateEnvironmentOptions = validateEnvironmentOptions;
38
+ exports.buildEnvironmentPlan = buildEnvironmentPlan;
39
+ const fs = __importStar(require("fs/promises"));
40
+ const path = __importStar(require("path"));
41
+ const catalog_1 = require("./catalog");
42
+ const archive_1 = require("./archive");
43
+ async function validateTarget(root) {
44
+ const absolute = path.resolve(root);
45
+ const relative = absolute.slice(path.parse(absolute).root.length).split(path.sep).join('/');
46
+ if (relative)
47
+ (0, archive_1.validateArchivePath)(relative);
48
+ const protectedRoots = [process.env.ProgramFiles, process.env['ProgramFiles(x86)'], process.env.SystemRoot, process.env.WINDIR, process.env.USERPROFILE, process.env.HOME].filter((p) => !!p).map(p => path.resolve(p).toLowerCase());
49
+ const normalized = absolute.toLowerCase();
50
+ if (absolute === path.parse(absolute).root || protectedRoots.some(p => normalized === p || ((p.includes('program files') || p.includes('windows')) && normalized.startsWith(p + path.sep)))) {
51
+ throw new Error('Environment root is a protected directory.');
52
+ }
53
+ // Reject links in all existing ancestors, not only the final directory.
54
+ for (let current = absolute;; current = path.dirname(current)) {
55
+ try {
56
+ const stat = await fs.lstat(current);
57
+ if (stat.isSymbolicLink() || !stat.isDirectory())
58
+ throw new Error('Environment root or ancestor is not a regular directory.');
59
+ if (current === absolute && (await fs.readdir(current)).length)
60
+ throw new Error('Environment root must be absent or empty.');
61
+ }
62
+ catch (error) {
63
+ if (error.code !== 'ENOENT')
64
+ throw error;
65
+ }
66
+ if (current === path.dirname(current))
67
+ break;
68
+ }
69
+ }
70
+ async function validateEnvironmentOptions(options) {
71
+ if (!options.root || !options.major || !/^(9\.1(?:-[A-Za-z0-9][A-Za-z0-9._-]*)?|dev)$/.test(options.major))
72
+ throw new Error('Supported product majors: 9.1, 9.1-* and dev (artifact-derived V91/V100).');
73
+ const edition = options.edition ?? 'cn';
74
+ const profile = options.profile ?? 'embedded';
75
+ const port = options.port ?? 51980;
76
+ if (!['cn', 'en'].includes(edition))
77
+ throw new Error('Edition must be cn or en.');
78
+ if (profile !== 'embedded')
79
+ throw new Error('Only the embedded profile is supported.');
80
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
81
+ throw new Error('Port must be an integer between 1 and 65535.');
82
+ const requestedModules = [...new Set((options.modules ?? []).flatMap(m => m.split(',')).map(m => m.trim()).filter(Boolean))];
83
+ const modules = requestedModules.filter(m => !['server', 'account', 'dataset', 'analysis', 'cot-worker', 'common', 'monitoring'].includes(m)).sort();
84
+ if (modules.some(m => !['dashboard', 'reporting', 'sheets', 'documents'].includes(m)))
85
+ throw new Error('Select optional modules: dashboard, reporting, sheets, documents. Wyn core server, account, dataset, analysis model and cot worker are always included.');
86
+ if (edition !== 'en' && modules.some(m => ['sheets', 'documents'].includes(m)))
87
+ throw new Error('Sheets and documents require EN edition.');
88
+ if (modules.includes('documents'))
89
+ throw new Error('Documents embedded execution is not supported: CollaborationService dependency is unverified.');
90
+ await validateTarget(options.root);
91
+ return { ...options, root: path.resolve(options.root), edition, profile, port, modules: modules };
92
+ }
93
+ async function buildEnvironmentPlan(options, client, onProgress) {
94
+ onProgress?.('Validating environment options and target directory');
95
+ const normalized = await validateEnvironmentOptions(options);
96
+ const autoAddedModules = ['server', 'monitoring', 'common', 'analysis'];
97
+ const artifacts = [];
98
+ const modules = [...autoAddedModules, ...normalized.modules];
99
+ for (const [index, module] of modules.entries()) {
100
+ const entry = catalog_1.catalog[module];
101
+ onProgress?.(`[${index + 1}/${modules.length}] Resolving ${entry.plugin}/${normalized.major}`);
102
+ let version;
103
+ let files;
104
+ try {
105
+ version = (await client.getLatestVersion(entry.plugin, normalized.major)).version;
106
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version))
107
+ throw new Error('Invalid resolved version');
108
+ files = await client.listArtifacts(entry.plugin, version);
109
+ }
110
+ catch {
111
+ throw new Error(`Unable to resolve artifacts for ${entry.plugin}/${normalized.major}.`);
112
+ }
113
+ const name = entry.filename(version, normalized.edition);
114
+ const candidates = files.filter(f => f.name === name);
115
+ if (candidates.length !== 1)
116
+ throw new Error(`${entry.plugin}/${version}: expected exactly one ${name}, found ${candidates.length}.`);
117
+ // Remote paths must remain credential-free relative object names.
118
+ const remotePath = candidates[0].path;
119
+ if (!remotePath || /[?:\\\x00-\x1f]/.test(remotePath) || remotePath.startsWith('/') || remotePath.split('/').includes('..'))
120
+ throw new Error(`${entry.plugin}/${version}: invalid artifact path.`);
121
+ onProgress?.(`[${index + 1}/${modules.length}] Selected ${entry.plugin}/${version}/${name}`);
122
+ artifacts.push({ module, plugin: entry.plugin, version, name, path: remotePath, mappings: entry.mappings.map(m => ({ ...m, assemblies: [...m.assemblies] })) });
123
+ }
124
+ return {
125
+ root: normalized.root, major: normalized.major, schema: 'pending', edition: normalized.edition, profile: normalized.profile, port: normalized.port,
126
+ selectedModules: normalized.modules, autoAddedModules, artifacts,
127
+ warnings: ['No services are registered or started.', 'Wyn core server, account, dataset, analysis model and cot worker are always included.', 'License, sample data, bootstrap data and optional language/styles-cache archives are not provisioned.'],
128
+ requirements: ['Windows for execution.', '.NET runtime requirements are discovered from downloaded runtimeconfig files.', 'A valid license and database initialization may be required before startup.'],
129
+ };
130
+ }
131
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/environment/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { ProcessIdentity, RunMode } from './registry';
2
+ export declare class PendingProcessError extends Error {
3
+ readonly identity: ProcessIdentity;
4
+ constructor(message: string, identity: ProcessIdentity);
5
+ }
6
+ export type Inspection = {
7
+ status: 'live';
8
+ identity: ProcessIdentity;
9
+ } | {
10
+ status: 'absent';
11
+ } | {
12
+ status: 'unknown';
13
+ reason?: 'query' | 'ambiguous';
14
+ };
15
+ export interface StartedProcess {
16
+ identity: ProcessIdentity;
17
+ /**
18
+ * Resolves when the direct child spawned by this backend exits. Absent for an
19
+ * independent background launch, which no longer has a parent process handle.
20
+ */
21
+ exited?: Promise<void>;
22
+ exitCode?: () => number | null;
23
+ unref(): void;
24
+ }
25
+ export interface ProcessBackend {
26
+ inspect(pid: number): Promise<Inspection>;
27
+ find(command: string): Promise<Inspection[]>;
28
+ start(dll: string, mode: RunMode, stdout: string, stderr: string): Promise<StartedProcess>;
29
+ stop(identity: ProcessIdentity): Promise<void>;
30
+ }
31
+ export declare function sameProcess(a: ProcessIdentity, b: ProcessIdentity): boolean;
32
+ export declare const monitorCommand: (dll: string) => string;
33
+ /** Split a raw Windows command line into tokens, tolerating extra/trailing spaces. */
34
+ export declare function commandTokens(command: string): string[] | undefined;
35
+ /**
36
+ * Best-effort list of running processes whose command line references a directory.
37
+ * Used to explain why a directory could not be removed (for example an editor or
38
+ * terminal that has it open). Never throws; returns an empty list when unknown.
39
+ */
40
+ export declare function describePathHolders(root: string): Promise<string[]>;
41
+ export declare function isMonitor(identity: ProcessIdentity, dll: string): boolean;
42
+ /**
43
+ * True when the identity is a `dotnet <assembly> --RunAsMonitor` invocation.
44
+ * Token based so the extra spaces cmd.exe introduces around arguments do not matter.
45
+ */
46
+ export declare function isMonitorProcess(identity: ProcessIdentity): boolean;
47
+ export declare function checkPort(port: number): Promise<void>;
48
+ export declare class WindowsProcessBackend implements ProcessBackend {
49
+ private readonly identityTimeoutMs;
50
+ /**
51
+ * @param identityTimeoutMs how long to wait for the OS to expose the started
52
+ * process before giving up. Injectable so tests do not wait for the default.
53
+ */
54
+ constructor(identityTimeoutMs?: number);
55
+ private query;
56
+ inspect(pid: number): Promise<Inspection>;
57
+ find(command: string): Promise<Inspection[]>;
58
+ start(dll: string, mode: RunMode, stdout: string, stderr: string): Promise<StartedProcess>;
59
+ /**
60
+ * Foreground: a direct child with inherited stdio, so the terminal console and
61
+ * Ctrl+C reach the monitor and its output stays visible.
62
+ */
63
+ private startForeground;
64
+ /**
65
+ * Background: start the monitor in its own hidden console.
66
+ *
67
+ * Two Windows constraints force this: Node's `detached: true` (DETACHED_PROCESS)
68
+ * leaves the child without any console, and Wyn.Server assigns `Console.Title` at
69
+ * the top of Main, which throws IOException without one; while a non-detached child
70
+ * does not survive the process that spawned it. `Start-Process -WindowStyle Hidden`
71
+ * gives `cmd.exe` a console that has no visible window, and cmd appends the monitor's
72
+ * output to the log files and stays alive with it, so the monitor is independent of
73
+ * this CLI. Its identity is then confirmed by exact command-line discovery.
74
+ */
75
+ private startBackground;
76
+ /**
77
+ * Start `dotnet <dll> --RunAsMonitor` in a hidden console through cmd.exe and
78
+ * return the PID of that console host. Redirection stays with cmd because
79
+ * Windows PowerShell 5.1 blocks in `Start-Process` when it redirects output.
80
+ */
81
+ private launchHiddenConsole;
82
+ /** Terminate exact-identity leftovers of this environment and report how many remain. */
83
+ private cleanupCandidates;
84
+ /** Explain a failed launch with the monitor's own log output. */
85
+ private describeLaunchFailure;
86
+ /** Bounded tail of a log file, used to explain why a monitor exited immediately. */
87
+ private lastOutput;
88
+ /** Wait for a promise without leaving a pending timer behind. */
89
+ private settle;
90
+ private descendants;
91
+ private stopFresh;
92
+ stop(identity: ProcessIdentity): Promise<void>;
93
+ }
94
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/environment/process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,381 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WindowsProcessBackend = exports.monitorCommand = exports.PendingProcessError = void 0;
37
+ exports.sameProcess = sameProcess;
38
+ exports.commandTokens = commandTokens;
39
+ exports.describePathHolders = describePathHolders;
40
+ exports.isMonitor = isMonitor;
41
+ exports.isMonitorProcess = isMonitorProcess;
42
+ exports.checkPort = checkPort;
43
+ const child_process_1 = require("child_process");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const net = __importStar(require("net"));
47
+ const os_1 = require("os");
48
+ const util_1 = require("util");
49
+ const exec = (0, util_1.promisify)(child_process_1.execFile);
50
+ class PendingProcessError extends Error {
51
+ identity;
52
+ constructor(message, identity) {
53
+ super(message);
54
+ this.identity = identity;
55
+ }
56
+ }
57
+ exports.PendingProcessError = PendingProcessError;
58
+ function sameProcess(a, b) { return a.pid === b.pid && a.createdAt === b.createdAt && a.command === b.command && a.executable.toLowerCase() === b.executable.toLowerCase(); }
59
+ const monitorCommand = (dll) => `dotnet "${dll}" --RunAsMonitor`;
60
+ exports.monitorCommand = monitorCommand;
61
+ /** Split a raw Windows command line into tokens, tolerating extra/trailing spaces. */
62
+ function commandTokens(command) {
63
+ return command.match(/(?:[^\s"]+|"[^"]*")+/g)?.map(token => token.replace(/^"|"$/g, ''));
64
+ }
65
+ /**
66
+ * Best-effort list of running processes whose command line references a directory.
67
+ * Used to explain why a directory could not be removed (for example an editor or
68
+ * terminal that has it open). Never throws; returns an empty list when unknown.
69
+ */
70
+ async function describePathHolders(root) {
71
+ if (process.platform !== 'win32')
72
+ return [];
73
+ // The needle travels through the environment so this helper's own PowerShell
74
+ // command line never matches itself.
75
+ const script = `$ErrorActionPreference='Stop'; $needle=$env:AFM_PATH_HOLDERS_NEEDLE; ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.ProcessId -ne ${process.pid} -and $_.CommandLine.ToLower().Contains($needle) } | ForEach-Object { @{pid=[int]$_.ProcessId; name=$_.Name} })`;
76
+ try {
77
+ const { stdout } = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 5000, maxBuffer: 1024 * 1024, env: { ...process.env, AFM_PATH_HOLDERS_NEEDLE: root.toLowerCase() } });
78
+ const parsed = JSON.parse(stdout);
79
+ const values = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
80
+ return values.filter(value => value && Number.isInteger(value.pid)).map(value => `${value.name} (pid ${value.pid})`);
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ }
86
+ function isMonitor(identity, dll) {
87
+ const tokens = commandTokens(identity.command);
88
+ return isMonitorProcess(identity) && !!tokens && path.isAbsolute(tokens[1]) && path.resolve(tokens[1]).toLowerCase() === path.resolve(dll).toLowerCase();
89
+ }
90
+ /**
91
+ * True when the identity is a `dotnet <assembly> --RunAsMonitor` invocation.
92
+ * Token based so the extra spaces cmd.exe introduces around arguments do not matter.
93
+ */
94
+ function isMonitorProcess(identity) {
95
+ const tokens = commandTokens(identity.command);
96
+ return path.win32.basename(identity.executable).toLowerCase() === 'dotnet.exe' && !!tokens && tokens.length === 3 && path.basename(tokens[0]).toLowerCase().replace(/\.exe$/, '') === 'dotnet' && tokens[2] === '--RunAsMonitor';
97
+ }
98
+ async function checkPort(port) {
99
+ const addresses = Object.values((0, os_1.networkInterfaces)()).flatMap(entries => entries ?? []).filter(entry => entry.family === 'IPv4' || entry.family === 'IPv6' && !entry.address.toLowerCase().startsWith('fe80:')).map(entry => entry.address);
100
+ for (const host of new Set(['127.0.0.1', '0.0.0.0', '::1', '::', ...addresses]))
101
+ await new Promise((resolve, reject) => {
102
+ const server = net.createServer();
103
+ server.once('error', (error) => { if (host.startsWith('::') && ['EAFNOSUPPORT', 'EADDRNOTAVAIL', 'EPROTONOSUPPORT'].includes(error.code ?? ''))
104
+ resolve();
105
+ else
106
+ reject(new Error(`Port ${port} is unavailable (${error.code}); choose an alternate --port.`)); });
107
+ server.listen({ port, host, ipv6Only: host === '::', exclusive: true }, () => server.close(error => error ? reject(error) : resolve()));
108
+ });
109
+ }
110
+ // Fixed CIM scripts; only validated integer PIDs are interpolated, never paths or user arguments.
111
+ class WindowsProcessBackend {
112
+ identityTimeoutMs;
113
+ /**
114
+ * @param identityTimeoutMs how long to wait for the OS to expose the started
115
+ * process before giving up. Injectable so tests do not wait for the default.
116
+ */
117
+ constructor(identityTimeoutMs = 10000) {
118
+ this.identityTimeoutMs = identityTimeoutMs;
119
+ }
120
+ async query(pid, timeout = 10000) {
121
+ if (process.platform !== 'win32')
122
+ return [{ status: 'unknown', reason: 'query' }];
123
+ if (pid !== undefined && (!Number.isInteger(pid) || pid <= 0))
124
+ return [{ status: 'unknown', reason: 'query' }];
125
+ const script = `$ErrorActionPreference='Stop'; $p=@(Get-CimInstance Win32_Process${pid === undefined ? ' -Filter "Name = \'dotnet.exe\'"' : ` -Filter "ProcessId = ${pid}"`}); ConvertTo-Json -Compress -InputObject @($p | ForEach-Object { @{pid=[int]$_.ProcessId; createdAt=$_.CreationDate.ToUniversalTime().ToString('o'); executable=$_.ExecutablePath; command=$_.CommandLine} })`;
126
+ try {
127
+ const { stdout } = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout, maxBuffer: 8 * 1024 * 1024 });
128
+ const values = JSON.parse(stdout);
129
+ if (!Array.isArray(values))
130
+ return [{ status: 'unknown', reason: 'query' }];
131
+ return values.map(identity => identity.command && identity.executable && identity.createdAt ? { status: 'live', identity } : { status: 'unknown', reason: 'query' });
132
+ }
133
+ catch {
134
+ return [{ status: 'unknown', reason: 'query' }];
135
+ }
136
+ }
137
+ async inspect(pid) { return (await this.query(pid))[0] ?? { status: 'absent' }; }
138
+ async find(command) {
139
+ const dll = command.slice(8, -16);
140
+ const result = [];
141
+ for (const item of await this.query()) {
142
+ if (item.status === 'unknown') {
143
+ result.push(item);
144
+ continue;
145
+ }
146
+ if (item.status !== 'live')
147
+ continue;
148
+ const tokens = commandTokens(item.identity.command);
149
+ if (!tokens || tokens.length < 2 || path.win32.basename(item.identity.executable).toLowerCase() !== 'dotnet.exe')
150
+ continue;
151
+ // A relative same-name launch cannot be attributed to an environment, but it is
152
+ // also never one of ours: environments are always started with an absolute path.
153
+ if (!path.isAbsolute(tokens[1])) {
154
+ if (path.win32.basename(tokens[1]).toLowerCase() === path.win32.basename(dll).toLowerCase())
155
+ result.push({ status: 'unknown', reason: 'ambiguous' });
156
+ }
157
+ else if (path.resolve(tokens[1]).toLowerCase() === path.resolve(dll).toLowerCase())
158
+ result.push(item);
159
+ }
160
+ return result;
161
+ }
162
+ async start(dll, mode, stdout, stderr) {
163
+ if (process.platform !== 'win32')
164
+ throw new Error('Runtime management is Windows-only.');
165
+ return mode === 'foreground' ? this.startForeground(dll, stderr) : this.startBackground(dll, stdout, stderr);
166
+ }
167
+ /**
168
+ * Foreground: a direct child with inherited stdio, so the terminal console and
169
+ * Ctrl+C reach the monitor and its output stays visible.
170
+ */
171
+ async startForeground(dll, stderr) {
172
+ const child = (0, child_process_1.spawn)('dotnet', [dll, '--RunAsMonitor'], { cwd: path.dirname(dll), shell: false, detached: false, stdio: 'inherit' });
173
+ const exited = new Promise(resolve => { child.once('exit', () => resolve()); child.once('error', () => resolve()); });
174
+ await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });
175
+ const pid = child.pid;
176
+ let inspected = { status: 'absent' };
177
+ const deadline = Date.now() + this.identityTimeoutMs;
178
+ while (child.exitCode === null && child.signalCode === null && Date.now() < deadline) {
179
+ inspected = (await this.query(pid, Math.max(1, Math.min(5000, deadline - Date.now()))))[0] ?? { status: 'absent' };
180
+ if (inspected.status === 'live')
181
+ break;
182
+ await new Promise(resolve => setTimeout(resolve, 100));
183
+ }
184
+ const identity = inspected.status === 'live' ? inspected.identity : undefined;
185
+ if (identity && !isMonitor(identity, dll)) {
186
+ // A live process with the expected PID whose command line is not the exact
187
+ // monitor invocation: terminate it through its own identity, or hand the
188
+ // verified identity back so the caller can persist a recoverable record.
189
+ try {
190
+ await this.stopFresh(identity);
191
+ }
192
+ catch {
193
+ child.unref();
194
+ throw new PendingProcessError('Launch cleanup uncertain. Registry recovery required.', identity);
195
+ }
196
+ throw new Error('Cannot establish exact monitor identity; launch cancelled.');
197
+ }
198
+ if (!identity) {
199
+ const exitedEarly = child.exitCode !== null || child.signalCode !== null;
200
+ if (!exitedEarly) {
201
+ try {
202
+ child.kill();
203
+ }
204
+ catch { /* already gone */ }
205
+ await this.settle(exited, 3000);
206
+ }
207
+ const detail = `Monitor ${exitedEarly ? `exited before its identity could be verified (exit code ${child.exitCode ?? 'null'}, signal ${child.signalCode ?? 'none'})` : `identity could not be verified within ${this.identityTimeoutMs}ms and the process was terminated`}`;
208
+ throw new Error(this.describeLaunchFailure(detail, dll, stderr));
209
+ }
210
+ if (child.exitCode !== null || child.signalCode !== null)
211
+ throw new Error('Monitor exited during launch.');
212
+ return { identity, exited, exitCode: () => child.exitCode, unref: () => child.unref() };
213
+ }
214
+ /**
215
+ * Background: start the monitor in its own hidden console.
216
+ *
217
+ * Two Windows constraints force this: Node's `detached: true` (DETACHED_PROCESS)
218
+ * leaves the child without any console, and Wyn.Server assigns `Console.Title` at
219
+ * the top of Main, which throws IOException without one; while a non-detached child
220
+ * does not survive the process that spawned it. `Start-Process -WindowStyle Hidden`
221
+ * gives `cmd.exe` a console that has no visible window, and cmd appends the monitor's
222
+ * output to the log files and stays alive with it, so the monitor is independent of
223
+ * this CLI. Its identity is then confirmed by exact command-line discovery.
224
+ */
225
+ async startBackground(dll, stdout, stderr) {
226
+ const hostPid = await this.launchHiddenConsole(dll, stdout, stderr);
227
+ let identity;
228
+ let hostAlive = true;
229
+ const deadline = Date.now() + this.identityTimeoutMs;
230
+ while (Date.now() < deadline) {
231
+ const live = (await this.find((0, exports.monitorCommand)(dll))).filter((candidate) => candidate.status === 'live' && isMonitor(candidate.identity, dll));
232
+ if (live.length === 1) {
233
+ identity = live[0].identity;
234
+ break;
235
+ }
236
+ if (live.length > 1)
237
+ throw new Error(`More than one monitor matches ${dll}; refusing to record an ambiguous identity.`);
238
+ // The hidden-console host only exits after its monitor exits.
239
+ hostAlive = (await this.inspect(hostPid)).status === 'live';
240
+ if (!hostAlive)
241
+ break;
242
+ await new Promise(resolve => setTimeout(resolve, 200));
243
+ }
244
+ if (!identity) {
245
+ const remaining = await this.cleanupCandidates(dll);
246
+ const detail = `Monitor identity could not be verified within ${this.identityTimeoutMs}ms (hidden-console host pid ${hostPid} is ${hostAlive ? 'still running' : 'gone'})${remaining ? `; ${remaining} process(es) still reference ${dll} and require manual inspection` : ''}`;
247
+ throw new Error(this.describeLaunchFailure(detail, dll, stderr));
248
+ }
249
+ return { identity, unref: () => { } };
250
+ }
251
+ /**
252
+ * Start `dotnet <dll> --RunAsMonitor` in a hidden console through cmd.exe and
253
+ * return the PID of that console host. Redirection stays with cmd because
254
+ * Windows PowerShell 5.1 blocks in `Start-Process` when it redirects output.
255
+ */
256
+ launchHiddenConsole(dll, stdout, stderr) {
257
+ const quote = (value) => `'${value.replace(/'/g, "''")}'`;
258
+ const argument = `/c dotnet "${dll}" --RunAsMonitor 1>>"${stdout}" 2>>"${stderr}"`;
259
+ const script = [
260
+ "$ErrorActionPreference='Stop'",
261
+ `$p = Start-Process -FilePath 'cmd.exe' -ArgumentList ${quote(argument)} -WorkingDirectory ${quote(path.dirname(dll))} -WindowStyle Hidden -PassThru`,
262
+ '$p.Id',
263
+ ].join('; ');
264
+ return new Promise((resolve, reject) => {
265
+ // Not detached: Node cannot read the launcher's stdout when it is detached on
266
+ // Windows. The launcher only creates the hidden-console host and exits.
267
+ const launcher = (0, child_process_1.spawn)('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { detached: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
268
+ let output = '';
269
+ let failure = '';
270
+ launcher.stdout?.on('data', chunk => { output += String(chunk); });
271
+ launcher.stderr?.on('data', chunk => { failure += String(chunk); });
272
+ launcher.once('error', reject);
273
+ launcher.once('close', code => {
274
+ const pid = Number(output.trim());
275
+ if (code === 0 && Number.isInteger(pid) && pid > 0)
276
+ resolve(pid);
277
+ else
278
+ reject(new Error(`Cannot start the hidden-console monitor (launcher exit ${code}): ${failure.trim() || 'no launcher output'}`));
279
+ });
280
+ });
281
+ }
282
+ /** Terminate exact-identity leftovers of this environment and report how many remain. */
283
+ async cleanupCandidates(dll) {
284
+ const candidates = await this.find((0, exports.monitorCommand)(dll)).catch(() => [{ status: 'unknown' }]);
285
+ for (const candidate of candidates)
286
+ if (candidate.status === 'live')
287
+ await this.stopFresh(candidate.identity).catch(() => undefined);
288
+ return (await this.find((0, exports.monitorCommand)(dll)).catch(() => [{ status: 'unknown' }])).filter(item => item.status !== 'absent').length;
289
+ }
290
+ /** Explain a failed launch with the monitor's own log output. */
291
+ describeLaunchFailure(detail, dll, stderr) {
292
+ const tail = this.lastOutput(stderr);
293
+ const consoleHint = /ConsolePal\.set_Title|Console\.Title|Invalid handle|句柄/i.test(tail)
294
+ ? ' Wyn.Server cannot run without an attached console because it assigns Console.Title at the start of Main; guard that call in Wyn.Server or start with --foreground.'
295
+ : '';
296
+ return `${detail}. ${tail}${consoleHint}`;
297
+ }
298
+ /** Bounded tail of a log file, used to explain why a monitor exited immediately. */
299
+ lastOutput(file, limit = 2000) {
300
+ try {
301
+ const tail = fs.readFileSync(file, 'utf8').slice(-limit).trim();
302
+ return tail ? `Last output from ${file}:\n${tail}` : `${file} is empty.`;
303
+ }
304
+ catch {
305
+ return `${file} is empty or unreadable.`;
306
+ }
307
+ }
308
+ /** Wait for a promise without leaving a pending timer behind. */
309
+ settle(promise, ms) {
310
+ return new Promise(resolve => {
311
+ const timer = setTimeout(resolve, ms);
312
+ timer.unref?.();
313
+ void promise.then(() => { clearTimeout(timer); resolve(); });
314
+ });
315
+ }
316
+ async descendants(parent, deadline) {
317
+ const result = [];
318
+ const visit = async (ancestor) => {
319
+ if (Date.now() >= deadline)
320
+ throw new Error('Timed out inspecting monitor process tree.');
321
+ if (result.length > 512)
322
+ throw new Error('Monitor process tree exceeds safety limit.');
323
+ const script = `$ErrorActionPreference='Stop'; ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${ancestor.pid}" | ForEach-Object { @{pid=[int]$_.ProcessId; createdAt=$_.CreationDate.ToUniversalTime().ToString('o'); executable=$_.ExecutablePath; command=$_.CommandLine} })`;
324
+ const raw = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: Math.max(1, Math.min(10000, deadline - Date.now())) });
325
+ const children = JSON.parse(raw.stdout);
326
+ if (!Array.isArray(children))
327
+ throw new Error('Unknown monitor process tree.');
328
+ for (const child of children) {
329
+ if (!child.createdAt || !child.command || !child.executable || !Number.isInteger(child.pid))
330
+ throw new Error('Unknown child process identity.');
331
+ // ParentProcessId can refer to an older process with a reused PID.
332
+ if (!Number.isFinite(Date.parse(child.createdAt)) || !Number.isFinite(Date.parse(ancestor.createdAt)) || Date.parse(child.createdAt) < Date.parse(ancestor.createdAt))
333
+ throw new Error('Child ancestry uncertain or parent PID reused; refusing tree kill.');
334
+ if (result.some(entry => entry.pid === child.pid))
335
+ throw new Error('Cyclic process tree.');
336
+ result.push(child);
337
+ await visit(child);
338
+ }
339
+ };
340
+ await visit(parent);
341
+ return result;
342
+ }
343
+ async stopFresh(identity) {
344
+ const deadline = Date.now() + 15000;
345
+ const inspect = async (pid) => { if (Date.now() >= deadline)
346
+ throw new Error('Timed out stopping monitor tree.'); return (await this.query(pid, Math.max(1, Math.min(10000, deadline - Date.now()))))[0] ?? { status: 'absent' }; };
347
+ const current = await inspect(identity.pid);
348
+ if (current.status !== 'live' || !sameProcess(current.identity, identity))
349
+ throw new Error('Process identity uncertain or changed; refusing kill.');
350
+ const descendants = await this.descendants(identity, deadline);
351
+ const immediate = await inspect(identity.pid);
352
+ if (immediate.status !== 'live' || !sameProcess(immediate.identity, identity))
353
+ throw new Error('Process identity changed before kill.');
354
+ if (Date.now() >= deadline)
355
+ throw new Error('Timed out before monitor tree termination.');
356
+ await exec('taskkill.exe', ['/PID', String(identity.pid), '/T', '/F'], { timeout: Math.max(1, Math.min(10000, deadline - Date.now())) });
357
+ for (let attempt = 0; attempt < 30; attempt++) {
358
+ let live = false;
359
+ for (const entry of [identity, ...descendants]) {
360
+ if (Date.now() >= deadline)
361
+ throw new Error('Timed out confirming monitor tree exit.');
362
+ const state = await inspect(entry.pid);
363
+ if (state.status === 'unknown')
364
+ throw new Error('Unable to confirm process tree exit.');
365
+ if (state.status === 'live' && sameProcess(state.identity, entry))
366
+ live = true;
367
+ }
368
+ if (!live)
369
+ return;
370
+ await new Promise(resolve => setTimeout(resolve, 100));
371
+ }
372
+ throw new Error('Timed out waiting for monitor tree exit.');
373
+ }
374
+ async stop(identity) {
375
+ if (!isMonitorProcess(identity))
376
+ throw new Error('Not a verified dotnet monitor.');
377
+ await this.stopFresh(identity);
378
+ }
379
+ }
380
+ exports.WindowsProcessBackend = WindowsProcessBackend;
381
+ //# sourceMappingURL=process.js.map