@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.
- package/README.md +6 -1
- package/index.js +198 -18
- 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
|
|
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 {
|
|
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
|
|
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'], {
|
|
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'], {
|
|
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,
|
|
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'], {
|
|
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 = {
|
|
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 || '
|
|
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 = {
|
|
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 = {
|
|
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 = {
|
|
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
|
-
|
|
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
|