@leverege/build-tools 2.102.0 → 2.103.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/.vscode/launch.json +10 -0
- package/README.md +22 -9
- package/package.json +4 -7
- package/src/Utils.mjs +2 -0
- package/src/chart-to-registry.mjs +12 -1
- 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/push-my-chart.mjs +13 -4
- package/src/repo-panopticon.mjs +415 -0
- package/src/pkgck.sh +0 -158
package/.vscode/launch.json
CHANGED
|
@@ -58,6 +58,16 @@
|
|
|
58
58
|
"runtimeExecutable": "node",
|
|
59
59
|
"console": "integratedTerminal"
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
"name": "chart-to-registry rsrc-srvr",
|
|
63
|
+
"type": "node",
|
|
64
|
+
"request": "launch",
|
|
65
|
+
"program": "${workspaceFolder}/src/chart-to-registry.mjs",
|
|
66
|
+
"args": [],
|
|
67
|
+
"cwd": "/Users/john/Work/leverege/cicd/@plt/resource-server",
|
|
68
|
+
"runtimeExecutable": "node",
|
|
69
|
+
"console": "integratedTerminal"
|
|
70
|
+
},
|
|
61
71
|
{
|
|
62
72
|
"name": "ingestor in monorepo",
|
|
63
73
|
"type": "node",
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Most tools assume the following are installed and configured:
|
|
|
16
16
|
- `kubectl` (with a valid cluster context)
|
|
17
17
|
- `helm` (v3+)
|
|
18
18
|
- `fzf` (for interactive pod selection in `k8x` and `klog`)
|
|
19
|
-
- Node.js 22+
|
|
19
|
+
- Node.js 22+ - prefer 24
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -85,13 +85,15 @@ init-my-chart [--dry-run] [--debug]
|
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
**`chart-to-registry`** — Pushes Helm charts to GCP Artifact Registry with version
|
|
88
|
-
checking and dependency resolution.
|
|
88
|
+
checking and dependency resolution. Warns if `appVersion` in Chart.yaml does not start
|
|
89
|
+
with `v` (bypass with `--ignore-appv`).
|
|
89
90
|
```bash
|
|
90
|
-
chart-to-registry --chart <dir> [--project <id>] [--repository <name>] [--force] [--dry-run]
|
|
91
|
+
chart-to-registry --chart <dir> [--project <id>] [--repository <name>] [--force] [--dry-run] [--ignore-appv]
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
**`push-my-chart`** — Publishes Helm charts to both GCS Chartmuseum (legacy) and
|
|
94
|
-
GCP Artifact Registry.
|
|
95
|
+
GCP Artifact Registry. Displays chart name, version, and appVersion before pushing;
|
|
96
|
+
warns if `appVersion` does not start with `v`.
|
|
95
97
|
```bash
|
|
96
98
|
push-my-chart [--chart <dir>] [--no-registry]
|
|
97
99
|
```
|
|
@@ -226,11 +228,20 @@ Manager, updating `.npmrc` / `.yarnrc.yml`. Skips refresh if the token is less t
|
|
|
226
228
|
refresh-npm-token [--project <id>] [--force] [--dry-run]
|
|
227
229
|
```
|
|
228
230
|
|
|
229
|
-
**`
|
|
230
|
-
|
|
231
|
+
**`repo-panopticon`** — Multi-repo health dashboard. Checks each repository against a suite
|
|
232
|
+
of indicators in a compact 80-column table. Monorepo-aware: workspace packages are
|
|
233
|
+
auto-discovered, grouped under their parent repo label, and checked individually.
|
|
231
234
|
```bash
|
|
232
|
-
|
|
235
|
+
repo-panopticon <dirs...> # check one or more repos / globs
|
|
236
|
+
repo-panopticon @plt/* @kud/* @srv/* # typical full-org sweep
|
|
237
|
+
repo-panopticon --no-pull @plt/* # skip git pull/fetch
|
|
238
|
+
repo-panopticon --limit 40 @srv/* # cap displayed rows (default 80)
|
|
239
|
+
repo-panopticon --concurrency 10 @srv/* # increase parallelism (default 5)
|
|
233
240
|
```
|
|
241
|
+
Columns: `mgr` (npm/yrn) · `pkg` (package.json version) · `git` (latest semver tag) ·
|
|
242
|
+
`scop` (package scope) · `cir` (CircleCI — ✘ missing · L legacy · 1 orb v1 · 2 orb v2) ·
|
|
243
|
+
`ESM` (`"type":"module"`) · `har` (.har hooks directory) · `lnt` (ESLint major version) ·
|
|
244
|
+
`bbl` (Babel presence). Repo name shown in orange when dirty or sync failed.
|
|
234
245
|
|
|
235
246
|
**`pkglint`** — Verifies required documentation files (README.md, LICENSE.md) are
|
|
236
247
|
present before publishing.
|
|
@@ -252,8 +263,9 @@ count-lines-of-code [--src-only]
|
|
|
252
263
|
|
|
253
264
|
### CI/CD Integration
|
|
254
265
|
|
|
255
|
-
**`circle-orb-it`** — Installs or updates CircleCI configuration (Leverege orb) and
|
|
256
|
-
GitHub Dependabot settings for a repository.
|
|
266
|
+
**`circle-orb-it`** — Installs or updates CircleCI configuration (Leverege orb v2.0.0) and
|
|
267
|
+
GitHub Dependabot settings for a repository. Auto-detects npm vs yarn-berry and writes
|
|
268
|
+
the appropriate template. Preserves existing customizations.
|
|
257
269
|
```bash
|
|
258
270
|
circle-orb-it [--dry-run] [--debug]
|
|
259
271
|
```
|
|
@@ -340,6 +352,7 @@ BUILD_TOOLS_DEBUG=1 docker-to-registry
|
|
|
340
352
|
|
|
341
353
|
| Tool | Replacement |
|
|
342
354
|
|------|-------------|
|
|
355
|
+
| `pkgck` | `repo-panopticon` |
|
|
343
356
|
| `docker-to-registry-py` | `docker-to-registry` |
|
|
344
357
|
| `chart-to-museum` | `push-my-chart` |
|
|
345
358
|
| `refresh-py-idx` | No longer needed |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"prepack": "npm exec prepack"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
|
-
"build-and-push": "src/build-and-push.sh",
|
|
20
19
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
21
20
|
"build-tools": "src/build-tools.mjs",
|
|
22
21
|
"change-up": "src/change-up.sh",
|
|
@@ -29,8 +28,6 @@
|
|
|
29
28
|
"decrypt-secrets": "src/decrypt-secrets.sh",
|
|
30
29
|
"dirty-git": "src/dirty-git.sh",
|
|
31
30
|
"docker-to-registry": "src/docker-to-registry.mjs",
|
|
32
|
-
"docker-to-registry-py": "src/docker-to-registry-py.mjs",
|
|
33
|
-
"docker-to-registry.sh": "src/docker-to-registry.sh",
|
|
34
31
|
"dockreate": "src/dockreate.sh",
|
|
35
32
|
"encrypt-secrets": "src/encrypt-secrets.sh",
|
|
36
33
|
"firebaseDeploy": "src/firebaseDeploy.mjs",
|
|
@@ -55,7 +52,7 @@
|
|
|
55
52
|
"npm-link": "src/npm-link.sh",
|
|
56
53
|
"npm-unlink": "src/npm-unlink.sh",
|
|
57
54
|
"overwhelm": "src/overwhelm.mjs",
|
|
58
|
-
"pkgck": "
|
|
55
|
+
"pkgck": "echo '*** Use repo-panopticon instead! ***",
|
|
59
56
|
"pkglint": "src/pkglint.sh",
|
|
60
57
|
"prepack": "src/prepack.mjs",
|
|
61
58
|
"prune-git": "src/prune-git.sh",
|
|
@@ -65,10 +62,10 @@
|
|
|
65
62
|
"push-my-chart": "src/push-my-chart.mjs",
|
|
66
63
|
"refresh-npm-token": "src/refresh-npm-token.mjs",
|
|
67
64
|
"refresh-py-idx": "src/refresh-py-idx.sh",
|
|
65
|
+
"repo-panopticon": "src/repo-panopticon.mjs",
|
|
68
66
|
"send-to-slack": "src/send-to-slack.mjs",
|
|
69
67
|
"service-man": "src/service-man/service-man.mjs",
|
|
70
68
|
"snapshot-cnpg": "src/snapshot-cnpg.mjs",
|
|
71
|
-
"tag-release": "src/tag-release.mjs",
|
|
72
69
|
"unleash": "src/unleash.mjs",
|
|
73
70
|
"upload-model": "src/upload-model.sh",
|
|
74
71
|
"yarn-build": "src/yarn-build.mjs"
|
|
@@ -109,7 +106,7 @@
|
|
|
109
106
|
"sloc": "^0.3.2",
|
|
110
107
|
"superstruct": "^2.0.2",
|
|
111
108
|
"tmp": "^0.2.5",
|
|
112
|
-
"toml": "^
|
|
109
|
+
"toml": "^4.1.1",
|
|
113
110
|
"yaml": "^2.9.0",
|
|
114
111
|
"zx": "^8.8.5"
|
|
115
112
|
},
|
package/src/Utils.mjs
CHANGED
|
@@ -534,6 +534,7 @@ export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
|
534
534
|
const chartName = chartYaml.name
|
|
535
535
|
const chartLibrary = chartYaml?.type === 'library'
|
|
536
536
|
const chartVersion = chartYaml.version
|
|
537
|
+
const chartAppVersion = chartYaml.appVersion
|
|
537
538
|
const chartPackage = `${chartName}-${chartVersion}.tgz`
|
|
538
539
|
const valuesText = fs.readFileSync( `${helmroot}/values.yaml`, 'utf8' )
|
|
539
540
|
const valuesYaml = YAML.load( valuesText )
|
|
@@ -573,6 +574,7 @@ export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
|
573
574
|
chartLibrary,
|
|
574
575
|
chartPackage,
|
|
575
576
|
chartVersion,
|
|
577
|
+
chartAppVersion,
|
|
576
578
|
hasDependencies,
|
|
577
579
|
depsOutOfSync,
|
|
578
580
|
imageRegistry,
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
errorExit,
|
|
15
15
|
establishOauthAccess,
|
|
16
16
|
log,
|
|
17
|
-
warning,
|
|
18
17
|
parseHelmChart,
|
|
18
|
+
proceed,
|
|
19
19
|
shellCmd,
|
|
20
|
+
warning,
|
|
20
21
|
} from './Utils.mjs'
|
|
21
22
|
|
|
22
23
|
const commandLineOptions = [ // Use commandLineOptions to tie into the Usage statements
|
|
@@ -46,6 +47,11 @@ const commandLineOptions = [ // Use commandLineOptions to tie into the Usage sta
|
|
|
46
47
|
type : Boolean,
|
|
47
48
|
description : '{green update artifact registry auth token and exit}',
|
|
48
49
|
},
|
|
50
|
+
{
|
|
51
|
+
name : 'ignore-appv',
|
|
52
|
+
type : Boolean,
|
|
53
|
+
description : '{green ignore the Char.yaml appVersion format}',
|
|
54
|
+
},
|
|
49
55
|
{
|
|
50
56
|
name : 'dry-run',
|
|
51
57
|
type : Boolean,
|
|
@@ -98,6 +104,11 @@ if ( semverLt( process.version, minNodejsVersion ) ) {
|
|
|
98
104
|
// load up all we need to know about the chart
|
|
99
105
|
const helmChart = await parseHelmChart( args.chart )
|
|
100
106
|
|
|
107
|
+
if ( !helmChart.chartAppVersion.startsWith( 'v' ) && !args.ignoreAppv ) {
|
|
108
|
+
warning( 'Chart.yaml appVersion does not begin with the letter "v"\n' )
|
|
109
|
+
await proceed( 'You sure about this?' )
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
// helm push helm-package.tgz oci://REGISTRY_LOCATION/REGISTRY_PROJECT/REGISTRY_REPOSITORY/charts
|
|
102
113
|
//
|
|
103
114
|
const registryLocation = args.location || helmChart.registryLocation
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
version: 2.1
|
|
2
2
|
|
|
3
3
|
orbs:
|
|
4
|
-
leverege: leverege/circle-nodejs-ci@
|
|
4
|
+
leverege: leverege/circle-nodejs-ci@2.0.0
|
|
5
5
|
|
|
6
6
|
workflows:
|
|
7
7
|
build-and-verify:
|
|
8
8
|
jobs:
|
|
9
9
|
- leverege/install-dependencies:
|
|
10
10
|
context: npm
|
|
11
|
-
# pkg-manager: yarn-berry # uncomment for Yarn Berry repos
|
|
12
11
|
- leverege/code-analysis:
|
|
12
|
+
context: npm
|
|
13
13
|
requires:
|
|
14
14
|
- leverege/install-dependencies
|
|
15
15
|
- leverege/code-coverage:
|
|
16
|
+
context:
|
|
17
|
+
- npm
|
|
18
|
+
- leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
16
19
|
coverage-threshold: 70 # percentage threshold for functions, lines, and statements
|
|
17
|
-
context: leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
18
20
|
enable-redis: false # set to true if tests require a live Redis connection
|
|
19
21
|
cache-config: '{"type":"inMemory"}' # sets CACHE_CONFIG env var - options: inMemory, redis
|
|
20
22
|
requires:
|
|
21
23
|
- leverege/install-dependencies
|
|
22
24
|
- leverege/generate-docs:
|
|
25
|
+
context: npm
|
|
23
26
|
requires:
|
|
24
27
|
- leverege/install-dependencies
|
|
25
28
|
- leverege/mirror-to-github:
|
|
@@ -11,14 +11,19 @@ import {
|
|
|
11
11
|
enableDebug,
|
|
12
12
|
errorExit,
|
|
13
13
|
getGitRootDirectory,
|
|
14
|
+
getIsYarnProject,
|
|
14
15
|
log,
|
|
16
|
+
mkdirSafe,
|
|
15
17
|
proceed,
|
|
18
|
+
warning,
|
|
16
19
|
} from '../Utils.mjs'
|
|
17
20
|
|
|
18
|
-
const ORB_VERSION = '
|
|
21
|
+
const ORB_VERSION = '2.0.0' // Keep this in sync with the versions in circle-npm.yml and circle-yarn.yml
|
|
19
22
|
const CIRCLE_DIR = '.circleci'
|
|
20
23
|
const CIRCLE_YML = `${CIRCLE_DIR}/config.yml`
|
|
21
|
-
const
|
|
24
|
+
const CIRCLE_NPM_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'circle-npm.yml' )
|
|
25
|
+
const CIRCLE_YARN_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'circle-yarn.yml' )
|
|
26
|
+
const CIRCLE_YML_TEMPLATE = isYarn => ( isYarn ? CIRCLE_YARN_TEMPLATE : CIRCLE_NPM_TEMPLATE )
|
|
22
27
|
const GITHUB_DIR = '.github'
|
|
23
28
|
const DEPENDABOT_FILE = `${GITHUB_DIR}/dependabot.yml`
|
|
24
29
|
const DEPENDABOT_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'dependabot.yml' )
|
|
@@ -52,9 +57,13 @@ if ( !gitRoot ) {
|
|
|
52
57
|
errorExit( 'This command must be run inside a git repository' )
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
const isYarn = getIsYarnProject( gitRoot )
|
|
61
|
+
const circleTemplate = CIRCLE_YML_TEMPLATE( isYarn )
|
|
62
|
+
log( chalk.blue( `\nPackage manager: ${isYarn ? 'yarn-berry' : 'npm'}` ) )
|
|
63
|
+
|
|
55
64
|
// Warn if this looks like a Python project since the orb is Node.js only
|
|
56
65
|
if ( fs.existsSync( path.join( gitRoot, 'pyproject.toml' ) ) ) {
|
|
57
|
-
|
|
66
|
+
warning( 'pyproject.toml detected - the leverege orb is Node.js only' )
|
|
58
67
|
await proceed( 'Continue anyway?' )
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -119,7 +128,7 @@ if ( !writeCircleYml && !writeDependabot ) {
|
|
|
119
128
|
|
|
120
129
|
if ( options.dryRun ) {
|
|
121
130
|
if ( writeCircleYml ) {
|
|
122
|
-
let dryRunContent = fs.readFileSync(
|
|
131
|
+
let dryRunContent = fs.readFileSync( circleTemplate, 'utf-8' )
|
|
123
132
|
if ( redisEnabled ) {
|
|
124
133
|
dryRunContent = dryRunContent
|
|
125
134
|
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
@@ -142,10 +151,8 @@ if ( options.dryRun ) {
|
|
|
142
151
|
// ---------- Write ----------
|
|
143
152
|
|
|
144
153
|
if ( writeCircleYml ) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
let circleYmlContent = fs.readFileSync( CIRCLE_YML_TEMPLATE, 'utf-8' )
|
|
154
|
+
mkdirSafe( circleDir )
|
|
155
|
+
let circleYmlContent = fs.readFileSync( circleTemplate, 'utf-8' )
|
|
149
156
|
if ( redisEnabled ) {
|
|
150
157
|
circleYmlContent = circleYmlContent
|
|
151
158
|
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
@@ -162,9 +169,7 @@ if ( writeCircleYml ) {
|
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
if ( writeDependabot ) {
|
|
165
|
-
|
|
166
|
-
fs.mkdirSync( githubDir, { recursive : true } )
|
|
167
|
-
}
|
|
172
|
+
mkdirSafe( githubDir )
|
|
168
173
|
fs.writeFileSync( dependabotPath, fs.readFileSync( DEPENDABOT_TEMPLATE, 'utf-8' ) )
|
|
169
174
|
log( chalk.green( `Dependabot config written => ${dependabotPath}` ) )
|
|
170
175
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
version: 2.1
|
|
2
|
+
|
|
3
|
+
orbs:
|
|
4
|
+
leverege: leverege/circle-nodejs-ci@2.0.0
|
|
5
|
+
|
|
6
|
+
workflows:
|
|
7
|
+
build-and-verify:
|
|
8
|
+
jobs:
|
|
9
|
+
- leverege/code-analysis:
|
|
10
|
+
context: npm
|
|
11
|
+
pkg-manager: yarn-berry
|
|
12
|
+
- leverege/code-coverage:
|
|
13
|
+
context:
|
|
14
|
+
- npm
|
|
15
|
+
- leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
16
|
+
pkg-manager: yarn-berry
|
|
17
|
+
coverage-threshold: 70 # percentage threshold for functions, lines, and statements
|
|
18
|
+
enable-redis: false # set to true if tests require a live Redis connection
|
|
19
|
+
cache-config: '{"type":"inMemory"}' # sets CACHE_CONFIG env var - options: inMemory, redis
|
|
20
|
+
- leverege/generate-docs:
|
|
21
|
+
context: npm
|
|
22
|
+
pkg-manager: yarn-berry
|
|
23
|
+
- leverege/mirror-to-github:
|
|
24
|
+
context: github-mirror
|
|
25
|
+
# github-team: platform # available teams: platform, utilities, pitcrew, recovr
|
package/src/push-my-chart.mjs
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
errorExit,
|
|
10
10
|
log,
|
|
11
11
|
parseHelmChart,
|
|
12
|
-
|
|
12
|
+
proceed,
|
|
13
|
+
shellCmd,
|
|
14
|
+
warning,
|
|
13
15
|
} from './Utils.mjs'
|
|
14
16
|
|
|
15
17
|
const commandLineOptions = [
|
|
@@ -56,11 +58,18 @@ if ( args.help ) {
|
|
|
56
58
|
const {
|
|
57
59
|
chartName,
|
|
58
60
|
chartVersion,
|
|
61
|
+
chartAppVersion,
|
|
59
62
|
depsOutOfSync,
|
|
60
63
|
} = await parseHelmChart( args.chart )
|
|
61
64
|
|
|
62
|
-
log( chalk.green( `Chart
|
|
63
|
-
log( chalk.green( `Version
|
|
65
|
+
log( chalk.green( `Chart => ${chalk.bold.yellow( chartName )}` ) )
|
|
66
|
+
log( chalk.green( `Version => ${chalk.bold.yellow( chartVersion )}` ) )
|
|
67
|
+
log( chalk.green( `AppVersion => ${chalk.bold.yellow( chartAppVersion )}` ) )
|
|
68
|
+
|
|
69
|
+
if ( !chartAppVersion.startsWith( 'v' ) ) {
|
|
70
|
+
warning( 'Chart.yaml appVersion does not begin with the letter "v"\n' )
|
|
71
|
+
await proceed( 'You sure about this?' )
|
|
72
|
+
}
|
|
64
73
|
|
|
65
74
|
if ( depsOutOfSync ) {
|
|
66
75
|
try {
|
|
@@ -89,7 +98,7 @@ if ( args.noRegistry ) {
|
|
|
89
98
|
|
|
90
99
|
try {
|
|
91
100
|
log( chalk.green( ' ... pushing to the Artifact Registry' ) )
|
|
92
|
-
const registry = await shellCmd( `chart-to-registry --chart ${args.chart}` )
|
|
101
|
+
const registry = await shellCmd( `chart-to-registry --chart ${args.chart} --ignore-appv` )
|
|
93
102
|
debug( { registry }, '<==chart-to-registry' )
|
|
94
103
|
process.exit( museumStatus )
|
|
95
104
|
} catch ( ex ) {
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable security/detect-non-literal-fs-filename, no-multi-spaces, key-spacing */
|
|
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 )
|
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"
|