rman 1.0.12 → 1.2.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.
Files changed (102) hide show
  1. package/README.md +90 -70
  2. package/cli.js +226 -14
  3. package/commands/build.command.js +1 -0
  4. package/commands/changed.command.js +2 -2
  5. package/commands/changelog.command.js +13 -15
  6. package/commands/config.command.js +61 -0
  7. package/commands/diff.command.js +9 -4
  8. package/commands/exec.command.js +2 -8
  9. package/commands/github-release.command.js +1 -0
  10. package/commands/info.command.d.ts +9 -0
  11. package/commands/info.command.js +12 -2
  12. package/commands/run.command.js +5 -8
  13. package/commands/test.command.js +1 -0
  14. package/commands/version.command.js +53 -14
  15. package/constants.js +1 -1
  16. package/core/config.d.ts +265 -17
  17. package/core/config.js +651 -76
  18. package/core/custom-command.d.ts +133 -0
  19. package/core/custom-command.js +99 -0
  20. package/core/extends-config.d.ts +27 -0
  21. package/core/extends-config.js +89 -0
  22. package/core/manifest.d.ts +222 -0
  23. package/core/manifest.js +150 -0
  24. package/core/merge-config.d.ts +70 -0
  25. package/core/merge-config.js +193 -0
  26. package/core/package.d.ts +73 -7
  27. package/core/package.js +86 -24
  28. package/core/plugin.d.ts +112 -0
  29. package/core/plugin.js +189 -0
  30. package/core/repository.d.ts +91 -1
  31. package/core/repository.js +277 -132
  32. package/core/resolve-target.d.ts +12 -0
  33. package/core/resolve-target.js +33 -0
  34. package/core/run-step.d.ts +75 -0
  35. package/core/run-step.js +1 -0
  36. package/core/version-scheme.d.ts +134 -0
  37. package/core/version-scheme.js +148 -0
  38. package/core/workspace.d.ts +68 -0
  39. package/core/workspace.js +83 -0
  40. package/index.d.ts +55 -8
  41. package/index.js +42 -7
  42. package/interfaces/rman-config.interface.d.ts +222 -46
  43. package/package.json +16 -7
  44. package/services/change-hash.service.d.ts +88 -0
  45. package/services/change-hash.service.js +112 -0
  46. package/services/changelog.service.d.ts +8 -13
  47. package/services/changelog.service.js +12 -11
  48. package/services/conventional-commits.service.d.ts +73 -0
  49. package/services/conventional-commits.service.js +116 -0
  50. package/services/docker-publish.service.js +1 -1
  51. package/services/exec.service.js +1 -1
  52. package/services/github-release.service.d.ts +2 -2
  53. package/services/github-release.service.js +10 -5
  54. package/services/list.service.js +5 -2
  55. package/services/run.service.d.ts +112 -6
  56. package/services/run.service.js +265 -89
  57. package/services/system-info.d.ts +22 -7
  58. package/services/system-info.js +8 -23
  59. package/services/version-plan.service.d.ts +244 -0
  60. package/services/version-plan.service.js +414 -0
  61. package/services/version.service.d.ts +102 -82
  62. package/services/version.service.js +226 -434
  63. package/services.d.ts +5 -3
  64. package/services.js +5 -3
  65. package/utils/bin-path.d.ts +59 -0
  66. package/utils/bin-path.js +82 -0
  67. package/utils/child-tracker.d.ts +16 -0
  68. package/utils/child-tracker.js +30 -0
  69. package/utils/exec.d.ts +13 -2
  70. package/utils/exec.js +17 -17
  71. package/utils/git.d.ts +9 -3
  72. package/utils/git.js +10 -2
  73. package/utils/package-filter.d.ts +33 -2
  74. package/utils/package-filter.js +47 -7
  75. package/utils/printable-config.d.ts +15 -0
  76. package/utils/printable-config.js +42 -0
  77. package/utils/release-version.js +3 -3
  78. package/utils/run-bin.d.ts +46 -0
  79. package/utils/run-bin.js +63 -0
  80. package/utils/version-stamp.d.ts +14 -6
  81. package/utils/version-stamp.js +25 -13
  82. package/commands/ci.command.js +0 -30
  83. package/commands/clean.command.d.ts +0 -3
  84. package/commands/clean.command.js +0 -36
  85. package/commands/publish.command.d.ts +0 -3
  86. package/commands/publish.command.js +0 -225
  87. package/rmanrc.schema.json +0 -392
  88. package/services/ci.service.d.ts +0 -40
  89. package/services/ci.service.js +0 -204
  90. package/services/clean.service.d.ts +0 -42
  91. package/services/clean.service.js +0 -226
  92. package/services/publish.service.d.ts +0 -79
  93. package/services/publish.service.js +0 -273
  94. package/utils/change-hash.d.ts +0 -68
  95. package/utils/change-hash.js +0 -98
  96. package/utils/conventional-commits.d.ts +0 -52
  97. package/utils/conventional-commits.js +0 -90
  98. package/utils/npm-run-path.d.ts +0 -67
  99. package/utils/npm-run-path.js +0 -63
  100. package/utils/workspace-range.d.ts +0 -17
  101. package/utils/workspace-range.js +0 -28
  102. /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
