@mikeyt23/node-cli-utils 1.0.0 → 1.2.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/LICENSE +6 -6
- package/README.md +102 -102
- package/index.js +245 -166
- package/package.json +22 -20
package/LICENSE
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Copyright 2021 Michael Thompson
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
-
|
|
5
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
-
|
|
1
|
+
Copyright 2021 Michael Thompson
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
7
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
# @mikeyt23/node-cli-utils
|
|
2
|
-
|
|
3
|
-
Simple node cli utils to:
|
|
4
|
-
|
|
5
|
-
- Wait for a node spawned process
|
|
6
|
-
- Copy env template file to env file
|
|
7
|
-
- Sync new env values from template to "real" env files
|
|
8
|
-
- Manage all env values in root files that then get copied to other locations
|
|
9
|
-
|
|
10
|
-
# Env Utility Function Notes
|
|
11
|
-
|
|
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.
|
|
13
|
-
|
|
14
|
-
# Examples
|
|
15
|
-
|
|
16
|
-
An example syncEnvFiles gulp task function:
|
|
17
|
-
|
|
18
|
-
```JavaScript
|
|
19
|
-
const projectDir = 'src/YourProjectDir/'
|
|
20
|
-
const serverAppDir = `src/${projectDir}`
|
|
21
|
-
const clientAppDir = `src/${projectDir}client-app/`
|
|
22
|
-
const dockerDir = 'docker/'
|
|
23
|
-
|
|
24
|
-
async function syncEnvFiles() {
|
|
25
|
-
const rootServerEnv = './.env.server'
|
|
26
|
-
const rootClientEnv = './.env.client'
|
|
27
|
-
|
|
28
|
-
// Copy new values from root .env.[category].template to .env.[category]
|
|
29
|
-
await copyNewEnvValues('./.env.server.template', rootServerEnv)
|
|
30
|
-
await copyNewEnvValues('./.env.client.template', rootClientEnv)
|
|
31
|
-
|
|
32
|
-
// Copy root .env.[category] to subdirectory .env files (overwrites target env file)
|
|
33
|
-
// Server env to server app and docker directories
|
|
34
|
-
await overwriteEnvFile(rootServerEnv, path.join(serverAppDir, '.env'))
|
|
35
|
-
await overwriteEnvFile(rootServerEnv, path.join(dockerDir, '.env'))
|
|
36
|
-
// Client env to client app directory
|
|
37
|
-
await overwriteEnvFile(rootClientEnv, path.join(clientAppDir, '.env'))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
exports.syncEnvFiles = syncEnvFiles
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
> **WARNING**: Be sure to .gitignore `.env`, `.env.server` and `.env.client` files in the root **AND** the location they're being copied to. Only the template files with placeholder values should go in source control. For example, your .gitignore could have `**/.env*` and then `!.env.server.template` and `!.env.client.template`, etc.
|
|
44
|
-
|
|
45
|
-
Example gulp task calling docker-compose:
|
|
46
|
-
|
|
47
|
-
```JavaScript
|
|
48
|
-
const spawnOptions = {...defaultSpawnOptions}
|
|
49
|
-
|
|
50
|
-
const dockerDirPath = 'docker/'
|
|
51
|
-
const dockerDepsProjectName = 'your_project'
|
|
52
|
-
const dockerDepsComposeName = 'docker-compose.deps.yml'
|
|
53
|
-
|
|
54
|
-
const dockerSpawnOptions = {...spawnOptions, cwd: path.resolve(__dirname, dockerDirPath)}
|
|
55
|
-
|
|
56
|
-
async function dockerDepsUpDetached() {
|
|
57
|
-
return waitForProcess(spawn('docker-compose', ['--project-name', dockerDepsProjectName, '-f', dockerDepsComposeName, 'up', '-d'], dockerSpawnOptions))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
exports.dockerDepsUpDetached = series(syncEnvFiles, dockerDepsUpDetached)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Example gulp task using util method `throwIfDockerNotRunning`:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```JavaScript
|
|
67
|
-
async function dockerDepsUp() {
|
|
68
|
-
await throwIfDockerNotRunning()
|
|
69
|
-
return waitForProcess(spawn('docker-compose', ['--project-name', dockerDepsProjectName, '-f', dockerDepsComposeName, 'up'], dockerSpawnOptions))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
exports.dockerDepsUp = series(syncEnvFiles, dockerDepsUp)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Example gulp task using util method `dockerContainerIsRunning`:
|
|
76
|
-
|
|
77
|
-
```JavaScript
|
|
78
|
-
async function throwIfDockerDepsNotUp() {
|
|
79
|
-
const postgresIsRunning = await dockerContainerIsRunning(`${dockerDepsProjectName}_postgresql`)
|
|
80
|
-
if (!postgresIsRunning) {
|
|
81
|
-
throw 'Docker dependencies are not running'
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function runDbMigrator() {
|
|
86
|
-
return waitForProcess(spawn('dotnet', [`publish/${DotnetNamespace}.DbMigrator.dll`], migratorSpawnOptions))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
exports.dbMigrate = series(throwIfDockerDepsNotUp, parallel(syncEnvFiles, deleteMigratorPublishDir), publishMigrator, runDbMigrator)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Example gulp task using util method `bashIntoRunningDockerContainer` and `argv` to pass in param `--imageName`:
|
|
93
|
-
|
|
94
|
-
```JavaScript
|
|
95
|
-
async function dockerBashRunningContainer() {
|
|
96
|
-
await throwIfDockerNotRunning()
|
|
97
|
-
await bashIntoRunningDockerContainer(`${dockerDepsProjectName}_${argv['imageName']}`)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
exports.dockerBash = dockerBashRunningContainer
|
|
101
|
-
```
|
|
102
|
-
|
|
1
|
+
# @mikeyt23/node-cli-utils
|
|
2
|
+
|
|
3
|
+
Simple node cli utils to:
|
|
4
|
+
|
|
5
|
+
- Wait for a node spawned process
|
|
6
|
+
- Copy env template file to env file
|
|
7
|
+
- Sync new env values from template to "real" env files
|
|
8
|
+
- Manage all env values in root files that then get copied to other locations
|
|
9
|
+
|
|
10
|
+
# Env Utility Function Notes
|
|
11
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
# Examples
|
|
15
|
+
|
|
16
|
+
An example syncEnvFiles gulp task function:
|
|
17
|
+
|
|
18
|
+
```JavaScript
|
|
19
|
+
const projectDir = 'src/YourProjectDir/'
|
|
20
|
+
const serverAppDir = `src/${projectDir}`
|
|
21
|
+
const clientAppDir = `src/${projectDir}client-app/`
|
|
22
|
+
const dockerDir = 'docker/'
|
|
23
|
+
|
|
24
|
+
async function syncEnvFiles() {
|
|
25
|
+
const rootServerEnv = './.env.server'
|
|
26
|
+
const rootClientEnv = './.env.client'
|
|
27
|
+
|
|
28
|
+
// Copy new values from root .env.[category].template to .env.[category]
|
|
29
|
+
await copyNewEnvValues('./.env.server.template', rootServerEnv)
|
|
30
|
+
await copyNewEnvValues('./.env.client.template', rootClientEnv)
|
|
31
|
+
|
|
32
|
+
// Copy root .env.[category] to subdirectory .env files (overwrites target env file)
|
|
33
|
+
// Server env to server app and docker directories
|
|
34
|
+
await overwriteEnvFile(rootServerEnv, path.join(serverAppDir, '.env'))
|
|
35
|
+
await overwriteEnvFile(rootServerEnv, path.join(dockerDir, '.env'))
|
|
36
|
+
// Client env to client app directory
|
|
37
|
+
await overwriteEnvFile(rootClientEnv, path.join(clientAppDir, '.env'))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.syncEnvFiles = syncEnvFiles
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **WARNING**: Be sure to .gitignore `.env`, `.env.server` and `.env.client` files in the root **AND** the location they're being copied to. Only the template files with placeholder values should go in source control. For example, your .gitignore could have `**/.env*` and then `!.env.server.template` and `!.env.client.template`, etc.
|
|
44
|
+
|
|
45
|
+
Example gulp task calling docker-compose:
|
|
46
|
+
|
|
47
|
+
```JavaScript
|
|
48
|
+
const spawnOptions = {...defaultSpawnOptions}
|
|
49
|
+
|
|
50
|
+
const dockerDirPath = 'docker/'
|
|
51
|
+
const dockerDepsProjectName = 'your_project'
|
|
52
|
+
const dockerDepsComposeName = 'docker-compose.deps.yml'
|
|
53
|
+
|
|
54
|
+
const dockerSpawnOptions = {...spawnOptions, cwd: path.resolve(__dirname, dockerDirPath)}
|
|
55
|
+
|
|
56
|
+
async function dockerDepsUpDetached() {
|
|
57
|
+
return waitForProcess(spawn('docker-compose', ['--project-name', dockerDepsProjectName, '-f', dockerDepsComposeName, 'up', '-d'], dockerSpawnOptions))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
exports.dockerDepsUpDetached = series(syncEnvFiles, dockerDepsUpDetached)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Example gulp task using util method `throwIfDockerNotRunning`:
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
```JavaScript
|
|
67
|
+
async function dockerDepsUp() {
|
|
68
|
+
await throwIfDockerNotRunning()
|
|
69
|
+
return waitForProcess(spawn('docker-compose', ['--project-name', dockerDepsProjectName, '-f', dockerDepsComposeName, 'up'], dockerSpawnOptions))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
exports.dockerDepsUp = series(syncEnvFiles, dockerDepsUp)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Example gulp task using util method `dockerContainerIsRunning`:
|
|
76
|
+
|
|
77
|
+
```JavaScript
|
|
78
|
+
async function throwIfDockerDepsNotUp() {
|
|
79
|
+
const postgresIsRunning = await dockerContainerIsRunning(`${dockerDepsProjectName}_postgresql`)
|
|
80
|
+
if (!postgresIsRunning) {
|
|
81
|
+
throw 'Docker dependencies are not running'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function runDbMigrator() {
|
|
86
|
+
return waitForProcess(spawn('dotnet', [`publish/${DotnetNamespace}.DbMigrator.dll`], migratorSpawnOptions))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.dbMigrate = series(throwIfDockerDepsNotUp, parallel(syncEnvFiles, deleteMigratorPublishDir), publishMigrator, runDbMigrator)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Example gulp task using util method `bashIntoRunningDockerContainer` and `argv` to pass in param `--imageName`:
|
|
93
|
+
|
|
94
|
+
```JavaScript
|
|
95
|
+
async function dockerBashRunningContainer() {
|
|
96
|
+
await throwIfDockerNotRunning()
|
|
97
|
+
await bashIntoRunningDockerContainer(`${dockerDepsProjectName}_${argv['imageName']}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
exports.dockerBash = dockerBashRunningContainer
|
|
101
|
+
```
|
|
102
|
+
|
package/index.js
CHANGED
|
@@ -1,166 +1,245 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
if (
|
|
46
|
-
throw
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
for (const key of
|
|
112
|
-
newEnvDict[key] =
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (a
|
|
122
|
-
return 1
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const fse = require('fs-extra')
|
|
3
|
+
const fsp = require('fs').promises
|
|
4
|
+
const which = require('which')
|
|
5
|
+
const { spawn, spawnSync } = require('child_process')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
const tar = require('tar')
|
|
8
|
+
|
|
9
|
+
const defaultSpawnOptions = {
|
|
10
|
+
shell: true,
|
|
11
|
+
stdio: ['ignore', 'inherit', 'inherit']
|
|
12
|
+
}
|
|
13
|
+
const spawnOptionsWithInput = { ...defaultSpawnOptions, stdio: 'inherit' }
|
|
14
|
+
|
|
15
|
+
function waitForProcess(childProcess) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
childProcess.once('exit', (returnCode) => {
|
|
18
|
+
if (returnCode === 0) {
|
|
19
|
+
resolve(returnCode)
|
|
20
|
+
} else {
|
|
21
|
+
reject(returnCode)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
childProcess.once('error', (err) => {
|
|
25
|
+
reject(err)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function copyNewEnvValues(fromPath, toPath) {
|
|
31
|
+
await copyEnv(fromPath, toPath, false)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function overwriteEnvFile(fromPath, toPath) {
|
|
35
|
+
await copyEnv(fromPath, toPath)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async function throwIfDockerNotRunning() {
|
|
40
|
+
if (!which.sync('docker')) {
|
|
41
|
+
throw Error('docker command not found')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let childProcess = spawnSync('docker', ['info'], { encoding: 'utf8' })
|
|
45
|
+
if (childProcess.error) {
|
|
46
|
+
throw childProcess.error
|
|
47
|
+
}
|
|
48
|
+
if (!childProcess.stdout || childProcess.stdout.includes('ERROR: error during connect')) {
|
|
49
|
+
throw Error('docker is not running')
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function bashIntoRunningDockerContainer(containerNamePartial, entryPoint = 'bash') {
|
|
54
|
+
await throwIfDockerNotRunning()
|
|
55
|
+
|
|
56
|
+
let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
|
|
57
|
+
if (childProcess.error) {
|
|
58
|
+
throw childProcess.error
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let matchingLines = childProcess.stdout.split('\n').filter(line => line.includes(containerNamePartial))
|
|
62
|
+
|
|
63
|
+
if (!matchingLines || matchingLines.length === 0) {
|
|
64
|
+
throw Error('container is not running')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (matchingLines.length > 1) {
|
|
68
|
+
throw Error('more than one container matches the provided containerNamePartial ' + containerNamePartial)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let stringArray = matchingLines[0].split(/(\s+)/)
|
|
72
|
+
|
|
73
|
+
let containerName = stringArray[stringArray.length - 1]
|
|
74
|
+
|
|
75
|
+
console.log('full container name: ' + containerName)
|
|
76
|
+
|
|
77
|
+
const args = ['exec', '-it', containerName, entryPoint]
|
|
78
|
+
return waitForProcess(spawn('docker', args, spawnOptionsWithInput))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function dockerContainerIsRunning(containerNamePartial) {
|
|
82
|
+
await throwIfDockerNotRunning()
|
|
83
|
+
|
|
84
|
+
let childProcess = spawnSync('docker', ['container', 'ls'], { encoding: 'utf8' })
|
|
85
|
+
if (childProcess.error) {
|
|
86
|
+
throw childProcess.error
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let matchingLines = childProcess.stdout.split('\n').filter(l => l.includes(containerNamePartial))
|
|
90
|
+
|
|
91
|
+
return !!matchingLines && matchingLines.length > 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function copyEnv(fromPath, toPath, overrideAll = true) {
|
|
95
|
+
await ensureFile(fromPath, toPath)
|
|
96
|
+
|
|
97
|
+
let templateDict = getEnvDictionary(fromPath)
|
|
98
|
+
let envDict = getEnvDictionary(toPath)
|
|
99
|
+
|
|
100
|
+
// Determine what keys are missing from .env that are in template
|
|
101
|
+
let templateKeys = Object.keys(templateDict)
|
|
102
|
+
let envKeys = Object.keys(envDict)
|
|
103
|
+
let missingKeys = templateKeys.filter(k => !envKeys.includes(k))
|
|
104
|
+
|
|
105
|
+
if (missingKeys.length > 0) {
|
|
106
|
+
console.log(`Adding missing keys in ${toPath}: `, missingKeys)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Merge missing values with existing
|
|
110
|
+
let newEnvDict = {}
|
|
111
|
+
for (const [key, value] of Object.entries(overrideAll ? templateDict : envDict)) {
|
|
112
|
+
newEnvDict[key] = value
|
|
113
|
+
}
|
|
114
|
+
for (const key of missingKeys) {
|
|
115
|
+
newEnvDict[key] = templateDict[key]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sort
|
|
119
|
+
let newDictEntries = Object.entries(newEnvDict)
|
|
120
|
+
let newSortedEntries = newDictEntries.sort((a, b) => {
|
|
121
|
+
if (a < b) {
|
|
122
|
+
return -1
|
|
123
|
+
}
|
|
124
|
+
if (a > b) {
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
return 0
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Write to .env file
|
|
131
|
+
let newEnvFileContent = ''
|
|
132
|
+
for (let kvp of newSortedEntries) {
|
|
133
|
+
newEnvFileContent += `${kvp[0]}=${kvp[1]}\n`
|
|
134
|
+
}
|
|
135
|
+
await fsp.writeFile(toPath, newEnvFileContent)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getEnvDictionary(filePath) {
|
|
139
|
+
let dict = {}
|
|
140
|
+
fs.readFileSync(filePath).toString().split('\n').forEach(function (line) {
|
|
141
|
+
if (line && line.indexOf('=') !== -1) {
|
|
142
|
+
line = line.replace('\r', '').trim()
|
|
143
|
+
let parts = line.split('=')
|
|
144
|
+
dict[parts[0].trim()] = parts[1].trim()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
return dict
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function ensureFile(fromPath, toPath) {
|
|
151
|
+
if (!fs.existsSync(toPath)) {
|
|
152
|
+
console.log('Creating new file ' + toPath)
|
|
153
|
+
await fsp.copyFile(fromPath, toPath)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
exports.defaultSpawnOptions = {
|
|
158
|
+
shell: true,
|
|
159
|
+
cwd: __dirname,
|
|
160
|
+
stdio: ['ignore', 'inherit', 'inherit']
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function createTarball(directoryToTarball, outputDirectory, tarballName) {
|
|
164
|
+
if (!directoryToTarball || directoryToTarball.length === 0) {
|
|
165
|
+
throw new Error('directoryToTarball is required')
|
|
166
|
+
}
|
|
167
|
+
if (!outputDirectory || outputDirectory.length === 0) {
|
|
168
|
+
throw new Error('outputDirectory is required')
|
|
169
|
+
}
|
|
170
|
+
if (!tarballName || tarballName.length === 0) {
|
|
171
|
+
throw new Error('tarballName is required')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tarballPath = path.join(outputDirectory, tarballName)
|
|
175
|
+
|
|
176
|
+
console.log('directory to create tarball from: ' + directoryToTarball)
|
|
177
|
+
console.log('output will be: ' + tarballPath)
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(directoryToTarball)) {
|
|
180
|
+
throw new Error('error: dirToTarball directory does not exist')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(outputDirectory)) {
|
|
184
|
+
fs.mkdirSync(outputDirectory)
|
|
185
|
+
} else {
|
|
186
|
+
if (fs.existsSync(tarballPath)) {
|
|
187
|
+
fs.unlinkSync(tarballPath)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await tar.c({ gzip: true, file: tarballPath }, [directoryToTarball])
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function dockerCompose(command, projectName, dockerRelativeDirectory, detached = false) {
|
|
195
|
+
if (!projectName || projectName.length === 0) {
|
|
196
|
+
throw new Error('projectName is required')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const dockerRelativeDir = dockerRelativeDirectory || 'docker'
|
|
200
|
+
const dockerWorkingDir = path.join(process.cwd(), dockerRelativeDir)
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(dockerWorkingDir)) {
|
|
203
|
+
throw new Error('Docker directory does not exist: ' + dockerWorkingDir)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await throwIfDockerNotRunning()
|
|
207
|
+
|
|
208
|
+
const dockerSpawnOptions = { ...defaultSpawnOptions, cwd: dockerWorkingDir }
|
|
209
|
+
|
|
210
|
+
let args = ['--project-name', projectName, command]
|
|
211
|
+
if (detached) {
|
|
212
|
+
args.push('-d')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return waitForProcess(spawn('docker-compose', args, dockerSpawnOptions))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function dockerDepsUp(projectName, dockerRelativeDirectory) {
|
|
219
|
+
return dockerCompose('up', projectName, dockerRelativeDirectory)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function dockerDepsUpDetached(projectName, dockerRelativeDirectory) {
|
|
223
|
+
return dockerCompose('up', projectName, dockerRelativeDirectory, true)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function dockerDepsDown(projectName, dockerRelativeDirectory) {
|
|
227
|
+
return dockerCompose('down', projectName, dockerRelativeDirectory)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function dockerDepsStop(projectName, dockerRelativeDirectory) {
|
|
231
|
+
return dockerCompose('stop', projectName, dockerRelativeDirectory)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
exports.defaultSpawnOptions = defaultSpawnOptions
|
|
235
|
+
exports.waitForProcess = waitForProcess
|
|
236
|
+
exports.copyNewEnvValues = copyNewEnvValues
|
|
237
|
+
exports.overwriteEnvFile = overwriteEnvFile
|
|
238
|
+
exports.throwIfDockerNotRunning = throwIfDockerNotRunning
|
|
239
|
+
exports.bashIntoRunningDockerContainer = bashIntoRunningDockerContainer
|
|
240
|
+
exports.dockerContainerIsRunning = dockerContainerIsRunning
|
|
241
|
+
exports.createTarball = createTarball
|
|
242
|
+
exports.dockerDepsUp = dockerDepsUp
|
|
243
|
+
exports.dockerDepsUpDetached = dockerDepsUpDetached
|
|
244
|
+
exports.dockerDepsDown = dockerDepsDown
|
|
245
|
+
exports.dockerDepsStop = dockerDepsStop
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mikeyt23/node-cli-utils",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Some node cli utility functions",
|
|
5
|
-
"author": "Mike Thompson",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"private": false,
|
|
8
|
-
"files": [
|
|
9
|
-
"index.js"
|
|
10
|
-
],
|
|
11
|
-
"homepage": "https://github.com/mikey-t/node-cli-utils",
|
|
12
|
-
"repository": {
|
|
13
|
-
"type": "git",
|
|
14
|
-
"url": "https://github.com/mikey-t/node-cli-utils.git"
|
|
15
|
-
},
|
|
16
|
-
"main": "index.js",
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@mikeyt23/node-cli-utils",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Some node cli utility functions",
|
|
5
|
+
"author": "Mike Thompson",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/mikey-t/node-cli-utils",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/mikey-t/node-cli-utils.git"
|
|
15
|
+
},
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"fs-extra": "^10.0.0",
|
|
19
|
+
"tar": "^6.1.11",
|
|
20
|
+
"which": "^2.0.2"
|
|
21
|
+
}
|
|
22
|
+
}
|