@mikeyt23/node-cli-utils 1.2.6 → 1.3.1

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 (3) hide show
  1. package/README.md +6 -1
  2. package/index.js +198 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,10 +6,15 @@ Simple node cli utils to:
6
6
  - Copy env template file to env file
7
7
  - Sync new env values from template to "real" env files
8
8
  - Manage all env values in root files that then get copied to other locations
9
+ - Dotnet helper methods:
10
+ - Pack and publish a NuGet package
11
+ - Wrapper commands to install or update dotnet ef tool
12
+ - Wrapper commands to spawn dotnet run and publish
13
+ - Helper methods for DB migration commands (see [mikey-t/db-migrations-dotnet](https://github.com/mikey-t/db-migrations-dotnet))
9
14
 
10
15
  # Env Utility Function Notes
11
16
 
12
- A project can have multiple locations that requires the same env values. For example, you might have a database that needs to be used by a docker project, your main app and also a db migration project. Rather than having to remember where each of the .env files is, we want to use the root directory and overwrite subdirectory env files with the ones in the root. We also want to add new `.env.template` values to the root `.env` files and propagate them to subdirectories - all without having to know about anything about any of the .env files except the ones in the root of the project.
17
+ A project can have multiple locations that require the same env values. For example, you might have a database that needs to be used by a docker project, your main app and also a db migration project. Rather than having to remember where each of the .env files is, we want to use the root directory and overwrite subdirectory env files with the ones in the root. We also want to add new `.env.template` values to the root `.env` files and propagate them to subdirectories - all without having to know about anything about any of the .env files except the ones in the root of the project.
13
18
 
14
19
  # Examples
15
20
 
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs')
2
2
  const fsp = require('fs').promises
3
3
  const which = require('which')
4
- const { spawn, spawnSync } = require('child_process')
4
+ const {spawn, spawnSync} = require('child_process')
5
5
  const path = require('path')
6
6
  const tar = require('tar')
7
7
 
@@ -9,7 +9,7 @@ const defaultSpawnOptions = {
9
9
  shell: true,
10
10
  stdio: ['ignore', 'inherit', 'inherit']
11
11
  }
12
- const spawnOptionsWithInput = { ...defaultSpawnOptions, stdio: 'inherit' }
12
+ const defaultSpawnOptionsWithInput = {...defaultSpawnOptions, stdio: 'inherit'}
13
13
 
14
14
  function waitForProcess(childProcess) {
15
15
  return new Promise((resolve, reject) => {
@@ -34,13 +34,12 @@ async function overwriteEnvFile(fromPath, toPath) {
34
34
  await copyEnv(fromPath, toPath)
35
35
  }
36
36
 
37
-
38
37
  async function throwIfDockerNotRunning() {
39
38
  if (!which.sync('docker')) {
40
39
  throw Error('docker command not found')
41
40
  }
42
41
 
43
- let childProcess = spawnSync('docker', ['info'], { encoding: 'utf8' })
42
+ let childProcess = spawnSync('docker', ['info'], {encoding: 'utf8'})
44
43
  if (childProcess.error) {
45
44
  throw childProcess.error
46
45
  }
@@ -52,7 +51,7 @@ async function throwIfDockerNotRunning() {
52
51
  async function bashIntoRunningDockerContainer(containerNamePartial, entryPoint = 'bash') {
53
52
  await throwIfDockerNotRunning()
54
53
 
55
- let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
54
+ let childProcess = spawnSync('docker', ['container', 'ls'], {encoding: 'utf8'})
56
55
  if (childProcess.error) {
57
56
  throw childProcess.error
58
57
  }
@@ -74,13 +73,13 @@ async function bashIntoRunningDockerContainer(containerNamePartial, entryPoint =
74
73
  console.log('full container name: ' + containerName)
75
74
 
76
75
  const args = ['exec', '-it', containerName, entryPoint]
77
- return waitForProcess(spawn('docker', args, spawnOptionsWithInput))
76
+ return waitForProcess(spawn('docker', args, defaultSpawnOptionsWithInput))
78
77
  }
79
78
 
80
79
  async function dockerContainerIsRunning(containerNamePartial) {
81
80
  await throwIfDockerNotRunning()
82
81
 
83
- let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
82
+ let childProcess = spawnSync('docker', ['container', 'ls'], {encoding: 'utf8'})
84
83
  if (childProcess.error) {
85
84
  throw childProcess.error
86
85
  }
@@ -189,7 +188,7 @@ async function createTarball(directoryToTarball, outputDirectory, tarballName, c
189
188
  }
190
189
  }
191
190
 
192
- let options = { gzip: true, file: tarballPath }
191
+ let options = {gzip: true, file: tarballPath}
193
192
  if (!!cwd) {
194
193
  options.cwd = cwd
195
194
  }
@@ -197,12 +196,12 @@ async function createTarball(directoryToTarball, outputDirectory, tarballName, c
197
196
  await tar.c(options, [directoryToTarball])
198
197
  }
199
198
 
200
- async function dockerCompose(command, projectName, dockerRelativeDirectory, detached = false) {
199
+ async function dockerCompose(command, projectName, dockerRelativeDirectory = 'docker', detached = false) {
201
200
  if (!projectName || projectName.length === 0) {
202
201
  throw new Error('projectName is required')
203
202
  }
204
203
 
205
- const dockerRelativeDir = dockerRelativeDirectory || 'docker'
204
+ const dockerRelativeDir = dockerRelativeDirectory || './'
206
205
  const dockerWorkingDir = path.join(process.cwd(), dockerRelativeDir)
207
206
 
208
207
  if (!fs.existsSync(dockerWorkingDir)) {
@@ -211,7 +210,7 @@ async function dockerCompose(command, projectName, dockerRelativeDirectory, deta
211
210
 
212
211
  await throwIfDockerNotRunning()
213
212
 
214
- const dockerSpawnOptions = { ...defaultSpawnOptions, cwd: dockerWorkingDir }
213
+ const dockerSpawnOptions = {...defaultSpawnOptions, cwd: dockerWorkingDir, stdio: 'inherit'}
215
214
 
216
215
  let args = ['--project-name', projectName, command]
217
216
  if (detached) {
@@ -222,19 +221,19 @@ async function dockerCompose(command, projectName, dockerRelativeDirectory, deta
222
221
  }
223
222
 
224
223
  async function dockerDepsUp(projectName, dockerRelativeDirectory) {
225
- return dockerCompose('up', projectName, dockerRelativeDirectory)
224
+ return await dockerCompose('up', projectName, dockerRelativeDirectory)
226
225
  }
227
226
 
228
227
  async function dockerDepsUpDetached(projectName, dockerRelativeDirectory) {
229
- return dockerCompose('up', projectName, dockerRelativeDirectory, true)
228
+ return await dockerCompose('up', projectName, dockerRelativeDirectory, true)
230
229
  }
231
230
 
232
231
  async function dockerDepsDown(projectName, dockerRelativeDirectory) {
233
- return dockerCompose('down', projectName, dockerRelativeDirectory)
232
+ return await dockerCompose('down', projectName, dockerRelativeDirectory)
234
233
  }
235
234
 
236
235
  async function dockerDepsStop(projectName, dockerRelativeDirectory) {
237
- return dockerCompose('stop', projectName, dockerRelativeDirectory)
236
+ return await dockerCompose('stop', projectName, dockerRelativeDirectory)
238
237
  }
239
238
 
240
239
  async function dotnetBuild(release = true) {
@@ -256,7 +255,7 @@ async function dotnetPack(projectDirectoryPath, release = true) {
256
255
  args.push('-c', 'Release')
257
256
  }
258
257
 
259
- const spawnOptions = { ...defaultSpawnOptions, cwd: projectDirectoryPath }
258
+ const spawnOptions = {...defaultSpawnOptions, cwd: projectDirectoryPath}
260
259
  logCommand('dotnet', args, spawnOptions)
261
260
  await waitForProcess(spawn('dotnet', args, spawnOptions))
262
261
  }
@@ -271,7 +270,7 @@ async function dotnetNugetPublish(projectDirectoryPath, csprojFilename, release
271
270
 
272
271
  const packageName = await getPackageName(projectDirectoryPath, csprojFilename)
273
272
  console.log('publishing package ' + packageName)
274
- const spawnOptions = { ...defaultSpawnOptions, cwd: packageDir }
273
+ const spawnOptions = {...defaultSpawnOptions, cwd: packageDir}
275
274
  await waitForProcess(spawn('dotnet', [
276
275
  'nuget',
277
276
  'push',
@@ -294,12 +293,187 @@ async function getPackageName(projectPath, csprojFilename) {
294
293
  return `${namespace}.${version}.nupkg`
295
294
  }
296
295
 
297
- async function logCommand(command, args, spawnOptions) {
296
+ function logCommand(command, args, spawnOptions) {
298
297
  console.log('running command: ' + `${command} ${args.join(' ')}`)
299
298
  console.log('with spawn options: ' + JSON.stringify(spawnOptions))
300
299
  }
301
300
 
301
+ async function dotnetDllCommand(relativeDllPath, argsArray, cwd = null, useStdin = false) {
302
+ throwIfRequiredIsFalsy(relativeDllPath, 'relativeDllPath')
303
+ throwIfRequiredArrayIsFalsyOrEmpty(argsArray, 'argsArray')
304
+
305
+ let args = [relativeDllPath, ...argsArray]
306
+
307
+ let spawnOptions = {...defaultSpawnOptions}
308
+ if (cwd !== null) {
309
+ spawnOptions = {...spawnOptions, cwd: cwd}
310
+ }
311
+ if (useStdin) {
312
+ spawnOptions = {...spawnOptions, stdio: 'inherit'}
313
+ }
314
+
315
+ return waitForProcess(spawn('dotnet', args, spawnOptions))
316
+ }
317
+
318
+ async function dotnetPublish(cwd = null, outputDir = 'publish') {
319
+ let spawnOptions = {...defaultSpawnOptions}
320
+ if (!!cwd) {
321
+ spawnOptions = {...spawnOptions, cwd: cwd}
322
+ }
323
+ if (!outputDir) {
324
+ outputDir = 'publish'
325
+ }
326
+ let args = ['publish', '-o', outputDir]
327
+ return waitForProcess(spawn('dotnet', args, spawnOptions))
328
+ }
329
+
330
+ async function dotnetDbMigrationsList(dbContextName, relativeDbMigratorDirectoryPath) {
331
+ throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
332
+ throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
333
+ let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
334
+ return waitForProcess(spawn('dotnet', ['ef', 'migrations', 'list', '--context', dbContextName], spawnOptions))
335
+ }
336
+
337
+ async function dotnetDbMigrate(dbContextName, relativeDbMigratorDirectoryPath, migrationName = '') {
338
+ throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
339
+ throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
340
+ let args = ['ef', 'database', 'update']
341
+ if (!!migrationName) {
342
+ args.push(migrationName)
343
+ }
344
+ args = [...args, '--context', dbContextName]
345
+ let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
346
+ return waitForProcess(spawn('dotnet', args, spawnOptions))
347
+ }
348
+
349
+ async function dotnetDbAddMigration(dbContextName, relativeDbMigratorDirectoryPath, migrationName, withBoilerplate = false) {
350
+ throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
351
+ throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
352
+ throwIfRequiredIsFalsy(migrationName, 'migrationName')
353
+
354
+ const migrationsOutputDir = `Migrations/${dbContextName}Migrations`
355
+
356
+ let args = ['ef', 'migrations', 'add', migrationName, '--context', dbContextName, '-o', migrationsOutputDir]
357
+ let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
358
+ await waitForProcess(spawn('dotnet', args, spawnOptions))
359
+
360
+ if (withBoilerplate) {
361
+ await dotnetDbAddMigrationBoilerplate(dbContextName, relativeDbMigratorDirectoryPath, migrationName)
362
+ }
363
+ }
364
+
365
+ async function dotnetDbAddMigrationBoilerplate(dbContextName, relativeDbMigratorDirectoryPath, migrationName) {
366
+ console.log(`Attempting to write boilerplate to generated migration C# file`)
367
+
368
+ const migrationsOutputDir = `Migrations/${dbContextName}Migrations`
369
+ const dirPath = path.join(relativeDbMigratorDirectoryPath, migrationsOutputDir)
370
+
371
+ console.log(`Checking for generated C# file in directory: ${dirPath}`)
372
+
373
+ const filenames = fs.readdirSync(dirPath).filter(fn => fn.endsWith(`${migrationName}.cs`));
374
+ if (!filenames || filenames.length === 0) {
375
+ console.log(`Unable to add boilerplate - could not find auto generated file in directory: ${dirPath}`)
376
+ }
377
+ const filename = filenames[0]
378
+ const filePath = path.join(dirPath, filename)
379
+
380
+ if (!fs.existsSync(filePath)) {
381
+ console.log(`Could not find the file to add boilerplate to at: ${filePath}`)
382
+ return
383
+ }
384
+
385
+ console.log(`Auto generated C# file to modify: ${filePath}`)
386
+
387
+ const usingLine = 'using MikeyT.DbMigrations;'
388
+ const upLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}.sql");`
389
+ const downLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}_Down.sql");`
390
+
391
+ const fileContents = await fsp.readFile(filePath, {encoding: 'utf8'})
392
+ let lines = fileContents.replaceAll('\r', '').split('\n')
393
+
394
+ let newLines = []
395
+
396
+ newLines.push(lines[0].trim())
397
+ newLines.push(usingLine)
398
+
399
+ let addUpLine = false
400
+ let addDownLine = false
401
+ let skipNextLineIfBlank = false
402
+ for (let i = 1; i < lines.length; i++) {
403
+ if (skipNextLineIfBlank && lines[i].trim().length === 0) {
404
+ skipNextLineIfBlank = false
405
+ continue
406
+ }
407
+ if (addUpLine) {
408
+ let newLine = lines[i].replace('{', `{\n\t\t\t${upLine}`)
409
+ newLines.push(newLine)
410
+ addUpLine = false
411
+ skipNextLineIfBlank = true
412
+ continue
413
+ }
414
+ if (addDownLine) {
415
+ let newLine = lines[i].replace('{', `{\n\t\t\t${downLine}`)
416
+ newLines.push(newLine)
417
+ addDownLine = false
418
+ skipNextLineIfBlank = true
419
+ continue
420
+ }
421
+ newLines.push(lines[i])
422
+ if (lines[i].includes('void Up')) {
423
+ addUpLine = true
424
+ }
425
+ if (lines[i].includes('void Down')) {
426
+ addDownLine = true
427
+ }
428
+ }
429
+
430
+ const newFileContents = newLines.join('\n')
431
+
432
+ await fsp.writeFile(filePath, newFileContents, {encoding: 'utf8'})
433
+
434
+ console.log(`Updated file with boilerplate - please ensure it is correct: ${filePath}`)
435
+
436
+ const upScriptPath = path.join(relativeDbMigratorDirectoryPath, `Scripts/${migrationName}.sql`)
437
+ const downScriptPath = path.join(relativeDbMigratorDirectoryPath, `Scripts/${migrationName}_Down.sql`)
438
+
439
+ console.log('Creating corresponding empty sql files (no action will be taken if they already exist):')
440
+ console.log(` - ${upScriptPath}`)
441
+ console.log(` - ${downScriptPath}`)
442
+
443
+ if (!fs.existsSync(upScriptPath)) {
444
+ await fsp.writeFile(upScriptPath, '', {encoding: 'utf8'})
445
+ } else {
446
+ console.log('Skipping Up sql script (already exists)')
447
+ }
448
+
449
+ if (!fs.existsSync(downScriptPath)) {
450
+ await fsp.writeFile(downScriptPath, '', {encoding: 'utf8'})
451
+ } else {
452
+ console.log('Skipping Down sql script (already exists)')
453
+ }
454
+ }
455
+
456
+ async function dotnetDbRemoveMigration(dbContextName, relativeDbMigratorDirectoryPath) {
457
+ throwIfRequiredIsFalsy(dbContextName, 'dbContextName')
458
+ throwIfRequiredIsFalsy(relativeDbMigratorDirectoryPath, 'relativeDbMigratorDirectoryPath')
459
+ let spawnOptions = {...defaultSpawnOptions, cwd: relativeDbMigratorDirectoryPath}
460
+ return waitForProcess(spawn('dotnet', ['ef', 'migrations', 'remove', '--context', dbContextName], spawnOptions))
461
+ }
462
+
463
+ function throwIfRequiredIsFalsy(requiredArg, argName) {
464
+ if (!requiredArg) {
465
+ throw Error(`${argName} is required`)
466
+ }
467
+ }
468
+
469
+ function throwIfRequiredArrayIsFalsyOrEmpty(requiredArrayArg, argName) {
470
+ if (!requiredArrayArg || requiredArrayArg.length === 0 || !Array.isArray(requiredArrayArg)) {
471
+ throw Error(`${argName} array is required`)
472
+ }
473
+ }
474
+
302
475
  exports.defaultSpawnOptions = defaultSpawnOptions
476
+ exports.defaultSpawnOptionsWithInput = defaultSpawnOptionsWithInput
303
477
  exports.waitForProcess = waitForProcess
304
478
  exports.copyNewEnvValues = copyNewEnvValues
305
479
  exports.overwriteEnvFile = overwriteEnvFile
@@ -314,3 +488,9 @@ exports.dockerDepsStop = dockerDepsStop
314
488
  exports.dotnetBuild = dotnetBuild
315
489
  exports.dotnetPack = dotnetPack
316
490
  exports.dotnetNugetPublish = dotnetNugetPublish
491
+ exports.dotnetDllCommand = dotnetDllCommand
492
+ exports.dotnetPublish = dotnetPublish
493
+ exports.dotnetDbMigrationsList = dotnetDbMigrationsList
494
+ exports.dotnetDbMigrate = dotnetDbMigrate
495
+ exports.dotnetDbAddMigration = dotnetDbAddMigration
496
+ exports.dotnetDbRemoveMigration = dotnetDbRemoveMigration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikeyt23/node-cli-utils",
3
- "version": "1.2.6",
3
+ "version": "1.3.1",
4
4
  "description": "Some node cli utility functions",
5
5
  "author": "Mike Thompson",
6
6
  "license": "MIT",