@naturalcycles/nodejs-lib 13.32.0 → 13.33.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.
- package/dist/csv/csvReader.js +2 -2
- package/dist/fs/fs2.js +6 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/slack/slack.service.js +2 -4
- package/dist/stream/ndjson/transformJsonParse.js +1 -1
- package/dist/stream/transform/transformSplit.js +2 -2
- package/dist/stream/transform/worker/workerClassProxy.js +2 -2
- package/dist/stream/writable/writableLimit.js +1 -1
- package/dist/stream/writable/writableVoid.js +1 -1
- package/dist/util/buildInfo.util.js +5 -5
- package/dist/util/exec2.d.ts +167 -0
- package/dist/util/exec2.js +204 -0
- package/dist/util/git2.d.ts +25 -0
- package/dist/util/git2.js +95 -0
- package/package.json +2 -1
- package/src/csv/csvReader.ts +2 -7
- package/src/fs/fs2.ts +11 -7
- package/src/index.ts +2 -2
- package/src/secret/secrets-decrypt.util.ts +1 -1
- package/src/secret/secrets-encrypt.util.ts +1 -1
- package/src/slack/slack.service.ts +2 -3
- package/src/stream/ndjson/transformJsonParse.ts +1 -1
- package/src/stream/stream.model.ts +2 -2
- package/src/stream/transform/transformSplit.ts +3 -3
- package/src/stream/transform/worker/workerClassProxy.js +2 -2
- package/src/stream/writable/writableLimit.ts +1 -1
- package/src/stream/writable/writableVoid.ts +1 -1
- package/src/util/buildInfo.util.ts +5 -10
- package/src/util/exec2.ts +326 -0
- package/src/util/git2.ts +105 -0
- package/dist/util/exec.util.d.ts +0 -10
- package/dist/util/exec.util.js +0 -58
- package/dist/util/git.util.d.ts +0 -18
- package/dist/util/git.util.js +0 -109
- package/src/util/exec.util.ts +0 -79
- package/src/util/git.util.ts +0 -113
package/src/csv/csvReader.ts
CHANGED
|
@@ -56,10 +56,7 @@ export function csvStringParse<T extends AnyObject = any>(
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function csvStringToArray(str: string): string[][] {
|
|
59
|
-
const objPattern =
|
|
60
|
-
String.raw`(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))`,
|
|
61
|
-
'gi',
|
|
62
|
-
)
|
|
59
|
+
const objPattern = /(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))/gi
|
|
63
60
|
let matches: RegExpExecArray | null
|
|
64
61
|
const arr: any[][] = [[]]
|
|
65
62
|
|
|
@@ -67,9 +64,7 @@ export function csvStringToArray(str: string): string[][] {
|
|
|
67
64
|
if (matches[1]!.length && matches[1] !== ',') {
|
|
68
65
|
arr.push([])
|
|
69
66
|
}
|
|
70
|
-
arr[arr.length - 1]!.push(
|
|
71
|
-
matches[2] ? matches[2].replaceAll(new RegExp('""', 'g'), '"') : matches[3],
|
|
72
|
-
)
|
|
67
|
+
arr[arr.length - 1]!.push(matches[2] ? matches[2].replaceAll('""', '"') : matches[3])
|
|
73
68
|
}
|
|
74
69
|
return arr
|
|
75
70
|
}
|
package/src/fs/fs2.ts
CHANGED
|
@@ -14,7 +14,7 @@ Credit to: fs-extra (https://github.com/jprichardson/node-fs-extra)
|
|
|
14
14
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { RmOptions } from 'node:fs'
|
|
17
|
+
import type { RmOptions, Stats } from 'node:fs'
|
|
18
18
|
import fs from 'node:fs'
|
|
19
19
|
import fsp from 'node:fs/promises'
|
|
20
20
|
import path from 'node:path'
|
|
@@ -182,7 +182,7 @@ class FS2 {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
ensureFile(filePath: string): void {
|
|
185
|
-
let stats
|
|
185
|
+
let stats: Stats | undefined
|
|
186
186
|
try {
|
|
187
187
|
stats = fs.statSync(filePath)
|
|
188
188
|
} catch {}
|
|
@@ -197,7 +197,10 @@ class FS2 {
|
|
|
197
197
|
}
|
|
198
198
|
} catch (err) {
|
|
199
199
|
// If the stat call above failed because the directory doesn't exist, create it
|
|
200
|
-
if ((err as any)?.code === 'ENOENT')
|
|
200
|
+
if ((err as any)?.code === 'ENOENT') {
|
|
201
|
+
this.ensureDir(dir)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
201
204
|
throw err
|
|
202
205
|
}
|
|
203
206
|
|
|
@@ -205,7 +208,7 @@ class FS2 {
|
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
async ensureFileAsync(filePath: string): Promise<void> {
|
|
208
|
-
let stats
|
|
211
|
+
let stats: Stats | undefined
|
|
209
212
|
try {
|
|
210
213
|
stats = await fsp.stat(filePath)
|
|
211
214
|
} catch {}
|
|
@@ -236,18 +239,19 @@ class FS2 {
|
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
emptyDir(dirPath: string): void {
|
|
239
|
-
let items
|
|
242
|
+
let items: string[]
|
|
240
243
|
try {
|
|
241
244
|
items = fs.readdirSync(dirPath)
|
|
242
245
|
} catch {
|
|
243
|
-
|
|
246
|
+
this.ensureDir(dirPath)
|
|
247
|
+
return
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
items.forEach(item => this.removePath(path.join(dirPath, item)))
|
|
247
251
|
}
|
|
248
252
|
|
|
249
253
|
async emptyDirAsync(dirPath: string): Promise<void> {
|
|
250
|
-
let items
|
|
254
|
+
let items: string[]
|
|
251
255
|
try {
|
|
252
256
|
items = await fsp.readdir(dirPath)
|
|
253
257
|
} catch {
|
package/src/index.ts
CHANGED
|
@@ -72,8 +72,8 @@ export * from './stream/writable/writableVoid'
|
|
|
72
72
|
export * from './string/inspect'
|
|
73
73
|
export * from './util/buildInfo.util'
|
|
74
74
|
export * from './util/env.util'
|
|
75
|
-
export * from './util/
|
|
76
|
-
export * from './util/
|
|
75
|
+
export * from './util/exec2'
|
|
76
|
+
export * from './util/git2'
|
|
77
77
|
export * from './util/lruMemoCache'
|
|
78
78
|
export * from './util/zip.util'
|
|
79
79
|
export * from './validation/ajv/ajv.util'
|
|
@@ -37,7 +37,7 @@ export function secretsDecrypt(
|
|
|
37
37
|
const filenames = fastGlob.sync(patterns)
|
|
38
38
|
|
|
39
39
|
filenames.forEach(filename => {
|
|
40
|
-
let plainFilename
|
|
40
|
+
let plainFilename: string
|
|
41
41
|
|
|
42
42
|
if (jsonMode) {
|
|
43
43
|
_assert(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`)
|
|
@@ -92,7 +92,7 @@ export class SlackService<CTX = any> {
|
|
|
92
92
|
if (msg.kv) {
|
|
93
93
|
;(msg.attachments ||= []).push({ fields: this.kvToFields(msg.kv) })
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
msg.kv = undefined // to not pass it all the way to Slack Api
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
let text: string
|
|
@@ -135,9 +135,8 @@ export class SlackService<CTX = any> {
|
|
|
135
135
|
// ignore (unless throwOnError is set)
|
|
136
136
|
if (msg.throwOnError) {
|
|
137
137
|
throw err
|
|
138
|
-
} else {
|
|
139
|
-
console.log(err)
|
|
140
138
|
}
|
|
139
|
+
console.log(err)
|
|
141
140
|
})
|
|
142
141
|
}
|
|
143
142
|
|
|
@@ -55,7 +55,7 @@ export function transformJsonParse<ROW = any>(
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Based on: https://stackoverflow.com/a/34557997/4919972
|
|
58
|
-
export const bufferReviver: Reviver = (
|
|
58
|
+
export const bufferReviver: Reviver = (_k, v) => {
|
|
59
59
|
if (v !== null && typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data)) {
|
|
60
60
|
return Buffer.from(v.data)
|
|
61
61
|
}
|
|
@@ -45,7 +45,7 @@ export interface ReadableTyped<T> extends Readable {
|
|
|
45
45
|
drop: (limit: number, opt?: ReadableSignalOptions) => ReadableTyped<T>
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
49
49
|
export interface WritableTyped<T> extends Writable {}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -59,7 +59,7 @@ export type ReadableBinary = Readable
|
|
|
59
59
|
*/
|
|
60
60
|
export type WritableBinary = Writable
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
63
63
|
export interface TransformTyped<IN, OUT> extends Transform {}
|
|
64
64
|
|
|
65
65
|
export interface TransformOptions {
|
|
@@ -18,7 +18,7 @@ export function transformSplitOnNewline(): TransformTyped<Buffer, Buffer> {
|
|
|
18
18
|
readableObjectMode: true,
|
|
19
19
|
writableHighWaterMark: 64 * 1024,
|
|
20
20
|
|
|
21
|
-
transform(buf: Buffer,
|
|
21
|
+
transform(buf: Buffer, _enc, done) {
|
|
22
22
|
let offset = 0
|
|
23
23
|
let lastMatch = 0
|
|
24
24
|
if (buffered) {
|
|
@@ -66,7 +66,7 @@ export function transformSplit(separator = '\n'): TransformTyped<Buffer, Buffer>
|
|
|
66
66
|
readableObjectMode: true,
|
|
67
67
|
writableHighWaterMark: 64 * 1024,
|
|
68
68
|
|
|
69
|
-
transform(buf: Buffer,
|
|
69
|
+
transform(buf: Buffer, _enc, done) {
|
|
70
70
|
let offset = 0
|
|
71
71
|
let lastMatch = 0
|
|
72
72
|
if (buffered) {
|
|
@@ -119,7 +119,7 @@ function firstNewlineMatch(buf: Buffer, offset: number): number {
|
|
|
119
119
|
|
|
120
120
|
function firstMatch(buf: Buffer, offset: number, matcher: Buffer): number {
|
|
121
121
|
if (offset >= buf.length) return -1
|
|
122
|
-
let i
|
|
122
|
+
let i: number
|
|
123
123
|
for (i = offset; i < buf.length; i++) {
|
|
124
124
|
if (buf[i] === matcher[0]) {
|
|
125
125
|
if (matcher.length > 1) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const started = Date.now()
|
|
2
|
-
const { workerData, parentPort } = require('worker_threads')
|
|
3
|
-
const { inspect } = require('util')
|
|
2
|
+
const { workerData, parentPort } = require('node:worker_threads')
|
|
3
|
+
const { inspect } = require('node:util')
|
|
4
4
|
const { workerFile, workerIndex, logEvery = 1000, metric = 'worker' } = workerData || {}
|
|
5
5
|
|
|
6
6
|
if (!workerFile) {
|
|
@@ -6,12 +6,7 @@ import {
|
|
|
6
6
|
UnixTimestampNumber,
|
|
7
7
|
} from '@naturalcycles/js-lib'
|
|
8
8
|
import { fs2 } from '../fs/fs2'
|
|
9
|
-
import {
|
|
10
|
-
gitCurrentBranchName,
|
|
11
|
-
gitCurrentCommitSha,
|
|
12
|
-
gitCurrentCommitTimestamp,
|
|
13
|
-
gitCurrentRepoName,
|
|
14
|
-
} from './git.util'
|
|
9
|
+
import { git2 } from './git2'
|
|
15
10
|
|
|
16
11
|
export interface GenerateBuildInfoOptions {
|
|
17
12
|
/**
|
|
@@ -24,10 +19,10 @@ export function generateBuildInfo(opt: GenerateBuildInfoOptions = {}): BuildInfo
|
|
|
24
19
|
const now = localTime.orNow(opt.overrideTimestamp)
|
|
25
20
|
const ts = now.unix
|
|
26
21
|
|
|
27
|
-
const rev = gitCurrentCommitSha()
|
|
28
|
-
const branchName = gitCurrentBranchName()
|
|
29
|
-
const repoName = gitCurrentRepoName()
|
|
30
|
-
const tsCommit = gitCurrentCommitTimestamp()
|
|
22
|
+
const rev = git2.gitCurrentCommitSha()
|
|
23
|
+
const branchName = git2.gitCurrentBranchName()
|
|
24
|
+
const repoName = git2.gitCurrentRepoName()
|
|
25
|
+
const tsCommit = git2.gitCurrentCommitTimestamp()
|
|
31
26
|
|
|
32
27
|
const ver = [now.toStringCompact(), repoName, branchName, rev].join('_')
|
|
33
28
|
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import cp from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
_since,
|
|
4
|
+
AnyObject,
|
|
5
|
+
AppError,
|
|
6
|
+
NumberOfMilliseconds,
|
|
7
|
+
UnixTimestampMillisNumber,
|
|
8
|
+
} from '@naturalcycles/js-lib'
|
|
9
|
+
import { dimGrey, white } from '../colors/colors'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Set of utility functions to work with Spawn / Exec.
|
|
13
|
+
*
|
|
14
|
+
* How to decide between Spawn and Exec?
|
|
15
|
+
*
|
|
16
|
+
* Long-running job that prints output, and no need to return the output - use Spawn.
|
|
17
|
+
*
|
|
18
|
+
* Short-running job, no need to print the output, might want to return the output - use Exec.
|
|
19
|
+
*
|
|
20
|
+
* Need to both print and return the output - use SpawnAsync.
|
|
21
|
+
*
|
|
22
|
+
* ***
|
|
23
|
+
*
|
|
24
|
+
* Spawn is good for long-running large-output processes, that continuously output data.
|
|
25
|
+
* E.g running `jest`.
|
|
26
|
+
*
|
|
27
|
+
* Exec is the opposite - good for short-running processes that output small data.
|
|
28
|
+
* Exec allows to return the output as a string.
|
|
29
|
+
* Exec doesn't stream data during execution, so the output/error will only be printed
|
|
30
|
+
* at the end.
|
|
31
|
+
* Exec always uses the shell (there's no option to disable it).
|
|
32
|
+
*/
|
|
33
|
+
class Exec2 {
|
|
34
|
+
/**
|
|
35
|
+
* Advanced/async version of Spawn.
|
|
36
|
+
* Consider simpler `spawn` or `exec` first, which are also sync.
|
|
37
|
+
*
|
|
38
|
+
* spawnAsync features:
|
|
39
|
+
*
|
|
40
|
+
* 1. Async
|
|
41
|
+
* 2. Allows to collect the output AND print it while running.
|
|
42
|
+
* 3. Returns SpawnOutput with stdout, stderr and exitCode.
|
|
43
|
+
* 4. Allows to not throw on error, but just return SpawnOutput for further inspection.
|
|
44
|
+
*
|
|
45
|
+
* Defaults:
|
|
46
|
+
*
|
|
47
|
+
* shell: true
|
|
48
|
+
* printWhileRunning: true
|
|
49
|
+
* collectOutputWhileRunning: true
|
|
50
|
+
* throwOnNonZeroCode: true
|
|
51
|
+
* log: true
|
|
52
|
+
*/
|
|
53
|
+
async spawnAsync(cmd: string, opt: SpawnAsyncOptions = {}): Promise<SpawnOutput> {
|
|
54
|
+
const started = Date.now()
|
|
55
|
+
this.logStart(cmd, opt)
|
|
56
|
+
const {
|
|
57
|
+
shell = true,
|
|
58
|
+
printWhileRunning = true,
|
|
59
|
+
collectOutputWhileRunning = true,
|
|
60
|
+
throwOnNonZeroCode = true,
|
|
61
|
+
cwd,
|
|
62
|
+
env,
|
|
63
|
+
} = opt
|
|
64
|
+
let stdout = ''
|
|
65
|
+
let stderr = ''
|
|
66
|
+
|
|
67
|
+
return await new Promise<SpawnOutput>((resolve, reject) => {
|
|
68
|
+
const p = cp.spawn(cmd, opt.args || [], {
|
|
69
|
+
shell,
|
|
70
|
+
cwd,
|
|
71
|
+
env,
|
|
72
|
+
// ...process.env, // not passing by default for security reasons
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
p.stdout.on('data', data => {
|
|
76
|
+
if (collectOutputWhileRunning) {
|
|
77
|
+
stdout += data.toString()
|
|
78
|
+
// console.log('stdout:', data.toString())
|
|
79
|
+
}
|
|
80
|
+
if (printWhileRunning) {
|
|
81
|
+
process.stdout.write(data)
|
|
82
|
+
// console.log('stderr:', data.toString())
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
p.stderr.on('data', data => {
|
|
86
|
+
if (collectOutputWhileRunning) {
|
|
87
|
+
stderr += data.toString()
|
|
88
|
+
}
|
|
89
|
+
if (printWhileRunning) {
|
|
90
|
+
process.stderr.write(data)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
p.on('close', code => {
|
|
95
|
+
this.logFinish(cmd, opt, started)
|
|
96
|
+
const exitCode = code || 0
|
|
97
|
+
const o: SpawnOutput = {
|
|
98
|
+
exitCode,
|
|
99
|
+
stdout: stdout.trim(),
|
|
100
|
+
stderr: stderr.trim(),
|
|
101
|
+
}
|
|
102
|
+
if (throwOnNonZeroCode && code) {
|
|
103
|
+
return reject(new SpawnError(`spawnAsync exited with code ${code}: ${cmd}`, o))
|
|
104
|
+
}
|
|
105
|
+
resolve(o)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reasons to use it:
|
|
112
|
+
* - Sync
|
|
113
|
+
* - Need to print output while running
|
|
114
|
+
*
|
|
115
|
+
* Limitations:
|
|
116
|
+
* - Cannot return stdout/stderr (use exec or spawnAsync for that)
|
|
117
|
+
*
|
|
118
|
+
* Defaults:
|
|
119
|
+
*
|
|
120
|
+
* shell: true
|
|
121
|
+
* log: true
|
|
122
|
+
*/
|
|
123
|
+
spawn(cmd: string, opt: SpawnOptions = {}): void {
|
|
124
|
+
const started = Date.now()
|
|
125
|
+
this.logStart(cmd, opt)
|
|
126
|
+
const { shell = true, cwd, env } = opt
|
|
127
|
+
|
|
128
|
+
const r = cp.spawnSync(cmd, opt.args, {
|
|
129
|
+
encoding: 'utf8',
|
|
130
|
+
stdio: 'inherit',
|
|
131
|
+
shell,
|
|
132
|
+
cwd,
|
|
133
|
+
env,
|
|
134
|
+
// ...process.env, // not passing by default for security reasons
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
this.logFinish(cmd, opt, started)
|
|
138
|
+
|
|
139
|
+
if (r.error) {
|
|
140
|
+
throw r.error
|
|
141
|
+
}
|
|
142
|
+
if (r.status) {
|
|
143
|
+
throw new Error(`spawn exited with code ${r.status}: ${cmd}`)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reasons to use it:
|
|
149
|
+
*
|
|
150
|
+
* - Sync
|
|
151
|
+
* - Need to return output
|
|
152
|
+
*
|
|
153
|
+
* Limitations:
|
|
154
|
+
* - Cannot print while running (use spawn or spawnAsync for that)
|
|
155
|
+
*
|
|
156
|
+
* Defaults:
|
|
157
|
+
*
|
|
158
|
+
* shell: true
|
|
159
|
+
* log: true
|
|
160
|
+
*/
|
|
161
|
+
exec(cmd: string, opt: ExecOptions = {}): string {
|
|
162
|
+
const started = Date.now()
|
|
163
|
+
this.logStart(cmd, opt)
|
|
164
|
+
const { cwd, env, timeout } = opt
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
return cp
|
|
168
|
+
.execSync(cmd, {
|
|
169
|
+
encoding: 'utf8',
|
|
170
|
+
// stdio: 'inherit', // no, otherwise we don't get the output returned
|
|
171
|
+
stdio: undefined,
|
|
172
|
+
// shell: undefined,
|
|
173
|
+
cwd,
|
|
174
|
+
timeout,
|
|
175
|
+
env,
|
|
176
|
+
// ...process.env, // not passing by default for security reasons
|
|
177
|
+
})
|
|
178
|
+
.trim()
|
|
179
|
+
} catch (err) {
|
|
180
|
+
// Not logging stderr, as it's printed by execSync by default (somehow)
|
|
181
|
+
// stdout is not printed by execSync though, therefor we print it here
|
|
182
|
+
// if ((err as any).stderr) {
|
|
183
|
+
// process.stderr.write((err as any).stderr)
|
|
184
|
+
// }
|
|
185
|
+
if ((err as any).stdout) {
|
|
186
|
+
process.stdout.write((err as any).stdout)
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`exec exited with code ${(err as any).status}: ${cmd}`)
|
|
189
|
+
} finally {
|
|
190
|
+
this.logFinish(cmd, opt, started)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throwOnNonZeroExitCode(o: SpawnOutput): void {
|
|
195
|
+
if (o.exitCode) {
|
|
196
|
+
throw new SpawnError(`spawn exited with code ${o.exitCode}`, o)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private logStart(cmd: string, opt: SpawnOptions | ExecOptions): void {
|
|
201
|
+
if (!opt.logStart && !opt.log) return
|
|
202
|
+
|
|
203
|
+
console.log(
|
|
204
|
+
[
|
|
205
|
+
dimGrey(...Object.entries(opt.env || {}).map(([k, v]) => [k, v].join('='))),
|
|
206
|
+
white(opt.name || cmd),
|
|
207
|
+
...((!opt.name && (opt as SpawnOptions).args) || []),
|
|
208
|
+
]
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.join(' '),
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private logFinish(
|
|
215
|
+
cmd: string,
|
|
216
|
+
opt: SpawnOptions | ExecOptions,
|
|
217
|
+
started: UnixTimestampMillisNumber,
|
|
218
|
+
): void {
|
|
219
|
+
if (!opt.logFinish && !opt.log) return
|
|
220
|
+
|
|
221
|
+
console.log([white(opt.name || cmd), dimGrey('took ' + _since(started))].join(' '))
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const exec2 = new Exec2()
|
|
226
|
+
|
|
227
|
+
export class SpawnError extends AppError<SpawnErrorData> {
|
|
228
|
+
constructor(message: string, data: SpawnErrorData) {
|
|
229
|
+
super(message, data, { name: 'SpawnError' })
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface SpawnErrorData extends SpawnOutput {}
|
|
234
|
+
|
|
235
|
+
export interface SpawnOutput {
|
|
236
|
+
/**
|
|
237
|
+
* Exit code of the spawned process.
|
|
238
|
+
* 0 means success, anything else means failure.
|
|
239
|
+
*/
|
|
240
|
+
exitCode: number
|
|
241
|
+
stdout: string
|
|
242
|
+
stderr: string
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface SpawnAsyncOptions extends SpawnOptions {
|
|
246
|
+
/**
|
|
247
|
+
* Defaults to true.
|
|
248
|
+
* If true - prints both stdout and stderr to console while running,
|
|
249
|
+
* otherwise runs "silently".
|
|
250
|
+
* Returns SpawnOutput in the same way, regardless of `printWhileRunning` setting.
|
|
251
|
+
*/
|
|
252
|
+
printWhileRunning?: boolean
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Defaults to true.
|
|
256
|
+
* If true - collects stdout and stderr while running, and return it in the end.
|
|
257
|
+
* stdout/stderr are collected and returned regardless if it returns with error or not.
|
|
258
|
+
* On success - stdout/stderr are available from `SpawnOutput`.
|
|
259
|
+
* On error - stdout/stderr are available from `SpawnError.data`.
|
|
260
|
+
*/
|
|
261
|
+
collectOutputWhileRunning?: boolean
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Defaults to true.
|
|
265
|
+
* If true - throws SpawnError if non-zero code is returned.
|
|
266
|
+
* SpawnError conveniently contains .data.stdout and .data.strerr for inspection.
|
|
267
|
+
* If false - will not throw, but return SpawnOutput with stdout, stderr and exitCode.
|
|
268
|
+
*/
|
|
269
|
+
throwOnNonZeroCode?: boolean
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface SpawnOptions {
|
|
273
|
+
args?: string[]
|
|
274
|
+
/**
|
|
275
|
+
* Defaults to true.
|
|
276
|
+
*/
|
|
277
|
+
logStart?: boolean
|
|
278
|
+
/**
|
|
279
|
+
* Defaults to true.
|
|
280
|
+
*/
|
|
281
|
+
logFinish?: boolean
|
|
282
|
+
/**
|
|
283
|
+
* Defaults to true.
|
|
284
|
+
* Controls/overrides both logStart and logFinish simultaneously.
|
|
285
|
+
*/
|
|
286
|
+
log?: boolean
|
|
287
|
+
/**
|
|
288
|
+
* Defaults to true.
|
|
289
|
+
*/
|
|
290
|
+
shell?: boolean
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* If specified - will be used as "command name" for logging purposes,
|
|
294
|
+
* instead of "cmd + args"
|
|
295
|
+
*/
|
|
296
|
+
name?: string
|
|
297
|
+
cwd?: string
|
|
298
|
+
|
|
299
|
+
env?: AnyObject
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface ExecOptions {
|
|
303
|
+
/**
|
|
304
|
+
* Defaults to false.
|
|
305
|
+
*/
|
|
306
|
+
logStart?: boolean
|
|
307
|
+
/**
|
|
308
|
+
* Defaults to false.
|
|
309
|
+
*/
|
|
310
|
+
logFinish?: boolean
|
|
311
|
+
/**
|
|
312
|
+
* Defaults to false.
|
|
313
|
+
* Controls/overrides both logStart and logFinish simultaneously.
|
|
314
|
+
*/
|
|
315
|
+
log?: boolean
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* If specified - will be used as "command name" for logging purposes,
|
|
319
|
+
* instead of "cmd + args"
|
|
320
|
+
*/
|
|
321
|
+
name?: string
|
|
322
|
+
cwd?: string
|
|
323
|
+
timeout?: NumberOfMilliseconds
|
|
324
|
+
|
|
325
|
+
env?: AnyObject
|
|
326
|
+
}
|
package/src/util/git2.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import cp from 'node:child_process'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { UnixTimestampNumber } from '@naturalcycles/js-lib'
|
|
4
|
+
import { grey } from '../colors/colors'
|
|
5
|
+
import { exec2 } from './exec2'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Set of utility functions to work with git.
|
|
9
|
+
*/
|
|
10
|
+
class Git2 {
|
|
11
|
+
getLastGitCommitMsg(): string {
|
|
12
|
+
return exec2.exec('git log -1 --pretty=%B')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
commitMessageToTitleMessage(msg: string): string {
|
|
16
|
+
const firstLine = msg.split('\n')[0]!
|
|
17
|
+
const [preTitle, title] = firstLine.split(': ')
|
|
18
|
+
return title || preTitle!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
gitHasUncommittedChanges(): boolean {
|
|
22
|
+
// git diff-index --quiet HEAD -- || echo "untracked"
|
|
23
|
+
try {
|
|
24
|
+
cp.execSync('git diff-index --quiet HEAD --', {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
})
|
|
27
|
+
return false
|
|
28
|
+
} catch {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns true if there were changes
|
|
35
|
+
*/
|
|
36
|
+
gitCommitAll(msg: string): boolean {
|
|
37
|
+
// git commit -a -m "style(lint-all): $GIT_MSG" || true
|
|
38
|
+
const cmd = `git commit -a --no-verify -m "${msg}"`
|
|
39
|
+
// const cmd = `git`
|
|
40
|
+
// const args = ['commit', '-a', '--no-verify', '-m', msg]
|
|
41
|
+
console.log(grey(cmd))
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
cp.execSync(cmd, {
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
})
|
|
47
|
+
return true
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @returns true if there are not pushed commits.
|
|
55
|
+
*/
|
|
56
|
+
gitIsAhead(): boolean {
|
|
57
|
+
// ahead=`git rev-list HEAD --not --remotes | wc -l | awk '{print $1}'`
|
|
58
|
+
const cmd = `git rev-list HEAD --not --remotes | wc -l | awk '{print $1}'`
|
|
59
|
+
const stdout = exec2.exec(cmd)
|
|
60
|
+
// console.log(`gitIsAhead: ${stdout}`)
|
|
61
|
+
return Number(stdout) > 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
gitPull(): void {
|
|
65
|
+
const cmd = 'git pull'
|
|
66
|
+
try {
|
|
67
|
+
cp.execSync(cmd, {
|
|
68
|
+
stdio: 'inherit',
|
|
69
|
+
})
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
gitPush(): void {
|
|
74
|
+
// git push --set-upstream origin $CIRCLE_BRANCH && echo "pushed, exiting" && exit 0
|
|
75
|
+
let cmd = 'git push'
|
|
76
|
+
|
|
77
|
+
const branchName = this.gitCurrentBranchName()
|
|
78
|
+
|
|
79
|
+
if (branchName) {
|
|
80
|
+
cmd += ` --set-upstream origin ${branchName}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
exec2.spawn(cmd, { logStart: true })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
gitCurrentCommitSha(full = false): string {
|
|
87
|
+
const sha = exec2.exec('git rev-parse HEAD')
|
|
88
|
+
return full ? sha : sha.slice(0, 7)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
gitCurrentCommitTimestamp(): UnixTimestampNumber {
|
|
92
|
+
return Number(exec2.exec('git log -1 --format=%ct'))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
gitCurrentBranchName(): string {
|
|
96
|
+
return exec2.exec('git rev-parse --abbrev-ref HEAD')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
gitCurrentRepoName(): string {
|
|
100
|
+
const originUrl = exec2.exec('git config --get remote.origin.url')
|
|
101
|
+
return path.basename(originUrl, '.git')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const git2 = new Git2()
|
package/dist/util/exec.util.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { SpawnOptions } from 'node:child_process';
|
|
2
|
-
export interface ExecOptions extends SpawnOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Defaults to true.
|
|
5
|
-
* Set to false to skip logging.
|
|
6
|
-
*/
|
|
7
|
-
log?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare function execVoidCommand(cmd: string, args?: string[], opt?: ExecOptions): Promise<void>;
|
|
10
|
-
export declare function execVoidCommandSync(cmd: string, args?: string[], opt?: ExecOptions): void;
|