rman 1.0.1 → 1.0.4

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 CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  [![NPM Version][npm-image]][npm-url]
4
4
  [![NPM Downloads][downloads-image]][downloads-url]
5
- [![Quality Check][qc-image]][qc-url]
6
- [![License][license-image]][license-url]
5
+ [![CI Tests][ci-test-image]][ci-test-url]
6
+ [![Test Coverage][coveralls-image]][coveralls-url]
7
7
 
8
8
  **rman** is a monorepo management CLI: a self-contained alternative to reaching for Lerna,
9
9
  Changesets, and a handful of shell scripts glued together. One tool for running scripts across
10
10
  packages, computing semantic version bumps from your commit history, publishing, changelogs,
11
11
  importing external repos with history intact, and more - all driven by a single, cascading
12
- `.rmanrc`/`.rman.yml` config.
12
+ `.rmanrc`/`.rmanrc.yml` config.
13
13
 
14
14
  Every command is also available as a **programmatic API** - see [docs/api.md](docs/api.md) if you
15
15
  want to call `rman`'s logic directly from a Node.js script instead of shelling out to the CLI.
@@ -243,6 +243,7 @@ rman version --changelog # also write/fold in each bumped package's CHAN
243
243
  rman version patch --push # commit, tag, and push in one go
244
244
  rman version patch --message "chore(release): {version}"
245
245
  rman version --ignore-dirty # exclude dirty packages instead of aborting the whole run
246
+ rman version patch --show # preview what an explicit patch bump would do, without applying it
246
247
  ```
247
248
 
248
249
  Severity, when not given explicitly, is auto-detected per package/group from
@@ -298,12 +299,13 @@ covered, then run `rman ci` to install it.
298
299
 
299
300
  `rman` reads config cascaded from the repository root down to each package's own directory (the
300
301
  same way a `tsconfig.json` `extends` chain works) - a value set closer to a package overrides the
301
- same key set further up. Three file forms are supported per directory, merged in increasing
302
- precedence: `package.json`'s own `"rman"` key, `.rman.yml` (YAML), and `.rmanrc` (**JSON**, despite
303
- the dotfile-style name).
302
+ same key set further up. Several file forms are supported per directory, merged in increasing
303
+ precedence: `package.json`'s own `"rman"` key, `.rmanrc.yml` (YAML), `.rmanrc` (**JSON**, despite
304
+ the dotfile-style name), and `.rmanrc.cjs`/`.rmanrc.mjs`/`.rmanrc.js` for config that needs real
305
+ logic (a JS module's default export).
304
306
 
305
307
  ```yaml
306
- # .rman.yml, at the repository root
308
+ # .rmanrc.yml, at the repository root
307
309
  packageManager: pnpm
308
310
  logLevel: info
