rman 0.38.0 → 1.0.2
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 +371 -21
- package/cli.js +51 -29
- 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 +8 -4
- package/rmanrc.schema.json +202 -0
- 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 +45 -59
- 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/commands/build-command.d.ts +0 -11
- package/commands/build-command.js +0 -26
- package/commands/changed-command.d.ts +0 -16
- package/commands/changed-command.js +0 -35
- package/commands/ci-command.d.ts +0 -24
- package/commands/ci-command.js +0 -71
- package/commands/execute-command.d.ts +0 -19
- package/commands/execute-command.js +0 -75
- package/commands/info-command.d.ts +0 -10
- package/commands/info-command.js +0 -57
- package/commands/list-command.d.ts +0 -38
- package/commands/list-command.js +0 -137
- package/commands/multi-task-command.d.ts +0 -22
- package/commands/multi-task-command.js +0 -68
- package/commands/publish-command.d.ts +0 -24
- package/commands/publish-command.js +0 -135
- package/commands/run-command.d.ts +0 -28
- package/commands/run-command.js +0 -134
- package/commands/version-command.d.ts +0 -25
- package/commands/version-command.js +0 -198
- package/core/command.d.ts +0 -35
- package/core/command.js +0 -117
- 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 -29
- 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 { RunService } from '../services/run.service.js';
|
|
2
|
+
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
3
|
+
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
4
|
+
export function applyRunOptions(cmd) {
|
|
5
|
+
return applyBranchGuardOptions(applyPackageFilterOptions(cmd))
|
|
6
|
+
.option('parallel', {
|
|
7
|
+
describe: 'Max packages to build at once: omit/true for CPU count (or .rmanrc run.<script>.concurrency), ' +
|
|
8
|
+
'a number for that many, false to run serially. Packages always build in dependency order.',
|
|
9
|
+
coerce: (v) => {
|
|
10
|
+
if (v === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (v === 'false' || v === false)
|
|
13
|
+
return false;
|
|
14
|
+
if (v === 'true' || v === true)
|
|
15
|
+
return true;
|
|
16
|
+
const n = Number(v);
|
|
17
|
+
return Number.isFinite(n) ? n : true;
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
.option('bail', {
|
|
21
|
+
describe: 'Stop on first failure (default: true, overridable per-package via .rmanrc run.<script>.bail)',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
})
|
|
24
|
+
.option('topo', {
|
|
25
|
+
describe: 'Respect package dependency order: a package waits for its dependencies and is skipped if one ' +
|
|
26
|
+
'fails. Set false for independent scripts (e.g. lint/test) - order becomes alphabetical and ' +
|
|
27
|
+
'failures never skip other packages. Default: true, overridable per-package via .rmanrc run.<script>.topo.',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
})
|
|
30
|
+
.option('progress', {
|
|
31
|
+
describe: 'Show a live progress panel (default: true; auto-disabled when not a TTY; .rmanrc run.<script>.progress)',
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
})
|
|
34
|
+
.option('changed', {
|
|
35
|
+
alias: 'c',
|
|
36
|
+
describe: 'Only run in packages that have changed since the last publish',
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
})
|
|
39
|
+
.option('changed-since', {
|
|
40
|
+
describe: 'Only run in packages that have changed since the given git commit/hash',
|
|
41
|
+
type: 'string',
|
|
42
|
+
})
|
|
43
|
+
.option('root', {
|
|
44
|
+
alias: 'r',
|
|
45
|
+
describe: 'Run across the whole repository even when the current directory is inside a single ' +
|
|
46
|
+
'package (which otherwise scopes the run to just that package). No effect elsewhere.',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
})
|
|
49
|
+
.conflicts('changed', 'changed-since');
|
|
50
|
+
}
|
|
51
|
+
export function readRunOptions(args) {
|
|
52
|
+
return {
|
|
53
|
+
...readPackageFilterOptions(args),
|
|
54
|
+
parallel: args.parallel,
|
|
55
|
+
topo: args.topo,
|
|
56
|
+
bail: args.bail,
|
|
57
|
+
progress: args.progress,
|
|
58
|
+
changed: args.changed,
|
|
59
|
+
changedSince: args.changedSince,
|
|
60
|
+
logLevel: args.logLevel,
|
|
61
|
+
root: args.root,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function initCli(repository, program) {
|
|
65
|
+
program.command({
|
|
66
|
+
command: 'run <script>',
|
|
67
|
+
describe: 'Run an npm script in each package',
|
|
68
|
+
builder: cmd => applyRunOptions(cmd)
|
|
69
|
+
.example('$0 run build', '')
|
|
70
|
+
.example('$0 run build --changed', '# Only in packages changed since the last publish')
|
|
71
|
+
.positional('script', {
|
|
72
|
+
describe: 'The script to run',
|
|
73
|
+
type: 'string',
|
|
74
|
+
}),
|
|
75
|
+
handler: async (args) => {
|
|
76
|
+
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
77
|
+
await RunService.runScript(repository, args.script, { ...readRunOptions(args), commandName: 'run' });
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RunService } from '../services/run.service.js';
|
|
2
|
+
import { assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
3
|
+
import { applyRunOptions, readRunOptions } from './run.command.js';
|
|
4
|
+
export function initCli(repository, program) {
|
|
5
|
+
program.command({
|
|
6
|
+
command: 'test',
|
|
7
|
+
describe: 'Alias for "run test"',
|
|
8
|
+
builder: cmd => applyRunOptions(cmd).example('$0 test', '# Tests packages'),
|
|
9
|
+
handler: async (args) => {
|
|
10
|
+
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
11
|
+
await RunService.runScript(repository, 'test', { ...readRunOptions(args), commandName: 'test' });
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import colors from 'ansi-colors';
|
|
3
|
+
import { VersionService } from '../services/version.service.js';
|
|
4
|
+
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
5
|
+
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
6
|
+
export function initCli(repository, program) {
|
|
7
|
+
program.command({
|
|
8
|
+
command: 'version [bump]',
|
|
9
|
+
describe: 'Bumps versions of changed packages (and their dependents), grouped via .rmanrc "group"',
|
|
10
|
+
builder: cmd => applyBranchGuardOptions(applyPackageFilterOptions(cmd))
|
|
11
|
+
.example('$0 version patch', '# Bump patch severity directly, applied immediately')
|
|
12
|
+
.example('$0 version', "# Auto-detect severity from commits, show the plan, don't write anything")
|
|
13
|
+
.example('$0 version --interactive', '# Show the plan either way, then ask for confirmation')
|
|
14
|
+
.positional('bump', {
|
|
15
|
+
describe: 'A release-type keyword ("patch"/"minor"/"major") or an explicit semver version. ' +
|
|
16
|
+
'Omit to auto-detect from commits and only preview the plan.',
|
|
17
|
+
type: 'string',
|
|
18
|
+
})
|
|
19
|
+
.option('interactive', {
|
|
20
|
+
alias: 'i',
|
|
21
|
+
describe: 'Show the plan and ask for confirmation before applying (with or without an explicit bump)',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
})
|
|
24
|
+
.option('ignore-dirty', {
|
|
25
|
+
describe: 'Exclude a package with uncommitted local changes instead of aborting the whole run',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
})
|
|
28
|
+
.option('push', {
|
|
29
|
+
describe: 'Push the resulting commit(s) and tag(s) to the remote once applied',
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
})
|
|
32
|
+
.option('message', {
|
|
33
|
+
alias: 'm',
|
|
34
|
+
describe: 'Override the commit message for every group this run commits (default: .rmanrc ' +
|
|
35
|
+
'version.commitMessage, or "chore(release): v{version}") - "{version}" is substituted ' +
|
|
36
|
+
"when a commit's own group shares one version.",
|
|
37
|
+
type: 'string',
|
|
38
|
+
})
|
|
39
|
+
.option('changelog', {
|
|
40
|
+
describe: "Also write each bumped package's CHANGELOG.md (same as running changelog --write " +
|
|
41
|
+
'separately) and fold it into the same commit as its version bump',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
})
|
|
44
|
+
.option('preid', {
|
|
45
|
+
describe: 'Make the bump a prerelease with this identifier (e.g. "beta" -> 1.2.3-beta.0). ' +
|
|
46
|
+
'Running again with the same --preid increments it (-> 1.2.3-beta.1); a different ' +
|
|
47
|
+
'identifier starts a fresh prerelease line. Ignored when bump is an explicit semver version.',
|
|
48
|
+
type: 'string',
|
|
49
|
+
}),
|
|
50
|
+
handler: async (args) => {
|
|
51
|
+
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
52
|
+
const bump = args.bump;
|
|
53
|
+
const plan = await VersionService.getPlan(repository, {
|
|
54
|
+
...readPackageFilterOptions(args),
|
|
55
|
+
bump,
|
|
56
|
+
ignoreDirty: args.ignoreDirty,
|
|
57
|
+
preid: args.preid,
|
|
58
|
+
});
|
|
59
|
+
printPlan(plan);
|
|
60
|
+
const errors = plan.filter(e => e.status === 'error');
|
|
61
|
+
if (errors.length) {
|
|
62
|
+
const message = `${errors.length} package(s) have uncommitted local changes ` +
|
|
63
|
+
'(pass --ignore-dirty to exclude them instead of aborting)';
|
|
64
|
+
console.log(colors.red(message));
|
|
65
|
+
const err = new Error(message);
|
|
66
|
+
err.logged = true;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
if (!plan.some(e => e.status === 'bump')) {
|
|
70
|
+
console.log(colors.gray('Nothing to version.'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let apply = !!bump;
|
|
74
|
+
if (args.interactive) {
|
|
75
|
+
apply = await confirm('Apply these changes?');
|
|
76
|
+
}
|
|
77
|
+
else if (!bump) {
|
|
78
|
+
console.log(colors.gray('Run again with an explicit bump, or --interactive, to apply.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!apply)
|
|
82
|
+
return;
|
|
83
|
+
const applied = await VersionService.applyPlan(repository, plan, {
|
|
84
|
+
push: args.push,
|
|
85
|
+
message: args.message,
|
|
86
|
+
changelog: args.changelog,
|
|
87
|
+
});
|
|
88
|
+
for (const entry of applied) {
|
|
89
|
+
if (entry.status === 'bump') {
|
|
90
|
+
console.log(colors.green('updated'), colors.cyan(entry.package.name), entry.from, '->', colors.yellow(entry.to));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function printPlan(entries) {
|
|
97
|
+
for (const e of entries) {
|
|
98
|
+
const name = colors.cyan(e.package.name);
|
|
99
|
+
const group = colors.gray(`(${e.group})`);
|
|
100
|
+
switch (e.status) {
|
|
101
|
+
case 'bump':
|
|
102
|
+
console.log(colors.green('bump'), name, group, e.from, '->', colors.yellow(e.to), colors.gray(e.reason ?? ''));
|
|
103
|
+
break;
|
|
104
|
+
case 'no-change':
|
|
105
|
+
console.log(colors.gray('no-change'), name, group, e.from);
|
|
106
|
+
break;
|
|
107
|
+
case 'skip':
|
|
108
|
+
console.log(colors.cyan('skip'), name, group, colors.gray(e.reason ?? ''));
|
|
109
|
+
break;
|
|
110
|
+
case 'error':
|
|
111
|
+
console.log(colors.red('error'), name, group, colors.red(e.reason ?? ''));
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function confirm(question) {
|
|
117
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
118
|
+
try {
|
|
119
|
+
const answer = await rl.question(`${question} (y/N) `);
|
|
120
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
rl.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '1.0.2';
|
package/core/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the rman configuration defined at a single directory level, merging
|
|
3
|
+
* (in increasing precedence): `package.json#rman`, `.rman.yml`, `.rmanrc`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function readDirConfig(dirname: string): any;
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the effective config for `targetDir` by cascading from `rootDir`
|
|
8
|
+
* down to `targetDir` (inclusive), the same way tsconfig's `extends` chain
|
|
9
|
+
* works: each directory level overrides the ones above it. This lets a
|
|
10
|
+
* package (or any intermediate directory) narrow or override the repository's
|
|
11
|
+
* root configuration for itself and everything below it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, any>): any;
|
package/core/config.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import merge from 'putil-merge';
|
|
5
|
+
/**
|
|
6
|
+
* Reads the rman configuration defined at a single directory level, merging
|
|
7
|
+
* (in increasing precedence): `package.json#rman`, `.rman.yml`, `.rmanrc`.
|
|
8
|
+
*/
|
|
9
|
+
export function readDirConfig(dirname) {
|
|
10
|
+
const result = {};
|
|
11
|
+
const pkgJsonFile = path.join(dirname, 'package.json');
|
|
12
|
+
if (fs.existsSync(pkgJsonFile)) {
|
|
13
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf-8'));
|
|
14
|
+
if (pkgJson && typeof pkgJson.rman === 'object')
|
|
15
|
+
merge(result, pkgJson.rman, { deep: true });
|
|
16
|
+
}
|
|
17
|
+
const ymlFile = path.join(dirname, '.rman.yml');
|
|
18
|
+
if (fs.existsSync(ymlFile)) {
|
|
19
|
+
const obj = yaml.load(fs.readFileSync(ymlFile, 'utf-8'));
|
|
20
|
+
if (obj && typeof obj === 'object')
|
|
21
|
+
merge(result, obj, { deep: true });
|
|
22
|
+
}
|
|
23
|
+
const rcFile = path.join(dirname, '.rmanrc');
|
|
24
|
+
if (fs.existsSync(rcFile)) {
|
|
25
|
+
const obj = JSON.parse(fs.readFileSync(rcFile, 'utf-8'));
|
|
26
|
+
if (obj && typeof obj === 'object')
|
|
27
|
+
merge(result, obj, { deep: true });
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the effective config for `targetDir` by cascading from `rootDir`
|
|
33
|
+
* down to `targetDir` (inclusive), the same way tsconfig's `extends` chain
|
|
34
|
+
* works: each directory level overrides the ones above it. This lets a
|
|
35
|
+
* package (or any intermediate directory) narrow or override the repository's
|
|
36
|
+
* root configuration for itself and everything below it.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveConfig(rootDir, targetDir, cache = new Map()) {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const dir of dirChain(rootDir, targetDir)) {
|
|
41
|
+
let local = cache.get(dir);
|
|
42
|
+
if (!local) {
|
|
43
|
+
local = readDirConfig(dir);
|
|
44
|
+
cache.set(dir, local);
|
|
45
|
+
}
|
|
46
|
+
merge(result, local, { deep: true });
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function dirChain(rootDir, targetDir) {
|
|
51
|
+
const rel = path.relative(rootDir, targetDir);
|
|
52
|
+
if (!rel || rel === '.' || rel.startsWith('..'))
|
|
53
|
+
return [rootDir];
|
|
54
|
+
const dirs = [rootDir];
|
|
55
|
+
let dir = rootDir;
|
|
56
|
+
for (const segment of rel.split(path.sep)) {
|
|
57
|
+
dir = path.join(dir, segment);
|
|
58
|
+
dirs.push(dir);
|
|
59
|
+
}
|
|
60
|
+
return dirs;
|
|
61
|
+
}
|
package/core/package.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export declare class Package {
|
|
|
2
2
|
readonly dirname: string;
|
|
3
3
|
private _json;
|
|
4
4
|
dependencies: string[];
|
|
5
|
+
/** Effective rman config for this package, cascaded from the repository root. */
|
|
6
|
+
config: any;
|
|
5
7
|
constructor(dirname: string);
|
|
6
8
|
get basename(): string;
|
|
7
9
|
get name(): string;
|
package/core/package.js
CHANGED
package/core/repository.d.ts
CHANGED
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
import { Package } from './package.js';
|
|
2
2
|
export declare class Repository extends Package {
|
|
3
3
|
readonly dirname: string;
|
|
4
|
-
readonly config: any;
|
|
5
4
|
readonly monorepo: boolean;
|
|
6
5
|
readonly packages: Package[];
|
|
6
|
+
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
7
|
+
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
8
|
+
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
9
|
+
readonly cwd: string;
|
|
7
10
|
readonly rootPackage: Package;
|
|
8
|
-
protected constructor(dirname: string,
|
|
11
|
+
protected constructor(dirname: string, monorepo: boolean, packages: Package[],
|
|
12
|
+
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
13
|
+
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
14
|
+
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
15
|
+
cwd?: string);
|
|
16
|
+
/**
|
|
17
|
+
* The package whose own directory contains `cwd` (the deepest match, so a package nested
|
|
18
|
+
* inside another's directory resolves to the innermost one) - or `undefined` when `cwd` *is*
|
|
19
|
+
* the repository root itself, or isn't inside any known package (e.g. a non-monorepo checkout,
|
|
20
|
+
* or a stray directory the workspace glob doesn't cover).
|
|
21
|
+
*/
|
|
22
|
+
get currentPackage(): Package | undefined;
|
|
9
23
|
getPackages(options?: {
|
|
10
24
|
scope?: string | string[];
|
|
11
25
|
toposort?: boolean;
|
|
12
26
|
}): Package[];
|
|
13
27
|
getPackage(name: string): Package | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Reports each package's git change status. `dirty` (uncommitted local
|
|
30
|
+
* changes) is always checked first, regardless of `hash` - a file can be
|
|
31
|
+
* dirty no matter what it's being compared against. Once a package isn't
|
|
32
|
+
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
33
|
+
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
34
|
+
* means it differs from that commit. Otherwise a package is `clean`.
|
|
35
|
+
*/
|
|
36
|
+
listStatus(options?: {
|
|
37
|
+
hash?: string;
|
|
38
|
+
}): Promise<Record<string, Repository.PackageStatus>>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the effective rman config for the repository root and every
|
|
41
|
+
* package, cascading root -> intermediate directories -> package directory,
|
|
42
|
+
* so a `.rmanrc` placed anywhere along that path overrides the levels above it.
|
|
43
|
+
*/
|
|
44
|
+
protected _resolveConfigs(): void;
|
|
14
45
|
protected _updateDependencies(): void;
|
|
15
46
|
static create(root?: string, options?: {
|
|
16
47
|
deep?: number;
|
|
17
48
|
}): Repository;
|
|
18
49
|
protected static _resolvePackages(dirname: string, patterns: string[]): Package[];
|
|
19
|
-
|
|
50
|
+
}
|
|
51
|
+
export declare namespace Repository {
|
|
52
|
+
type PackageStatus = 'dirty' | 'committed' | 'changed' | 'clean';
|
|
20
53
|
}
|
package/core/repository.js
CHANGED
|
@@ -1,27 +1,55 @@
|
|
|
1
1
|
import glob from 'fast-glob';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import * as yaml from 'js-yaml';
|
|
4
3
|
import path from 'path';
|
|
5
|
-
import
|
|
4
|
+
import { GitHelper } from '../utils/git.js';
|
|
5
|
+
import { resolveConfig } from './config.js';
|
|
6
6
|
import { Package } from './package.js';
|
|
7
7
|
export class Repository extends Package {
|
|
8
8
|
dirname;
|
|
9
|
-
config;
|
|
10
9
|
monorepo;
|
|
11
10
|
packages;
|
|
11
|
+
cwd;
|
|
12
12
|
rootPackage;
|
|
13
|
-
constructor(dirname,
|
|
13
|
+
constructor(dirname, monorepo, packages,
|
|
14
|
+
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
15
|
+
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
16
|
+
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
17
|
+
cwd = dirname) {
|
|
14
18
|
super(dirname);
|
|
15
19
|
this.dirname = dirname;
|
|
16
|
-
this.config = config;
|
|
17
20
|
this.monorepo = monorepo;
|
|
18
21
|
this.packages = packages;
|
|
22
|
+
this.cwd = cwd;
|
|
19
23
|
this.rootPackage = new Package(dirname);
|
|
20
24
|
if (!monorepo)
|
|
21
25
|
this.packages = [this.rootPackage];
|
|
26
|
+
this._resolveConfigs();
|
|
27
|
+
this._updateDependencies();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The package whose own directory contains `cwd` (the deepest match, so a package nested
|
|
31
|
+
* inside another's directory resolves to the innermost one) - or `undefined` when `cwd` *is*
|
|
32
|
+
* the repository root itself, or isn't inside any known package (e.g. a non-monorepo checkout,
|
|
33
|
+
* or a stray directory the workspace glob doesn't cover).
|
|
34
|
+
*/
|
|
35
|
+
get currentPackage() {
|
|
36
|
+
if (path.resolve(this.cwd) === path.resolve(this.dirname))
|
|
37
|
+
return undefined;
|
|
38
|
+
let best;
|
|
39
|
+
for (const pkg of this.packages) {
|
|
40
|
+
const rel = path.relative(pkg.dirname, this.cwd);
|
|
41
|
+
const isSelfOrDescendant = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
42
|
+
if (isSelfOrDescendant && (!best || pkg.dirname.length > best.dirname.length))
|
|
43
|
+
best = pkg;
|
|
44
|
+
}
|
|
45
|
+
return best;
|
|
22
46
|
}
|
|
23
47
|
getPackages(options) {
|
|
24
|
-
|
|
48
|
+
let result = [...this.packages];
|
|
49
|
+
if (options?.scope) {
|
|
50
|
+
const scopes = Array.isArray(options.scope) ? options.scope : [options.scope];
|
|
51
|
+
result = result.filter(p => scopes.includes(p.name));
|
|
52
|
+
}
|
|
25
53
|
if (options?.toposort)
|
|
26
54
|
topoSortPackages(result);
|
|
27
55
|
return result;
|
|
@@ -29,6 +57,50 @@ export class Repository extends Package {
|
|
|
29
57
|
getPackage(name) {
|
|
30
58
|
return this.packages.find(p => p.name === name);
|
|
31
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Reports each package's git change status. `dirty` (uncommitted local
|
|
62
|
+
* changes) is always checked first, regardless of `hash` - a file can be
|
|
63
|
+
* dirty no matter what it's being compared against. Once a package isn't
|
|
64
|
+
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
65
|
+
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
66
|
+
* means it differs from that commit. Otherwise a package is `clean`.
|
|
67
|
+
*/
|
|
68
|
+
async listStatus(options) {
|
|
69
|
+
const hash = options?.hash;
|
|
70
|
+
const git = new GitHelper({ cwd: this.dirname });
|
|
71
|
+
const packages = this.getPackages();
|
|
72
|
+
const belongsTo = (p, files) => files.some(f => !path.relative(p.dirname, f).startsWith('..'));
|
|
73
|
+
const [dirtyFiles, referenceFiles] = await Promise.all([
|
|
74
|
+
git.listDirtyFiles({ absolute: true }),
|
|
75
|
+
hash ? git.listChangedSince(hash, { absolute: true }) : git.listCommittedFiles({ absolute: true }),
|
|
76
|
+
]);
|
|
77
|
+
const result = {};
|
|
78
|
+
for (const p of packages) {
|
|
79
|
+
if (belongsTo(p, dirtyFiles))
|
|
80
|
+
result[p.name] = 'dirty';
|
|
81
|
+
else if (belongsTo(p, referenceFiles))
|
|
82
|
+
result[p.name] = hash ? 'changed' : 'committed';
|
|
83
|
+
else
|
|
84
|
+
result[p.name] = 'clean';
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the effective rman config for the repository root and every
|
|
90
|
+
* package, cascading root -> intermediate directories -> package directory,
|
|
91
|
+
* so a `.rmanrc` placed anywhere along that path overrides the levels above it.
|
|
92
|
+
*/
|
|
93
|
+
_resolveConfigs() {
|
|
94
|
+
const cache = new Map();
|
|
95
|
+
const rootConfig = resolveConfig(this.dirname, this.dirname, cache);
|
|
96
|
+
this.config = rootConfig;
|
|
97
|
+
this.rootPackage.config = rootConfig;
|
|
98
|
+
for (const pkg of this.packages) {
|
|
99
|
+
if (pkg === this.rootPackage)
|
|
100
|
+
continue;
|
|
101
|
+
pkg.config = resolveConfig(this.dirname, pkg.dirname, cache);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
32
104
|
_updateDependencies() {
|
|
33
105
|
const deps = {};
|
|
34
106
|
for (const pkg of this.packages) {
|
|
@@ -38,7 +110,7 @@ export class Repository extends Package {
|
|
|
38
110
|
...pkg.json.peerDependencies,
|
|
39
111
|
...pkg.json.optionalDependencies,
|
|
40
112
|
};
|
|
41
|
-
const configDeps =
|
|
113
|
+
const configDeps = pkg.config.packages?.[pkg.name]?.dependencies;
|
|
42
114
|
if (configDeps) {
|
|
43
115
|
if (Array.isArray(configDeps))
|
|
44
116
|
configDeps.forEach(x => (o[x] = o[x] || '*'));
|
|
@@ -60,13 +132,16 @@ export class Repository extends Package {
|
|
|
60
132
|
return;
|
|
61
133
|
circularCheck.push(pkg.name);
|
|
62
134
|
for (const s of pkg.dependencies) {
|
|
63
|
-
|
|
135
|
+
/** `target` starts out *as* the top-level package's own `dependencies` array, so its
|
|
136
|
+
* direct entries are trivially "already in target" - recursing only when newly-added
|
|
137
|
+
* would mean a direct dependency's own transitive deps never get pulled in. Recurse
|
|
138
|
+
* unconditionally (guarded by circularCheck); only skip re-adding an existing entry,
|
|
139
|
+
* and never let the top-level package end up depending on itself via a cycle. */
|
|
140
|
+
if (s !== circularCheck[0] && !target.includes(s))
|
|
64
141
|
target.push(s);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
}
|
|
142
|
+
const p = this.getPackage(s);
|
|
143
|
+
if (p)
|
|
144
|
+
deepFindDependencies(p, target);
|
|
70
145
|
}
|
|
71
146
|
};
|
|
72
147
|
for (const pkg of this.packages) {
|
|
@@ -84,10 +159,7 @@ export class Repository extends Package {
|
|
|
84
159
|
const pkgJson = JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
85
160
|
if (Array.isArray(pkgJson.workspaces)) {
|
|
86
161
|
const packages = this._resolvePackages(pkgDirname, pkgJson.workspaces);
|
|
87
|
-
|
|
88
|
-
const repo = new Repository(pkgDirname, config, true, packages);
|
|
89
|
-
repo._updateDependencies();
|
|
90
|
-
return repo;
|
|
162
|
+
return new Repository(pkgDirname, true, packages, dirname);
|
|
91
163
|
}
|
|
92
164
|
/** If we reach to the root of the project */
|
|
93
165
|
if (fs.existsSync(path.join(pkgDirname, '.git')))
|
|
@@ -95,11 +167,7 @@ export class Repository extends Package {
|
|
|
95
167
|
}
|
|
96
168
|
pkgDirname = path.resolve(pkgDirname, '..');
|
|
97
169
|
}
|
|
98
|
-
|
|
99
|
-
const repo = new Repository(dirname, config, false, []);
|
|
100
|
-
repo._updateDependencies();
|
|
101
|
-
return repo;
|
|
102
|
-
// throw new Error('No monorepo project detected');
|
|
170
|
+
return new Repository(dirname, false, [], dirname);
|
|
103
171
|
}
|
|
104
172
|
static _resolvePackages(dirname, patterns) {
|
|
105
173
|
const packages = [];
|
|
@@ -118,29 +186,6 @@ export class Repository extends Package {
|
|
|
118
186
|
}
|
|
119
187
|
return packages;
|
|
120
188
|
}
|
|
121
|
-
static _readConfig(dirname) {
|
|
122
|
-
const result = {};
|
|
123
|
-
const f = path.resolve(dirname, 'package.json');
|
|
124
|
-
let pkgJson;
|
|
125
|
-
if (fs.existsSync(f)) {
|
|
126
|
-
pkgJson = JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
127
|
-
}
|
|
128
|
-
if (pkgJson && typeof pkgJson.rman === 'object')
|
|
129
|
-
merge(result, pkgJson.rman, { deep: true });
|
|
130
|
-
let filename = path.resolve(dirname, '.rman.yml');
|
|
131
|
-
if (fs.existsSync(filename)) {
|
|
132
|
-
const obj = yaml.load(fs.readFileSync(filename, 'utf-8'));
|
|
133
|
-
if (obj && typeof obj === 'object')
|
|
134
|
-
merge(result, obj, { deep: true });
|
|
135
|
-
}
|
|
136
|
-
filename = path.resolve(dirname, '.rmanrc');
|
|
137
|
-
if (fs.existsSync(filename)) {
|
|
138
|
-
const obj = JSON.parse(fs.readFileSync(filename, 'utf-8'));
|
|
139
|
-
if (obj && typeof obj === 'object')
|
|
140
|
-
merge(result, obj, { deep: true });
|
|
141
|
-
}
|
|
142
|
-
return result;
|
|
143
|
-
}
|
|
144
189
|
}
|
|
145
190
|
function topoSortPackages(packages) {
|
|
146
191
|
packages.sort((a, b) => {
|
package/index.d.ts
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic API - the same logic the CLI commands run, importable directly without going
|
|
3
|
+
* through yargs/argv. Each domain's logic lives in a `<Name>` namespace under `./services/*.ts`
|
|
4
|
+
* (e.g. `ChangelogService`, `CiService`, `CleanService`, `ListService`, `RunService`,
|
|
5
|
+
* `SystemInfo`, `VersionService`), re-exported here via `./services.js`. Purpose-specific functions, not one
|
|
6
|
+
* generic `run`/`get` per domain (see e.g. `SystemInfo.getSystemInfo`/`getRepositoryInfo`, kept
|
|
7
|
+
* separate since they're genuinely independent capabilities, not just steps of one operation) -
|
|
8
|
+
* CLI-only concerns (argv parsing, `--help` text, and all console/file presentation) stay in
|
|
9
|
+
* `cli.ts` and the individual `commands/*.command.ts` modules, which are not exported here.
|
|
10
|
+
*/
|
|
11
|
+
export { Package } from './core/package.js';
|
|
12
|
+
export { Repository } from './core/repository.js';
|
|
13
|
+
export * from './services.js';
|
|
14
|
+
export type { DetectChangeHashOptions } from './utils/change-hash.js';
|
|
15
|
+
export { detectChangeHash } from './utils/change-hash.js';
|
|
16
|
+
export type { LogLevel } from './utils/logger.js';
|
|
17
|
+
export { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
package/index.js
CHANGED
|
@@ -1 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic API - the same logic the CLI commands run, importable directly without going
|
|
3
|
+
* through yargs/argv. Each domain's logic lives in a `<Name>` namespace under `./services/*.ts`
|
|
4
|
+
* (e.g. `ChangelogService`, `CiService`, `CleanService`, `ListService`, `RunService`,
|
|
5
|
+
* `SystemInfo`, `VersionService`), re-exported here via `./services.js`. Purpose-specific functions, not one
|
|
6
|
+
* generic `run`/`get` per domain (see e.g. `SystemInfo.getSystemInfo`/`getRepositoryInfo`, kept
|
|
7
|
+
* separate since they're genuinely independent capabilities, not just steps of one operation) -
|
|
8
|
+
* CLI-only concerns (argv parsing, `--help` text, and all console/file presentation) stay in
|
|
9
|
+
* `cli.ts` and the individual `commands/*.command.ts` modules, which are not exported here.
|
|
10
|
+
*/
|
|
11
|
+
export { Package } from './core/package.js';
|
|
12
|
+
export { Repository } from './core/repository.js';
|
|
13
|
+
export * from './services.js';
|
|
14
|
+
export { detectChangeHash } from './utils/change-hash.js';
|
|
15
|
+
export { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rman",
|
|
3
3
|
"description": "Repository manager",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@netlify/parse-npm-script": "^0.1.2",
|
|
9
9
|
"ansi-colors": "^4.1.3",
|
|
10
|
-
"easy-table": "^1.2.0",
|
|
11
10
|
"cross-dirname": "^0.1.0",
|
|
11
|
+
"easy-table": "^1.2.0",
|
|
12
12
|
"envinfo": "^7.21.0",
|
|
13
13
|
"fast-glob": "^3.3.3",
|
|
14
|
+
"fast-tokenizer": "^1.10.0",
|
|
14
15
|
"ini": "^7.0.0",
|
|
15
16
|
"is-ci": "^4.1.0",
|
|
16
17
|
"js-yaml": "^5.4.1",
|
|
18
|
+
"micromatch": "^4.0.8",
|
|
17
19
|
"npmlog": "^7.0.1",
|
|
18
20
|
"power-tasks": "^1.14.2",
|
|
19
21
|
"putil-merge": "^3.13.0",
|
|
@@ -33,7 +35,8 @@
|
|
|
33
35
|
"types": "./index.d.ts",
|
|
34
36
|
"default": "./index.js"
|
|
35
37
|
},
|
|
36
|
-
"./package.json": "./package.json"
|
|
38
|
+
"./package.json": "./package.json",
|
|
39
|
+
"./rmanrc.schema.json": "./rmanrc.schema.json"
|
|
37
40
|
},
|
|
38
41
|
"bin": {
|
|
39
42
|
"rman": "cli.js"
|
|
@@ -55,5 +58,6 @@
|
|
|
55
58
|
"repository",
|
|
56
59
|
"build",
|
|
57
60
|
"lerna"
|
|
58
|
-
]
|
|
61
|
+
],
|
|
62
|
+
"packageManager": "yarn@3.4.1+sha512.a333bf9c2557d71f5ba87dd1446cde24bb9c35301ec29dd2baba577b3f9d1ab61348b66a1324de9088c40a996c5b9203794da8bf14cab9fae6a6eccc00cb8078"
|
|
59
63
|
}
|