rman 1.0.9 → 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 +43 -25
- package/cli.js +2 -0
- package/commands/github-release.command.d.ts +3 -0
- package/commands/github-release.command.js +119 -0
- package/commands/publish.command.js +6 -41
- 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 +98 -30
- package/package.json +1 -1
- package/rmanrc.schema.json +63 -47
- package/services/github-release.service.d.ts +10 -9
- package/services/github-release.service.js +15 -22
- 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/change-hash.d.ts +2 -2
- package/utils/change-hash.js +2 -2
- package/utils/npm-run-path.d.ts +1 -1
- package/utils/version-stamp.d.ts +40 -0
- package/utils/version-stamp.js +76 -0
package/services/run.service.js
CHANGED
|
@@ -26,14 +26,21 @@ export var RunService;
|
|
|
26
26
|
* `getScriptSteps`:
|
|
27
27
|
* run:
|
|
28
28
|
* build:
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* before: [node ./generate.js, node ./validate.js]
|
|
30
|
+
* exec: tsc -b
|
|
31
|
+
* after: node ./copy-assets.js
|
|
32
32
|
* override: true # use these even if the package *does* define its own
|
|
33
|
+
*
|
|
34
|
+
* A bare string is shorthand for `exec`, which is by far the common case - a script that is just
|
|
35
|
+
* a command, with nothing to configure about how it runs:
|
|
36
|
+
* run:
|
|
37
|
+
* test: mocha # same as test: { exec: mocha }
|
|
33
38
|
*/
|
|
34
39
|
function getConfig(pkg, script) {
|
|
35
40
|
const runCfg = pkg.config?.run;
|
|
36
41
|
const cfg = runCfg && typeof runCfg === 'object' ? runCfg[script] : undefined;
|
|
42
|
+
if (typeof cfg === 'string' || Array.isArray(cfg))
|
|
43
|
+
return { exec: cfg };
|
|
37
44
|
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
38
45
|
}
|
|
39
46
|
RunService.getConfig = getConfig;
|
|
@@ -197,10 +204,14 @@ export var RunService;
|
|
|
197
204
|
const ifStatusCache = new Map();
|
|
198
205
|
/** Repo-wide bookend: root's own pre/post hooks run once each, exclusively, around every package
|
|
199
206
|
* (unless root itself opts out via `run.<script>.skip`, fails its own `run.<script>.if`, or the
|
|
200
|
-
* run is scoped to a single package by `cwdScope` - a repo-wide bookend has no place there).
|
|
201
|
-
|
|
207
|
+
* run is scoped to a single package by `cwdScope` - a repo-wide bookend has no place there).
|
|
208
|
+
*
|
|
209
|
+
* Only in a monorepo. Without one the root *is* the single package, already in the loop below
|
|
210
|
+
* with the same hooks and the same directory - a bookend would simply run each of them a
|
|
211
|
+
* second time. */
|
|
212
|
+
const rootIf = !cwdScope && repository.monorepo && parseIfExpr(rootCfg.if);
|
|
202
213
|
const rootIfPasses = rootIf ? await evaluateIf(repository, repository.rootPackage, rootIf, ifStatusCache) : true;
|
|
203
|
-
const rootSkipped = !!cwdScope || rootCfg.skip === true || !rootIfPasses;
|
|
214
|
+
const rootSkipped = !!cwdScope || !repository.monorepo || rootCfg.skip === true || !rootIfPasses;
|
|
204
215
|
const rootSteps = rootSkipped ? [] : getScriptSteps(repository.rootPackage, script);
|
|
205
216
|
const rootPre = rootSteps.filter(s => s.name === 'pre' + script);
|
|
206
217
|
const rootPost = rootSteps.filter(s => s.name === 'post' + script);
|
|
@@ -255,11 +266,36 @@ export var RunService;
|
|
|
255
266
|
}));
|
|
256
267
|
}
|
|
257
268
|
if (!children.length) {
|
|
258
|
-
|
|
259
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Two different nothings, and only one of them is fine.
|
|
271
|
+
*
|
|
272
|
+
* Nobody in the repository defines this script at all: the name is a mistake - a typo, or a
|
|
273
|
+
* script that used to exist - and `npm run` fails on exactly this. Staying silent is how
|
|
274
|
+
* `rman run qc` sat in a CI pipeline for months reporting success while running nothing, with
|
|
275
|
+
* `qc` defined only on the root (whose own scripts a monorepo never runs, only its
|
|
276
|
+
* `pre`/`post` bookends).
|
|
277
|
+
*
|
|
278
|
+
* Everything was filtered out instead - `--scope`, `--changed`, `run.<script>.skip`, an
|
|
279
|
+
* `if:` that didn't match: zero is the correct answer to what was asked, and asking "build
|
|
280
|
+
* only what changed" when nothing changed must not fail a pipeline.
|
|
281
|
+
*/
|
|
282
|
+
/** `getPackages()` and nothing else - in a monorepo that excludes the root, which is the
|
|
283
|
+
* point: the root contributes only `pre`/`post` bookends, never the script itself, so a
|
|
284
|
+
* `qc` defined *only* there is exactly the mistake above rather than an excuse for it. (And
|
|
285
|
+
* had the root contributed a bookend, `children` wouldn't be empty.) In a single-package
|
|
286
|
+
* repository the root *is* the one package, and is covered. */
|
|
287
|
+
const definedSomewhere = repository.getPackages().some(pkg => getScriptSteps(pkg, script).length > 0);
|
|
288
|
+
if (definedSomewhere) {
|
|
289
|
+
console.log(colors.gray(`Nothing to run - every package was filtered out of "${script}".`));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const message = `No package defines a "${script}" script.`;
|
|
293
|
+
console.log(colors.red(message));
|
|
294
|
+
const err = new Error(message);
|
|
295
|
+
err.logged = true;
|
|
296
|
+
throw err;
|
|
260
297
|
}
|
|
261
298
|
panel.start();
|
|
262
|
-
let failed = false;
|
|
263
299
|
try {
|
|
264
300
|
/** bail:false here - each package's own resolved bail setting decides whether to call
|
|
265
301
|
* `rootTask.abort()` itself (see `runSteps`), since power-tasks' own `bail` can't vary per package. */
|
|
@@ -267,13 +303,25 @@ export var RunService;
|
|
|
267
303
|
await rootTask.toPromise();
|
|
268
304
|
}
|
|
269
305
|
catch {
|
|
270
|
-
|
|
306
|
+
// Swallowed on purpose: whether the root promise rejected says nothing reliable about the
|
|
307
|
+
// run - see below. The per-package tallies are what decide.
|
|
271
308
|
}
|
|
272
309
|
finally {
|
|
310
|
+
/** A package's own `bail` aborts the root task, which settles its promise *immediately* while
|
|
311
|
+
* the packages already in flight keep running. Waiting for every child here is what makes
|
|
312
|
+
* the summary below describe a finished run rather than a snapshot of one still going -
|
|
313
|
+
* measured: it printed "0 succeeded, 1 failed, 3 skipped" and then three of those "skipped"
|
|
314
|
+
* packages went on to succeed. */
|
|
315
|
+
await Promise.allSettled(children.map(child => child.toPromise()));
|
|
273
316
|
panel.stop();
|
|
274
317
|
}
|
|
275
|
-
panel.printSummary();
|
|
276
|
-
|
|
318
|
+
const summary = panel.printSummary();
|
|
319
|
+
/** The tallies, never `rootTask.toPromise()`'s own outcome: with a sibling still in flight at
|
|
320
|
+
* the moment one package failed, that promise *resolves*, and this command used to exit 0 on a
|
|
321
|
+
* run it had just reported as failed - non-deterministically, since it came down to which
|
|
322
|
+
* packages happened to still be running (measured: `1 0 1 1 0` across five identical runs).
|
|
323
|
+
* A single failed package must fail the command, every time. */
|
|
324
|
+
if (summary.failedCount > 0) {
|
|
277
325
|
const err = new Error(`"${script}" failed`);
|
|
278
326
|
err.logged = true;
|
|
279
327
|
throw err;
|
|
@@ -336,9 +384,9 @@ function getScriptSteps(pkg, script) {
|
|
|
336
384
|
if (cfg.override === true || !hasOwn)
|
|
337
385
|
json.scripts[key] = value;
|
|
338
386
|
};
|
|
339
|
-
applyConfigScript(script, cfg.
|
|
340
|
-
applyConfigScript('pre' + script, cfg.
|
|
341
|
-
applyConfigScript('post' + script, cfg.
|
|
387
|
+
applyConfigScript(script, cfg.exec);
|
|
388
|
+
applyConfigScript('pre' + script, cfg.before);
|
|
389
|
+
applyConfigScript('post' + script, cfg.after);
|
|
342
390
|
json.scripts[script] = json.scripts[script] || '#';
|
|
343
391
|
const info = parseNpmScript(json, 'npm run ' + script);
|
|
344
392
|
if (!info?.raw?.length)
|
|
@@ -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/change-hash.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export declare function tagPattern(pkg: Package): string;
|
|
|
15
15
|
export declare function findLatestTag(git: GitHelper, pkg: Package): Promise<string | undefined>;
|
|
16
16
|
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
17
17
|
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
18
|
-
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
19
|
-
*
|
|
18
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
19
|
+
* `github-release` (finding the release that tag belongs to), and `detectChangeHash`'s own npm
|
|
20
20
|
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
21
21
|
export declare function expandTag(pkg: Package, version: string): string;
|
|
22
22
|
/** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*` becomes
|
package/utils/change-hash.js
CHANGED
|
@@ -22,8 +22,8 @@ export async function findLatestTag(git, pkg) {
|
|
|
22
22
|
}
|
|
23
23
|
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
24
24
|
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
25
|
-
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
26
|
-
*
|
|
25
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
26
|
+
* `github-release` (finding the release that tag belongs to), and `detectChangeHash`'s own npm
|
|
27
27
|
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
28
28
|
export function expandTag(pkg, version) {
|
|
29
29
|
return applyTagPattern(tagPattern(pkg), pkg.name, version);
|
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
|
+
}
|