309
311
  allowBranch: [main, release/*]
@@ -337,10 +339,26 @@ run:
337
339
  { "group": "plugins" }
338
340
  ```
339
341
 
340
- See [docs/api.md#configuration-rmanrc-rmanyml](docs/api.md#configuration-rmanrc-rmanyml) for the
342
+ See [docs/api.md#configuration-rmanrc-rmanrcyml](docs/api.md#configuration-rmanrc-rmanrcyml) for the
341
343
  full key reference (every `run.<script>.*` sub-key, `clean.*`, `changelog.*`, precedence rules,
342
344
  and which keys are root-level-only today).
343
345
 
346
+ **Editor autocomplete:** `rman` ships a JSON Schema for `.rmanrc`/`.rmanrc.yml` at
347
+ `rman/rmanrc.schema.json` - add `"$schema": "./node_modules/rman/rmanrc.schema.json"` to your
348
+ `.rmanrc` (or the equivalent `# yaml-language-server: $schema=...` comment in `.rmanrc.yml`) to get
349
+ autocomplete and validation in VS Code/WebStorm. For a `.rmanrc.cjs`/`.mjs`/`.js` config, wrap it in
350
+ the exported `defineConfig()` helper instead for the same autocomplete via the `RmanConfig` type:
351
+
352
+ ```js
353
+ // .rmanrc.mjs
354
+ import { defineConfig } from 'rman';
355
+ export default defineConfig({ packageManager: 'pnpm' });
356
+ ```
357
+
358
+ See
359
+ [docs/api.md#editor-support-json-schema](docs/api.md#editor-support-json-schema) for details,
360
+ including a WebStorm setup that needs no changes to the config file itself.
361
+
344
362
  ## Programmatic API
345
363
 
346
364
  Every command above is a thin wrapper around an exported service function - call them directly
@@ -349,7 +367,7 @@ from your own Node.js scripts without shelling out to the `rman` binary:
349
367
  ```ts
350
368
  import { Repository, VersionService } from 'rman';
351
369
 
352
- const repository = Repository.create();
370
+ const repository = await Repository.create();
353
371
  const plan = await VersionService.getPlan(repository);
354
372
  await VersionService.applyPlan(repository, plan, { changelog: true, push: true });
355
373
  ```
@@ -366,11 +384,11 @@ Full reference, with detailed examples for every service (`VersionService`, `Pub
366
384
 
367
385
  rman is available under the [MIT](LICENSE) license.
368
386
 
369
- [npm-image]: https://img.shields.io/npm/v/rman.svg
387
+ [npm-image]: https://img.shields.io/npm/v/rman
370
388
  [npm-url]: https://npmjs.org/package/rman
371
389
  [downloads-image]: https://img.shields.io/npm/dm/rman.svg
372
390
  [downloads-url]: https://npmjs.org/package/rman
373
- [qc-image]: https://github.com/panates/rman/actions/workflows/qc.yml/badge.svg
374
- [qc-url]: https://github.com/panates/rman/actions/workflows/qc.yml
375
- [license-image]: https://img.shields.io/npm/l/rman.svg
376
- [license-url]: https://github.com/panates/rman/blob/main/LICENSE
391
+ [ci-test-image]: https://github.com/panates/rman/actions/workflows/test.yml/badge.svg
392
+ [ci-test-url]: https://github.com/panates/rman/actions/workflows/test.yml
393
+ [coveralls-image]: https://img.shields.io/coveralls/panates/rman/dev.svg
394
+ [coveralls-url]: https://coveralls.io/r/panates/rman
package/cli.js CHANGED
@@ -23,10 +23,10 @@ import { Repository } from './core/repository.js';
23
23
  import { LOG_LEVELS } from './utils/logger.js';
24
24
  export async function runCli(options) {
25
25
  try {
26
- const repository = Repository.create(options?.cwd);
26
+ const repository = await Repository.create(options?.cwd);
27
27
  const _argv = options?.argv || hideBin(process.argv);
28
28
  const program = yargs(_argv)
29
- .scriptName('rman2')
29
+ .scriptName('rman')
30
30
  .version(version)
31
31
  .alias('version', 'v')
32
32
  .usage('$0 <cmd> [options...]')
@@ -11,6 +11,7 @@ export function initCli(repository, program) {
11
11
  .example('$0 version patch', '# Bump patch severity directly, applied immediately')
12
12
  .example('$0 version', "# Auto-detect severity from commits, show the plan, don't write anything")
13
13
  .example('$0 version --interactive', '# Show the plan either way, then ask for confirmation')
14
+ .example('$0 version patch --show', '# Preview what an explicit patch bump would do, without applying it')
14
15
  .positional('bump', {
15
16
  describe: 'A release-type keyword ("patch"/"minor"/"major") or an explicit semver version. ' +
16
17
  'Omit to auto-detect from commits and only preview the plan.',
@@ -21,6 +22,13 @@ export function initCli(repository, program) {
21
22
  describe: 'Show the plan and ask for confirmation before applying (with or without an explicit bump)',
22
23
  type: 'boolean',
23
24
  })
25
+ .option('show', {
26
+ describe: 'Show the resulting plan for the given bump without applying it - unlike omitting bump ' +
27
+ 'entirely, this still uses the given release-type keyword/version to compute the plan, ' +
28
+ 'just never writes it.',
29
+ type: 'boolean',
30
+ })
31
+ .conflicts('show', 'interactive')
24
32
  .option('ignore-dirty', {
25
33
  describe: 'Exclude a package with uncommitted local changes instead of aborting the whole run',
26
34
  type: 'boolean',
@@ -70,6 +78,10 @@ export function initCli(repository, program) {
70
78
  console.log(colors.gray('Nothing to version.'));
71
79
  return;
72
80
  }
81
+ if (args.show) {
82
+ console.log(colors.gray('Preview only (--show) - nothing was written.'));
83
+ return;
84
+ }
73
85
  let apply = !!bump;
74
86
  if (args.interactive) {
75
87
  apply = await confirm('Apply these changes?');
package/constants.js CHANGED
@@ -1 +1 @@
1
- export const version = '1.0.1';
1
+ export const version = '1.0.4';
package/core/config.d.ts CHANGED
@@ -1,8 +1,82 @@
1
+ /**
2
+ * The shape of `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` (and `package.json`'s own
3
+ * `"rman"` key) - see docs/api.md#configuration-rmanrc-rmanrcyml for the full reference. Every
4
+ * field is optional and cascades from the repository root down to each package's own directory.
5
+ * Purely a typing aid (used by `defineConfig` below, and importable on its own for a `.rmanrc.ts`/
6
+ * `.mts` authored config, or a plain `: RmanConfig` annotation) - never read by rman itself, which
7
+ * only ever sees the plain JS object a JS config file exports.
8
+ */
9
+ export interface RmanConfig {
10
+ packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
11
+ logLevel?: 'silent' | 'error' | 'info' | 'verbose';
12
+ allowBranch?: string | string[];
13
+ ignoreBranch?: string | string[];
14
+ group?: boolean | string;
15
+ version?: RmanConfig.VersionOptions;
16
+ changelog?: RmanConfig.ChangelogOptions;
17
+ clean?: RmanConfig.CleanOptions;
18
+ /** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). */
19
+ run?: Record<string, RmanConfig.RunScriptOptions>;
20
+ /** Keyed by the in-repo package's own name. */
21
+ packages?: Record<string, RmanConfig.PackageOptions>;
22
+ }
23
+ export declare namespace RmanConfig {
24
+ interface VersionOptions {
25
+ commitMessage?: string;
26
+ script?: string | string[];
27
+ preScript?: string | string[];
28
+ postScript?: string | string[];
29
+ }
30
+ interface ChangelogOptions {
31
+ ignoreTypes?: string[];
32
+ template?: string;
33
+ filePath?: string;
34
+ tagPattern?: string;
35
+ }
36
+ interface CleanOptions {
37
+ include?: string | string[];
38
+ exclude?: string | string[];
39
+ skip?: boolean;
40
+ }
41
+ interface RunScriptOptions {
42
+ concurrency?: number;
43
+ topo?: boolean;
44
+ bail?: boolean;
45
+ progress?: boolean;
46
+ logLevel?: 'silent' | 'error' | 'info' | 'verbose';
47
+ changedSince?: string;
48
+ skip?: boolean;
49
+ if?: string;
50
+ script?: string | string[];
51
+ preScript?: string | string[];
52
+ postScript?: string | string[];
53
+ override?: boolean;
54
+ }
55
+ interface PackageOptions {
56
+ dependencies?: string[] | Record<string, string>;
57
+ }
58
+ }
59
+ /**
60
+ * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
61
+ * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
62
+ * unchanged; this exists purely so TypeScript can infer/check against `RmanConfig`, not for any
63
+ * runtime behavior:
64
+ *
65
+ * // .rmanrc.mjs
66
+ * import { defineConfig } from 'rman';
67
+ * export default defineConfig({ packageManager: 'pnpm' });
68
+ *
69
+ * // .rmanrc.cjs
70
+ * const { defineConfig } = require('rman');
71
+ * module.exports = defineConfig({ packageManager: 'pnpm' });
72
+ */
73
+ export declare function defineConfig(config: RmanConfig): RmanConfig;
1
74
  /**
2
75
  * Reads the rman configuration defined at a single directory level, merging
3
- * (in increasing precedence): `package.json#rman`, `.rman.yml`, `.rmanrc`.
76
+ * (in increasing precedence): `package.json#rman`, `.rmanrc.yml`, `.rmanrc`,
77
+ * then `.rmanrc.cjs`/`.rmanrc.mjs`/`.rmanrc.js` (whichever exist, in that order).
4
78
  */
5
- export declare function readDirConfig(dirname: string): any;
79
+ export declare function readDirConfig(dirname: string): Promise<RmanConfig>;
6
80
  /**
7
81
  * Resolves the effective config for `targetDir` by cascading from `rootDir`
8
82
  * down to `targetDir` (inclusive), the same way tsconfig's `extends` chain
@@ -10,4 +84,4 @@ export declare function readDirConfig(dirname: string): any;
10
84
  * package (or any intermediate directory) narrow or override the repository's
11
85
  * root configuration for itself and everything below it.
12
86
  */
13
- export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, any>): any;
87
+ export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, RmanConfig>): Promise<RmanConfig>;
package/core/config.js CHANGED
@@ -1,12 +1,60 @@
1
1
  import fs from 'fs';
