@jacebenson/jsn 4.3.7-dev.35 → 4.3.7-dev.36
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/package.json +1 -1
- package/src/commands/midservers.js +48 -3
- package/src/midserver-direct.js +14 -3
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
3
4
|
import { input, password as passwordPrompt, select } from '@inquirer/prompts';
|
|
4
5
|
import { confirmDelete, formatRecordForDisplay, getStringField, interactiveList, canPrompt, assertSafeExactMatch } from '../helpers.js';
|
|
5
6
|
import { FormatAuto } from '../output.js';
|
|
@@ -171,6 +172,40 @@ function midDiagnosticsCommand(kind, wrap) {
|
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
// Progress for long, otherwise-silent direct-install phases. Human terminals
|
|
176
|
+
// only, and on stderr, so the stdout envelope and --json output stay clean.
|
|
177
|
+
export function directProgress(app) {
|
|
178
|
+
const human = app.output?.getFormat() === FormatAuto && (canPrompt() || process.stderr.isTTY === true);
|
|
179
|
+
if (!human) return () => {};
|
|
180
|
+
const mib = bytes => (Number(bytes || 0) / (1024 * 1024)).toFixed(0);
|
|
181
|
+
return event => {
|
|
182
|
+
if (event.phase === 'download-start') {
|
|
183
|
+
process.stderr.write(`Downloading the official MID ZIP from ServiceNow (about 532 MiB).\n ${event.archiveURL}\n`);
|
|
184
|
+
} else if (event.phase === 'download') {
|
|
185
|
+
const percent = event.total ? ` (${Math.floor((event.received / event.total) * 100)}%)` : '';
|
|
186
|
+
process.stderr.write(`\r ${mib(event.received)} MiB${percent} downloaded...`);
|
|
187
|
+
if (event.done) process.stderr.write('\n');
|
|
188
|
+
} else if (event.phase === 'extract-start') {
|
|
189
|
+
process.stderr.write(`Extracting ${event.entries} files into ${event.installDir}...\n`);
|
|
190
|
+
} else if (event.phase === 'extract-done') {
|
|
191
|
+
process.stderr.write('Extraction complete. Configuring the MID Server...\n');
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A registration wait is minutes of silence otherwise; report elapsed progress.
|
|
197
|
+
export function registrationProgress(app, timeoutMs) {
|
|
198
|
+
const human = app.output?.getFormat() === FormatAuto && (canPrompt() || process.stderr.isTTY === true);
|
|
199
|
+
if (!human) return undefined;
|
|
200
|
+
const seconds = Math.round(timeoutMs / 1000);
|
|
201
|
+
let lastReport = 0;
|
|
202
|
+
return elapsedMs => {
|
|
203
|
+
if (elapsedMs - lastReport < 15000) return;
|
|
204
|
+
lastReport = elapsedMs;
|
|
205
|
+
process.stderr.write(`Waiting for ServiceNow to register the MID Server... ${Math.round(elapsedMs / 1000)}s of ${seconds}s\n`);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
174
209
|
async function listAgents(app, argv) {
|
|
175
210
|
const columns = argv.columns
|
|
176
211
|
? [...new Set(argv.columns.split(',').map(column => column.trim()).filter(Boolean))]
|
|
@@ -266,10 +301,11 @@ async function promptMidCredentials(app, argv) {
|
|
|
266
301
|
}
|
|
267
302
|
}
|
|
268
303
|
|
|
269
|
-
export async function waitForMidServerUp(app, name, timeoutMs, intervalMs) {
|
|
304
|
+
export async function waitForMidServerUp(app, name, timeoutMs, intervalMs, onWait) {
|
|
270
305
|
const started = Date.now();
|
|
271
306
|
const fullName = name;
|
|
272
307
|
while (Date.now() - started <= timeoutMs) {
|
|
308
|
+
onWait?.(Date.now() - started);
|
|
273
309
|
const params = new URLSearchParams({
|
|
274
310
|
sysparm_query: `name=${fullName}`,
|
|
275
311
|
sysparm_limit: '1',
|
|
@@ -472,15 +508,24 @@ export function midserversCmd(wrap) {
|
|
|
472
508
|
const existing = await app.sdk.list(AGENT_TABLE, new URLSearchParams({ sysparm_query: `name=${shortName}`, sysparm_fields: 'sys_id,name', sysparm_limit: '1' }));
|
|
473
509
|
if (existing.length) throw new Error(`MID '${shortName}' already exists in ServiceNow; choose a new name. Existing runtime and record were not changed.`);
|
|
474
510
|
const installation = await installDirectMidServer({
|
|
475
|
-
instanceURL: app.getEffectiveInstance(), ...credentials, name, directory: argv.directory,
|
|
511
|
+
instanceURL: app.getEffectiveInstance(), ...credentials, name, directory: argv.directory,
|
|
512
|
+
runNow: decision === 'start', onProgress: directProgress(app),
|
|
476
513
|
});
|
|
477
514
|
if (installation.state === 'prepared') {
|
|
478
515
|
app.ok(installation, { summary: `Prepared native MID '${installation.name}' in ${installation.installDir}; not started or registered. Start with jsn midservers start ${installation.name}${argv.directory ? ' --directory ' + JSON.stringify(installation.installDir) : ''}. Windows start runs in the foreground console; no service is installed.` });
|
|
479
516
|
return;
|
|
480
517
|
}
|
|
481
518
|
let record;
|
|
482
|
-
|
|
519
|
+
const directWait = Math.max(1, argv['wait-timeout']);
|
|
520
|
+
const directInterval = Math.max(500, argv['wait-interval']);
|
|
521
|
+
const onWait = registrationProgress(app, directWait);
|
|
522
|
+
const started = Date.now();
|
|
523
|
+
try {
|
|
524
|
+
record = await waitForMidServerUp(app, installation.name, directWait, directInterval, onWait);
|
|
525
|
+
onWait?.(directWait);
|
|
526
|
+
}
|
|
483
527
|
catch {
|
|
528
|
+
onWait?.(Math.max(directWait, Date.now() - started));
|
|
484
529
|
app.ok({ ...installation, registration: 'pending', validated: false }, { summary: `Native MID start command completed; registration is pending. Runtime retained for diagnostics at ${installation.installDir}; no forced teardown.` });
|
|
485
530
|
return;
|
|
486
531
|
}
|
package/src/midserver-direct.js
CHANGED
|
@@ -71,20 +71,28 @@ function checkParents(directory, platform) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
export async function downloadMidZip(url, archive, fetchImpl = fetch) {
|
|
74
|
+
export async function downloadMidZip(url, archive, fetchImpl = fetch, onProgress) {
|
|
75
75
|
const response = await fetchImpl(url, { redirect: 'error', signal: AbortSignal.timeout(600000) });
|
|
76
76
|
if (!response.ok || !response.body) throw new Error(`MID ZIP download failed (HTTP ${response.status})`);
|
|
77
77
|
const length = Number(response.headers.get('content-length'));
|
|
78
78
|
if (length > MAX_ARCHIVE) throw new Error('MID ZIP exceeds 1 GiB download limit');
|
|
79
|
+
onProgress?.({ phase: 'download', received: 0, total: Number.isFinite(length) ? length : 0 });
|
|
79
80
|
const fd = fs.openSync(archive, 'wx', 0o600);
|
|
80
81
|
let size = 0;
|
|
82
|
+
let reported = 0;
|
|
83
|
+
const step = Math.max(4 * 1024 * 1024, Math.floor((Number.isFinite(length) ? length : MAX_ARCHIVE) / 100));
|
|
81
84
|
try {
|
|
82
85
|
for await (const chunk of response.body) {
|
|
83
86
|
size += chunk.length;
|
|
84
87
|
if (size > MAX_ARCHIVE) throw new Error('MID ZIP exceeds 1 GiB download limit');
|
|
85
88
|
fs.writeSync(fd, chunk);
|
|
89
|
+
if (onProgress && size - reported >= step) {
|
|
90
|
+
reported = size;
|
|
91
|
+
onProgress({ phase: 'download', received: size, total: Number.isFinite(length) ? length : 0 });
|
|
92
|
+
}
|
|
86
93
|
}
|
|
87
94
|
if (length && size !== length) throw new Error('Incomplete MID ZIP download');
|
|
95
|
+
onProgress?.({ phase: 'download', received: size, total: size, done: true });
|
|
88
96
|
} finally { fs.closeSync(fd); }
|
|
89
97
|
}
|
|
90
98
|
|
|
@@ -189,7 +197,7 @@ export function removeDirectInstallation(metadata, run = defaultRun) {
|
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
export async function installDirectMidServer({ instanceURL, username, password, name, directory, releaseName: suppliedRelease,
|
|
192
|
-
runNow = false, platform = process.platform, arch = process.arch, fetchImpl = fetch, download = downloadMidZip,
|
|
200
|
+
runNow = false, platform = process.platform, arch = process.arch, fetchImpl = fetch, download = downloadMidZip, onProgress,
|
|
193
201
|
protect = protectDirectory, run = defaultRun }) {
|
|
194
202
|
const native = detectDirectPlatform(platform, arch);
|
|
195
203
|
if (native === 'linux' && process.getuid?.() === 0) throw new Error('Run direct MID installation as a non-root user');
|
|
@@ -216,9 +224,12 @@ export async function installDirectMidServer({ instanceURL, username, password,
|
|
|
216
224
|
let prepared = false;
|
|
217
225
|
try {
|
|
218
226
|
protect(installDir, native);
|
|
219
|
-
|
|
227
|
+
onProgress?.({ phase: 'download-start', archiveURL, platform: native, installDir, name: shortName });
|
|
228
|
+
await download(archiveURL, archive, fetchImpl, onProgress);
|
|
220
229
|
const entries = inspectMidZip(archive);
|
|
230
|
+
onProgress?.({ phase: 'extract-start', entries: entries.length, installDir });
|
|
221
231
|
await extractMidZip(archive, installDir, entries);
|
|
232
|
+
onProgress?.({ phase: 'extract-done', entries: entries.length, installDir });
|
|
222
233
|
const agent = path.join(installDir, 'agent');
|
|
223
234
|
for (const filename of ['config.xml', 'conf/wrapper-override.conf', native === 'linux' ? 'bin/mid.sh' : 'bin/mid.bat']) {
|
|
224
235
|
if (!fs.statSync(path.join(agent, filename)).isFile()) throw new Error(`Vendor ZIP missing ${filename}`);
|