@zairakai/js-dev-tools 3.3.2 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -77,6 +77,9 @@ variables:
|
|
|
77
77
|
NODE_VERSION: "24"
|
|
78
78
|
NODE_IMAGE: "zairakai/node:latest-dev"
|
|
79
79
|
NODE_TEST_IMAGE: "zairakai/node:latest-test"
|
|
80
|
+
# The jobs of the automatic updates sign with gpg: the image has it since 1.3.0. It is an exact version, published on
|
|
81
|
+
# Docker Hub like our other images: the runner pulls it, whatever the old image it keeps locally.
|
|
82
|
+
NODE_UPDATE_IMAGE: "zairakai/node:1.3.0-dev"
|
|
80
83
|
SHELLCHECK_IMAGE: "koalaman/shellcheck-alpine:v0.10.0"
|
|
81
84
|
RELEASE_CLI_IMAGE: "registry.gitlab.com/gitlab-org/release-cli:v0.18.0"
|
|
82
85
|
ALPINE_IMAGE: "alpine:3.18"
|
|
@@ -449,7 +452,7 @@ js:release:
|
|
|
449
452
|
deps:update:
|
|
450
453
|
<<: *base_job
|
|
451
454
|
stage: install
|
|
452
|
-
image: ${
|
|
455
|
+
image: ${NODE_UPDATE_IMAGE}
|
|
453
456
|
needs: []
|
|
454
457
|
script:
|
|
455
458
|
- node --version
|
|
@@ -474,7 +477,7 @@ deps:release:
|
|
|
474
477
|
deps:tag:
|
|
475
478
|
<<: *base_job
|
|
476
479
|
stage: release
|
|
477
|
-
image: ${
|
|
480
|
+
image: ${NODE_UPDATE_IMAGE}
|
|
478
481
|
variables:
|
|
479
482
|
GIT_DEPTH: 0
|
|
480
483
|
script:
|
package/README.md
CHANGED
|
@@ -178,6 +178,8 @@ The pipeline template updates the dependencies of a project by itself, and this
|
|
|
178
178
|
- `deps:release` brings `develop` into `main` after such an update, and `deps:tag` tags `main` when a published range (`dependencies`, `peerDependencies`) changed. A project that changed only its development dependencies is not tagged: its published archive is the same.
|
|
179
179
|
- After the publication of a tag, `deps:cascade` waits for the version to be on the registry, then starts the update of the projects that have a `.dependency-update.json`.
|
|
180
180
|
|
|
181
|
+
The same script serves the PHP projects (`composer.json`): a job in the PHP image runs `composer update`, then this script commits, signs and opens the merge request, and `deps:tag` looks at `require` only. The jobs that sign (`deps:update`, `deps:tag`) run on an exact version of the node image (`NODE_UPDATE_IMAGE`, published on Docker Hub), because they need `gpg` and the image of the runner is not updated by itself.
|
|
182
|
+
|
|
181
183
|
The exceptions of a project live in `.dependency-update.json` at its root (everything is optional):
|
|
182
184
|
|
|
183
185
|
```json
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zairakai/js-dev-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "The unified and centralized development toolkit for JavaScript/TypeScript projects - ESLint, Prettier, Stylelint, Knip, Vitest, Git Hooks, and GitLab CI pipeline templates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dev-tools",
|
package/scripts/setup-project.sh
CHANGED
|
@@ -254,6 +254,12 @@ setup_file() {
|
|
|
254
254
|
local target="$2"
|
|
255
255
|
local name="$3"
|
|
256
256
|
|
|
257
|
+
# In js-dev-tools itself the source and the target are the same file: removing the target would delete the source
|
|
258
|
+
if [[ "$source" -ef "$target" ]]; then
|
|
259
|
+
track_skipped "$name"
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
|
|
257
263
|
if [[ "$CI_MODE" == "true" ]] || [[ "$FORCE_OVERWRITE" == "true" ]]; then
|
|
258
264
|
if [[ -f "$target" ]] && [[ ! -L "$target" ]] && [[ "$CI_MODE" != "true" ]]; then
|
|
259
265
|
if backup_file "$target" 2>/dev/null; then
|
|
@@ -39,6 +39,17 @@ const DEFAULTS = {
|
|
|
39
39
|
const RUNTIME_SECTIONS = ['dependencies', 'peerDependencies', 'optionalDependencies']
|
|
40
40
|
const ALL_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
|
|
41
41
|
|
|
42
|
+
/** The manifest of each ecosystem, with the sections that are published and all of them. */
|
|
43
|
+
const ECOSYSTEMS = {
|
|
44
|
+
npm: { file: 'package.json', runtime: RUNTIME_SECTIONS, all: ALL_SECTIONS },
|
|
45
|
+
composer: { file: 'composer.json', runtime: ['require'], all: ['require', 'require-dev'] },
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The ecosystem of the project: a package.json at its root, otherwise a composer.json. */
|
|
49
|
+
export function detectEcosystem(exists = existsSync) {
|
|
50
|
+
return exists('package.json') ? 'npm' : 'composer'
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
/** The configuration of the project, completed with the defaults. */
|
|
43
54
|
export function loadConfig(path = CONFIG_FILE) {
|
|
44
55
|
const user = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : {}
|
|
@@ -78,15 +89,15 @@ function sorted(section) {
|
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
/** True when a range that is published (dependencies, peerDependencies, optionalDependencies) is not the same. */
|
|
81
|
-
export function runtimeChanged(before, after) {
|
|
82
|
-
return
|
|
92
|
+
export function runtimeChanged(before, after, sections = RUNTIME_SECTIONS) {
|
|
93
|
+
return sections.some((section) => sorted(before?.[section]) !== sorted(after?.[section]))
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
/** The dependencies whose range changed, with the section, the old range and the new one. */
|
|
86
|
-
export function diffDependencies(before, after) {
|
|
97
|
+
export function diffDependencies(before, after, sections = ALL_SECTIONS) {
|
|
87
98
|
const rows = []
|
|
88
99
|
|
|
89
|
-
for (const section of
|
|
100
|
+
for (const section of sections) {
|
|
90
101
|
const old = before?.[section] ?? {}
|
|
91
102
|
const current = after?.[section] ?? {}
|
|
92
103
|
|
|
@@ -129,12 +140,12 @@ export function signerIdentity(env) {
|
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
/** What `tag` has to do: the version to create, or null (and why). */
|
|
132
|
-
export function planTag({ config, last, commits, before, after }) {
|
|
143
|
+
export function planTag({ config, last, commits, before, after, sections = RUNTIME_SECTIONS }) {
|
|
133
144
|
if ('never' === config.tag.when) return { version: null, reason: 'the project is never tagged by the update' }
|
|
134
145
|
if (!last) return { version: null, reason: 'there is no tag yet' }
|
|
135
146
|
if (0 === commits.length) return { version: null, reason: 'no update since the last tag' }
|
|
136
147
|
|
|
137
|
-
if ('runtime' === config.tag.when && !runtimeChanged(before, after)) {
|
|
148
|
+
if ('runtime' === config.tag.when && !runtimeChanged(before, after, sections)) {
|
|
138
149
|
return { version: null, reason: 'only the development dependencies changed: the published archive is the same' }
|
|
139
150
|
}
|
|
140
151
|
|
|
@@ -305,18 +316,22 @@ async function update(config) {
|
|
|
305
316
|
}
|
|
306
317
|
|
|
307
318
|
configureSigning()
|
|
308
|
-
const
|
|
319
|
+
const ecosystem = ECOSYSTEMS[detectEcosystem()]
|
|
320
|
+
const before = JSON.parse(run('git', ['show', `HEAD:${ecosystem.file}`]))
|
|
309
321
|
const branch = `${BRANCH_PREFIX}-${new Date().toISOString().slice(0, 10).replaceAll('-', '')}`
|
|
310
322
|
run('git', ['switch', '-c', branch])
|
|
311
323
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
324
|
+
// A PHP project was prepared by the job before this one, in its own image: only npm is updated here.
|
|
325
|
+
if ('package.json' === ecosystem.file) {
|
|
326
|
+
// The new versions wait for the cooling period. The packages of the group are trusted: no waiting for them.
|
|
327
|
+
const reject = 0 < config.reject.length ? ['--reject', config.reject.join(',')] : []
|
|
328
|
+
ncu(['--cooldown', config.cooldown, ...reject])
|
|
329
|
+
ncu(['--filter', '@zairakai/*', ...reject])
|
|
330
|
+
run('npm', ['install', '--no-audit', '--no-fund'])
|
|
331
|
+
}
|
|
317
332
|
|
|
318
|
-
const after = JSON.parse(readFileSync(
|
|
319
|
-
const rows = diffDependencies(before, after)
|
|
333
|
+
const after = JSON.parse(readFileSync(ecosystem.file, 'utf8'))
|
|
334
|
+
const rows = diffDependencies(before, after, ecosystem.all)
|
|
320
335
|
|
|
321
336
|
if ('' === run('git', ['status', '--porcelain'])) {
|
|
322
337
|
log('Everything is up to date.')
|
|
@@ -383,10 +398,11 @@ async function tag(config) {
|
|
|
383
398
|
.split('\n')
|
|
384
399
|
.filter(Boolean)
|
|
385
400
|
: []
|
|
386
|
-
const
|
|
387
|
-
const
|
|
401
|
+
const ecosystem = ECOSYSTEMS[detectEcosystem()]
|
|
402
|
+
const before = last ? JSON.parse(run('git', ['show', `${last}:${ecosystem.file}`])) : null
|
|
403
|
+
const after = JSON.parse(readFileSync(ecosystem.file, 'utf8'))
|
|
388
404
|
|
|
389
|
-
const plan = planTag({ config, last, commits, before, after })
|
|
405
|
+
const plan = planTag({ config, last, commits, before, after, sections: ecosystem.runtime })
|
|
390
406
|
|
|
391
407
|
if (!plan.version) {
|
|
392
408
|
log(`No tag: ${plan.reason}.`)
|
|
@@ -400,6 +416,21 @@ async function tag(config) {
|
|
|
400
416
|
log(`Tag ${plan.version} signed and pushed (last tag ${last}): ${plan.reason}. Its pipeline publishes the package.`)
|
|
401
417
|
}
|
|
402
418
|
|
|
419
|
+
/** True when the registry of the ecosystem (npm or Packagist) lists the version. */
|
|
420
|
+
async function isAvailable(ecosystem, name, version) {
|
|
421
|
+
if ('npm' === ecosystem) {
|
|
422
|
+
return run('npm', ['view', `${name}@${version}`, 'version', '--prefer-online']) === version
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const response = await fetch(`https://repo.packagist.org/p2/${name}.json`)
|
|
426
|
+
|
|
427
|
+
if (!response.ok) return false
|
|
428
|
+
|
|
429
|
+
const data = await response.json()
|
|
430
|
+
|
|
431
|
+
return (data.packages?.[name] ?? []).some((release) => release.version === version)
|
|
432
|
+
}
|
|
433
|
+
|
|
403
434
|
async function cascade(config) {
|
|
404
435
|
assertEnvironment()
|
|
405
436
|
|
|
@@ -408,7 +439,8 @@ async function cascade(config) {
|
|
|
408
439
|
return
|
|
409
440
|
}
|
|
410
441
|
|
|
411
|
-
const
|
|
442
|
+
const ecosystem = detectEcosystem()
|
|
443
|
+
const name = JSON.parse(readFileSync(ECOSYSTEMS[ecosystem].file, 'utf8')).name
|
|
412
444
|
const version = process.env.CI_COMMIT_TAG
|
|
413
445
|
|
|
414
446
|
if (!version) throw new Error('The cascade runs on the pipeline of a tag.')
|
|
@@ -418,7 +450,7 @@ async function cascade(config) {
|
|
|
418
450
|
|
|
419
451
|
for (let attempt = 0; 60 > attempt && !available; attempt++) {
|
|
420
452
|
try {
|
|
421
|
-
available =
|
|
453
|
+
available = await isAvailable(ecosystem, name, version)
|
|
422
454
|
} catch {
|
|
423
455
|
available = false
|
|
424
456
|
}
|