rman 1.0.10 → 1.0.12
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 +26 -15
- package/constants.js +1 -1
- package/core/config.d.ts +128 -6
- package/core/config.js +176 -7
- package/core/repository.d.ts +8 -3
- package/core/repository.js +75 -11
- package/interfaces/rman-config.interface.d.ts +76 -13
- package/package.json +1 -1
- package/rmanrc.schema.json +54 -37
- package/services/publish.service.js +113 -47
- package/services/run.service.d.ts +8 -3
- package/services/run.service.js +63 -15
- package/services/version.service.js +67 -3
- package/utils/npm-run-path.d.ts +1 -1
- package/utils/version-stamp.d.ts +40 -0
- package/utils/version-stamp.js +76 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import semver from 'semver';
|
|
3
4
|
import { detectChangeHash, expandTag } from '../utils/change-hash.js';
|
|
@@ -6,6 +7,7 @@ import { exec } from '../utils/exec.js';
|
|
|
6
7
|
import { GitHelper } from '../utils/git.js';
|
|
7
8
|
import { filterPackages } from '../utils/package-filter.js';
|
|
8
9
|
import { expandReleaseTag, findLastReleaseVersion, formatCalendarVersion, isCalendarVersion, usesCalendarVersion, } from '../utils/release-version.js';
|
|
10
|
+
import { stampVersionConstant, stampVersionLabel } from '../utils/version-stamp.js';
|
|
9
11
|
import { parseWorkspaceRange } from '../utils/workspace-range.js';
|
|
10
12
|
import { ChangelogService } from './changelog.service.js';
|
|
11
13
|
export var VersionService;
|
|
@@ -133,9 +135,13 @@ export var VersionService;
|
|
|
133
135
|
const isRealEntry = (e) => !(repository.monorepo && e.package === repository.rootPackage);
|
|
134
136
|
const bumped = plan.filter(e => e.status === 'bump' && isRealEntry(e));
|
|
135
137
|
const bumpedByName = new Map(bumped.map(e => [e.package.name, e]));
|
|
138
|
+
/** Repo-relative paths of every file stamped with the new version below (a Dockerfile label, a
|
|
139
|
+
* source constant), so each lands in the same commit as the bump that made it stale - keyed by
|
|
140
|
+
* package, the way `changelogFileByPackage` is. */
|
|
141
|
+
const stampedByPackage = new Map();
|
|
136
142
|
for (const entry of bumped) {
|
|
137
143
|
const pkg = entry.package;
|
|
138
|
-
await runVersionScript(pkg, '
|
|
144
|
+
await runVersionScript(pkg, 'before', 'preversion');
|
|
139
145
|
pkg.json.version = entry.to;
|
|
140
146
|
for (const depKey of DEPENDENCY_KEYS) {
|
|
141
147
|
const deps = pkg.json[depKey];
|
|
@@ -157,9 +163,14 @@ export var VersionService;
|
|
|
157
163
|
deps[depName] = '^' + depEntry.to;
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
|
-
await runVersionScript(pkg, '
|
|
166
|
+
await runVersionScript(pkg, 'exec', 'version');
|
|
161
167
|
pkg.writeJson();
|
|
162
|
-
|
|
168
|
+
// Before `postversion`, so a script that reacts to the bump sees the whole new state.
|
|
169
|
+
const stamped = [stampDockerfile(pkg, entry.to), ...stampSourceFiles(pkg, entry.to)].filter((f) => !!f);
|
|
170
|
+
if (stamped.length) {
|
|
171
|
+
stampedByPackage.set(pkg.name, stamped.map(f => path.relative(repository.dirname, f)));
|
|
172
|
+
}
|
|
173
|
+
await runVersionScript(pkg, 'after', 'postversion');
|
|
163
174
|
}
|
|
164
175
|
const rootEntry = repository.monorepo ? plan.find(e => e.package === repository.rootPackage) : undefined;
|
|
165
176
|
if (rootEntry?.status === 'bump') {
|
|
@@ -218,6 +229,7 @@ export var VersionService;
|
|
|
218
229
|
const changelogFile = changelogFileByPackage.get(e.package.name);
|
|
219
230
|
if (changelogFile)
|
|
220
231
|
files.push(changelogFile);
|
|
232
|
+
files.push(...(stampedByPackage.get(e.package.name) ?? []));
|
|
221
233
|
}
|
|
222
234
|
await git.commit(files, buildCommitMessage(repository, groupEntries, options.message));
|
|
223
235
|
const tags = new Set(groupEntries.map(e => expandTag(e.package, e.to)));
|
|
@@ -490,3 +502,55 @@ async function runVersionScript(pkg, cfgKey, npmScriptName) {
|
|
|
490
502
|
if (command)
|
|
491
503
|
await exec(command, { cwd: pkg.dirname, stdio: 'inherit' });
|
|
492
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* Keeps a package's Dockerfile `org.opencontainers.image.version` label in step with the version
|
|
507
|
+
* just written, returning the absolute path when it actually changed (so the caller can fold it
|
|
508
|
+
* into the same commit) and `undefined` otherwise.
|
|
509
|
+
*
|
|
510
|
+
* Here rather than in a build script: the label is a *statement of the package's version*, so it
|
|
511
|
+
* belongs to whatever writes that version - which keeps it in the bump commit, leaves the tree
|
|
512
|
+
* clean, and makes it right for anyone building the Dockerfile by hand. A build-time rewrite is
|
|
513
|
+
* both later than it needs to be and invisible to git.
|
|
514
|
+
*
|
|
515
|
+
* The same path `publish --target docker` builds from (`publish.docker.dockerfile`, default
|
|
516
|
+
* `Dockerfile`), so the two can never disagree about which file this is. Opt out with `.rmanrc
|
|
517
|
+
* "version": { "stampDockerfile": false }`; a package with no Dockerfile, or one that doesn't
|
|
518
|
+
* declare the label, is a no-op either way.
|
|
519
|
+
*/
|
|
520
|
+
function stampDockerfile(pkg, version) {
|
|
521
|
+
if (pkg.config?.version?.stampDockerfile === false)
|
|
522
|
+
return undefined;
|
|
523
|
+
const file = path.resolve(pkg.dirname, pkg.config?.publish?.docker?.dockerfile || 'Dockerfile');
|
|
524
|
+
if (!fs.existsSync(file))
|
|
525
|
+
return undefined;
|
|
526
|
+
const stamped = stampVersionLabel(fs.readFileSync(file, 'utf-8'), version);
|
|
527
|
+
if (stamped === undefined)
|
|
528
|
+
return undefined;
|
|
529
|
+
fs.writeFileSync(file, stamped, 'utf-8');
|
|
530
|
+
return file;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Keeps every `.rmanrc "version.stamp"` source file's `version` constant in step with the version
|
|
534
|
+
* just written, returning the absolute paths of the ones that actually changed.
|
|
535
|
+
*
|
|
536
|
+
* Explicitly listed rather than discovered: unlike the OCI Dockerfile label there is no standard
|
|
537
|
+
* saying "this file holds the version", and the assignment this matches is deliberately broad. A
|
|
538
|
+
* listed file a package doesn't have is a silent no-op, which is what lets one `"[*]"` declaration
|
|
539
|
+
* cover a repo where only some packages carry one.
|
|
540
|
+
*/
|
|
541
|
+
function stampSourceFiles(pkg, version) {
|
|
542
|
+
const configured = pkg.config?.version?.stamp;
|
|
543
|
+
const patterns = typeof configured === 'string' ? [configured] : (configured ?? []);
|
|
544
|
+
const stamped = [];
|
|
545
|
+
for (const rel of patterns) {
|
|
546
|
+
const file = path.resolve(pkg.dirname, rel);
|
|
547
|
+
if (!fs.existsSync(file))
|
|
548
|
+
continue;
|
|
549
|
+
const next = stampVersionConstant(fs.readFileSync(file, 'utf-8'), version);
|
|
550
|
+
if (next === undefined)
|
|
551
|
+
continue;
|
|
552
|
+
fs.writeFileSync(file, next, 'utf-8');
|
|
553
|
+
stamped.push(file);
|
|
554
|
+
}
|
|
555
|
+
return stamped;
|
|
556
|
+
}
|
package/utils/npm-run-path.d.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Keeping a package's version truthful wherever the package itself declares it - its Dockerfile's
|
|
2
|
+
* OCI label, and any source file that hard-codes it as a constant. Both are `version`'s job rather
|
|
3
|
+
* than a build step's: a version is being written, these are places it is written, and doing it
|
|
4
|
+
* here puts them in the bump commit instead of leaving the repository disagreeing with itself. */
|
|
5
|
+
/** The OCI label whose value is, by specification, "version of the packaged software" - so a
|
|
6
|
+
* package's own `package.json` version is the only correct value it can hold, which is what makes
|
|
7
|
+
* stamping it automatic rather than something to configure. */
|
|
8
|
+
export declare const OCI_VERSION_LABEL = "org.opencontainers.image.version";
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites every `org.opencontainers.image.version` value in a Dockerfile's `LABEL` instructions to
|
|
11
|
+
* `version`, returning the new content - or `undefined` when there was nothing to change (no such
|
|
12
|
+
* label, or it already holds this version), so a caller can skip writing the file at all.
|
|
13
|
+
*
|
|
14
|
+
* Only ever *rewrites*: a Dockerfile that doesn't declare the label is left exactly as it is rather
|
|
15
|
+
* than having one inserted. Which labels an image carries is the author's decision; keeping one
|
|
16
|
+
* they already declared truthful is not.
|
|
17
|
+
*
|
|
18
|
+
* The existing quoting style is preserved, so the diff is the version and nothing else.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stampVersionLabel(content: string, version: string): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Rewrites the string assigned to a `name` constant (default `version`) in a source file to
|
|
23
|
+
* `version`, returning the new content - or `undefined` when there was nothing to change, so a
|
|
24
|
+
* caller can skip writing the file at all. Both an assignment and an object property are matched:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* export const version = '1'; -> export const version = '6.0.10';
|
|
28
|
+
* { name: 'app', version: '1' } -> { name: 'app', version: '6.0.10' }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Unlike the Dockerfile label there is no standard naming a file as holding the version, so this
|
|
32
|
+
* only ever runs against files `.rmanrc "version.stamp"` explicitly lists - which is also what
|
|
33
|
+
* keeps a match this broad safe.
|
|
34
|
+
*
|
|
35
|
+
* Stamping the *source* rather than the build output is the point: a build-time rewrite leaves the
|
|
36
|
+
* checked-in file claiming some placeholder, so anything running from source (tests, ts-node, the
|
|
37
|
+
* dev loop) reports that placeholder, git never records the released version, and the rewrite has
|
|
38
|
+
* to be redone on every build.
|
|
39
|
+
*/
|
|
40
|
+
export declare function stampVersionConstant(content: string, version: string, name?: string): string | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Keeping a package's version truthful wherever the package itself declares it - its Dockerfile's
|
|
2
|
+
* OCI label, and any source file that hard-codes it as a constant. Both are `version`'s job rather
|
|
3
|
+
* than a build step's: a version is being written, these are places it is written, and doing it
|
|
4
|
+
* here puts them in the bump commit instead of leaving the repository disagreeing with itself. */
|
|
5
|
+
/** The OCI label whose value is, by specification, "version of the packaged software" - so a
|
|
6
|
+
* package's own `package.json` version is the only correct value it can hold, which is what makes
|
|
7
|
+
* stamping it automatic rather than something to configure. */
|
|
8
|
+
export const OCI_VERSION_LABEL = 'org.opencontainers.image.version';
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites every `org.opencontainers.image.version` value in a Dockerfile's `LABEL` instructions to
|
|
11
|
+
* `version`, returning the new content - or `undefined` when there was nothing to change (no such
|
|
12
|
+
* label, or it already holds this version), so a caller can skip writing the file at all.
|
|
13
|
+
*
|
|
14
|
+
* Only ever *rewrites*: a Dockerfile that doesn't declare the label is left exactly as it is rather
|
|
15
|
+
* than having one inserted. Which labels an image carries is the author's decision; keeping one
|
|
16
|
+
* they already declared truthful is not.
|
|
17
|
+
*
|
|
18
|
+
* The existing quoting style is preserved, so the diff is the version and nothing else.
|
|
19
|
+
*/
|
|
20
|
+
export function stampVersionLabel(content, version) {
|
|
21
|
+
const lines = content.split('\n');
|
|
22
|
+
let changed = false;
|
|
23
|
+
// A LABEL's key=value pairs can spill over several lines via trailing backslashes, so the
|
|
24
|
+
// instruction a line belongs to is tracked rather than assumed from the line itself. Anything
|
|
25
|
+
// outside a LABEL is left alone - the same key in an ARG, an ENV or a comment is not a label.
|
|
26
|
+
let continuing = false;
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
const inLabel = continuing || /^\s*LABEL\b/i.test(line);
|
|
30
|
+
continuing = inLabel && /\\\s*$/.test(line);
|
|
31
|
+
if (!inLabel)
|
|
32
|
+
continue;
|
|
33
|
+
lines[i] = line.replace(LABEL_VALUE, (whole, key, value) => {
|
|
34
|
+
const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
|
|
35
|
+
const next = `${key}${quote}${version}${quote}`;
|
|
36
|
+
if (next !== whole)
|
|
37
|
+
changed = true;
|
|
38
|
+
return next;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return changed ? lines.join('\n') : undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rewrites the string assigned to a `name` constant (default `version`) in a source file to
|
|
45
|
+
* `version`, returning the new content - or `undefined` when there was nothing to change, so a
|
|
46
|
+
* caller can skip writing the file at all. Both an assignment and an object property are matched:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* export const version = '1'; -> export const version = '6.0.10';
|
|
50
|
+
* { name: 'app', version: '1' } -> { name: 'app', version: '6.0.10' }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* Unlike the Dockerfile label there is no standard naming a file as holding the version, so this
|
|
54
|
+
* only ever runs against files `.rmanrc "version.stamp"` explicitly lists - which is also what
|
|
55
|
+
* keeps a match this broad safe.
|
|
56
|
+
*
|
|
57
|
+
* Stamping the *source* rather than the build output is the point: a build-time rewrite leaves the
|
|
58
|
+
* checked-in file claiming some placeholder, so anything running from source (tests, ts-node, the
|
|
59
|
+
* dev loop) reports that placeholder, git never records the released version, and the rewrite has
|
|
60
|
+
* to be redone on every build.
|
|
61
|
+
*/
|
|
62
|
+
export function stampVersionConstant(content, version, name = 'version') {
|
|
63
|
+
let changed = false;
|
|
64
|
+
const pattern = new RegExp(`(\\b${escapeRegExp(name)}\\s*[:=]\\s*)(['"\`])([^'"\`\\n]*)\\2`, 'g');
|
|
65
|
+
const result = content.replace(pattern, (whole, prefix, quote) => {
|
|
66
|
+
const next = `${prefix}${quote}${version}${quote}`;
|
|
67
|
+
if (next !== whole)
|
|
68
|
+
changed = true;
|
|
69
|
+
return next;
|
|
70
|
+
});
|
|
71
|
+
return changed ? result : undefined;
|
|
72
|
+
}
|
|
73
|
+
const LABEL_VALUE = new RegExp(`(${OCI_VERSION_LABEL.replace(/\./g, '\\.')}\\s*=\\s*)("[^"\\n]*"|'[^'\\n]*'|[^\\s\\\\]+)`, 'g');
|
|
74
|
+
function escapeRegExp(value) {
|
|
75
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
76
|
+
}
|