@leverege/build-tools 2.106.6 → 2.108.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/README.md CHANGED
@@ -251,7 +251,7 @@ repo-panopticon --limit 40 @srv/* # cap displayed rows (default 80)
251
251
  repo-panopticon --concurrency 10 @srv/* # increase parallelism (default 5)
252
252
  ```
253
253
  Columns: `mgr` (npm/yrn) · `pkg` (package.json version) · `git` (latest semver tag) ·
254
- `scop` (package scope) · `cir` (CircleCI — ✘ missing · L legacy · 1 orb v1 · 2 orb v2) ·
254
+ `scop` (package scope) · `cir` (CI status — ✘ missing · L legacy · 1 orb v1 · 2 orb v2 · ★ orb v2 + GHA ci.yml) ·
255
255
  `ESM` (`"type":"module"`) · `har` (.har hooks directory) · `lnt` (ESLint major version) ·
256
256
  `bbl` (Babel presence). Repo name shown in orange when dirty or sync failed.
257
257
 
@@ -282,6 +282,14 @@ the appropriate template. Preserves existing customizations.
282
282
  circle-orb-it [--dry-run] [--debug]
283
283
  ```
284
284
 
285
+ **`github-actions-it`** — Installs or updates GitHub Actions CI configuration for a
286
+ Leverege library repository. Reads existing `.circleci/config.yml` to preserve settings
287
+ (coverage threshold, cache config, GCP context) when migrating from CircleCI. Generates
288
+ `.github/workflows/ci.yml` using the shared `library-ci.yml` reusable workflow.
289
+ ```bash
290
+ github-actions-it [--dry-run] [--debug]
291
+ ```
292
+
285
293
  **`send-to-slack`** — Sends messages to a Slack channel, retrieving the webhook URL
286
294
  from GCP Secret Manager.
287
295
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.106.6",
3
+ "version": "2.108.0",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -8,12 +8,12 @@
8
8
  "url": "git+ssh://git@bitbucket.org/leverege/build-tools.git"
9
9
  },
10
10
  "scripts": {
11
- "clean": "rm -rf lib",
12
11
  "lint": "eslint",
12
+ "lint-fix": "eslint --fix",
13
+ "prepack": "npm exec prepack",
13
14
  "prepare": "[ $SKIP_PREPARE ] && exit 0 || ./src/hook-and-release.mjs",
14
15
  "test": "mocha -t 60000 --exit 'test/**/!(*e2e*).mjs'",
15
- "test-all": "mocha -t 60000 --exit 'test/**/**.mjs'",
16
- "prepack": "npm exec prepack"
16
+ "test-all": "mocha -t 60000 --exit 'test/**/**.mjs'"
17
17
  },
18
18
  "bin": {
19
19
  "build-cnpg-image": "src/build-cnpg-image.mjs",
@@ -34,6 +34,7 @@
34
34
  "firebaseServe": "src/firebaseServe.mjs",
35
35
  "gcp-snapshots": "src/gcp-snapshots.mjs",
36
36
  "generate-docs": "src/generate-docs.mjs",
37
+ "github-actions-it": "src/github-actions-it.mjs",
37
38
  "getfbcfg": "src/getfbcfg.mjs",
38
39
  "getjson": "src/getjson.mjs",
39
40
  "git-tar": "src/git-tar.sh",
@@ -46,6 +47,7 @@
46
47
  "helmwhat": "src/helmup.sh",
47
48
  "hook-and-release": "src/hook-and-release.mjs",
48
49
  "init-my-chart": "src/init-my-chart/init-my-chart.mjs",
50
+ "sync-chart-version": "src/sync-chart-version.mjs",
49
51
  "k8scale": "src/k8scale.sh",
50
52
  "k8x": "src/k8x.sh",
51
53
  "klog": "src/klog.sh",
@@ -84,7 +86,7 @@
84
86
  "chalk": "^5.6.2",
85
87
  "command-line-args": "^6.0.2",
86
88
  "command-line-usage": "^7.0.4",
87
- "commander": "^14.0.3",
89
+ "commander": "^15.0.0",
88
90
  "deepmerge": "^4.3.1",
89
91
  "enquirer": "^2.4.1",
90
92
  "execa": "^9.6.1",
@@ -102,7 +104,7 @@
102
104
  "p-limit": "^7.3.0",
103
105
  "package-up": "^5.0.0",
104
106
  "readline-sync": "^1.4.10",
105
- "semver": "7.8.2",
107
+ "semver": "7.8.4",
106
108
  "shell-quote": "^1.8.4",
107
109
  "simple-git": "^3.36.0",
108
110
  "sloc": "^0.3.2",
@@ -117,6 +119,6 @@
117
119
  "chai": "^6.2.2",
118
120
  "eslint": "^9",
119
121
  "mocha": "^11.7.6",
120
- "npm": "^11.16.0"
122
+ "npm": "^11.17.0"
121
123
  }
