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

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.25",
3
+ "version": "4.3.7-dev.27",
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": {
@@ -1,9 +1,11 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
1
3
  import { input, password as passwordPrompt, select } from '@inquirer/prompts';
2
4
  import { confirmDelete, formatRecordForDisplay, getStringField, interactiveList, canPrompt } from '../helpers.js';
3
5
  import { FormatAuto } from '../output.js';
4
6
  import { declareCapabilities } from '../capabilities.js';
5
7
  import { resolveRecord, unwrapSysId } from '../resolve-record.js';
6
- import { detectDocker, installDockerMidServer, validateMidCredentials } from '../midserver-install.js';
8
+ import { detectDocker, defaultRun, installDockerMidServer, midInstallDirectory, validateMidCredentials } from '../midserver-install.js';
7
9
 
8
10
  const AGENT_TABLE = 'ecc_agent';
9
11
  const QUEUE_TABLE = 'ecc_queue';
@@ -190,6 +192,24 @@ async function waitForMidServerUp(app, name, timeoutMs, intervalMs) {
190
192
  throw new Error(`MID Server '${fullName}' did not report Up within ${timeoutMs}ms`);
191
193
  }
192
194
 
195
+ function removeLocalMidServer(instanceURL, name) {
196
+ const installDir = midInstallDirectory(instanceURL, name);
197
+ const composePath = path.join(installDir, 'docker-compose.yaml');
198
+ if (!fs.existsSync(composePath)) return false;
199
+ try {
200
+ defaultRun('docker', ['compose', '-f', composePath, 'down'], { cwd: installDir });
201
+ } catch {
202
+ defaultRun('sudo', ['systemctl', 'start', 'docker']);
203
+ defaultRun('sudo', ['docker', 'compose', '-f', composePath, 'down'], { cwd: installDir });
204
+ }
205
+ try {
206
+ fs.rmSync(installDir, { recursive: true, force: true });
207
+ } catch {
208
+ defaultRun('sudo', ['rm', '-rf', '--', installDir]);
209
+ }
210
+ return true;
211
+ }
212
+
193
213
  export function midserversCmd(wrap) {
194
214
  return {
195
215
  command: 'midservers [subcommand]',
@@ -250,6 +270,7 @@ export function midserversCmd(wrap) {
250
270
  ]
251
271
  : dockerStatus.installed && dockerStatus.compose
252
272
  ? [
273
+ { name: 'Run with sudo', value: 'sudo' },
253
274
  { name: 'Prepare files now, run Docker later', value: 'prepare' },
254
275
  { name: 'Direct install (not available yet)', value: 'direct' },
255
276
  ]
@@ -267,8 +288,13 @@ export function midserversCmd(wrap) {
267
288
  name,
268
289
  releaseName: credentials.releaseName,
269
290
  directory: argv.directory,
270
- runNow: installMethod !== 'prepare',
291
+ runNow: false,
271
292
  });
293
+ if (installMethod === 'sudo') {
294
+ defaultRun('sudo', ['systemctl', 'start', 'docker']);
295
+ defaultRun('sudo', ['docker', 'compose', '-f', installation.composePath, 'up', '-d'], { cwd: installation.installDir });
296
+ installation.prepared = false;
297
+ }
272
298
  if (installation.prepared) {
273
299
  app.ok(installation, { summary: `Prepared MID Server files in ${installation.installDir}. Run: ${installation.runCommand}` });
274
300
  return;
@@ -296,7 +322,14 @@ export function midserversCmd(wrap) {
296
322
  handler: wrap(async (argv, app) => {
297
323
  let record;
298
324
  if (argv.identifier) {
299
- record = await resolveMidServer(app.sdk, argv.identifier);
325
+ try {
326
+ record = await resolveMidServer(app.sdk, argv.identifier);
327
+ } catch (error) {
328
+ if (error.code !== 'not_found') throw error;
329
+ const localDir = midInstallDirectory(app.getEffectiveInstance(), argv.identifier);
330
+ if (!fs.existsSync(path.join(localDir, 'docker-compose.yaml'))) throw error;
331
+ record = null;
332
+ }
300
333
  } else {
301
334
  if (!canPrompt() || app.output.getFormat() !== FormatAuto) {
302
335
  throw new Error('An identifier is required outside an interactive terminal');
@@ -317,11 +350,12 @@ export function midserversCmd(wrap) {
317
350
  });
318
351
  if (!record) return;
319
352
  }
320
- const sysId = unwrapSysId(record);
353
+ const sysId = record ? unwrapSysId(record) : null;
321
354
  const name = getStringField(record, 'name') || argv.identifier || sysId;
322
355
  await confirmDelete(app, argv, `Remove MID Server '${name}'`);
323
- await app.sdk.delete(AGENT_TABLE, sysId);
324
- app.ok({ name, sys_id: sysId, deleted: true }, { summary: `Removed MID Server '${name}'` });
356
+ const localRemoved = removeLocalMidServer(app.getEffectiveInstance(), name);
357
+ if (record) await app.sdk.delete(AGENT_TABLE, sysId);
358
+ app.ok({ name, sys_id: sysId, deleted: Boolean(record), local_compose_removed: localRemoved }, { summary: `Removed MID Server '${name}'${localRemoved ? ' and its local Compose stack' : ''}` });
325
359
  }),
326
360
  })
327
361
  .command({
@@ -90,6 +90,11 @@ function instanceName(instanceURL) {
90
90
  return hostname.replace(/\.service-now\.com$|\.servicenowservices\.com$/i, '').replace(/[^A-Za-z0-9._-]/g, '_');
91
91
  }
92
92
 
93
+ export function midInstallDirectory(instanceURL, name, homeDirectory = os.homedir()) {
94
+ const shortName = normalizeMidName(name);
95
+ return path.join(homeDirectory, '.servicenow', 'mids', instanceName(instanceURL), shortName);
96
+ }
97
+
93
98
  export async function installDockerMidServer({
94
99
  instanceURL,
95
100
  username,
@@ -106,8 +111,7 @@ export async function installDockerMidServer({
106
111
  const displayName = `mid.server.${shortName}`;
107
112
  const releaseName = suppliedReleaseName || (await validateMidCredentials(instanceURL, username, password, fetchImpl)).releaseName;
108
113
  const recipeURL = buildMidRecipeUrl(releaseName);
109
- const serviceNowInstance = instanceName(instanceURL);
110
- const installDir = directory || path.join(os.homedir(), '.servicenow', 'mids', serviceNowInstance, shortName);
114
+ const installDir = directory || midInstallDirectory(instanceURL, name);
111
115
  fs.mkdirSync(installDir, { recursive: true, mode: 0o700 });
112
116
  const exportDir = path.join(installDir, 'export');
113
117
  fs.mkdirSync(exportDir, { recursive: true, mode: 0o700 });