@leverege/build-tools 2.102.0 → 2.104.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/.github/dependabot.yml +7 -0
- package/.vscode/launch.json +10 -0
- package/README.md +22 -9
- package/eslint.config.mjs +13 -0
- package/package.json +7 -10
- package/src/Docker.mjs +3 -3
- package/src/Utils.mjs +6 -4
- package/src/build-cnpg-image.mjs +1 -1
- package/src/chart-compass.mjs +3 -3
- package/src/chart-to-registry.mjs +13 -2
- package/src/circle-orb-it/{circle-orb.yml → circle-npm.yml} +6 -3
- package/src/circle-orb-it/circle-orb-it.mjs +16 -11
- package/src/circle-orb-it/circle-yarn.yml +25 -0
- package/src/custom-images/bitnami-audits/bitnami-audit.mjs +1 -1
- package/src/dashboard/DashboardManager.mjs +6 -0
- package/src/firebaseDeploy.mjs +0 -1
- package/src/firebaseServe.mjs +0 -1
- package/src/overwhelm.mjs +6 -28
- package/src/push-my-chart.mjs +13 -4
- package/src/repo-panopticon.mjs +415 -0
- package/src/service-man/config/CommandLine.mjs +6 -3
- package/src/service-man/config/Config.mjs +1 -1
- package/src/service-man/gcloud-k8s/RolesManager.mjs +1 -1
- package/src/service-man/gcloud-k8s/SecretsManager.mjs +3 -3
- package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +4 -4
- package/src/unleash.mjs +1 -1
- package/src/pkgck.sh +0 -158
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { program } from 'commander'
|
|
7
|
+
import chalk from 'chalk'
|
|
8
|
+
import { glob } from 'glob'
|
|
9
|
+
import ora from 'ora'
|
|
10
|
+
import pLimit from 'p-limit'
|
|
11
|
+
import simpleGit from 'simple-git'
|
|
12
|
+
|
|
13
|
+
import { clear, enableDebug, log, parseJsonFile } from './Utils.mjs'
|
|
14
|
+
|
|
15
|
+
// ---------- Symbols ----------
|
|
16
|
+
|
|
17
|
+
const SYM_GOOD = chalk.green( '✔' )
|
|
18
|
+
const SYM_BAD = chalk.red( '✘' )
|
|
19
|
+
|
|
20
|
+
// ---------- Checks ----------
|
|
21
|
+
// Each check: { label: string, fn: ({ dir, pkg, isWorkspace }) => { value: string } }
|
|
22
|
+
// Add new checks here — the table expands automatically.
|
|
23
|
+
|
|
24
|
+
const CHECKS = [
|
|
25
|
+
{
|
|
26
|
+
label : 'cir',
|
|
27
|
+
fn : ( { dir } ) => {
|
|
28
|
+
const cfgPath = path.join( dir, '.circleci', 'config.yml' )
|
|
29
|
+
if ( !fs.existsSync( cfgPath ) ) return { value : SYM_BAD }
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync( cfgPath, 'utf8' )
|
|
32
|
+
const match = content.match( /leverege\/circle-nodejs-ci@(\d+)\.\d+\.\d+/ )
|
|
33
|
+
if ( !match || parseInt( match[1], 10 ) === 0 ) return { value : chalk.hex( '#FFA500' )( 'L' ) }
|
|
34
|
+
const major = parseInt( match[1], 10 )
|
|
35
|
+
return { value : major === 1 ? chalk.yellow( '1' ) : chalk.green( `${major}` ) }
|
|
36
|
+
} catch {
|
|
37
|
+
return { value : chalk.hex( '#FFA500' )( 'L' ) }
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label : 'ESM',
|
|
43
|
+
fn : ( { pkg } ) => {
|
|
44
|
+
const ok = pkg?.type === 'module'
|
|
45
|
+
return { value : ok ? SYM_GOOD : SYM_BAD }
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label : 'har',
|
|
50
|
+
fn : ( { gitRoot } ) => {
|
|
51
|
+
const ok = fs.existsSync( path.join( gitRoot, '.har' ) )
|
|
52
|
+
return { value : ok ? SYM_GOOD : SYM_BAD }
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label : 'lnt',
|
|
57
|
+
fn : ( { dir, pkg, gitRoot } ) => {
|
|
58
|
+
if ( !pkg?.scripts?.lint ) return { value : SYM_BAD }
|
|
59
|
+
try {
|
|
60
|
+
// Prefer package-local node_modules; fall back to monorepo root (hoisted)
|
|
61
|
+
const local = path.join( dir, 'node_modules', 'eslint', 'package.json' )
|
|
62
|
+
const hoisted = path.join( gitRoot, 'node_modules', 'eslint', 'package.json' )
|
|
63
|
+
const eslintPkg = JSON.parse( fs.readFileSync(
|
|
64
|
+
fs.existsSync( local ) ? local : hoisted, 'utf8'
|
|
65
|
+
) )
|
|
66
|
+
const major = parseInt( eslintPkg.version.split( '.' )[0], 10 )
|
|
67
|
+
return { value : major <= 8 ? chalk.yellow( `${major}` ) : chalk.green( `${major}` ) }
|
|
68
|
+
} catch {
|
|
69
|
+
return { value : chalk.dim( '?' ) }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label : 'bbl',
|
|
75
|
+
fn : ( { dir, pkg } ) => {
|
|
76
|
+
const configFiles = [ '.babelrc', '.babelrc.js', '.babelrc.json', 'babel.config.js', 'babel.config.json', 'babel.config.cjs' ]
|
|
77
|
+
const hasConfig = configFiles.some( f => fs.existsSync( path.join( dir, f ) ) )
|
|
78
|
+
const allDeps = { ...( pkg?.dependencies ?? {} ), ...( pkg?.devDependencies ?? {} ) }
|
|
79
|
+
const hasDep = Object.keys( allDeps ).some( k => k.includes( 'babel' ) )
|
|
80
|
+
return { value : ( hasConfig || hasDep ) ? SYM_BAD : chalk.dim( '–' ) }
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// ---------- Helpers ----------
|
|
86
|
+
|
|
87
|
+
const ANSI_RE = /\x1B\[[0-9;]*m/g // eslint-disable-line no-control-regex
|
|
88
|
+
|
|
89
|
+
function visLen( str ) {
|
|
90
|
+
return str.replace( ANSI_RE, '' ).length
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function pad( str, width ) {
|
|
94
|
+
return str + ' '.repeat( Math.max( 0, width - visLen( str ) ) )
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function center( str, width ) {
|
|
98
|
+
const gap = Math.max( 0, width - visLen( str ) )
|
|
99
|
+
const left = Math.floor( gap / 2 )
|
|
100
|
+
return ' '.repeat( left ) + str + ' '.repeat( gap - left )
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const NONSEMVER = '!SEMVER'
|
|
104
|
+
|
|
105
|
+
// Strict x.y.z only — pre-release and build metadata are flagged !SEMVER
|
|
106
|
+
function parseVersion( raw ) {
|
|
107
|
+
if ( !raw ) return null
|
|
108
|
+
const v = raw.startsWith( 'v' ) ? raw : `v${raw}`
|
|
109
|
+
return /^v\d+\.\d+\.\d+$/.test( v ) ? v : NONSEMVER
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function fmtPkgVer( ver ) {
|
|
113
|
+
if ( !ver ) return chalk.dim( '?' )
|
|
114
|
+
if ( ver === NONSEMVER ) return chalk.yellow( NONSEMVER )
|
|
115
|
+
return chalk.green( ver )
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function fmtGitVer( gitVer, pkgVer ) {
|
|
119
|
+
if ( !gitVer ) return chalk.bgRed.white( 'UNTAGGD' )
|
|
120
|
+
if ( gitVer === NONSEMVER ) return chalk.yellow( NONSEMVER )
|
|
121
|
+
if ( gitVer === pkgVer ) return chalk.green( gitVer )
|
|
122
|
+
return chalk.yellow( gitVer )
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function fmtScope( pkg ) {
|
|
126
|
+
const name = pkg?.name ?? ''
|
|
127
|
+
const match = name.match( /^(@[^/]+)/ )
|
|
128
|
+
if ( match ) return chalk.green( match[1].slice( 0, 4 ) ) // '@' + 3 chars: @leverege → @lev
|
|
129
|
+
if ( pkg?.private === true ) return chalk.yellow( 'PRVT' )
|
|
130
|
+
return chalk.bgRed.white( 'PUBL' )
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Extracts { repoName, group } from a directory path.
|
|
134
|
+
// For workspace packages (gitRoot !== dir), group is the monorepo's org/name path
|
|
135
|
+
// and repoName is the package's own directory name.
|
|
136
|
+
// For root repos, group is the @xxx path component and repoName is relative to it.
|
|
137
|
+
function repoLabel( dir, gitRoot ) {
|
|
138
|
+
const parts = dir.split( path.sep )
|
|
139
|
+
const atIdx = parts.findIndex( p => /^@\w+$/.test( p ) )
|
|
140
|
+
|
|
141
|
+
if ( gitRoot && gitRoot !== dir ) {
|
|
142
|
+
// Workspace package — group under the monorepo's label
|
|
143
|
+
const rootParts = gitRoot.split( path.sep )
|
|
144
|
+
const rootAtIdx = rootParts.findIndex( p => /^@\w+$/.test( p ) )
|
|
145
|
+
const group = rootAtIdx === -1 ?
|
|
146
|
+
path.basename( gitRoot ) :
|
|
147
|
+
rootParts.slice( rootAtIdx ).join( '/' )
|
|
148
|
+
return { repoName : path.basename( dir ), group }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if ( atIdx === -1 ) return { repoName : path.basename( dir ), group : null }
|
|
152
|
+
const group = parts[atIdx]
|
|
153
|
+
const repoName = parts.slice( atIdx + 1 ).join( '/' )
|
|
154
|
+
return { repoName, group }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------- Per-repo ----------
|
|
158
|
+
|
|
159
|
+
async function checkRepo( dir, { pull } ) {
|
|
160
|
+
if ( !fs.existsSync( path.join( dir, 'package.json' ) ) ) return null
|
|
161
|
+
|
|
162
|
+
let gitRoot
|
|
163
|
+
try {
|
|
164
|
+
gitRoot = ( await simpleGit( dir ).revparse( [ '--show-toplevel' ] ) ).trim()
|
|
165
|
+
} catch {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const isWorkspace = gitRoot !== dir
|
|
170
|
+
const git = simpleGit( gitRoot )
|
|
171
|
+
const pkg = await parseJsonFile( path.join( dir, 'package.json' ) )
|
|
172
|
+
|
|
173
|
+
// Monorepo roots are represented by their workspace packages — skip the root itself
|
|
174
|
+
if ( !isWorkspace && pkg?.workspaces ) return null
|
|
175
|
+
|
|
176
|
+
const pkgVer = parseVersion( pkg?.version )
|
|
177
|
+
|
|
178
|
+
let syncOk = true
|
|
179
|
+
if ( isWorkspace ) {
|
|
180
|
+
try {
|
|
181
|
+
const relDir = path.relative( gitRoot, dir )
|
|
182
|
+
const status = await git.status( [ '--', relDir ] )
|
|
183
|
+
syncOk = status.files.length === 0
|
|
184
|
+
} catch {
|
|
185
|
+
syncOk = false
|
|
186
|
+
}
|
|
187
|
+
} else if ( pull ) {
|
|
188
|
+
try {
|
|
189
|
+
await git.pull()
|
|
190
|
+
await git.fetch( [ '--tags' ] )
|
|
191
|
+
} catch {
|
|
192
|
+
syncOk = false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let gitVer = null
|
|
197
|
+
if ( isWorkspace ) {
|
|
198
|
+
// Look for workspace-style tags: pkg-name/vX.Y.Z
|
|
199
|
+
const pkgBaseName = ( pkg?.name ?? path.basename( dir ) ).replace( /^@[^/]+\//, '' )
|
|
200
|
+
try {
|
|
201
|
+
const tagOutput = ( await git.raw( [ 'tag', '--list', `${pkgBaseName}/v*` ] ) ).trim()
|
|
202
|
+
if ( tagOutput ) {
|
|
203
|
+
const semverTags = tagOutput.split( '\n' )
|
|
204
|
+
.map( ( t ) => { const m = t.trim().match( /(?:^|\/)v(\d+\.\d+\.\d+)$/ ); return m ? m[1] : null } )
|
|
205
|
+
.filter( Boolean )
|
|
206
|
+
if ( semverTags.length ) {
|
|
207
|
+
semverTags.sort( ( a, b ) => {
|
|
208
|
+
const av = a.split( '.' ).map( Number )
|
|
209
|
+
const bv = b.split( '.' ).map( Number )
|
|
210
|
+
return ( bv[0] - av[0] ) || ( bv[1] - av[1] ) || ( bv[2] - av[2] )
|
|
211
|
+
} )
|
|
212
|
+
gitVer = `v${semverTags[0]}`
|
|
213
|
+
} else {
|
|
214
|
+
gitVer = NONSEMVER
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch { /* no tags → null = UNTAGGD */ }
|
|
218
|
+
} else {
|
|
219
|
+
// git describe gives the nearest tag reachable from HEAD, matching original pkgck behavior
|
|
220
|
+
try {
|
|
221
|
+
const tag = ( await git.raw( [ 'describe', '--abbrev=0', '--tags' ] ) ).trim()
|
|
222
|
+
// Handles 'v1.2.3' and workspace-style 'pkg-name/v1.2.3'
|
|
223
|
+
const match = tag.match( /(?:^|\/)v(\d+\.\d+\.\d+)$/ )
|
|
224
|
+
gitVer = match ? `v${match[1]}` : NONSEMVER
|
|
225
|
+
} catch { /* no tags → null = UNTAGGD */ }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let needsPush = ''
|
|
229
|
+
if ( pkgVer && pkgVer !== NONSEMVER && gitVer === pkgVer ) {
|
|
230
|
+
try {
|
|
231
|
+
const pkgBaseName = ( pkg?.name ?? path.basename( dir ) ).replace( /^@[^/]+\//, '' )
|
|
232
|
+
const tagToCheck = isWorkspace ? `${pkgBaseName}/${pkgVer}` : pkgVer
|
|
233
|
+
const remote = await git.listRemote( [ '--tags', 'origin' ] )
|
|
234
|
+
if ( !remote.includes( tagToCheck ) ) needsPush = chalk.yellow( '!tags' )
|
|
235
|
+
} catch { /* ignore */ }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const isYarn = fs.existsSync( path.join( gitRoot, 'yarn.lock' ) ) || fs.existsSync( path.join( gitRoot, '.yarn' ) )
|
|
239
|
+
const pkgMgr = isYarn ? chalk.cyan( 'yrn' ) : chalk.red( 'npm' )
|
|
240
|
+
|
|
241
|
+
const { repoName, group } = repoLabel( dir, gitRoot )
|
|
242
|
+
return {
|
|
243
|
+
repoName,
|
|
244
|
+
group,
|
|
245
|
+
pkgMgr,
|
|
246
|
+
pkgVer : fmtPkgVer( pkgVer ),
|
|
247
|
+
gitVer : fmtGitVer( gitVer, pkgVer ),
|
|
248
|
+
dirty : !syncOk,
|
|
249
|
+
scope : fmtScope( pkg ),
|
|
250
|
+
isWorkspace,
|
|
251
|
+
checks : CHECKS.map( ( { label, fn } ) => ( { label, ...fn( { dir, pkg, isWorkspace, gitRoot } ) } ) ),
|
|
252
|
+
needsPush,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---------- Table ----------
|
|
257
|
+
|
|
258
|
+
function renderTable( rows ) {
|
|
259
|
+
const pkgW = Math.max( 3, ...rows.map( r => visLen( r.pkgVer ) ) )
|
|
260
|
+
const gitW = Math.max( 7, ...rows.map( r => visLen( r.gitVer ) ) )
|
|
261
|
+
const scopeW = Math.max( 4, ...rows.map( r => visLen( r.scope ) ) )
|
|
262
|
+
|
|
263
|
+
// Fixed width of all columns except name. Elements: mgr pkg git scope checks (no needsPush).
|
|
264
|
+
// join(' ') puts 2-space sep between N+5 elements = N+4 separators.
|
|
265
|
+
const checkLabelW = CHECKS.reduce( ( sum, c ) => sum + c.label.length, 0 )
|
|
266
|
+
const fixedW = 3 + pkgW + gitW + scopeW + checkLabelW + ( CHECKS.length + 4 ) * 2
|
|
267
|
+
|
|
268
|
+
// Name area: 2-space indent + repo name. Group label prints on its own line above each section.
|
|
269
|
+
const hasGroups = rows.some( r => r.group )
|
|
270
|
+
const indent = hasGroups ? 2 : 0
|
|
271
|
+
const maxRepoW = Math.min( 22, Math.max( 4, ...rows.map( r => r.repoName.length ) ) )
|
|
272
|
+
const nameW = Math.min( indent + maxRepoW, 80 - fixedW )
|
|
273
|
+
const repoW = nameW - indent
|
|
274
|
+
|
|
275
|
+
const nameHdr = pad( `${' '.repeat( indent )}name`, nameW )
|
|
276
|
+
const hdr = [
|
|
277
|
+
nameHdr,
|
|
278
|
+
center( 'mgr', 3 ),
|
|
279
|
+
center( 'pkg', pkgW ),
|
|
280
|
+
center( 'git', gitW ),
|
|
281
|
+
center( 'scop', scopeW ),
|
|
282
|
+
...CHECKS.map( c => center( c.label, c.label.length ) ),
|
|
283
|
+
].join( ' ' )
|
|
284
|
+
|
|
285
|
+
const separator = chalk.dim( '─'.repeat( visLen( hdr ) ) )
|
|
286
|
+
|
|
287
|
+
log( '' )
|
|
288
|
+
log( chalk.dim( hdr ) )
|
|
289
|
+
log( separator )
|
|
290
|
+
|
|
291
|
+
let currentGroup
|
|
292
|
+
for ( const row of rows ) {
|
|
293
|
+
if ( row.group !== currentGroup ) {
|
|
294
|
+
currentGroup = row.group
|
|
295
|
+
if ( hasGroups ) log( chalk.cyan( row.group ?? '' ) )
|
|
296
|
+
}
|
|
297
|
+
const nameStr = ' '.repeat( indent ) + row.repoName.slice( 0, repoW )
|
|
298
|
+
const nameCell = row.dirty ? chalk.hex( '#FFA500' )( nameStr ) : nameStr
|
|
299
|
+
const line = [
|
|
300
|
+
pad( nameCell, nameW ),
|
|
301
|
+
pad( row.pkgMgr, 3 ),
|
|
302
|
+
pad( row.pkgVer, pkgW ),
|
|
303
|
+
pad( row.gitVer, gitW ),
|
|
304
|
+
pad( row.scope, scopeW ),
|
|
305
|
+
...row.checks.map( c => center( c.value, c.label.length ) ),
|
|
306
|
+
].join( ' ' ) + ( row.needsPush ? ` ${row.needsPush}` : '' )
|
|
307
|
+
log( line )
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
log( '' )
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------- CLI ----------
|
|
314
|
+
|
|
315
|
+
program
|
|
316
|
+
.name( 'package-checker' )
|
|
317
|
+
.description( chalk.green( 'A panopticon for your git repos!' ) )
|
|
318
|
+
.argument( '<dirs...>', 'directories to check' )
|
|
319
|
+
.option( '--no-pull', 'skip git pull and tag fetch' )
|
|
320
|
+
.option( '--no-clear', 'do not clear the screen before output' )
|
|
321
|
+
.option( '--concurrency <n>', 'max parallel repos', '5' )
|
|
322
|
+
.option( '--limit <n>', 'max rows to display', '80' )
|
|
323
|
+
.option( '--debug', 'enable debug logging' )
|
|
324
|
+
.addHelpText( 'after', `
|
|
325
|
+
${chalk.yellow( 'Columns:' )}
|
|
326
|
+
${chalk.green( 'pkg' )} version from package.json
|
|
327
|
+
${chalk.green( 'git' )} latest semver git tag (green=match, yellow=mismatch, red=untagged)
|
|
328
|
+
${chalk.green( 'name' )} orange if dirty: pull/fetch failed (regular repos) or local modifications present (workspace packages)
|
|
329
|
+
${chalk.green( 'scope' )} package scope: @org, PRVT, or PUBL
|
|
330
|
+
${chalk.green( 'cir' )} CircleCI config: ✘=missing, L=legacy, 1=orb v1, 2=orb v2
|
|
331
|
+
${chalk.green( 'ESM' )} "type": "module" in package.json
|
|
332
|
+
${chalk.green( 'har' )} .har directory present at repo root
|
|
333
|
+
${chalk.green( 'lnt' )} ESLint version from node_modules (✘=no lint script, ?=not installed, yellow=v8, green=v9+)
|
|
334
|
+
|
|
335
|
+
${chalk.yellow( 'Monorepos:' )}
|
|
336
|
+
Workspace packages are discovered automatically — passing a monorepo root
|
|
337
|
+
expands its workspaces field so you never need to add packages/* globs.
|
|
338
|
+
Git tags are looked up as pkg-name/vX.Y.Z. Pull/fetch runs at the root.
|
|
339
|
+
|
|
340
|
+
${chalk.yellow( 'Examples:' )}
|
|
341
|
+
${chalk.green( 'package-checker ~/repos/my-service' )}
|
|
342
|
+
${chalk.green( 'package-checker --no-pull ~/repos/*' )}
|
|
343
|
+
${chalk.green( 'package-checker --concurrency 10 @plt/* @kud/* @srv/* @tpi/*' )}
|
|
344
|
+
` )
|
|
345
|
+
.parse()
|
|
346
|
+
|
|
347
|
+
const dirs = program.args
|
|
348
|
+
const options = program.opts()
|
|
349
|
+
if ( options.debug ) { enableDebug() }
|
|
350
|
+
|
|
351
|
+
if ( options.clear ) { clear() }
|
|
352
|
+
|
|
353
|
+
// Auto-expand workspace packages from any monorepo roots in the input.
|
|
354
|
+
// Supports both array and yarn-style { packages: [...] } workspaces fields.
|
|
355
|
+
async function expandWorkspaces( dir ) {
|
|
356
|
+
try {
|
|
357
|
+
const pkg = await parseJsonFile( path.join( dir, 'package.json' ) )
|
|
358
|
+
const patterns = Array.isArray( pkg?.workspaces ) ?
|
|
359
|
+
pkg.workspaces :
|
|
360
|
+
( pkg?.workspaces?.packages ?? [] )
|
|
361
|
+
if ( !patterns.length ) return []
|
|
362
|
+
const matches = await Promise.all(
|
|
363
|
+
patterns.map( p => glob( p, { cwd : dir, absolute : true } ) )
|
|
364
|
+
)
|
|
365
|
+
return matches.flat().filter( d => !/test|demo/i.test( path.basename( d ) ) )
|
|
366
|
+
} catch {
|
|
367
|
+
return []
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const resolvedDirs = dirs.map( dir => path.resolve( dir ) )
|
|
372
|
+
const wsExpansions = ( await Promise.all( resolvedDirs.map( expandWorkspaces ) ) ).flat()
|
|
373
|
+
const allDirs = [ ...new Set( [ ...resolvedDirs, ...wsExpansions ] ) ]
|
|
374
|
+
|
|
375
|
+
const total = allDirs.length
|
|
376
|
+
let completed = 0
|
|
377
|
+
const spinner = ora( `Checking repos (0/${total})...` ).start()
|
|
378
|
+
|
|
379
|
+
process.on( 'SIGINT', () => { spinner.stop(); process.exit( 1 ) } )
|
|
380
|
+
|
|
381
|
+
const limit = pLimit( parseInt( options.concurrency, 10 ) )
|
|
382
|
+
const results = await Promise.all(
|
|
383
|
+
allDirs.map( dir => limit( async () => {
|
|
384
|
+
const result = await checkRepo( dir, { pull : options.pull } )
|
|
385
|
+
spinner.text = `Checking repos (${++completed}/${total})...`
|
|
386
|
+
return result
|
|
387
|
+
} ) )
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
spinner.stop()
|
|
391
|
+
|
|
392
|
+
const rows = results
|
|
393
|
+
.filter( Boolean )
|
|
394
|
+
.sort( ( a, b ) => {
|
|
395
|
+
const ga = a.group ?? ''
|
|
396
|
+
const gb = b.group ?? ''
|
|
397
|
+
if ( ga !== gb ) return ga.localeCompare( gb )
|
|
398
|
+
return a.repoName.localeCompare( b.repoName )
|
|
399
|
+
} )
|
|
400
|
+
|
|
401
|
+
if ( rows.length === 0 ) {
|
|
402
|
+
log( chalk.yellow( '\nNo valid repositories found.\n' ) )
|
|
403
|
+
process.exit( 0 )
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const maxRows = parseInt( options.limit, 10 )
|
|
407
|
+
const checked = rows.length
|
|
408
|
+
const displayed = rows.slice( 0, maxRows )
|
|
409
|
+
|
|
410
|
+
renderTable( displayed )
|
|
411
|
+
|
|
412
|
+
const summary = checked > displayed.length ?
|
|
413
|
+
`total repositories checked: ${checked} (${displayed.length} displayed)\n` :
|
|
414
|
+
`total repositories checked: ${checked}\n`
|
|
415
|
+
log( summary )
|
|
@@ -94,12 +94,14 @@ const cliOptionList = [ // Use cliOptionList to tie into the Usage statements
|
|
|
94
94
|
default : false,
|
|
95
95
|
description : '{green display this help screen}',
|
|
96
96
|
},
|
|
97
|
-
{
|
|
97
|
+
{
|
|
98
|
+
name : 'version',
|
|
98
99
|
type : Boolean,
|
|
99
100
|
default : false,
|
|
100
101
|
description : '{green returns the build-tools repo version}',
|
|
101
102
|
},
|
|
102
|
-
{
|
|
103
|
+
{
|
|
104
|
+
name : 'verbose',
|
|
103
105
|
alias : 'v',
|
|
104
106
|
type : Boolean,
|
|
105
107
|
default : false,
|
|
@@ -114,7 +116,8 @@ const sections = [
|
|
|
114
116
|
{green This utility attempts to ease the pain of managing IAM roles and the creation of service accounts for new clusters and services.}
|
|
115
117
|
`,
|
|
116
118
|
},
|
|
117
|
-
{
|
|
119
|
+
{
|
|
120
|
+
header : 'Options',
|
|
118
121
|
optionList : cliOptionList,
|
|
119
122
|
},
|
|
120
123
|
]
|
|
@@ -24,7 +24,7 @@ export default class RolesManager {
|
|
|
24
24
|
/* eslint-disable-next-line no-await-in-loop */
|
|
25
25
|
await shellCmd( `gcloud iam roles ${iamVerb} ${roleName} --project ${k8sProject}` )
|
|
26
26
|
iamVerb = 'update' // no err then the role exists - do an update
|
|
27
|
-
} catch ( err ) {
|
|
27
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
28
28
|
iamVerb = 'create' // failed call so role does not exist - create it
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -42,7 +42,7 @@ export default class SecretsManager {
|
|
|
42
42
|
try {
|
|
43
43
|
await shellCmd( `gcloud secrets describe ${key} --project ${project}` )
|
|
44
44
|
exists = true
|
|
45
|
-
} catch ( err ) {
|
|
45
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
46
46
|
exists = false
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -72,7 +72,7 @@ export default class SecretsManager {
|
|
|
72
72
|
if ( labelStr ) createCmd.push( `--labels=${labelStr}` )
|
|
73
73
|
if ( annotationStr ) createCmd.push( `--set-annotations=${annotationStr}` )
|
|
74
74
|
await shellCmd( createCmd, { input : value } )
|
|
75
|
-
} catch ( err ) {
|
|
75
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
76
76
|
log( chalk.red( `\nSM secret write failed for ${key}\n` ) )
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -136,7 +136,7 @@ ${dataLines}
|
|
|
136
136
|
log( ` Applying ExternalSecret => ${chalk.green( name )} in ${chalk.yellow( namespace )}` )
|
|
137
137
|
try {
|
|
138
138
|
await shellCmd( [ 'kubectl', 'apply', '-f', '-' ], { input : yaml } ) // eslint-disable-line no-await-in-loop
|
|
139
|
-
} catch ( err ) {
|
|
139
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
140
140
|
log( chalk.red( `\nExternalSecret apply failed for ${name}\n` ) )
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -74,7 +74,7 @@ export default class ServiceAccountManager {
|
|
|
74
74
|
try {
|
|
75
75
|
await shellCmd( `${command} ${options}` ) // eslint-disable-line no-await-in-loop
|
|
76
76
|
return true
|
|
77
|
-
} catch ( err ) {
|
|
77
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
78
78
|
log( ' IAM propagating => ', chalk.yellow( role ) )
|
|
79
79
|
}
|
|
80
80
|
await delayWithBackoff( retryCount ) // eslint-disable-line no-await-in-loop
|
|
@@ -108,7 +108,7 @@ export default class ServiceAccountManager {
|
|
|
108
108
|
try {
|
|
109
109
|
await shellCmd( `${command} ${options}` ) // eslint-disable-line no-await-in-loop
|
|
110
110
|
return true
|
|
111
|
-
} catch ( err ) {
|
|
111
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
112
112
|
await delayWithBackoff( retryCount ) // eslint-disable-line no-await-in-loop
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -136,7 +136,7 @@ export default class ServiceAccountManager {
|
|
|
136
136
|
if ( shellOut.match( `email: ${serviceName}` ) ) {
|
|
137
137
|
return true
|
|
138
138
|
}
|
|
139
|
-
} catch ( err ) {
|
|
139
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
140
140
|
log( ' SA propagating => ', chalk.yellow( serviceName ) )
|
|
141
141
|
await delayWithBackoff( retryCount ) // eslint-disable-line no-await-in-loop
|
|
142
142
|
}
|
|
@@ -220,7 +220,7 @@ export default class ServiceAccountManager {
|
|
|
220
220
|
log( '\nSkip existing SvcAct =>', chalk.yellow( saName ) )
|
|
221
221
|
continue
|
|
222
222
|
}
|
|
223
|
-
} catch ( err ) {
|
|
223
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
224
224
|
log( '\nCreating new SvcAcct =>', chalk.green( saName ) )
|
|
225
225
|
}
|
|
226
226
|
|
package/src/unleash.mjs
CHANGED
|
@@ -39,7 +39,7 @@ const npmRunner = async ( script ) => {
|
|
|
39
39
|
const cmd = `npm run ${script}`
|
|
40
40
|
const results = await shellCmd( cmd, { stdio : 'inherit' } )
|
|
41
41
|
return results
|
|
42
|
-
} catch ( err ) {
|
|
42
|
+
} catch ( err ) { // eslint-disable-line no-unused-vars
|
|
43
43
|
process.exit( 1 )
|
|
44
44
|
}
|
|
45
45
|
return undefined
|
package/src/pkgck.sh
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
C_off='\e[0m'
|
|
4
|
-
C_yellow='\e[93m'
|
|
5
|
-
C_red='\e[101m'
|
|
6
|
-
C_green='\e[92m'
|
|
7
|
-
|
|
8
|
-
good() {
|
|
9
|
-
printf "\e[32m✔${C_off}"
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
meh() {
|
|
13
|
-
printf "\e[36m✩${C_off}"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
bad() {
|
|
17
|
-
printf "\e[31m✘${C_off}"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
semver() {
|
|
21
|
-
local version=$1
|
|
22
|
-
local isSemver=`echo $version | egrep -e "^v\d+\.\d+\.\d+$"`
|
|
23
|
-
if [ -z "$version" ];
|
|
24
|
-
then
|
|
25
|
-
printf "UNTAGGD"
|
|
26
|
-
elif [ $isSemver ];
|
|
27
|
-
then
|
|
28
|
-
printf "$version"
|
|
29
|
-
else
|
|
30
|
-
printf "!SEMVER"
|
|
31
|
-
fi
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
check_babel () {
|
|
35
|
-
local C_stat=$(bad)
|
|
36
|
-
[ -f .babelrc ] && grep --silent @leverege/babel-preset-leverege-node .babelrc
|
|
37
|
-
[ $? -eq 0 ] && C_stat=$(good)
|
|
38
|
-
[ ! -f .babelrc ] && C_stat=$(meh)
|
|
39
|
-
printf $C_stat
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
check_har () {
|
|
43
|
-
local C_stat=$(bad)
|
|
44
|
-
local prep="`node -p \"require('./package.json').scripts.prepare\"`"
|
|
45
|
-
local isHar=`echo $prep | egrep -e "hook-and-release"`
|
|
46
|
-
[ ! -z "$isHar" ] && C_stat=$(good)
|
|
47
|
-
printf $C_stat
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
check_circle () {
|
|
51
|
-
local C_stat=$(bad)
|
|
52
|
-
[ -f ".circleci/config.yml" ] && C_stat=$(good)
|
|
53
|
-
printf $C_stat
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
check_eslint () {
|
|
57
|
-
local C_stat=$(bad)
|
|
58
|
-
[ -f .eslintrc ] && grep --silent @leverege/eslint-config-leverege .eslintrc
|
|
59
|
-
[ $? -eq 0 ] && C_stat=$(good)
|
|
60
|
-
printf $C_stat
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
check_license () {
|
|
64
|
-
local C_stat=$(meh)
|
|
65
|
-
local lic="`node -p \"require('./package.json').scripts.licenseCheck == undefined\"`"
|
|
66
|
-
[ $lic == true ] && C_stat=$(good)
|
|
67
|
-
printf $C_stat
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
# Invoke as: version_ck $GITTAG $NPMVER
|
|
71
|
-
#
|
|
72
|
-
check_git_version() {
|
|
73
|
-
local tag=$1
|
|
74
|
-
local pkg=$2
|
|
75
|
-
local clr=$C_green
|
|
76
|
-
|
|
77
|
-
if [ "$tag" == 'UNTAGGD' ];
|
|
78
|
-
then
|
|
79
|
-
clr=$C_red
|
|
80
|
-
elif [ "$tag" != "$pkg" ];
|
|
81
|
-
then
|
|
82
|
-
clr=$C_yellow
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
printf "$clr$tag$C_off"
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
#check_nodemon() {
|
|
89
|
-
# nodemon="`getjson --file ./package.json --key nodemon`"
|
|
90
|
-
#}
|
|
91
|
-
|
|
92
|
-
check_scope() {
|
|
93
|
-
private="`node -p \"require('./package.json').private == true\"`"
|
|
94
|
-
scoped="`getjson --file ./package.json --key name --regex '^@\w{3}'`"
|
|
95
|
-
result="${C_red}PUBL${C_off}"
|
|
96
|
-
if [[ "$scoped" == "" ]] && [[ "$private" == "true" ]];
|
|
97
|
-
then
|
|
98
|
-
result="${C_yellow}PRVT"
|
|
99
|
-
elif [[ "$scoped" != "" ]];
|
|
100
|
-
then
|
|
101
|
-
result="${C_green}${scoped}"
|
|
102
|
-
elif [[ "$private" == "true" ]];
|
|
103
|
-
then
|
|
104
|
-
result="${C_green}prv"
|
|
105
|
-
fi
|
|
106
|
-
printf "${result}${C_off}"
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
check_ESM() {
|
|
110
|
-
local C_stat=$(bad)
|
|
111
|
-
jstype="`getjson --file package.json --key type`"
|
|
112
|
-
[[ "$jstype" == "module" ]] && C_stat=$(good)
|
|
113
|
-
printf $C_stat
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
repos=0
|
|
117
|
-
check_repo() {
|
|
118
|
-
local dir=$1
|
|
119
|
-
[ ! -d $dir/.git ] || [ ! -f $dir/package.json ] && return
|
|
120
|
-
cd $dir &> /dev/null
|
|
121
|
-
local gperrs=0
|
|
122
|
-
git pull -q &> /dev/null; [ $? -ne 0 ] && let "gperrs++"
|
|
123
|
-
git pull -q --tags &> /dev/null; [ $? -ne 0 ] && let "gperrs++"
|
|
124
|
-
|
|
125
|
-
# It is necessary to use the output from printf to set localmods in order
|
|
126
|
-
# to get the ANSI escape codes expanded.
|
|
127
|
-
local localmods=$(good)
|
|
128
|
-
[ $gperrs -ne 0 ] && localmods=$(bad)
|
|
129
|
-
pkgver=$(semver "v`node -p \"require('./package.json').version\"`")
|
|
130
|
-
gitver=$(semver `git describe --abbrev=0 2>/dev/null`)
|
|
131
|
-
[ $? -ne 0 ] && gitver='UNTAGGD'
|
|
132
|
-
local needspush=
|
|
133
|
-
if [ "$pkgver" == "$gitver" ];
|
|
134
|
-
then
|
|
135
|
-
git ls-remote --tags origin | grep $gitver &> /dev/null
|
|
136
|
-
[ $? -ne 0 ] && needspush='!tags'
|
|
137
|
-
fi
|
|
138
|
-
local babel7=$(check_babel)
|
|
139
|
-
local hookar=$(check_har)
|
|
140
|
-
local circle=$(check_circle)
|
|
141
|
-
local eslint=$(check_eslint)
|
|
142
|
-
local esmlib=$(check_ESM)
|
|
143
|
-
local license=$(check_license)
|
|
144
|
-
local scoped=$(check_scope)
|
|
145
|
-
[ $(( $repos % 15)) -eq 0 ] && printf "\n%35s pkg git cln scop cir lic ESM har\n"
|
|
146
|
-
printf "%32s: %-8s %-16s %s %8s %s %s %s %s %s\n" ${dir:0:32} $pkgver $(check_git_version $gitver $pkgver) $localmods $scoped $circle $license $esmlib $hookar "$needspush"
|
|
147
|
-
let "repos++"
|
|
148
|
-
cd - &> /dev/null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
clear
|
|
152
|
-
|
|
153
|
-
pkgs="$@"
|
|
154
|
-
for dir in $@; do
|
|
155
|
-
check_repo $dir
|
|
156
|
-
done
|
|
157
|
-
|
|
158
|
-
printf "\ntotal repoitories checked: $repos\n\n"
|