122
124
  }
@@ -0,0 +1,187 @@
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 YAML from 'js-yaml'
9
+
10
+ import {
11
+ enableDebug,
12
+ errorExit,
13
+ getGitRootDirectory,
14
+ getIsYarnProject,
15
+ log,
16
+ mkdirSafe,
17
+ proceed,
18
+ } from './Utils.mjs'
19
+
20
+ const GH_ACTIONS_REF = 'development'
21
+ const CIRCLE_YML = '.circleci/config.yml'
22
+ const GH_WORKFLOWS_DIR = '.github/workflows'
23
+ const GH_CI_YML = `${GH_WORKFLOWS_DIR}/ci.yml`
24
+ const DEFAULT_COVERAGE_THRESHOLD = 70
25
+ const DEFAULT_CACHE_CONFIG = '{"type":"inMemory"}'
26
+
27
+ program
28
+ .name( 'github-actions-it' )
29
+ .description( chalk.green( 'Installs or updates GitHub Actions CI configuration for a Leverege library repository' ) )
30
+ .option( '--debug', 'Enable debug logging' )
31
+ .option( '--dry-run', 'Show what would be written without making changes' )
32
+ .addHelpText( 'after', `
33
+ ${chalk.yellow( 'Files managed:' )}
34
+ ${chalk.green( GH_CI_YML )} GitHub Actions CI workflow
35
+
36
+ ${chalk.yellow( 'Notes:' )}
37
+ Run from anywhere inside a git repository.
38
+ Reads .circleci/config.yml to preserve existing settings when present.
39
+ Redis is always available on port 6379 — no configuration needed.
40
+ Yarn auth and nodeLinker are handled by the workflow writing ~/.yarnrc.yml on the runner.
41
+
42
+ ${chalk.yellow( 'Examples:' )}
43
+ ${chalk.green( 'github-actions-it' )} install or upgrade
44
+ ${chalk.green( 'github-actions-it --dry-run' )} preview without writing files
45
+ ` )
46
+ .parse()
47
+
48
+ const options = program.opts()
49
+ if ( options.debug ) { enableDebug() }
50
+
51
+ const gitRoot = await getGitRootDirectory()
52
+ if ( !gitRoot ) {
53
+ errorExit( 'This command must be run inside a git repository' )
54
+ }
55
+
56
+ // ---------- Parse existing CircleCI config ----------
57
+
58
+ const circleYmlPath = path.join( gitRoot, CIRCLE_YML )
59
+
60
+ let enableGcp = false
61
+ let coverageThreshold = DEFAULT_COVERAGE_THRESHOLD
62
+ let cacheConfig = DEFAULT_CACHE_CONFIG
63
+ let redisEnabled = false
64
+ let isYarnFromCircle = false
65
+
66
+ if ( fs.existsSync( circleYmlPath ) ) {
67
+ const circleConfig = YAML.load( fs.readFileSync( circleYmlPath, 'utf8' ) )
68
+
69
+ const allJobs = []
70
+ for ( const workflow of Object.values( circleConfig.workflows ?? {} ) ) {
71
+ for ( const job of ( workflow.jobs ?? [] ) ) {
72
+ if ( typeof job === 'object' ) {
73
+ for ( const [ name, config ] of Object.entries( job ) ) {
74
+ allJobs.push( { name, config: config ?? {} } )
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ const coverageJob = allJobs.find( j => j.name === 'leverege/code-coverage' )
81
+ if ( coverageJob ) {
82
+ const { config } = coverageJob
83
+ coverageThreshold = config['coverage-threshold'] ?? DEFAULT_COVERAGE_THRESHOLD
84
+ cacheConfig = config['cache-config'] ?? DEFAULT_CACHE_CONFIG
85
+ redisEnabled = config['enable-redis'] === true
86
+
87
+ const ctx = config.context
88
+ const contexts = Array.isArray( ctx ) ? ctx : ( ctx ? [ ctx ] : [] )
89
+ enableGcp = contexts.includes( 'leverege-gcp' )
90
+ }
91
+
92
+ isYarnFromCircle = allJobs.some( j => j.config['pkg-manager'] === 'yarn-berry' )
93
+
94
+ log( chalk.blue( '\nParsed CircleCI config:' ) )
95
+ log( ` pkg-manager: ${isYarnFromCircle ? 'yarn-berry' : 'npm'}` )
96
+ log( ` enable-gcp: ${enableGcp}` )
97
+ log( ` coverage-threshold: ${coverageThreshold}` )
98
+ log( ` cache-config: ${cacheConfig}` )
99
+ log( ` enable-redis: ${redisEnabled}` )
100
+ } else {
101
+ log( chalk.yellow( '\nNo .circleci/config.yml found — using defaults' ) )
102
+ }
103
+
104
+ const isYarn = isYarnFromCircle || getIsYarnProject( gitRoot )
105
+ log( chalk.blue( `\nPackage manager: ${isYarn ? 'yarn-berry' : 'npm'}` ) )
106
+
107
+ if ( redisEnabled ) {
108
+ log( chalk.yellow( '\nRedis detected — Redis service is always available on port 6379 in GitHub Actions' ) )
109
+ }
110
+
111
+ // ---------- Determine what needs writing ----------
112
+
113
+ const ciYmlPath = path.join( gitRoot, GH_CI_YML )
114
+ let ciYmlExists = false
115
+
116
+ if ( fs.existsSync( ciYmlPath ) ) {
117
+ ciYmlExists = true
118
+ const existingCi = fs.readFileSync( ciYmlPath, 'utf8' )
119
+ if ( existingCi.includes( `library-ci.yml@${GH_ACTIONS_REF}` ) ) {
120
+ log( chalk.yellow( '\nExisting GitHub Actions CI config found — will update' ) )
121
+ } else {
122
+ log( chalk.yellow( '\nExisting .github/workflows/ci.yml found (not managed by github-actions-it) — will overwrite' ) )
123
+ }
124
+ }
125
+
126
+ // ---------- Generate content ----------
127
+
128
+ function generateCiYml() {
129
+ const withLines = []
130
+ if ( enableGcp ) {
131
+ withLines.push( ' enable-gcp: true' )
132
+ withLines.push( ' gcp-project: leverege-registry' )
133
+ }
134
+ if ( coverageThreshold !== DEFAULT_COVERAGE_THRESHOLD ) {
135
+ withLines.push( ` coverage-threshold: ${coverageThreshold}` )
136
+ }
137
+ if ( cacheConfig !== DEFAULT_CACHE_CONFIG ) {
138
+ withLines.push( ` cache-config: '${cacheConfig}'` )
139
+ }
140
+
141
+ const lines = [
142
+ 'name: CI',
143
+ '',
144
+ 'on:',
145
+ ' push:',
146
+ ' pull_request:',
147
+ ' workflow_dispatch:',
148
+ '',
149
+ 'jobs:',
150
+ ' ci:',
151
+ ' permissions:',
152
+ ' id-token: write',
153
+ ' contents: read',
154
+ ` uses: Leverege/github-actions/.github/workflows/library-ci.yml@${GH_ACTIONS_REF}`,
155
+ ]
156
+
157
+ if ( withLines.length > 0 ) {
158
+ lines.push( ' with:' )
159
+ lines.push( ...withLines )
160
+ }
161
+
162
+ lines.push( ' secrets: inherit', '' )
163
+ return lines.join( '\n' )
164
+ }
165
+
166
+ // ---------- Dry run ----------
167
+
168
+ if ( options.dryRun ) {
169
+ log( chalk.green( `\n[dry-run] Would write ${GH_CI_YML}:\n` ) )
170
+ log( generateCiYml() )
171
+ process.exit( 0 )
172
+ }
173
+
174
+ // ---------- Confirm overwrites ----------
175
+
176
+ if ( ciYmlExists ) {
177
+ await proceed( `Overwrite ${GH_CI_YML}?` )
178
+ }
179
+
180
+ // ---------- Write ----------
181
+
182
+ mkdirSafe( path.join( gitRoot, GH_WORKFLOWS_DIR ) )
183
+ fs.writeFileSync( ciYmlPath, generateCiYml() )
184
+ log( chalk.green( `\nGitHub Actions CI config written => ${GH_CI_YML}` ) )
185
+
186
+ log( chalk.yellow( '\nNext steps:' ) )
187
+ log( ` git add ${GH_CI_YML} && git commit -m "CHORE add GitHub Actions CI"\n` )
@@ -70,12 +70,13 @@ prometheus:
70
70
  ruleSelectorNilUsesHelmValues: false
71
71
  serviceMonitorSelectorNilUsesHelmValues: false
72
72
  probeSelectorNilUsesHelmValues: false
73
- maximumStartupDurationSeconds: 60
73
+ maximumStartupDurationSeconds: 300
74
74
  resources:
75
75
  requests:
76
76
  cpu: 500m
77
77
  memory: 2Gi
78
78
  retention: 30d
79
+ retentionSize: 48GiB
79
80
  storageSpec:
80
81
  volumeClaimTemplate:
81
82
  spec:
@@ -32,7 +32,9 @@ const CHECKS = [
32
32
  const match = content.match( /leverege\/circle-nodejs-ci@(\d+)\.\d+\.\d+/ )
33
33
  if ( !match || parseInt( match[1], 10 ) === 0 ) return { value : chalk.hex( '#FFA500' )( 'L' ) }
34
34
  const major = parseInt( match[1], 10 )
35
- return { value : major === 1 ? chalk.yellow( '1' ) : chalk.green( `${major}` ) }
35
+ if ( major === 1 ) return { value : chalk.yellow( '1' ) }
36
+ const hasGHA = fs.existsSync( path.join( dir, '.github', 'workflows', 'ci.yml' ) )
37
+ return { value : hasGHA ? chalk.green( '★' ) : chalk.green( `${major}` ) }
36
38
  } catch {
37
39
  return { value : chalk.hex( '#FFA500' )( 'L' ) }
38
40
  }
@@ -327,7 +329,7 @@ ${chalk.yellow( 'Columns:' )}
327
329
  ${chalk.green( 'git' )} latest semver git tag (green=match, yellow=mismatch, red=untagged)
328
330
  ${chalk.green( 'name' )} orange if dirty: pull/fetch failed (regular repos) or local modifications present (workspace packages)
329
331
  ${chalk.green( 'scope' )} package scope: @org, PRVT, or PUBL
330
- ${chalk.green( 'cir' )} CircleCI config: ✘=missing, L=legacy, 1=orb v1, 2=orb v2
332
+ ${chalk.green( 'cir' )} CI status: ✘=missing, L=legacy, 1=orb v1, 2=orb v2, ★=orb v2 + GHA ci.yml
331
333
  ${chalk.green( 'ESM' )} "type": "module" in package.json
332
334
  ${chalk.green( 'har' )} .har directory present at repo root
333
335
  ${chalk.green( 'lnt' )} ESLint version from node_modules (✘=no lint script, ?=not installed, yellow=v8, green=v9+)
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable */
3
+ /*
4
+ * Keep helm/Chart.yaml in sync with package.json (the "mirror" model):
5
+ * version = <package.json version>
6
+ * appVersion = v<package.json version>
7
+ *
8
+ * package.json's version is the single source of truth. Run automatically from
9
+ * the pre-commit hook (.har/pre-commit) and available manually as `yarn sync:chart`.
10
+ *
11
+ * - Self-stages helm/Chart.yaml so the change lands in the triggering commit.
12
+ * - Idempotent: a no-op (exit 0, no git add) when already in sync.
13
+ * - Line-targeted edits, so no YAML dependency is needed.
14
+ */
15
+
16
+ import fs from 'node:fs'
17
+ import { execSync } from 'node:child_process'
18
+
19
+ const pkg = JSON.parse( fs.readFileSync( new URL( '../package.json', import.meta.url ), 'utf8' ) )
20
+ const version = pkg.version
21
+ const appVersion = `v${version}`
22
+
23
+ const chartUrl = new URL( '../helm/Chart.yaml', import.meta.url )
24
+ let chart
25
+ try {
26
+ chart = fs.readFileSync( chartUrl, 'utf8' )
27
+ } catch {
28
+ process.exit( 0 ) // no chart in this project — nothing to sync
29
+ }
30
+
31
+ // Match the top-level `version:` (not `apiVersion:`) and `appVersion:` lines.
32
+ const updated = chart
33
+ .replace( /^version:.*$/m, `version: ${version}` )
34
+ .replace( /^appVersion:.*$/m, `appVersion: ${appVersion}` )
35
+
36
+ if ( updated === chart ) {
37
+ process.exit( 0 ) // already in sync
38
+ }
39
+
40
+ fs.writeFileSync( chartUrl, updated )
41
+ console.log( `sync-chart-version: helm/Chart.yaml -> version: ${version}, appVersion: ${appVersion}` )
42
+
43
+ try {
44
+ execSync( 'git add helm/Chart.yaml', { stdio : 'ignore' } )
45
+ } catch {
46
+ // Not in a git context (or add failed) — the file is written regardless.
47
+ }