@@ -1,392 +0,0 @@
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. Any string value may embed ${{ ... }} expressions - real JavaScript, evaluated per package against pkg, repository, env and semver - so one declaration at the root can still say something package-specific (\"../../coverage/${{ pkg.basename }}\", \"panates/${{ pkg.basename }}:${{ semver.major(pkg.version) }}\"). A string that is nothing but one expression keeps that value's own type. A bare {{ ... }} is left alone, so a command carrying someone else's template survives untouched.",
6
- "type": "object",
7
- "additionalProperties": false,
8
- "patternProperties": {
9
- "^\\[.+\\]$": {
10
- "$ref": "#",
11
- "description": "Config for the packages this selector names, rather than for the package of the directory declaring it - \"[*]\" for every package in the repository, \"[*-dialect]\" for a glob over package names, \"[pkg-a]\" for one. In YAML the quotes are REQUIRED: a bare [*] parses as a flow sequence and * as an alias indicator. Precedence, lowest first: \"[*]\", then other selectors in declaration order, then the package's own unmarked config."
12
- }
13
- },
14
- "properties": {
15
- "$schema": {
16
- "type": "string",
17
- "description": "Path/URL to this schema - not read by rman itself, purely for editor tooling."
18
- },
19
- "packageManager": {
20
- "type": "string",
21
- "enum": [
22
- "npm",
23
- "yarn",
24
- "pnpm",
25
- "bun"
26
- ],
27
- "default": "npm",
28
- "description": "Package manager used by \"ci\"/\"publish\". Root-level only - an explicit --package-manager CLI flag wins over this."
29
- },
30
- "logLevel": {
31
- "$ref": "#/definitions/logLevel",
32
- "default": "info",
33
- "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."
34
- },
35
- "allowBranch": {
36
- "$ref": "#/definitions/stringOrStringArray",
37
- "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)."
38
- },
39
- "ignoreBranch": {
40
- "$ref": "#/definitions/stringOrStringArray",
41
- "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)."
42
- },
43
- "group": {
44
- "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.",
45
- "default": true,
46
- "oneOf": [
47
- {
48
- "type": "boolean"
49
- },
50
- {
51
- "type": "string",
52
- "minLength": 1
53
- }
54
- ]
55
- },
56
- "version": {
57
- "type": "object",
58
- "description": "Options for the \"version\" command/VersionService.",
59
- "additionalProperties": false,
60
- "properties": {
61
- "commitMessage": {
62
- "type": "string",
63
- "default": "chore(release): v{version}",
64
- "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."
65
- },
66
- "changelog": {
67
- "type": "boolean",
68
- "default": false,
69
- "description": "Default for \"version --changelog\" when the CLI flag isn't given - an explicit --changelog/--no-changelog still wins. Root-level only."
70
- },
71
- "releaseTagPattern": {
72
- "type": "string",
73
- "default": "release-*",
74
- "description": "Root-level. Tag naming the repository's own release, as opposed to the per-package/group tags \"changelog.tagPattern\" names - only created when the root is on a calendar version (a repo with more than one version line). Must NOT match any package's \"changelog.tagPattern\", or that package's changelog boundary resolves to the repository release instead of its own."
75
- },
76
- "stampDockerfile": {
77
- "type": "boolean",
78
- "default": true,
79
- "description": "Per-package cascaded. Keep this package's Dockerfile \"org.opencontainers.image.version\" label in step with the version being written, in the same commit as the bump. The label's value is by specification the version of the packaged software, so there is only one correct value for it. Only ever rewrites a label the Dockerfile already declares (never inserts one), and reads the same path \"publish --target docker\" builds from (\"publish.docker.dockerfile\")."
80
- },
81
- "stamp": {
82
- "$ref": "#/definitions/stringOrStringArray",
83
- "description": "Source files whose \"version\" constant is rewritten to the version being written, in the same commit as the bump - paths relative to the package's own directory (e.g. [\"src/constants.ts\"]). Per-package cascaded; a listed file a package doesn't have is a silent no-op. Stamps the source rather than the build output, so anything running from source reports the real version and git records it."
84
- },
85
- "before": {
86
- "$ref": "#/definitions/stringOrStringArray",
87
- "description": "Command(s) to run as this package's own \"preversion\" step, when its package.json does not define one itself."
88
- },
89
- "exec": {
90
- "$ref": "#/definitions/stringOrStringArray",
91
- "description": "Command(s) to run as this package's own \"version\" npm-lifecycle step, when its package.json does not define one itself."
92
- },
93
- "after": {
94
- "$ref": "#/definitions/stringOrStringArray",
95
- "description": "Command(s) to run as this package's own \"postversion\" step, when its package.json does not define one itself."
96
- }
97
- }
98
- },
99
- "changelog": {
100
- "type": "object",
101
- "description": "Options for the \"changelog\" command/ChangelogService, and for \"version --changelog\".",
102
- "additionalProperties": false,
103
- "properties": {
104
- "ignoreTypes": {
105
- "type": "array",
106
- "items": {
107
- "type": "string"
108
- },
109
- "default": [],
110
- "description": "Conventional Commit types (e.g. \"chore\", \"ci\") dropped entirely from changelog output, instead of being folded into \"Other Changes\". Per-package cascaded."
111
- },
112
- "template": {
113
- "type": "string",
114
- "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."
115
- },
116
- "filePath": {
117
- "type": "string",
118
- "default": "CHANGELOG.md",
119
- "description": "Where \"--write\" prepends this package's entry, relative to that package's own directory. Per-package cascaded."
120
- },
121
- "tagPattern": {
122
- "type": "string",
123
- "default": "v*",
124
- "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."
125
- }
126
- }
127
- },
128
- "clean": {
129
- "type": "object",
130
- "description": "Options for the \"clean\" command/CleanService.",
131
- "additionalProperties": false,
132
- "properties": {
133
- "include": {
134
- "$ref": "#/definitions/stringOrStringArray",
135
- "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)."
136
- },
137
- "exclude": {
138
- "$ref": "#/definitions/stringOrStringArray",
139
- "description": "Glob(s) protected from \"include\" (and from the built-in TypeScript-artifact cleanup), relative to this package's own directory. Per-package cascaded."
140
- },
141
- "skip": {
142
- "type": "boolean",
143
- "default": false,
144
- "description": "Opts this package out of \"clean\" entirely. Per-package cascaded."
145
- }
146
- }
147
- },
148
- "publish": {
149
- "type": "object",
150
- "description": "Options for the \"publish\" command/PublishService and DockerPublishService. Per-package cascaded.",
151
- "additionalProperties": false,
152
- "properties": {
153
- "target": {
154
- "description": "Which registry \"publish\" ships this package to. Default [\"npm\"]. A package that only ships Docker images (typically also \"private\": true) sets [\"docker\"]; both works too. Each target answers \"is this version already out there?\" against its own registry: npm via \"npm view\", docker via \"docker manifest inspect\". This is strictly about package distribution - the repository's GitHub Release is not a target here, see \"githubRelease\" and the \"github-release\" command.",
155
- "default": [
156
- "npm"
157
- ],
158
- "oneOf": [
159
- {
160
- "$ref": "#/definitions/publishTarget"
161
- },
162
- {
163
- "type": "array",
164
- "items": {
165
- "$ref": "#/definitions/publishTarget"
166
- }
167
- }
168
- ]
169
- },
170
- "directory": {
171
- "type": "string",
172
- "description": "Where this package's publishable output lives, relative to its own directory (e.g. \"build\"). Per-package cascaded, so a root \"[*]\" block can say it once instead of repeating publishConfig.directory in every package.json - which still wins when a package declares it. Publishing from such a directory means the manifest there is GENERATED by publish, from the package's own, with devDependencies, non-install scripts, \"private\" and publishConfig.directory removed and \"workspace:\" ranges resolved. There is nothing to configure about it."
173
- },
174
- "docker": {
175
- "$ref": "#/definitions/dockerPublishConfig",
176
- "description": "Required once \"docker\" is one of this package's \"publish.target\"s."
177
- },
178
- "skip": {
179
- "type": "boolean",
180
- "default": false,
181
- "description": "Excludes this package from \"publish\" entirely (every target), regardless of \"target\"/\"private\" - a single, explicit \"never published\" statement. \"changelog\" also skips it by default (see its own --include-skipped). Independent of \"version\", which never consults this."
182
- }
183
- }
184
- },
185
- "githubRelease": {
186
- "$ref": "#/definitions/githubReleaseConfig",
187
- "description": "Details of the repository's GitHub Release (\"github-release\"/GithubReleaseService). Entirely optional - every required fact already has a sensible default source. Nothing here decides whether a release is cut: a release records that the repository shipped, so it is always cut. \"assets\" is per-package cascaded; the rest is root-level only."
188
- },
189
- "run": {
190
- "type": "object",
191
- "description": "Per-script options for \"run\"/\"build\"/\"test\"/RunService, keyed by npm script name (e.g. \"build\", \"lint\", \"test\"). A bare string (or array of them) is shorthand for { \"exec\": ... } - \"test\": \"mocha\" and \"test\": { \"exec\": \"mocha\" } mean exactly the same thing.",
192
- "additionalProperties": {
193
- "oneOf": [
194
- {
195
- "$ref": "#/definitions/stringOrStringArray"
196
- },
197
- {
198
- "$ref": "#/definitions/runScriptConfig"
199
- }
200
- ]
201
- }
202
- },
203
- "dependencies": {
204
- "description": "In-repo packages this one depends on beyond what its real package.json declares, 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. Declare it from the root through a selector (\"[pkg-a]\": { \"dependencies\": [...] }) or in the package's own .rmanrc.",
205
- "oneOf": [
206
- {
207
- "type": "array",
208
- "items": {
209
- "type": "string"
210
- }
211
- },
212
- {
213
- "type": "object",
214
- "additionalProperties": {
215
- "type": "string"
216
- }
217
- }
218
- ]
219
- }
220
- },
221
- "definitions": {
222
- "logLevel": {
223
- "type": "string",
224
- "enum": [
225
- "silent",
226
- "error",
227
- "info",
228
- "verbose"
229
- ]
230
- },
231
- "stringOrStringArray": {
232
- "oneOf": [
233
- {
234
- "type": "string",
235
- "minLength": 1
236
- },
237
- {
238
- "type": "array",
239
- "items": {
240
- "type": "string",
241
- "minLength": 1
242
- }
243
- }
244
- ]
245
- },
246
- "publishTarget": {
247
- "type": "string",
248
- "enum": [
249
- "npm",
250
- "docker"
251
- ]
252
- },
253
- "dockerPublishConfig": {
254
- "type": "object",
255
- "additionalProperties": false,
256
- "required": [
257
- "image"
258
- ],
259
- "properties": {
260
- "image": {
261
- "type": "string",
262
- "minLength": 1,
263
- "description": "DockerHub image name/repository - bare (e.g. \"my-app\") to be prefixed with --docker-namespace/DOCKERHUB_NAMESPACE, or already-namespaced (contains a \"/\") to use verbatim."
264
- },
265
- "dockerfile": {
266
- "type": "string",
267
- "default": "Dockerfile",
268
- "description": "Relative to the package's own directory."
269
- },
270
- "platforms": {
271
- "type": "array",
272
- "items": {
273
- "type": "string"
274
- },
275
- "default": [
276
- "linux/amd64"
277
- ],
278
- "description": "\"docker buildx build --platform\" targets."
279
- },
280
- "cwd": {
281
- "type": "string",
282
- "description": "Build cwd override, relative to the repository root - only needed when the Dockerfile's own COPY/ADD paths expect something other than the package's own directory."
283
- },
284
- "buildContexts": {
285
- "type": "object",
286
- "additionalProperties": {
287
- "type": "string"
288
- },
289
- "description": "Named \"docker buildx build --build-context <name>=<path>\" entries, keyed by name - each path relative to the package's own directory (or absolute)."
290
- },
291
- "buildArgs": {
292
- "type": "object",
293
- "additionalProperties": {
294
- "type": "string"
295
- },
296
- "description": "\"docker buildx build --build-arg <name>=<value>\" entries - a value of exactly \"$NAME\" expands to the NAME environment variable at build time."
297
- },
298
- "readme": {
299
- "type": "string",
300
- "default": "DOCKER_README.md",
301
- "description": "A file (relative to the package's own directory) whose contents become the DockerHub repo's full description, if present."
302
- }
303
- }
304
- },
305
- "githubReleaseConfig": {
306
- "type": "object",
307
- "additionalProperties": false,
308
- "properties": {
309
- "assets": {
310
- "type": "array",
311
- "items": {
312
- "type": "string"
313
- },
314
- "description": "Files to attach to the release, as glob patterns relative to the package's own directory (e.g. [\"dist/*.tar.gz\"]). Read from every package, since one release covers the whole source tree. A release with no assets is still valid."
315
- },
316
- "repository": {
317
- "type": "string",
318
- "description": "\"owner/repo\". Default: parsed from the \"origin\" remote's URL."
319
- },
320
- "draft": {
321
- "type": "boolean",
322
- "default": false,
323
- "description": "Create the release as an unpublished draft."
324
- },
325
- "prerelease": {
326
- "type": "boolean",
327
- "description": "Default: whether the version being released is itself a semver prerelease."
328
- }
329
- }
330
- },
331
- "runScriptConfig": {
332
- "type": "object",
333
- "additionalProperties": false,
334
- "properties": {
335
- "concurrency": {
336
- "type": "integer",
337
- "minimum": 1,
338
- "description": "Max packages built at once for this script. Default: CPU count."
339
- },
340
- "topo": {
341
- "type": "boolean",
342
- "default": true,
343
- "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."
344
- },
345
- "bail": {
346
- "type": "boolean",
347
- "default": true,
348
- "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."
349
- },
350
- "progress": {
351
- "type": "boolean",
352
- "default": true,
353
- "description": "Show the live progress panel for this script (auto-disabled off a TTY)."
354
- },
355
- "logLevel": {
356
- "$ref": "#/definitions/logLevel",
357
- "description": "Verbosity of the classic per-step log for this package/script, when the live panel is off."
358
- },
359
- "changedSince": {
360
- "type": "string",
361
- "description": "Root-level fallback commit/hash for \"--changed-since\", used only when the CLI flag is not given."
362
- },
363
- "skip": {
364
- "type": "boolean",
365
- "default": false,
366
- "description": "Excludes this package (or the repository root's own pre/post hooks) from running this script entirely."
367
- },
368
- "if": {
369
- "type": "string",
370
- "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\"."
371
- },
372
- "before": {
373
- "$ref": "#/definitions/stringOrStringArray",
374
- "description": "Command(s) to run as this package's own \"pre<script>\" hook, when its package.json does not define one."
375
- },
376
- "exec": {
377
- "$ref": "#/definitions/stringOrStringArray",
378
- "description": "Command(s) to run as this package's own script, when its package.json does not define one for this script name."
379
- },
380
- "after": {
381
- "$ref": "#/definitions/stringOrStringArray",
382
- "description": "Command(s) to run as this package's own \"post<script>\" hook, when its package.json does not define one."
383
- },
384
- "override": {
385
- "type": "boolean",
386
- "default": false,
387
- "description": "When true, \"before\"/\"exec\"/\"after\" above replace the package's own package.json definition even when it already has one."
388
- }
389
- }
390
- }
391
- }
392
- }
@@ -1,40 +0,0 @@
1
- import type { Repository } from '../core/repository.js';
2
- import { type LogLevel } from '../utils/logger.js';
3
- import { type PackageFilterOptions } from '../utils/package-filter.js';
4
- export declare namespace CiService {
5
- type PackageManager = (typeof PACKAGE_MANAGERS)[number];
6
- interface Options extends PackageFilterOptions {
7
- packageManager?: PackageManager;
8
- /** Show the live progress panel while running. Default true, same as `run`/`build`; auto-disabled
9
- * when stdout isn't a TTY. Doesn't affect what's printed once done - see `run`. */
10
- progress?: boolean;
11
- /** Verbosity of the classic per-step log (only applies when the live panel is off). Falls back to
12
- * the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
13
- logLevel?: LogLevel;
14
- }
15
- /** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
16
- * explicit CLI value wins over it. Defaults to 'npm'. */
17
- function resolvePackageManager(repository: Repository, cliValue?: PackageManager): PackageManager;
18
- /** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
19
- * that actually existed (and were removed), so the caller can log only those. */
20
- function wipe(dirname: string): Promise<string[]>;
21
- /**
22
- * `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
23
- * included), deletes `node_modules` and any lockfile - or, if the package defines its own
24
- * `"ci"` script, runs that instead. Once every package is clean, installs once at the root
25
- * with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
26
- *
27
- * Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
28
- * runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
29
- *
30
- * Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
31
- * have independently meaningful outcomes the way a build or test run does - wiping a package is
32
- * trivial and the one step that can genuinely fail, the install, is a single operation for the
33
- * whole repository. Counting "N succeeded" across packages would just be noise, so only actual
34
- * failures get called out (by name, with whatever output they produced), followed by one plain
35
- * completed/failed line.
36
- */
37
- function reinstall(repository: Repository, options?: Options): Promise<void>;
38
- }
39
- declare const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "bun"];
40
- export {};
@@ -1,204 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import colors from 'ansi-colors';
4
- import { exec } from '../utils/exec.js';
5
- import { Logger, resolveRootLogLevel } from '../utils/logger.js';
6
- import { filterPackages } from '../utils/package-filter.js';
7
- import { formatDuration, ProgressPanel } from '../utils/progress-panel.js';
8
- export var CiService;
9
- (function (CiService) {
10
- /** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
11
- * explicit CLI value wins over it. Defaults to 'npm'. */
12
- function resolvePackageManager(repository, cliValue) {
13
- if (cliValue)
14
- return cliValue;
15
- const configured = repository.config?.packageManager;
16
- if (configured === undefined)
17
- return 'npm';
18
- if (PACKAGE_MANAGERS.includes(configured))
19
- return configured;
20
- throw new Error(`Invalid "packageManager" in .rmanrc: "${configured}" (expected one of: ${PACKAGE_MANAGERS.join(', ')})`);
21
- }
22
- CiService.resolvePackageManager = resolvePackageManager;
23
- /** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
24
- * that actually existed (and were removed), so the caller can log only those. */
25
- async function wipe(dirname) {
26
- const removed = [];
27
- for (const name of ['node_modules', ...LOCK_FILES]) {
28
- const target = path.join(dirname, name);
29
- const existed = await fs
30
- .access(target)
31
- .then(() => true)
32
- .catch(() => false);
33
- if (!existed)
34
- continue;
35
- await fs.rm(target, { recursive: true, force: true });
36
- removed.push(name);
37
- }
38
- return removed;
39
- }
40
- CiService.wipe = wipe;
41
- /**
42
- * `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
43
- * included), deletes `node_modules` and any lockfile - or, if the package defines its own
44
- * `"ci"` script, runs that instead. Once every package is clean, installs once at the root
45
- * with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
46
- *
47
- * Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
48
- * runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
49
- *
50
- * Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
51
- * have independently meaningful outcomes the way a build or test run does - wiping a package is
52
- * trivial and the one step that can genuinely fail, the install, is a single operation for the
53
- * whole repository. Counting "N succeeded" across packages would just be noise, so only actual
54
- * failures get called out (by name, with whatever output they produced), followed by one plain
55
- * completed/failed line.
56
- */
57
- async function reinstall(repository, options = {}) {
58
- const packageManager = resolvePackageManager(repository, options.packageManager);
59
- const logger = new Logger(options.logLevel ?? resolveRootLogLevel(repository));
60
- // root is handled separately below - for a non-monorepo, getPackages() would otherwise
61
- // include it a second time (it doubles as "the" package).
62
- const packages = filterPackages(repository.getPackages().filter(p => p !== repository.rootPackage), options);
63
- const progress = options.progress ?? true;
64
- const panel = new ProgressPanel('CI', !!process.stdout.isTTY && progress);
65
- const runStartedAt = Date.now();
66
- panel.start();
67
- const items = [];
68
- let failed = false;
69
- try {
70
- const results = await Promise.allSettled(packages.map(pkg => {
71
- const item = panel.addItem(pkg.name);
72
- items.push(item);
73
- return ciForPackage(pkg, item, panel.enabled, logger);
74
- }));
75
- if (results.some(r => r.status === 'rejected'))
76
- failed = true;
77
- const rootItem = panel.addItem('root');
78
- items.push(rootItem);
79
- try {
80
- await ciForRoot(repository, rootItem, panel.enabled, packageManager, logger);
81
- }
82
- catch {
83
- failed = true;
84
- }
85
- }
86
- finally {
87
- panel.stop();
88
- }
89
- for (const item of items) {
90
- if (item.status !== 'failed')
91
- continue;
92
- logger.error(colors.red.bold('X'), item.name);
93
- if (item.log.length)
94
- logger.error(colors.red(item.log.join('\n')));
95
- }
96
- const totalElapsed = formatDuration(Date.now() - runStartedAt);
97
- const summary = [failed ? colors.red('ci failed') : colors.green('ci completed'), colors.gray(`(${totalElapsed})`)];
98
- if (failed)
99
- logger.error(...summary);
100
- else
101
- logger.info(...summary);
102
- if (failed) {
103
- const err = new Error('"ci" failed');
104
- err.logged = true;
105
- throw err;
106
- }
107
- }
108
- CiService.reinstall = reinstall;
109
- })(CiService || (CiService = {}));
110
- const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
111
- const LOCK_FILES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lock', 'bun.lockb'];
112
- /** Runs a shell `command` in `cwd`, driving `item` for the live panel when it's on, or falling
113
- * back to the plain "classic" log line `logLine` prints first - the same split `run`'s classic
114
- * log uses. `logLine` is expected to go through a `Logger` (so it respects `--log-level`), not a
115
- * raw `console.log`. */
116
- async function runStep(item, panelEnabled, cwd, command, logLine) {
117
- if (panelEnabled) {
118
- await exec(command, {
119
- cwd,
120
- stdio: 'pipe',
121
- onLine: line => {
122
- item.log.push(line);
123
- item.lastLine = line;
124
- },
125
- });
126
- }
127
- else {
128
- logLine();
129
- await exec(command, { cwd, stdio: 'inherit' });
130
- }
131
- }
132
- /** Logs (or reflects onto the live panel) the outcome of a `wipe()` call - including the "nothing
133
- * to remove" case, which otherwise prints nothing at all in the classic log and can look like the
134
- * package was never processed (most workspace layouts hoist deps to the root's `node_modules`,
135
- * so an individual package legitimately has nothing of its own to wipe most of the time). */
136
- function logWipeResult(item, panelEnabled, logger, label, removed) {
137
- if (panelEnabled) {
138
- item.lastLine = removed.length ? `removed ${removed.join(', ')}` : 'already clean';
139
- return;
140
- }
141
- if (!removed.length) {
142
- logger.info(colors.gray('clean'), colors.cyan(label));
143
- return;
144
- }
145
- for (const name of removed)
146
- logger.info(colors.yellow('rmdir'), colors.cyan(label), name);
147
- }
148
- /** A package can opt out of the default wipe by defining its own `"ci"` npm script - if present,
149
- * that runs instead of the wipe, same as any other rman script. */
150
- async function ciForPackage(pkg, item, panelEnabled, logger) {
151
- item.status = 'running';
152
- item.startedAt = Date.now();
153
- try {
154
- const script = pkg.json.scripts?.ci;
155
- if (typeof script === 'string' && script) {
156
- item.currentStep = 'ci';
157
- await runStep(item, panelEnabled, pkg.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan(item.name), script));
158
- }
159
- else {
160
- item.currentStep = 'wipe';
161
- const removed = await CiService.wipe(pkg.dirname);
162
- logWipeResult(item, panelEnabled, logger, item.name, removed);
163
- }
164
- item.status = 'success';
165
- }
166
- catch (e) {
167
- item.status = 'failed';
168
- throw e;
169
- }
170
- finally {
171
- item.finishedAt = Date.now();
172
- }
173
- }
174
- /** The root gets one extra step over a regular package once it isn't opting out with its own
175
- * `"ci"` script: after its own wipe, it installs once for the whole repository. */
176
- async function ciForRoot(repository, item, panelEnabled, packageManager, logger) {
177
- item.status = 'running';
178
- item.startedAt = Date.now();
179
- try {
180
- const script = repository.rootPackage.json.scripts?.ci;
181
- if (typeof script === 'string' && script) {
182
- item.currentStep = 'ci';
183
- await runStep(item, panelEnabled, repository.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan('root'), script));
184
- }
185
- else {
186
- item.stepsTotal = 2;
187
- item.stepIndex = 0;
188
- item.currentStep = 'wipe';
189
- const removed = await CiService.wipe(repository.dirname);
190
- logWipeResult(item, panelEnabled, logger, 'root', removed);
191
- item.stepIndex = 1;
192
- item.currentStep = 'install';
193
- await runStep(item, panelEnabled, repository.dirname, `${packageManager} install`, () => logger.info(colors.cyan('install'), `Running "${packageManager} install"`));
194
- }
195
- item.status = 'success';
196
- }
197
- catch (e) {
198
- item.status = 'failed';
199
- throw e;
200
- }
201
- finally {
202
- item.finishedAt = Date.now();
203
- }
204
- }