@mikeyt23/node-cli-utils 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 (2) hide show
  1. package/index.js +239 -22
  2. package/package.json +4 -3
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const fs = require('fs')
2
2
  const fsp = require('fs').promises
3
+ const fse = require('fs-extra')
3
4
  const which = require('which')
4
- const {spawn, spawnSync} = require('child_process')
5
+ const { spawn, spawnSync } = require('child_process')
5
6
  const path = require('path')
6
7
  const tar = require('tar')
7
8
 
@@ -9,7 +10,7 @@ const defaultSpawnOptions = {
9
10
  shell: true,
10
11
  stdio: ['ignore', 'inherit', 'inherit']
11
12
  }
12
- const defaultSpawnOptionsWithInput = {...defaultSpawnOptions, stdio: 'inherit'}
13
+ const defaultSpawnOptionsWithInput = { ...defaultSpawnOptions, stdio: 'inherit' }
13
14
 
14
15
  function waitForProcess(childProcess) {
15
16
  return new Promise((resolve, reject) => {
@@ -39,7 +40,7 @@ async function throwIfDockerNotRunning() {
39
40
  throw Error('docker command not found')
40
41
  }
41
42
 
42
- let childProcess = spawnSync('docker', ['info'], {encoding: 'utf8'})
43
+ let childProcess = spawnSync('docker', ['info'], { encoding: 'utf8' })
43
44
  if (childProcess.error) {
44
45
  throw childProcess.error
45
46
  }
@@ -51,7 +52,7 @@ async function throwIfDockerNotRunning() {
51
52
  async function bashIntoRunningDockerContainer(containerNamePartial, entryPoint = 'bash') {
52
53
  await throwIfDockerNotRunning()
53
54
 
54
- let childProcess = spawnSync('docker', ['container', 'ls'], {encoding: 'utf8'})
55
+ let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
55
56
  if (childProcess.error) {
56
57
  throw childProcess.error
57
58
  }
@@ -79,7 +80,7 @@ async function bashIntoRunningDockerContainer(containerNamePartial, entryPoint =
79
80
  async function dockerContainerIsRunning(containerNamePartial) {
80
81
  await throwIfDockerNotRunning()
81
82
 
82
- let childProcess = spawnSync('docker', ['container', 'ls'], {encoding: 'utf8'})
83
+ let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
83
84
  if (childProcess.error) {
84
85
  throw childProcess.error
85
86
  }
@@ -188,12 +189,12 @@ async function createTarball(directoryToTarball, outputDirectory, tarballName, c
188
189
  }
189
190
  }
190
191
 
191
- let options = {gzip: true, file: tarballPath}
192
+ let options = { gzip: true, file: tarballPath }
192
193
  if (!!cwd) {
193
194
  options.cwd = cwd
194
195
  }
195
196
 
196
- await tar.c(options, [directoryToTarball])
197
+ tar.c(options, [directoryToTarball])
197
198
  }
198
199
 
199
200
  async function dockerCompose(command, projectName, dockerRelativeDirectory = 'docker', detached = false) {
@@ -210,7 +211,7 @@ async function dockerCompose(command, projectName, dockerRelativeDirectory = 'do
210
211
 
211
212
  await throwIfDockerNotRunning()
212
213
 
213
- const dockerSpawnOptions = {...defaultSpawnOptions, cwd: dockerWorkingDir, stdio: 'inherit'}
214
+ const dockerSpawnOptions = { ...defaultSpawnOptions, cwd: dockerWorkingDir, stdio: 'inherit' }
214
215
 
215
216
  let args = ['--project-name', projectName, command]
216
217
  if (detached) {
@@ -255,7 +256,7 @@ async function dotnetPack(projectDirectoryPath, release = true) {
255
256
  args.push('-c', 'Release')
256
257
  }
257
258
 
258
- const spawnOptions = {...defaultSpawnOptions, cwd: projectDirectoryPath}
259
+ const spawnOptions = { ...defaultSpawnOptions, cwd: projectDirectoryPath }
259
260
  logCommand('dotnet', args, spawnOptions)
260
261
  await waitForProcess(spawn('dotnet', args, spawnOptions))
261
262
  }
@@ -270,7 +271,7 @@ async function dotnetNugetPublish(projectDirectoryPath, csprojFilename, release
270
271
 
271
272
  const packageName = await getPackageName(projectDirectoryPath, csprojFilename)
272
273
  console.log('publishing package ' + packageName)
273
- const spawnOptions = {...defaultSpawnOptions, cwd: packageDir}
274
+ const spawnOptions = { ...defaultSpawnOptions, cwd: packageDir }
274
275
  await waitForProcess(spawn('dotnet', [
275
276
  'nuget',
276
277
  'push',
@@ -304,21 +305,21 @@ async function dotnetDllCommand(relativeDllPath, argsArray, cwd = null, useStdin
304
305
 
305
306
  let args = [relativeDllPath, ...argsArray]
306
307
 
307
- let spawnOptions = {...defaultSpawnOptions}
308
+ let spawnOptions = { ...defaultSpawnOptions }
308
309
  if (cwd !== null) {
309
- spawnOptions = {...spawnOptions, cwd: cwd}
310
+ spawnOptions = { ...spawnOptions, cwd: cwd }
310
311
  }
311
312
  if (useStdin) {
312
- spawnOptions = {...spawnOptions, stdio: 'inherit'}
313
+ spawnOptions = { ...spawnOptions, stdio: 'inherit' }
313
314
  }
314
315
 
315
316
  return waitForProcess(spawn('dotnet', args, spawnOptions))
316
317
  }
317
318
 
318
319
  async function dotnetPublish(cwd = null, outputDir = 'publish') {
319
- let spawnOptions = {...defaultSpawnOptions}
320
+ let spawnOptions = { ...defaultSpawnOptions }
320
321
  if (!!cwd) {
321
- spawnOptions = {...spawnOptions, cwd: cwd}
322
+ spawnOptions = { ...spawnOptions, cwd: cwd }
322
323
  }
323
324
  if (!outputDir) {
324
325
  outputDir = 'publish'
@@ -330,7 +331,7 @@ async function dotnetPublish(cwd = null, outputDir = 'publish') {
330
331
  async function dotnetDbMigrationsList(dbContextName, relativeDbMigratorDirectoryPath) {
331
332
  throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
332
333
  throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
333
- let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
334
+ let spawnOptions = { ...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath }
334
335
  return waitForProcess(spawn('dotnet', ['ef', 'migrations', 'list', '--context', dbContextName], spawnOptions))
335
336
  }
336
337
 
@@ -342,23 +343,121 @@ async function dotnetDbMigrate(dbContextName, relativeDbMigratorDirectoryPath, m
342
343
  args.push(migrationName)
343
344
  }
344
345
  args = [...args, '--context', dbContextName]
345
- let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
346
+ let spawnOptions = { ...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath }
346
347
  return waitForProcess(spawn('dotnet', args, spawnOptions))
347
348
  }
348
349
 
349
- async function dotnetDbAddMigration(dbContextName, relativeDbMigratorDirectoryPath, migrationName) {
350
+ async function dotnetDbAddMigration(dbContextName, relativeDbMigratorDirectoryPath, migrationName, withBoilerplate = false) {
350
351
  throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
351
352
  throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
352
353
  throwIfRequiredIsFalsy(migrationName, 'migrationName')
353
- let args = ['ef', 'migrations', 'add', migrationName, '--context', dbContextName, '-o', `Migrations/${dbContextName}Migrations`]
354
- let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
355
- return waitForProcess(spawn('dotnet', args, spawnOptions))
354
+
355
+ const migrationsOutputDir = `Migrations/${dbContextName}Migrations`
356
+
357
+ let args = ['ef', 'migrations', 'add', migrationName, '--context', dbContextName, '-o', migrationsOutputDir]
358
+ let spawnOptions = { ...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath }
359
+ await waitForProcess(spawn('dotnet', args, spawnOptions))
360
+
361
+ if (withBoilerplate) {
362
+ await dotnetDbAddMigrationBoilerplate(dbContextName, relativeDbMigratorDirectoryPath, migrationName)
363
+ }
364
+ }
365
+
366
+ async function dotnetDbAddMigrationBoilerplate(dbContextName, relativeDbMigratorDirectoryPath, migrationName) {
367
+ console.log(`Attempting to write boilerplate to generated migration C# file`)
368
+
369
+ const migrationsOutputDir = `Migrations/${dbContextName}Migrations`
370
+ const dirPath = path.join(relativeDbMigratorDirectoryPath, migrationsOutputDir)
371
+
372
+ console.log(`Checking for generated C# file in directory: ${dirPath}`)
373
+
374
+ const filenames = fs.readdirSync(dirPath).filter(fn => fn.endsWith(`${migrationName}.cs`));
375
+ if (!filenames || filenames.length === 0) {
376
+ console.log(`Unable to add boilerplate - could not find auto generated file in directory: ${dirPath}`)
377
+ }
378
+ const filename = filenames[0]
379
+ const filePath = path.join(dirPath, filename)
380
+
381
+ if (!fs.existsSync(filePath)) {
382
+ console.log(`Could not find the file to add boilerplate to at: ${filePath}`)
383
+ return
384
+ }
385
+
386
+ console.log(`Auto generated C# file to modify: ${filePath}`)
387
+
388
+ const usingLine = 'using MikeyT.DbMigrations;'
389
+ const upLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}.sql");`
390
+ const downLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}_Down.sql");`
391
+
392
+ const fileContents = await fsp.readFile(filePath, { encoding: 'utf8' })
393
+ let lines = fileContents.replaceAll('\r', '').split('\n')
394
+
395
+ let newLines = []
396
+
397
+ newLines.push(lines[0].trim())
398
+ newLines.push(usingLine)
399
+
400
+ let addUpLine = false
401
+ let addDownLine = false
402
+ let skipNextLineIfBlank = false
403
+ for (let i = 1; i < lines.length; i++) {
404
+ if (skipNextLineIfBlank && lines[i].trim().length === 0) {
405
+ skipNextLineIfBlank = false
406
+ continue
407
+ }
408
+ if (addUpLine) {
409
+ let newLine = lines[i].replace('{', `{\n\t\t\t${upLine}`)
410
+ newLines.push(newLine)
411
+ addUpLine = false
412
+ skipNextLineIfBlank = true
413
+ continue
414
+ }
415
+ if (addDownLine) {
416
+ let newLine = lines[i].replace('{', `{\n\t\t\t${downLine}`)
417
+ newLines.push(newLine)
418
+ addDownLine = false
419
+ skipNextLineIfBlank = true
420
+ continue
421
+ }
422
+ newLines.push(lines[i])
423
+ if (lines[i].includes('void Up')) {
424
+ addUpLine = true
425
+ }
426
+ if (lines[i].includes('void Down')) {
427
+ addDownLine = true
428
+ }
429
+ }
430
+
431
+ const newFileContents = newLines.join('\n')
432
+
433
+ await fsp.writeFile(filePath, newFileContents, { encoding: 'utf8' })
434
+
435
+ console.log(`Updated file with boilerplate - please ensure it is correct: ${filePath}`)
436
+
437
+ const upScriptPath = path.join(relativeDbMigratorDirectoryPath, `Scripts/${migrationName}.sql`)
438
+ const downScriptPath = path.join(relativeDbMigratorDirectoryPath, `Scripts/${migrationName}_Down.sql`)
439
+
440
+ console.log('Creating corresponding empty sql files (no action will be taken if they already exist):')
441
+ console.log(` - ${upScriptPath}`)
442
+ console.log(` - ${downScriptPath}`)
443
+
444
+ if (!fs.existsSync(upScriptPath)) {
445
+ await fsp.writeFile(upScriptPath, '', { encoding: 'utf8' })
446
+ } else {
447
+ console.log('Skipping Up sql script (already exists)')
448
+ }
449
+
450
+ if (!fs.existsSync(downScriptPath)) {
451
+ await fsp.writeFile(downScriptPath, '', { encoding: 'utf8' })
452
+ } else {
453
+ console.log('Skipping Down sql script (already exists)')
454
+ }
356
455
  }
357
456
 
358
457
  async function dotnetDbRemoveMigration(dbContextName, relativeDbMigratorDirectoryPath) {
359
458
  throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
360
459
  throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
361
- let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
460
+ let spawnOptions = { ...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath }
362
461
  return waitForProcess(spawn('dotnet', ['ef', 'migrations', 'remove', '--context', dbContextName], spawnOptions))
363
462
  }
364
463
 
@@ -374,6 +473,120 @@ function throwIfRequiredArrayIsFalsyOrEmpty(requiredArrayArg, argName) {
374
473
  }
375
474
  }
376
475
 
476
+ async function generateCertWithOpenSsl(url, outputDirectory = './cert') {
477
+ if (!url) {
478
+ throw Error('Param \'url\' is required.')
479
+ }
480
+
481
+ // Check if openssl is installed
482
+ let macOpenSslPath
483
+ if (process.platform !== 'darwin') {
484
+ if (!which.sync('openssl', { nothrow: true })) {
485
+ throw Error('openssl is required but was not found in the path')
486
+ }
487
+ } else {
488
+ console.log('*****************************************************************')
489
+ console.log('* Important: mac support requires openssl be installed via brew *')
490
+ console.log('*****************************************************************')
491
+
492
+ macOpenSslPath = `${getBrewOpensslPath()}/bin/openssl`
493
+ console.log(`openssl path: ${macOpenSslPath}`)
494
+ }
495
+
496
+ console.log('openssl is installed, continuing...')
497
+
498
+ fse.mkdirpSync(outputDirectory)
499
+
500
+ const keyName = url + '.key'
501
+ const crtName = url + '.crt'
502
+ const pfxName = url + '.pfx'
503
+
504
+ pfxPath = path.join(outputDirectory, pfxName)
505
+
506
+ if (fse.pathExistsSync(pfxPath)) {
507
+ throw Error(`File ${pfxPath} already exists. Delete this first if you want to generate a new version.`)
508
+ }
509
+
510
+ console.log(`attempting to generate cert ${pfxName}`)
511
+
512
+ const genCertSpawnArgs = { ...defaultSpawnOptions, cwd: outputDirectory }
513
+
514
+ const genKeyAndCrtArgs = `req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout ${keyName} -out ${crtName} -subj "/CN=${url}" -addext "subjectAltName=DNS:${url},IP:127.0.0.1"`.split(' ')
515
+
516
+ const cmd = process.platform !== 'darwin' ? 'openssl' : macOpenSslPath
517
+
518
+ console.log('cmd: ' + cmd)
519
+
520
+ await waitForProcess(spawn(cmd, genKeyAndCrtArgs, genCertSpawnArgs))
521
+
522
+ console.log('converting key and crt to pfx...')
523
+
524
+ const convertToPfxArgs = `pkcs12 -certpbe AES-256-CBC -export -out ${pfxName} -aes256 -inkey ${keyName} -in ${crtName} -password pass:`.split(' ')
525
+
526
+ await waitForProcess(spawn(cmd, convertToPfxArgs, genCertSpawnArgs))
527
+ }
528
+
529
+ function getBrewOpensslPath() {
530
+ let childProc = spawnSync('brew', ['--prefix', 'openssl'], { encoding: 'utf-8' })
531
+ if (childProc.error) {
532
+ throw Error('error attempting to find openssl installed by brew')
533
+ }
534
+
535
+ const output = childProc.stdout
536
+
537
+ if (!output || output.length === 0 || output.toLowerCase().startsWith('error')) {
538
+ throw Error('unexpected output while attempting to find openssl')
539
+ }
540
+
541
+ return output.replace('\n', '')
542
+ }
543
+
544
+ async function winInstallCert(urlOrCertFilename, relativeCertDirectoryPath = './cert') {
545
+ if (!urlOrCertFilename) {
546
+ throw Error('Param \'urlOrCertFilename\' is required.')
547
+ }
548
+
549
+ console.log('******************************\n* Requires admin permissions *\n******************************')
550
+
551
+ let certName = urlOrCertFilename.endsWith('.pfx') ? urlOrCertFilename : urlOrCertFilename + '.pfx'
552
+
553
+ const certPath = path.join(process.cwd(), relativeCertDirectoryPath, certName)
554
+
555
+ if (!fse.pathExistsSync(certPath)) {
556
+ throw Error(`File ${certPath} does not exist. Generate this first if you want to install it.`)
557
+ }
558
+
559
+ const psCommand = `$env:PSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine'); Import-PfxCertificate -FilePath '${certPath}' -CertStoreLocation Cert:\\LocalMachine\\Root`
560
+
561
+ await waitForProcess(spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psCommand]))
562
+ }
563
+
564
+ async function winUninstallCert(urlOrSubject) {
565
+ if (!urlOrSubject) {
566
+ throw Error('Param \'urlOrSubject\' is required.')
567
+ }
568
+
569
+ console.log('******************************\n* Requires admin permissions *\n******************************')
570
+
571
+ const psCommand = `$env:PSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine'); Get-ChildItem Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -match '${urlOrSubject}' } | Remove-Item`;
572
+
573
+ await waitForProcess(spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psCommand]))
574
+ }
575
+
576
+ function linuxInstallCert() {
577
+ const instructions = `
578
+ Automated linux cert install not supported (chrome does not use system certs without significant extra configuration).
579
+
580
+ Manual Instructions:
581
+ - In Chrome, go to chrome://settings/certificates
582
+ - Select Authorities -> import
583
+ - Select your generated .crt file (in the ./cert/ directory by default - if you haven't generated it, see the generateCertWithOpenSsl method)
584
+ - Check box for "Trust certificate for identifying websites"
585
+ - Click OK
586
+ - Reload site`
587
+ console.log(instructions)
588
+ }
589
+
377
590
  exports.defaultSpawnOptions = defaultSpawnOptions
378
591
  exports.defaultSpawnOptionsWithInput = defaultSpawnOptionsWithInput
379
592
  exports.waitForProcess = waitForProcess
@@ -396,3 +609,7 @@ exports.dotnetDbMigrationsList = dotnetDbMigrationsList
396
609
  exports.dotnetDbMigrate = dotnetDbMigrate
397
610
  exports.dotnetDbAddMigration = dotnetDbAddMigration
398
611
  exports.dotnetDbRemoveMigration = dotnetDbRemoveMigration
612
+ exports.generateCertWithOpenSsl = generateCertWithOpenSsl
613
+ exports.winInstallCert = winInstallCert
614
+ exports.winUninstallCert = winUninstallCert
615
+ exports.linuxInstallCert = linuxInstallCert
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikeyt23/node-cli-utils",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Some node cli utility functions",
5
5
  "author": "Mike Thompson",
6
6
  "license": "MIT",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "main": "index.js",
17
17
  "dependencies": {
18
- "tar": "^6.1.11",
19
- "which": "^2.0.2"
18
+ "fs-extra": "^11.1.1",
19
+ "tar": "^6.1.15",
20
+ "which": "^3.0.1"
20
21
  }
21
22
  }