@jacebenson/jsn 4.3.7-dev.27 → 4.3.7-dev.29

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacebenson/jsn",
3
- "version": "4.3.7-dev.27",
3
+ "version": "4.3.7-dev.29",
4
4
  "description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -195,12 +195,14 @@ async function waitForMidServerUp(app, name, timeoutMs, intervalMs) {
195
195
  function removeLocalMidServer(instanceURL, name) {
196
196
  const installDir = midInstallDirectory(instanceURL, name);
197
197
  const composePath = path.join(installDir, 'docker-compose.yaml');
198
+ const envPath = path.join(installDir, '.env');
198
199
  if (!fs.existsSync(composePath)) return false;
200
+ const composeArgs = ['compose', ...(fs.existsSync(envPath) ? ['--env-file', envPath] : []), '-f', composePath, 'down'];
199
201
  try {
200
- defaultRun('docker', ['compose', '-f', composePath, 'down'], { cwd: installDir });
202
+ defaultRun('docker', composeArgs, { cwd: installDir });
201
203
  } catch {
202
204
  defaultRun('sudo', ['systemctl', 'start', 'docker']);
203
- defaultRun('sudo', ['docker', 'compose', '-f', composePath, 'down'], { cwd: installDir });
205
+ defaultRun('sudo', ['docker', ...composeArgs], { cwd: installDir });
204
206
  }
205
207
  try {
206
208
  fs.rmSync(installDir, { recursive: true, force: true });
@@ -210,6 +212,21 @@ function removeLocalMidServer(instanceURL, name) {
210
212
  return true;
211
213
  }
212
214
 
215
+ function showMidServerLogs(app, identifier, argv) {
216
+ const installDir = midInstallDirectory(app.getEffectiveInstance(), identifier);
217
+ const composePath = path.join(installDir, 'docker-compose.yaml');
218
+ const envPath = path.join(installDir, '.env');
219
+ if (!fs.existsSync(composePath)) throw new Error(`No local Compose installation found for MID Server '${identifier}'`);
220
+ const args = ['compose', ...(fs.existsSync(envPath) ? ['--env-file', envPath] : []), '-f', composePath, 'logs', '--tail', String(argv.tail)];
221
+ if (argv.follow) args.push('--follow');
222
+ try {
223
+ defaultRun('docker', args, { cwd: installDir });
224
+ } catch {
225
+ defaultRun('sudo', ['systemctl', 'start', 'docker']);
226
+ defaultRun('sudo', ['docker', ...args], { cwd: installDir });
227
+ }
228
+ }
229
+
213
230
  export function midserversCmd(wrap) {
214
231
  return {
215
232
  command: 'midservers [subcommand]',
@@ -238,6 +255,26 @@ export function midserversCmd(wrap) {
238
255
  app.ok(record, { summary: `MID Server: ${getStringField(record, 'name') || argv.identifier}` });
239
256
  }),
240
257
  })
258
+ .command({
259
+ command: 'logs <identifier>',
260
+ describe: 'Show local Docker Compose logs for a MID Server',
261
+ builder: (b) => b
262
+ .option('tail', { type: 'number', default: 100, describe: 'Number of log lines' })
263
+ .option('follow', { type: 'boolean', default: false, describe: 'Follow the logs' }),
264
+ handler: wrap(async (argv, app) => {
265
+ showMidServerLogs(app, argv.identifier, argv);
266
+ }),
267
+ })
268
+ .command({
269
+ command: '<identifier> logs',
270
+ describe: 'Show local Docker Compose logs for a MID Server',
271
+ builder: (b) => b
272
+ .option('tail', { type: 'number', default: 100, describe: 'Number of log lines' })
273
+ .option('follow', { type: 'boolean', default: false, describe: 'Follow the logs' }),
274
+ handler: wrap(async (argv, app) => {
275
+ showMidServerLogs(app, argv.identifier, argv);
276
+ }),
277
+ })
241
278
  .command({
242
279
  command: 'add',
243
280
  describe: 'Install and verify a MID Server',
@@ -246,7 +283,7 @@ export function midserversCmd(wrap) {
246
283
  .option('username', { type: 'string', describe: 'MID Server username (password is always prompted)' })
247
284
  .option('install-method', { type: 'string', choices: ['docker', 'direct'], describe: 'Installation method (prompted when omitted)' })
248
285
  .option('directory', { type: 'string', describe: 'Docker installation directory' })
249
- .option('wait-timeout', { type: 'number', default: 120000, describe: 'How long to wait for the MID Server to report Up' })
286
+ .option('wait-timeout', { type: 'number', default: 300000, describe: 'How long to wait for the MID Server to report Up' })
250
287
  .option('wait-interval', { type: 'number', default: 3000, describe: 'MID Server status polling interval in milliseconds' }),
251
288
  handler: wrap(async (argv, app) => {
252
289
  app.requireInstance();
@@ -292,13 +329,14 @@ export function midserversCmd(wrap) {
292
329
  });
293
330
  if (installMethod === 'sudo') {
294
331
  defaultRun('sudo', ['systemctl', 'start', 'docker']);
295
- defaultRun('sudo', ['docker', 'compose', '-f', installation.composePath, 'up', '-d'], { cwd: installation.installDir });
332
+ defaultRun('sudo', ['docker', 'compose', '--env-file', path.join(installation.installDir, '.env'), '-f', installation.composePath, 'up', '-d'], { cwd: installation.installDir });
296
333
  installation.prepared = false;
297
334
  }
298
335
  if (installation.prepared) {
299
336
  app.ok(installation, { summary: `Prepared MID Server files in ${installation.installDir}. Run: ${installation.runCommand}` });
300
337
  return;
301
338
  }
339
+ console.log('MID Server container started. ServiceNow may take a few minutes to register it and report Up.');
302
340
  let record;
303
341
  try {
304
342
  record = await waitForMidServerUp(app, installation.name, Math.max(1, argv['wait-timeout']), Math.max(500, argv['wait-interval']));
@@ -359,22 +397,48 @@ export function midserversCmd(wrap) {
359
397
  }),
360
398
  })
361
399
  .command({
362
- command: 'execute <identifier>',
400
+ command: 'execute [identifier]',
363
401
  describe: 'Execute a shell command through a MID Server',
364
402
  builder: (b) => b
365
- .option('command', { alias: 'c', type: 'string', demandOption: true, describe: 'Command line to execute on the MID Server host' })
403
+ .option('command', { alias: 'c', type: 'string', describe: 'Command line to execute on the MID Server host' })
366
404
  .option('timeout', { type: 'number', default: DEFAULT_TIMEOUT, describe: 'Response timeout in milliseconds' })
367
405
  .option('interval', { type: 'number', default: DEFAULT_INTERVAL, describe: 'Polling interval in milliseconds' }),
368
406
  handler: wrap(async (argv, app) => {
369
- const record = await resolveMidServer(app.sdk, argv.identifier);
407
+ let record;
408
+ if (argv.identifier) {
409
+ record = await resolveMidServer(app.sdk, argv.identifier);
410
+ } else {
411
+ if (!canPrompt() || app.output.getFormat() !== FormatAuto) {
412
+ throw new Error('An identifier and --command are required outside an interactive terminal');
413
+ }
414
+ record = await interactiveList({
415
+ app,
416
+ table: AGENT_TABLE,
417
+ singular: 'MID Server',
418
+ columns: LIST_FIELDS,
419
+ limit: 50,
420
+ labelField: 'name',
421
+ message: 'Select a MID Server to execute on',
422
+ formatLabel: (candidate) => {
423
+ const name = getStringField(candidate, 'name') || getStringField(candidate, 'sys_id');
424
+ const status = getStringField(candidate, 'status');
425
+ return status ? `${name} [${status}]` : name;
426
+ },
427
+ });
428
+ if (!record) return;
429
+ }
430
+ const command = argv.command || await input({
431
+ message: 'Command to execute:',
432
+ validate: value => value.trim() ? true : 'Command is required',
433
+ });
370
434
  assertMidServerUp(record);
371
435
  const agent = agentName(record);
372
436
  const output = await app.sdk.create(QUEUE_TABLE, {
373
437
  agent,
374
438
  topic: 'Command',
375
439
  queue: 'output',
376
- name: argv.command,
377
- payload: buildCommandPayload(argv.command),
440
+ name: command,
441
+ payload: buildCommandPayload(command),
378
442
  });
379
443
  const outputSysId = unwrapSysId(output);
380
444
  if (!outputSysId) throw new Error('ServiceNow did not return the ECC Queue sys_id for the command');
@@ -384,7 +448,7 @@ export function midserversCmd(wrap) {
384
448
  const result = parseCommandResult(queueValue(response.payload));
385
449
  app.ok({
386
450
  mid_server: getStringField(record, 'name'),
387
- command: argv.command,
451
+ command,
388
452
  output: { sys_id: outputSysId, ...output },
389
453
  response: { ...response, ...result },
390
454
  }, { summary: `Executed command on MID Server '${getStringField(record, 'name')}'` });
@@ -114,7 +114,8 @@ export async function installDockerMidServer({
114
114
  const installDir = directory || midInstallDirectory(instanceURL, name);
115
115
  fs.mkdirSync(installDir, { recursive: true, mode: 0o700 });
116
116
  const exportDir = path.join(installDir, 'export');
117
- fs.mkdirSync(exportDir, { recursive: true, mode: 0o700 });
117
+ fs.mkdirSync(exportDir, { recursive: true, mode: 0o777 });
118
+ fs.chmodSync(exportDir, 0o777);
118
119
  const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsn-mid-'));
119
120
  const filename = path.basename(recipeURL);
120
121
  const archive = path.join(workDir, filename);
@@ -148,18 +149,18 @@ export async function installDockerMidServer({
148
149
  composePath,
149
150
  serviceName,
150
151
  prepared: true,
151
- runCommand: `docker compose -f ${composePath} up -d`,
152
+ runCommand: `docker compose --env-file ${envPath} -f ${composePath} up -d`,
152
153
  };
153
154
  }
154
155
  run('docker', ['build', '--tag', `${serviceName}:${releaseName}`, persistedRecipeDir]);
155
- run('docker', ['compose', '-f', composePath, 'up', '-d'], { cwd: installDir, env });
156
+ run('docker', ['compose', '--env-file', envPath, '-f', composePath, 'up', '-d'], { cwd: installDir, env });
156
157
  return {
157
158
  name: displayName,
158
159
  releaseName,
159
160
  installDir,
160
161
  composePath,
161
162
  serviceName,
162
- rollback: () => run('docker', ['compose', '-f', composePath, 'down'], { cwd: installDir, env }),
163
+ rollback: () => run('docker', ['compose', '--env-file', envPath, '-f', composePath, 'down'], { cwd: installDir, env }),
163
164
  };
164
165
  } finally {
165
166
  fs.rmSync(workDir, { recursive: true, force: true });