2
2
  import * as yaml from 'js-yaml';
3
+ import { createRequire } from 'module';
3
4
  import path from 'path';
4
5
  import merge from 'putil-merge';
6
+ import { pathToFileURL } from 'url';
7
+ /**
8
+ * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
9
+ * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
10
+ * unchanged; this exists purely so TypeScript can infer/check against `RmanConfig`, not for any
11
+ * runtime behavior:
12
+ *
13
+ * // .rmanrc.mjs
14
+ * import { defineConfig } from 'rman';
15
+ * export default defineConfig({ packageManager: 'pnpm' });
16
+ *
17
+ * // .rmanrc.cjs
18
+ * const { defineConfig } = require('rman');
19
+ * module.exports = defineConfig({ packageManager: 'pnpm' });
20
+ */
21
+ export function defineConfig(config) {
22
+ return config;
23
+ }
24
+ /** `.rmanrc.cjs`/`.rmanrc.mjs`/`.rmanrc.js`, checked in this order - a JS module whose default
25
+ * export (or, lacking one, the module's own exports object) is the config. Both CommonJS (`.cjs`,
26
+ * or a `.js` under a `"type": "commonjs"` package.json) and native ESM (`.mjs`, or a `.js` under
27
+ * `"type": "module"`) are supported - the reason `readDirConfig`/`resolveConfig` are async at all. */
28
+ const JS_CONFIG_FILES = ['.rmanrc.cjs', '.rmanrc.mjs', '.rmanrc.js'];
29
+ const requireJsConfig = createRequire(import.meta.url);
30
+ /**
31
+ * Loads `file`'s config object. Tries `require()` first - not just an optimization: a CommonJS
32
+ * module's `module.exports` is more reliably observed this way than through dynamic `import()`'s
33
+ * CJS-interop synthesis, which some ESM loader hooks (e.g. ts-node/swc-node-style transpilers
34
+ * registered via `--import`) can end up short-circuiting into an empty object. `require()` throws
35
+ * `ERR_REQUIRE_ESM` for a genuinely-ESM file (`.mjs`, or `.js` under `"type": "module"`) - only
36
+ * then does this fall back to `import()`, the one case that actually needs it. Either path can
37
+ * hand back an ES module namespace instead of a plain object (Node's `require(esm)` support does
38
+ * this too, not just `import()`), so `.default` is preferred whenever present.
39
+ */
40
+ async function loadJsConfig(file) {
41
+ let mod;
42
+ try {
43
+ mod = requireJsConfig(file);
44
+ }
45
+ catch (e) {
46
+ if (e?.code !== 'ERR_REQUIRE_ESM')
47
+ throw e;
48
+ mod = await import(pathToFileURL(file).href);
49
+ }
50
+ return mod?.default ?? mod;
51
+ }
5
52
  /**
6
53
  * Reads the rman configuration defined at a single directory level, merging
7
- * (in increasing precedence): `package.json#rman`, `.rman.yml`, `.rmanrc`.
54
+ * (in increasing precedence): `package.json#rman`, `.rmanrc.yml`, `.rmanrc`,
55
+ * then `.rmanrc.cjs`/`.rmanrc.mjs`/`.rmanrc.js` (whichever exist, in that order).
8
56
  */
