rman 0.37.4 → 1.0.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.
- package/README.md +364 -21
- package/cli.d.ts +1 -0
- package/cli.js +52 -28
- package/commands/build.command.d.ts +3 -0
- package/commands/build.command.js +14 -0
- package/commands/changed.command.d.ts +3 -0
- package/commands/changed.command.js +29 -0
- package/commands/changelog.command.d.ts +3 -0
- package/commands/changelog.command.js +63 -0
- package/commands/ci.command.d.ts +3 -0
- package/commands/ci.command.js +30 -0
- package/commands/clean.command.d.ts +3 -0
- package/commands/clean.command.js +36 -0
- package/commands/diff.command.d.ts +3 -0
- package/commands/diff.command.js +51 -0
- package/commands/exec.command.d.ts +3 -0
- package/commands/exec.command.js +83 -0
- package/commands/import.command.d.ts +3 -0
- package/commands/import.command.js +27 -0
- package/commands/info.command.d.ts +3 -0
- package/commands/info.command.js +61 -0
- package/commands/list.command.d.ts +3 -0
- package/commands/list.command.js +113 -0
- package/commands/publish.command.d.ts +3 -0
- package/commands/publish.command.js +147 -0
- package/commands/run.command.d.ts +6 -0
- package/commands/run.command.js +80 -0
- package/commands/test.command.d.ts +3 -0
- package/commands/test.command.js +14 -0
- package/commands/version.command.d.ts +3 -0
- package/commands/version.command.js +125 -0
- package/constants.js +1 -1
- package/core/config.d.ts +13 -0
- package/core/config.js +61 -0
- package/core/package.d.ts +2 -0
- package/core/package.js +2 -0
- package/core/repository.d.ts +36 -3
- package/core/repository.js +90 -45
- package/index.d.ts +17 -1
- package/index.js +15 -1
- package/package.json +11 -8
- package/services/changelog.service.d.ts +99 -0
- package/services/changelog.service.js +272 -0
- package/services/ci.service.d.ts +40 -0
- package/services/ci.service.js +204 -0
- package/services/clean.service.d.ts +42 -0
- package/services/clean.service.js +226 -0
- package/services/exec.service.d.ts +35 -0
- package/services/exec.service.js +114 -0
- package/services/import.service.d.ts +30 -0
- package/services/import.service.js +84 -0
- package/services/list.service.d.ts +27 -0
- package/services/list.service.js +25 -0
- package/services/publish.service.d.ts +79 -0
- package/services/publish.service.js +199 -0
- package/services/run.service.d.ts +99 -0
- package/services/run.service.js +432 -0
- package/services/system-info.d.ts +16 -0
- package/services/system-info.js +25 -0
- package/services/version.service.d.ts +95 -0
- package/services/version.service.js +450 -0
- package/services.d.ts +10 -0
- package/services.js +10 -0
- package/utils/branch-guard.d.ts +25 -0
- package/utils/branch-guard.js +61 -0
- package/utils/change-hash.d.ts +55 -0
- package/utils/change-hash.js +80 -0
- package/utils/conventional-commits.d.ts +39 -0
- package/utils/conventional-commits.js +43 -0
- package/utils/exec.d.ts +5 -6
- package/utils/exec.js +46 -60
- package/utils/git.d.ts +86 -0
- package/utils/git.js +276 -0
- package/utils/live-region.d.ts +16 -0
- package/utils/live-region.js +59 -0
- package/utils/logger.d.ts +24 -0
- package/utils/logger.js +39 -0
- package/utils/npm-run-path.d.ts +1 -1
- package/utils/npm-run-path.js +10 -10
- package/utils/package-filter.d.ts +32 -0
- package/utils/package-filter.js +73 -0
- package/utils/progress-panel.d.ts +59 -0
- package/utils/progress-panel.js +149 -0
- package/utils/workspace-range.d.ts +17 -0
- package/utils/workspace-range.js +28 -0
- package/bin/rman.mjs +0 -4
- package/commands/build-command.d.ts +0 -11
- package/commands/build-command.js +0 -24
- package/commands/changed-command.d.ts +0 -16
- package/commands/changed-command.js +0 -32
- package/commands/ci-command.d.ts +0 -24
- package/commands/ci-command.js +0 -68
- package/commands/execute-command.d.ts +0 -19
- package/commands/execute-command.js +0 -72
- package/commands/info-command.d.ts +0 -10
- package/commands/info-command.js +0 -55
- package/commands/list-command.d.ts +0 -38
- package/commands/list-command.js +0 -134
- package/commands/multi-task-command.d.ts +0 -22
- package/commands/multi-task-command.js +0 -65
- package/commands/publish-command.d.ts +0 -24
- package/commands/publish-command.js +0 -131
- package/commands/run-command.d.ts +0 -28
- package/commands/run-command.js +0 -131
- package/commands/version-command.d.ts +0 -25
- package/commands/version-command.js +0 -195
- package/core/command.d.ts +0 -35
- package/core/command.js +0 -116
- package/core/constants.d.ts +0 -1
- package/core/constants.js +0 -2
- package/core/logger.d.ts +0 -12
- package/core/logger.js +0 -2
- package/utils/file-utils.d.ts +0 -4
- package/utils/file-utils.js +0 -28
- package/utils/git-utils.d.ts +0 -25
- package/utils/git-utils.js +0 -66
- package/utils/npm-utils.d.ts +0 -11
- package/utils/npm-utils.js +0 -33
- package/utils/package-not-found-error.d.ts +0 -2
- package/utils/package-not-found-error.js +0 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
/** `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
|
|
4
|
+
* release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
|
|
5
|
+
* for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
|
|
6
|
+
* use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
|
|
7
|
+
* shared by every package (e.g. the default `v*`). */
|
|
8
|
+
export function tagPattern(pkg) {
|
|
9
|
+
const cfg = pkg.config?.changelog;
|
|
10
|
+
return typeof cfg?.tagPattern === 'string' && cfg.tagPattern ? cfg.tagPattern : DEFAULT_TAG_PATTERN;
|
|
11
|
+
}
|
|
12
|
+
/** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
|
|
13
|
+
* *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
|
|
14
|
+
* HEAD actually descends from, since no single package "owns" that tag. `undefined` if never
|
|
15
|
+
* tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
|
|
16
|
+
* (reading the last-documented version) and `version` (finding the boundary a bump measures
|
|
17
|
+
* "since"). */
|
|
18
|
+
export async function findLatestTag(git, pkg) {
|
|
19
|
+
const pattern = tagPattern(pkg);
|
|
20
|
+
const expanded = pattern.replace('{name}', pkg.name);
|
|
21
|
+
return pattern.includes('{name}') ? (await git.listTags(expanded))[0] : await git.describeTag(expanded);
|
|
22
|
+
}
|
|
23
|
+
/** Strips the pattern's literal prefix (everything before its first `*`) from `tag` to get just
|
|
24
|
+
* the version part - e.g. tag `@sqb/builder@1.2.3` against pattern `@sqb/builder@*` -> `1.2.3`.
|
|
25
|
+
* A pattern with no `*` is returned as its own "version" verbatim (an exact tag, nothing to strip). */
|
|
26
|
+
export function extractVersion(tag, expandedPattern) {
|
|
27
|
+
const starIdx = expandedPattern.indexOf('*');
|
|
28
|
+
if (starIdx === -1)
|
|
29
|
+
return tag;
|
|
30
|
+
const prefix = expandedPattern.slice(0, starIdx);
|
|
31
|
+
return tag.startsWith(prefix) ? tag.slice(prefix.length) : tag;
|
|
32
|
+
}
|
|
33
|
+
/** Looks up `name`'s currently-published version on the npm registry, or `undefined` if it isn't
|
|
34
|
+
* published there at all (private, scoped-but-unpublished, no network, ...) - the real npm CLI
|
|
35
|
+
* call `detectChangeHash` uses by default; injectable via its `npmViewVersion` option so tests
|
|
36
|
+
* aren't making real registry calls. */
|
|
37
|
+
export async function defaultNpmViewVersion(name, cwd) {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await execFileAsync('npm', ['view', name, 'version'], { cwd });
|
|
40
|
+
return stdout.trim() || undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the commit/hash a package's changes should be measured "since" - the boundary
|
|
48
|
+
* `changelog --from` uses, but reusable anywhere a command wants to answer "what changed for this
|
|
49
|
+
* package". An explicit `options.from` (anything but `"npm"`) is returned as-is, applying the same
|
|
50
|
+
* way to every package. Otherwise, it's auto-detected from the package's currently-published npm
|
|
51
|
+
* version: looked up via `npmViewVersion`, then mapped to a git tag using `.rmanrc
|
|
52
|
+
* changelog.tagPattern` (so independent and fixed monorepo versioning schemes both work - see
|
|
53
|
+
* `tagPattern`) - and, if `catchUpFile` is given and exists, widened to also cover anything that
|
|
54
|
+
* file hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be
|
|
55
|
+
* resolved at all (unpublished, no network, no matching tag, no catch-up file) - callers should
|
|
56
|
+
* fall back to their own default in that case (e.g. `GitHelper.listCommits`'s "not yet pushed"
|
|
57
|
+
* default when no hash is given).
|
|
58
|
+
*/
|
|
59
|
+
export async function detectChangeHash(git, pkg, options = {}) {
|
|
60
|
+
if (options.from && options.from !== 'npm')
|
|
61
|
+
return options.from;
|
|
62
|
+
const npmViewVersion = options.npmViewVersion ?? defaultNpmViewVersion;
|
|
63
|
+
const publishedVersion = await npmViewVersion(pkg.name, git.cwd);
|
|
64
|
+
let npmHash;
|
|
65
|
+
if (publishedVersion) {
|
|
66
|
+
const pattern = tagPattern(pkg);
|
|
67
|
+
const expanded = pattern.replace('{name}', pkg.name);
|
|
68
|
+
const starIdx = expanded.indexOf('*');
|
|
69
|
+
const tag = starIdx === -1 ? expanded : expanded.slice(0, starIdx) + publishedVersion + expanded.slice(starIdx + 1);
|
|
70
|
+
npmHash = (await git.tagExists(tag)) ? tag : undefined;
|
|
71
|
+
}
|
|
72
|
+
const fileHash = options.catchUpFile ? await git.lastCommitTouching(options.catchUpFile) : undefined;
|
|
73
|
+
if (!fileHash)
|
|
74
|
+
return npmHash;
|
|
75
|
+
if (!npmHash)
|
|
76
|
+
return fileHash;
|
|
77
|
+
return (await git.mergeBase(npmHash, fileHash)) ?? npmHash;
|
|
78
|
+
}
|
|
79
|
+
const execFileAsync = promisify(execFile);
|
|
80
|
+
const DEFAULT_TAG_PATTERN = 'v*';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
|
|
3
|
+
* Commits' subject-line shape. Anything that doesn't match falls into "Other Changes" as-is (for
|
|
4
|
+
* changelog entries) or defaults to a patch-level change (for version bump severity) - see
|
|
5
|
+
* `parseConventionalCommit`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONVENTIONAL_PATTERN: RegExp;
|
|
8
|
+
/** A bare version-bump commit (`"6.0.1"`, `"v2.3.0-beta.1"`, ...) - many release tools commit the
|
|
9
|
+
* version bump itself with just the new version number as the message. That's a release marker,
|
|
10
|
+
* not a real change worth describing (or worth bumping a version over on its own), so it's
|
|
11
|
+
* dropped everywhere a real change is being looked for. */
|
|
12
|
+
export declare const VERSION_BUMP_PATTERN: RegExp;
|
|
13
|
+
export interface ParsedCommitSubject {
|
|
14
|
+
type: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
/** A `!` right before the `:` (e.g. `feat!:`) - Conventional Commits' inline breaking-change
|
|
17
|
+
* marker. Doesn't cover a `BREAKING CHANGE:` footer, since only the subject line is available. */
|
|
18
|
+
breaking: boolean;
|
|
19
|
+
description: string;
|
|
20
|
+
}
|
|
21
|
+
/** Parses a commit subject as Conventional Commits, or `undefined` if it doesn't match at all
|
|
22
|
+
* (a non-conventional message - still a real change, just with no `type` to key off of). */
|
|
23
|
+
export declare function parseConventionalCommit(subject: string): ParsedCommitSubject | undefined;
|
|
24
|
+
/** Whether a commit `body` carries a Conventional Commits `BREAKING CHANGE:` (or
|
|
25
|
+
* `BREAKING-CHANGE:`) footer - the other, footer-based way to mark a breaking change, alongside
|
|
26
|
+
* the inline `!` the subject line alone can carry (see `parseConventionalCommit`, whose own
|
|
27
|
+
* `breaking` only ever reflects that marker, never a footer, since it only sees the subject). */
|
|
28
|
+
export declare function hasBreakingChangeFooter(body: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* A `Release-As: patch|minor|major` footer in a commit `body` - lets that one commit's own
|
|
31
|
+
* contribution to a detected bump severity be overridden by hand, regardless of what its subject
|
|
32
|
+
* line (or a `BREAKING CHANGE:` footer) would otherwise imply. The motivating case: a `feat:`
|
|
33
|
+
* commit that needs to ship right now as a patch, without waiting for the rest of a minor's worth
|
|
34
|
+
* of work to land - `Release-As: patch` on just that commit ships it alone, at patch severity,
|
|
35
|
+
* while a later genuine `feat:` (with no override) still correctly triggers a minor of its own.
|
|
36
|
+
* Case-insensitive; the last match wins if a body somehow has more than one, matching how multiple
|
|
37
|
+
* git trailers of the same key are conventionally read (later overrides earlier).
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseReleaseAs(body: string): 'patch' | 'minor' | 'major' | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
|
|
3
|
+
* Commits' subject-line shape. Anything that doesn't match falls into "Other Changes" as-is (for
|
|
4
|
+
* changelog entries) or defaults to a patch-level change (for version bump severity) - see
|
|
5
|
+
* `parseConventionalCommit`.
|
|
6
|
+
*/
|
|
7
|
+
export const CONVENTIONAL_PATTERN = /^(\w+)(\(([^)]+)\))?(!)?:\s*(.+)$/;
|
|
8
|
+
/** A bare version-bump commit (`"6.0.1"`, `"v2.3.0-beta.1"`, ...) - many release tools commit the
|
|
9
|
+
* version bump itself with just the new version number as the message. That's a release marker,
|
|
10
|
+
* not a real change worth describing (or worth bumping a version over on its own), so it's
|
|
11
|
+
* dropped everywhere a real change is being looked for. */
|
|
12
|
+
export const VERSION_BUMP_PATTERN = /^v?\d+\.\d+\.\d+(?:[-+][\w.]+)?$/;
|
|
13
|
+
/** Parses a commit subject as Conventional Commits, or `undefined` if it doesn't match at all
|
|
14
|
+
* (a non-conventional message - still a real change, just with no `type` to key off of). */
|
|
15
|
+
export function parseConventionalCommit(subject) {
|
|
16
|
+
const m = CONVENTIONAL_PATTERN.exec(subject);
|
|
17
|
+
if (!m)
|
|
18
|
+
return undefined;
|
|
19
|
+
const [, type, , scope, breakingMark, description] = m;
|
|
20
|
+
return { type: type.toLowerCase(), scope, breaking: !!breakingMark, description };
|
|
21
|
+
}
|
|
22
|
+
/** Whether a commit `body` carries a Conventional Commits `BREAKING CHANGE:` (or
|
|
23
|
+
* `BREAKING-CHANGE:`) footer - the other, footer-based way to mark a breaking change, alongside
|
|
24
|
+
* the inline `!` the subject line alone can carry (see `parseConventionalCommit`, whose own
|
|
25
|
+
* `breaking` only ever reflects that marker, never a footer, since it only sees the subject). */
|
|
26
|
+
export function hasBreakingChangeFooter(body) {
|
|
27
|
+
return /^BREAKING[ -]CHANGE:/im.test(body);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A `Release-As: patch|minor|major` footer in a commit `body` - lets that one commit's own
|
|
31
|
+
* contribution to a detected bump severity be overridden by hand, regardless of what its subject
|
|
32
|
+
* line (or a `BREAKING CHANGE:` footer) would otherwise imply. The motivating case: a `feat:`
|
|
33
|
+
* commit that needs to ship right now as a patch, without waiting for the rest of a minor's worth
|
|
34
|
+
* of work to land - `Release-As: patch` on just that commit ships it alone, at patch severity,
|
|
35
|
+
* while a later genuine `feat:` (with no override) still correctly triggers a minor of its own.
|
|
36
|
+
* Case-insensitive; the last match wins if a body somehow has more than one, matching how multiple
|
|
37
|
+
* git trailers of the same key are conventionally read (later overrides earlier).
|
|
38
|
+
*/
|
|
39
|
+
export function parseReleaseAs(body) {
|
|
40
|
+
const matches = [...body.matchAll(/^release-as:\s*(patch|minor|major)\s*$/gim)];
|
|
41
|
+
const last = matches.at(-1);
|
|
42
|
+
return last ? last[1].toLowerCase() : undefined;
|
|
43
|
+
}
|
package/utils/exec.d.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export interface ExecOptions {
|
|
2
|
+
/** 'inherit' streams the child directly to our stdio (used for non-TTY/CI passthrough).
|
|
3
|
+
* 'pipe' (default) captures output so the caller can drive a live view via onLine. */
|
|
3
4
|
stdio?: 'inherit' | 'pipe';
|
|
4
5
|
cwd?: string;
|
|
5
6
|
argv?: string[];
|
|
6
7
|
env?: Record<string, string | undefined>;
|
|
7
8
|
shell?: boolean;
|
|
8
|
-
onSpawn?: (childProcess: ChildProcess) => void;
|
|
9
9
|
onLine?: (line: string, stdio: 'stderr' | 'stdout') => void;
|
|
10
|
-
onData?: (data: string, stdio: 'stderr' | 'stdout') => void;
|
|
11
10
|
throwOnError?: boolean;
|
|
12
11
|
}
|
|
13
|
-
export interface
|
|
12
|
+
export interface ExecResult {
|
|
14
13
|
code?: number;
|
|
15
14
|
error?: Error;
|
|
16
15
|
stdout?: string;
|
|
17
16
|
}
|
|
18
|
-
export declare function exec(command: string, options?:
|
|
17
|
+
export declare function exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
package/utils/exec.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
1
|
+
import { ChildProcess, spawn } from 'child_process';
|
|
2
2
|
import { onExit } from 'signal-exit';
|
|
3
3
|
import { npmRunPathEnv } from './npm-run-path.js';
|
|
4
|
-
const runningChildren = new Map();
|
|
5
4
|
export async function exec(command, options) {
|
|
6
5
|
const opts = {
|
|
7
6
|
shell: true,
|
|
@@ -12,72 +11,60 @@ export async function exec(command, options) {
|
|
|
12
11
|
...npmRunPathEnv({ cwd: opts.cwd }),
|
|
13
12
|
...opts.env,
|
|
14
13
|
};
|
|
15
|
-
if (process.env.TS_NODE_PROJECT)
|
|
16
|
-
delete opts.env.TS_NODE_PROJECT;
|
|
17
14
|
opts.cwd = opts.cwd || process.cwd();
|
|
18
15
|
const spawnOptions = {
|
|
19
|
-
stdio: opts.stdio
|
|
16
|
+
stdio: opts.stdio === 'inherit' ? 'inherit' : 'pipe',
|
|
20
17
|
env: opts.env,
|
|
21
18
|
cwd: opts.cwd,
|
|
22
19
|
shell: opts.shell,
|
|
23
20
|
windowsHide: true,
|
|
24
21
|
};
|
|
25
|
-
const result = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
const result = { code: undefined, stdout: '' };
|
|
23
|
+
/** Separate buffers per stream - stdout/stderr chunks must never be spliced into the same line. */
|
|
24
|
+
let stdoutBuffer = '';
|
|
25
|
+
let stderrBuffer = '';
|
|
26
|
+
const processLines = (data, stdio, flush) => {
|
|
27
|
+
result.stdout += data;
|
|
28
|
+
if (!opts.onLine)
|
|
29
|
+
return;
|
|
30
|
+
let buf = (stdio === 'stdout' ? stdoutBuffer : stderrBuffer) + data;
|
|
31
|
+
/** Only synthesize a trailing newline if there's real unterminated content left - an
|
|
32
|
+
* already-empty buffer must not produce a spurious blank line on every flush. */
|
|
33
|
+
if (flush && buf.length && !buf.endsWith('\n'))
|
|
34
|
+
buf += '\n';
|
|
38
35
|
let i;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
chunk = chunk.substring(i + 1);
|
|
44
|
-
if (opts.onLine)
|
|
45
|
-
opts.onLine(line, stdio);
|
|
36
|
+
while ((i = buf.indexOf('\n')) >= 0) {
|
|
37
|
+
const line = buf.slice(0, i);
|
|
38
|
+
buf = buf.slice(i + 1);
|
|
39
|
+
opts.onLine(line, stdio);
|
|
46
40
|
}
|
|
47
|
-
|
|
41
|
+
if (stdio === 'stdout')
|
|
42
|
+
stdoutBuffer = buf;
|
|
43
|
+
else
|
|
44
|
+
stderrBuffer = buf;
|
|
48
45
|
};
|
|
49
46
|
const child = spawn(command, opts.argv || [], spawnOptions);
|
|
50
|
-
if (child.pid)
|
|
47
|
+
if (child.pid)
|
|
51
48
|
runningChildren.set(child.pid, child);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
child.stdout?.on('data', data => {
|
|
56
|
-
processData(data, 'stdout');
|
|
57
|
-
processLines('stdout');
|
|
58
|
-
});
|
|
59
|
-
child.stderr?.on('data', data => {
|
|
60
|
-
processData(data, 'stderr');
|
|
61
|
-
processLines('stderr');
|
|
62
|
-
});
|
|
49
|
+
child.stdout?.on('data', data => processLines(String(data), 'stdout'));
|
|
50
|
+
child.stderr?.on('data', data => processLines(String(data), 'stderr'));
|
|
63
51
|
return new Promise((resolve, reject) => {
|
|
64
|
-
let resolved;
|
|
52
|
+
let resolved = false;
|
|
65
53
|
child.on('error', (err) => {
|
|
66
54
|
if (child.pid)
|
|
67
55
|
runningChildren.delete(child.pid);
|
|
68
|
-
processLines('stdout', true);
|
|
69
|
-
processLines('stderr', true);
|
|
56
|
+
processLines('', 'stdout', true);
|
|
57
|
+
processLines('', 'stderr', true);
|
|
70
58
|
if (resolved)
|
|
71
59
|
return;
|
|
72
|
-
result.code = err.code || 1;
|
|
73
|
-
if (!err) {
|
|
74
|
-
const text = `Command failed (${result.code})`;
|
|
75
|
-
err = new Error(text);
|
|
76
|
-
}
|
|
77
|
-
if (typeof err === 'string')
|
|
78
|
-
err = new Error(err);
|
|
79
|
-
result.error = err;
|
|
80
60
|
resolved = true;
|
|
61
|
+
result.code = err.code || 1;
|
|
62
|
+
result.error =
|
|
63
|
+
typeof err === 'string'
|
|
64
|
+
? new Error(err)
|
|
65
|
+
: err instanceof Error
|
|
66
|
+
? err
|
|
67
|
+
: new Error(`Command failed (${result.code})`);
|
|
81
68
|
if (opts.throwOnError)
|
|
82
69
|
return reject(result.error);
|
|
83
70
|
resolve(result);
|
|
@@ -85,22 +72,21 @@ export async function exec(command, options) {
|
|
|
85
72
|
child.on('close', (code) => {
|
|
86
73
|
if (child.pid)
|
|
87
74
|
runningChildren.delete(child.pid);
|
|
88
|
-
processLines('stdout', true);
|
|
89
|
-
processLines('stderr', true);
|
|
75
|
+
processLines('', 'stdout', true);
|
|
76
|
+
processLines('', 'stderr', true);
|
|
90
77
|
if (resolved)
|
|
91
78
|
return;
|
|
92
|
-
result.code = code;
|
|
93
79
|
resolved = true;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
result.error = new Error(
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
result.code = code;
|
|
81
|
+
if (code)
|
|
82
|
+
result.error = new Error(`Command failed (${code})`);
|
|
83
|
+
if (result.error && opts.throwOnError)
|
|
84
|
+
return reject(result.error);
|
|
85
|
+
resolve(result);
|
|
99
86
|
});
|
|
100
87
|
});
|
|
101
88
|
}
|
|
89
|
+
const runningChildren = new Map();
|
|
102
90
|
onExit(() => {
|
|
103
|
-
runningChildren.forEach(child =>
|
|
104
|
-
child.kill();
|
|
105
|
-
});
|
|
91
|
+
runningChildren.forEach(child => child.kill());
|
|
106
92
|
});
|
package/utils/git.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface GitOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
}
|
|
4
|
+
export interface CommitInfo {
|
|
5
|
+
sha: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
/** Everything after the subject line (blank line included) - where Conventional Commits'
|
|
8
|
+
* footers/trailers live (`BREAKING CHANGE:`, `Release-As:`, ...) - see `conventional-commits.ts`. */
|
|
9
|
+
body: string;
|
|
10
|
+
/** Absolute paths of every file this commit touched. */
|
|
11
|
+
files: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare class GitHelper {
|
|
14
|
+
cwd: string;
|
|
15
|
+
constructor(options?: GitOptions);
|
|
16
|
+
/** The current branch name, or `undefined` in detached HEAD state (or outside a git repository) -
|
|
17
|
+
* what `branch-guard.ts` checks `allowBranch`/`ignoreBranch` against. */
|
|
18
|
+
currentBranch(): Promise<string | undefined>;
|
|
19
|
+
/** Files with uncommitted local changes (working tree + index). */
|
|
20
|
+
listDirtyFiles(options?: {
|
|
21
|
+
absolute?: boolean;
|
|
22
|
+
}): Promise<string[]>;
|
|
23
|
+
/** Files committed on the current branch but not yet in its upstream. */
|
|
24
|
+
listCommittedFiles(options?: {
|
|
25
|
+
absolute?: boolean;
|
|
26
|
+
}): Promise<string[]>;
|
|
27
|
+
/** Files changed since `hash` (committed diffs plus any uncommitted local changes). */
|
|
28
|
+
listChangedSince(hash: string, options?: {
|
|
29
|
+
absolute?: boolean;
|
|
30
|
+
}): Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Real commits only (never uncommitted/dirty changes - those have no message and can't be
|
|
33
|
+
* described in a changelog), oldest first: with `hash`, everything committed since that ref;
|
|
34
|
+
* without one, whatever's committed on the current branch but not yet in its upstream (same
|
|
35
|
+
* source as `listCommittedFiles`, via `git cherry`).
|
|
36
|
+
*/
|
|
37
|
+
listCommits(options?: {
|
|
38
|
+
hash?: string;
|
|
39
|
+
}): Promise<CommitInfo[]>;
|
|
40
|
+
/** Every commit reachable from HEAD, oldest first - for a boundary-free "everything so far"
|
|
41
|
+
* view (e.g. a package that's never been tagged/released at all, so there's no "since" ref to
|
|
42
|
+
* measure from and `listCommits()`'s own no-hash fallback, upstream push status, doesn't apply -
|
|
43
|
+
* a repo with no configured remote at all is common and shouldn't read as "nothing happened"). */
|
|
44
|
+
listAllCommits(): Promise<CommitInfo[]>;
|
|
45
|
+
private _commitInfoFor;
|
|
46
|
+
/** Tag names matching the glob `pattern` (e.g. `"v*"`, `"pkg-a@*"`), newest version first. */
|
|
47
|
+
listTags(pattern: string): Promise<string[]>;
|
|
48
|
+
/** Whether `tag` exists as an exact git tag name (not a glob - a literal ref check). */
|
|
49
|
+
tagExists(tag: string): Promise<boolean>;
|
|
50
|
+
/** The most recent commit that changed `file` (tracked history only) - `undefined` if it has
|
|
51
|
+
* never been committed. Answers "since when has this file last been updated", independent of
|
|
52
|
+
* any tag/version scheme - e.g. to check whether a changelog file has fallen behind. */
|
|
53
|
+
lastCommitTouching(file: string): Promise<string | undefined>;
|
|
54
|
+
/** The best common ancestor of `a` and `b` - `undefined` if none exists (unrelated histories,
|
|
55
|
+
* an invalid ref, ...). */
|
|
56
|
+
mergeBase(a: string, b: string): Promise<string | undefined>;
|
|
57
|
+
/** The most recent tag matching the glob `pattern` that HEAD actually descends from (unlike
|
|
58
|
+
* `listTags`, this follows commit ancestry rather than just sorting tag names - the right
|
|
59
|
+
* choice for a single repo-wide tag scheme, where a package has no tag of its own). */
|
|
60
|
+
describeTag(pattern: string): Promise<string | undefined>;
|
|
61
|
+
/** Stages and commits exactly `files` (relative to `cwd`, or absolute) with `message` - never a
|
|
62
|
+
* blanket `git add -A`, so the commit only ever contains what the caller explicitly asked for. */
|
|
63
|
+
commit(files: string[], message: string): Promise<void>;
|
|
64
|
+
/** Creates an annotated tag `name` pointing at HEAD, with `message` (defaults to `name`).
|
|
65
|
+
* Throws if a tag with that name already exists - callers wanting idempotent tagging should
|
|
66
|
+
* check `tagExists` first. */
|
|
67
|
+
createTag(name: string, message?: string): Promise<void>;
|
|
68
|
+
/** Pushes the current branch to `remote` (default `"origin"`), and its tags too unless
|
|
69
|
+
* `options.tags` is `false`. */
|
|
70
|
+
push(options?: {
|
|
71
|
+
remote?: string;
|
|
72
|
+
tags?: boolean;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
/** Raw `git diff <hash>..HEAD` text (committed changes plus any uncommitted local ones, same as
|
|
75
|
+
* plain `git diff <hash>`) - unlike `listChangedSince`, the actual patch content, not just
|
|
76
|
+
* which files changed. `pathspec`, if given, narrows it to just that file/directory. */
|
|
77
|
+
diff(hash: string, pathspec?: string): Promise<string>;
|
|
78
|
+
/** `git format-patch --root -o <outputDir> HEAD` - one `.patch` file per commit reachable from
|
|
79
|
+
* HEAD (oldest first), `--root` included so the very first commit gets one too. Returns the
|
|
80
|
+
* patch file paths, in commit order - what `import` replays into another repository. */
|
|
81
|
+
formatPatches(outputDir: string): Promise<string[]>;
|
|
82
|
+
/** Applies `patchFiles` (as produced by `formatPatches`, in order) via `git am`, preserving each
|
|
83
|
+
* original commit's author/date/message. `--3way` lets a patch that no longer applies cleanly
|
|
84
|
+
* fall back to a content-level three-way merge instead of failing outright. */
|
|
85
|
+
applyPatches(patchFiles: string[]): Promise<void>;
|
|
86
|
+
}
|