9
- export function readDirConfig(dirname) {
57
+ export async function readDirConfig(dirname) {
10
58
  const result = {};
11
59
  const pkgJsonFile = path.join(dirname, 'package.json');
12
60
  if (fs.existsSync(pkgJsonFile)) {
@@ -14,7 +62,7 @@ export function readDirConfig(dirname) {
14
62
  if (pkgJson && typeof pkgJson.rman === 'object')
15
63
  merge(result, pkgJson.rman, { deep: true });
16
64
  }
17
- const ymlFile = path.join(dirname, '.rman.yml');
65
+ const ymlFile = path.join(dirname, '.rmanrc.yml');
18
66
  if (fs.existsSync(ymlFile)) {
19
67
  const obj = yaml.load(fs.readFileSync(ymlFile, 'utf-8'));
20
68
  if (obj && typeof obj === 'object')
@@ -26,6 +74,14 @@ export function readDirConfig(dirname) {
26
74
  if (obj && typeof obj === 'object')
27
75
  merge(result, obj, { deep: true });
28
76
  }
77
+ for (const jsFileName of JS_CONFIG_FILES) {
78
+ const jsFile = path.join(dirname, jsFileName);
79
+ if (fs.existsSync(jsFile)) {
80
+ const obj = await loadJsConfig(jsFile);
81
+ if (obj && typeof obj === 'object')
82
+ merge(result, obj, { deep: true });
83
+ }
84
+ }
29
85
  return result;
30
86
  }
31
87
  /**
@@ -35,12 +91,12 @@ export function readDirConfig(dirname) {
35
91
  * package (or any intermediate directory) narrow or override the repository's
36
92
  * root configuration for itself and everything below it.
37
93
  */
38
- export function resolveConfig(rootDir, targetDir, cache = new Map()) {
94
+ export async function resolveConfig(rootDir, targetDir, cache = new Map()) {
39
95
  const result = {};
40
96
  for (const dir of dirChain(rootDir, targetDir)) {
41
97
  let local = cache.get(dir);
42
98
  if (!local) {
43
- local = readDirConfig(dir);
99
+ local = await readDirConfig(dir);
44
100
  cache.set(dir, local);
45
101
  }
46
102
  merge(result, local, { deep: true });
package/core/package.d.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import type { RmanConfig } from './config.js';
1
2
  export declare class Package {
2
3
  readonly dirname: string;
3
4
  private _json;
4
5
  dependencies: string[];
5
6
  /** Effective rman config for this package, cascaded from the repository root. */
6
- config: any;
7
+ config: RmanConfig;
7
8
  constructor(dirname: string);
8
9
  get basename(): string;
9
10
  get name(): string;
@@ -41,11 +41,15 @@ export declare class Repository extends Package {
41
41
  * package, cascading root -> intermediate directories -> package directory,
42
42
  * so a `.rmanrc` placed anywhere along that path overrides the levels above it.
43
43
  */
44
- protected _resolveConfigs(): void;
44
+ protected _resolveConfigs(): Promise<void>;
45
45
  protected _updateDependencies(): void;
46
46
  static create(root?: string, options?: {
47
47
  deep?: number;
48
- }): Repository;
48
+ }): Promise<Repository>;
49
+ /** Finishes constructing `repo` with the async work a constructor can't do itself - resolving
50
+ * `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` config (which may need a dynamic `import()`)
51
+ * before the dependency graph is built from it. */
52
+ private static _init;
49
53
  protected static _resolvePackages(dirname: string, patterns: string[]): Package[];
50
54
  }
51
55
  export declare namespace Repository {
@@ -23,8 +23,9 @@ export class Repository extends Package {
23
23
  this.rootPackage = new Package(dirname);
24
24
  if (!monorepo)
25
25
  this.packages = [this.rootPackage];
26
- this._resolveConfigs();
27
- this._updateDependencies();
26
+ // Config resolution can load a `.rmanrc.cjs`/`.mjs`/`.js` module (dynamic `import()`, always
27
+ // async) - a constructor can't `await`, so `create()` finishes this instance off via `_init()`
28
+ // once construction itself (synchronous) completes.
28
29
  }
29
30
  /**
30
31
  * The package whose own directory contains `cwd` (the deepest match, so a package nested
@@ -90,15 +91,15 @@ export class Repository extends Package {
90
91
  * package, cascading root -> intermediate directories -> package directory,
91
92
  * so a `.rmanrc` placed anywhere along that path overrides the levels above it.
92
93
  */
93
- _resolveConfigs() {
94
+ async _resolveConfigs() {
94
95
  const cache = new Map();
95
- const rootConfig = resolveConfig(this.dirname, this.dirname, cache);
96
+ const rootConfig = await resolveConfig(this.dirname, this.dirname, cache);
96
97
  this.config = rootConfig;
97
98
  this.rootPackage.config = rootConfig;
98
99
  for (const pkg of this.packages) {
99
100
  if (pkg === this.rootPackage)
100
101
  continue;
101
- pkg.config = resolveConfig(this.dirname, pkg.dirname, cache);
102
+ pkg.config = await resolveConfig(this.dirname, pkg.dirname, cache);
102
103
  }
103
104
  }
104
105
  _updateDependencies() {
@@ -149,7 +150,7 @@ export class Repository extends Package {
149
150
  deepFindDependencies(pkg, pkg.dependencies);
150
151
  }
151
152
  }
152
- static create(root, options) {
153
+ static async create(root, options) {
153
154
  const dirname = root || process.cwd();
154
155
  let deep = options?.deep ?? 10;
155
156
  let pkgDirname = dirname;
@@ -159,7 +160,7 @@ export class Repository extends Package {
159
160
  const pkgJson = JSON.parse(fs.readFileSync(f, 'utf-8'));
160
161
  if (Array.isArray(pkgJson.workspaces)) {
161
162
  const packages = this._resolvePackages(pkgDirname, pkgJson.workspaces);
162
- return new Repository(pkgDirname, true, packages, dirname);
163
+ return Repository._init(new Repository(pkgDirname, true, packages, dirname));
163
164
  }
164
165
  /** If we reach to the root of the project */
165
166
  if (fs.existsSync(path.join(pkgDirname, '.git')))
@@ -167,7 +168,15 @@ export class Repository extends Package {
167
168
  }
168
169
  pkgDirname = path.resolve(pkgDirname, '..');
169
170
  }
170
- return new Repository(dirname, false, [], dirname);
171
+ return Repository._init(new Repository(dirname, false, [], dirname));
172
+ }
173
+ /** Finishes constructing `repo` with the async work a constructor can't do itself - resolving
174
+ * `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` config (which may need a dynamic `import()`)
175
+ * before the dependency graph is built from it. */
176
+ static async _init(repo) {
177
+ await repo._resolveConfigs();
178
+ repo._updateDependencies();
179
+ return repo;
171
180
  }
172
181
  static _resolvePackages(dirname, patterns) {
173
182
  const packages = [];
package/index.d.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  * CLI-only concerns (argv parsing, `--help` text, and all console/file presentation) stay in
9
9
  * `cli.ts` and the individual `commands/*.command.ts` modules, which are not exported here.
10
10
  */
11
+ export type { RmanConfig } from './core/config.js';
12
+ export { defineConfig } from './core/config.js';
11
13
  export { Package } from './core/package.js';
12
14
  export { Repository } from './core/repository.js';
13
15
  export * from './services.js';
package/index.js CHANGED
@@ -1,13 +1,4 @@
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
- */
1
+ export { defineConfig } from './core/config.js';
11
2
  export { Package } from './core/package.js';
12
3
  export { Repository } from './core/repository.js';
13
4
  export * from './services.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "1.0.1",
4
+ "version": "1.0.4",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {
@@ -35,7 +35,8 @@
35
35
  "types": "./index.d.ts",
36
36
  "default": "./index.js"
37
37
  },
38
- "./package.json": "./package.json"
38
+ "./package.json": "./package.json",
39
+ "./rmanrc.schema.json": "./rmanrc.schema.json"
39
40
  },
40
41
  "bin": {
41
42
  "rman": "cli.js"
@@ -0,0 +1,202 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/panates/rman/main/schemas/rmanrc.schema.json",
4
+ "title": "rman configuration",
5
+ "description": "Schema for rman's .rmanrc (JSON) and .rmanrc.yml (YAML) config files, and the \"rman\" key in package.json. See docs/api.md#configuration-rmanrc-rmanrcyml for the full reference.",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "Path/URL to this schema - not read by rman itself, purely for editor tooling."
11
+ },
12
+ "packageManager": {
13
+ "type": "string",
14
+ "enum": ["npm", "yarn", "pnpm", "bun"],
15
+ "default": "npm",
16
+ "description": "Package manager used by \"ci\"/\"publish\". Root-level only - an explicit --package-manager CLI flag wins over this."
17
+ },
18
+ "logLevel": {
19
+ "$ref": "#/definitions/logLevel",
20
+ "default": "info",
21
+ "description": "Default verbosity of the classic per-step log for run/build/test/ci/clean. Root-level only. An explicit --log-level CLI flag wins over this."
22
+ },
23
+ "allowBranch": {
24
+ "$ref": "#/definitions/stringOrStringArray",
25
+ "description": "Refuse to run a state-changing command unless the current git branch matches one of these globs. Root-level only. An explicit --allow-branch CLI flag replaces this entirely (never merges)."
26
+ },
27
+ "ignoreBranch": {
28
+ "$ref": "#/definitions/stringOrStringArray",
29
+ "description": "Refuse to run a state-changing command if the current git branch matches one of these globs. Root-level only. An explicit --ignore-branch CLI flag replaces this entirely (never merges)."
30
+ },
31
+ "group": {
32
+ "description": "Release-versioning group for \"version\"/\"publish\": true (default) puts the package in the implicit repo-wide group; a string joins exactly the other packages sharing that string; false makes it a solo, fully independent group. Per-package cascaded.",
33
+ "default": true,
34
+ "oneOf": [{ "type": "boolean" }, { "type": "string", "minLength": 1 }]
35
+ },
36
+ "version": {
37
+ "type": "object",
38
+ "description": "Options for the \"version\" command/VersionService.",
39
+ "additionalProperties": false,
40
+ "properties": {
41
+ "commitMessage": {
42
+ "type": "string",
43
+ "default": "chore(release): v{version}",
44
+ "description": "Commit message for every group a \"version\" run commits. \"{version}\" is substituted when every bumped package in that commit shares one version. Root-level only."
45
+ },
46
+ "script": {
47
+ "$ref": "#/definitions/stringOrStringArray",
48
+ "description": "Command(s) to run as this package's own \"version\" npm-lifecycle step, when its package.json does not define one itself."
49
+ },
50
+ "preScript": {
51
+ "$ref": "#/definitions/stringOrStringArray",
52
+ "description": "Command(s) to run as this package's own \"preversion\" step, when its package.json does not define one itself."
53
+ },
54
+ "postScript": {
55
+ "$ref": "#/definitions/stringOrStringArray",
56
+ "description": "Command(s) to run as this package's own \"postversion\" step, when its package.json does not define one itself."
57
+ }
58
+ }
59
+ },
60
+ "changelog": {
61
+ "type": "object",
62
+ "description": "Options for the \"changelog\" command/ChangelogService, and for \"version --changelog\".",
63
+ "additionalProperties": false,
64
+ "properties": {
65
+ "ignoreTypes": {
66
+ "type": "array",
67
+ "items": { "type": "string" },
68
+ "default": [],
69
+ "description": "Conventional Commit types (e.g. \"chore\", \"ci\") dropped entirely from changelog output, instead of being folded into \"Other Changes\". Per-package cascaded."
70
+ },
71
+ "template": {
72
+ "type": "string",
73
+ "description": "Path (relative to the repository root) to a changelog template file - not the template text itself. Supports {{package}}/{{version}}/{{date}}/{{commits}} and {{features}}/{{fixes}}/{{other}}. Per-package cascaded."
74
+ },
75
+ "filePath": {
76
+ "type": "string",
77
+ "default": "CHANGELOG.md",
78
+ "description": "Where \"--write\" prepends this package's entry, relative to that package's own directory. Per-package cascaded."
79
+ },
80
+ "tagPattern": {
81
+ "type": "string",
82
+ "default": "v*",
83
+ "description": "Glob for this package's release tags. Include \"{name}\" for independent per-package tags (e.g. \"{name}@*\"); omit it for one shared repo-wide tag scheme. Per-package cascaded."
84
+ }
85
+ }
86
+ },
87
+ "clean": {
88
+ "type": "object",
89
+ "description": "Options for the \"clean\" command/CleanService.",
90
+ "additionalProperties": false,
91
+ "properties": {
92
+ "include": {
93
+ "$ref": "#/definitions/stringOrStringArray",
94
+ "description": "Extra glob(s) to remove, resolved relative to this package's own directory. Per-package cascaded (a package's own value replaces the root's, it does not merge)."
95
+ },
96
+ "exclude": {
97
+ "$ref": "#/definitions/stringOrStringArray",
98
+ "description": "Glob(s) protected from \"include\" (and from the built-in TypeScript-artifact cleanup), relative to this package's own directory. Per-package cascaded."
99
+ },
100
+ "skip": {
101
+ "type": "boolean",
102
+ "default": false,
103
+ "description": "Opts this package out of \"clean\" entirely. Per-package cascaded."
104
+ }
105
+ }
106
+ },
107
+ "run": {
108
+ "type": "object",
109
+ "description": "Per-script options for \"run\"/\"build\"/\"test\"/RunService, keyed by npm script name (e.g. \"build\", \"lint\", \"test\").",
110
+ "additionalProperties": { "$ref": "#/definitions/runScriptConfig" }
111
+ },
112
+ "packages": {
113
+ "type": "object",
114
+ "description": "Per-package overrides, keyed by the in-repo package's own name.",
115
+ "additionalProperties": {
116
+ "type": "object",
117
+ "additionalProperties": false,
118
+ "properties": {
119
+ "dependencies": {
120
+ "description": "Extra in-repo \"dependencies\" not present in this package's real package.json, purely for rman's own dependency graph (topo-sort, --deps/--dependents, run's task scheduling). An array defaults each entry's range to \"*\"; an object gives an explicit name -> range map.",
121
+ "oneOf": [
122
+ { "type": "array", "items": { "type": "string" } },
123
+ { "type": "object", "additionalProperties": { "type": "string" } }
124
+ ]
125
+ }
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "definitions": {
131
+ "logLevel": {
132
+ "type": "string",
133
+ "enum": ["silent", "error", "info", "verbose"]
134
+ },
135
+ "stringOrStringArray": {
136
+ "oneOf": [
137
+ { "type": "string", "minLength": 1 },
138
+ { "type": "array", "items": { "type": "string", "minLength": 1 } }
139
+ ]
140
+ },
141
+ "runScriptConfig": {
142
+ "type": "object",
143
+ "additionalProperties": false,
144
+ "properties": {
145
+ "concurrency": {
146
+ "type": "integer",
147
+ "minimum": 1,
148
+ "description": "Max packages built at once for this script. Default: CPU count."
149
+ },
150
+ "topo": {
151
+ "type": "boolean",
152
+ "default": true,
153
+ "description": "Respect the package dependency graph: a package waits for its dependencies and is skipped if one fails. Set false for independent scripts (lint/test) - alphabetical order, no failure skipping."
154
+ },
155
+ "bail": {
156
+ "type": "boolean",
157
+ "default": true,
158
+ "description": "Stop the whole batch on this package's own failure. Unusual precedence: this package-level value outranks even an explicit CLI --bail/--no-bail flag."
159
+ },
160
+ "progress": {
161
+ "type": "boolean",
162
+ "default": true,
163
+ "description": "Show the live progress panel for this script (auto-disabled off a TTY)."
164
+ },
165
+ "logLevel": {
166
+ "$ref": "#/definitions/logLevel",
167
+ "description": "Verbosity of the classic per-step log for this package/script, when the live panel is off."
168
+ },
169
+ "changedSince": {
170
+ "type": "string",
171
+ "description": "Root-level fallback commit/hash for \"--changed-since\", used only when the CLI flag is not given."
172
+ },
173
+ "skip": {
174
+ "type": "boolean",
175
+ "default": false,
176
+ "description": "Excludes this package (or the repository root's own pre/post hooks) from running this script entirely."
177
+ },
178
+ "if": {
179
+ "type": "string",
180
+ "description": "Conditional-execution expression: atoms \"changed\"/\"dirty\"/\"committed\" (optionally \"= <hash>\" or \"= {ENV_VAR}\"), combined with and/or/not/(...). E.g. \"changed\", \"(changed or dirty) and not committed\"."
181
+ },
182
+ "script": {
183
+ "$ref": "#/definitions/stringOrStringArray",
184
+ "description": "Command(s) to run as this package's own script, when its package.json does not define one for this script name."
185
+ },
186
+ "preScript": {
187
+ "$ref": "#/definitions/stringOrStringArray",
188
+ "description": "Command(s) to run as this package's own \"pre<script>\" hook, when its package.json does not define one."
189
+ },
190
+ "postScript": {
191
+ "$ref": "#/definitions/stringOrStringArray",
192
+ "description": "Command(s) to run as this package's own \"post<script>\" hook, when its package.json does not define one."
193
+ },
194
+ "override": {
195
+ "type": "boolean",
196
+ "default": false,
197
+ "description": "When true, \"script\"/\"preScript\"/\"postScript\" above replace the package's own package.json definition even when it already has one."
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }