@enke.dev/bumper 0.0.1 → 0.0.3

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 (3) hide show
  1. package/README.md +49 -5
  2. package/dist/cli.mjs +66 -50
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -84,9 +84,31 @@ bumper help # show help (also: bumper --help)
84
84
  bumper detect # show context + applicable modules for the cwd
85
85
  bumper detect /path --json # machine-readable detection
86
86
  bumper update # run every applicable module, in order
87
- bumper update --dry-run # print intended steps, change nothing
88
- bumper update --only node,pnpm
89
- bumper update --skip github-actions
87
+ bumper update /path/to/repo # target another repo (defaults to cwd)
88
+ ```
89
+
90
+ ### Flags
91
+
92
+ All flags apply to `bumper update` (except `--json`, on `detect`). Repeatable flags can be given
93
+ several times or comma-separated in one value.
94
+
95
+ | Flag | Repeatable | What it does |
96
+ | ------------------ | :--------: | -------------------------------------------------------------------------------------------- |
97
+ | `--dry-run` | no | Print every intended step, change nothing on disk. |
98
+ | `--only <id>` | yes | Run **only** the named module(s); everything else is skipped. |
99
+ | `--skip <id>` | yes | Run everything **except** the named module(s). |
100
+ | `--exclude <path>` | yes | Skip a repo-relative path this run only, without editing config (see [Excludes](#excludes)). |
101
+ | `--json` | no | `detect` only — emit machine-readable detection output. |
102
+
103
+ `--only` and `--skip` take module ids from the [Modules](#modules) table (`node`, `types-node`,
104
+ `bun`, `npm`, `pnpm`, `docker`, `github-actions`).
105
+
106
+ ```sh
107
+ bumper update --dry-run # preview, no writes
108
+ bumper update --only node,pnpm # just the Node runtime + pnpm modules
109
+ bumper update --skip github-actions # everything but the actions pinner
110
+ bumper update --exclude examples # skip a path this run, without editing config
111
+ bumper update --exclude examples,fixtures # repeat the flag or comma-separate several
90
112
  ```
91
113
 
92
114
  ## Modules
@@ -100,7 +122,7 @@ then the remaining file-rewriting features:
100
122
  | id | kind | detects | does |
101
123
  | ---------------- | --------------- | ------------------------------- | ------------------------------------------------------- |
102
124
  | `node` | runtime | node runtime / `.node-version` | install latest LTS via fnm/asdf, write `.node-version` |
103
- | `types-node` | feature | `@types/node` in any package | pin spec to Node LTS major |
125
+ | `types-node` | feature | `@types/node` in any package | pin spec to exact latest in the Node LTS major line |
104
126
  | `bun` | package-manager | bun packageManager / lockfile | self-upgrade, bump specs, pin `.bun-version`, reinstall |
105
127
  | `npm` | package-manager | npm packageManager / lockfile | bump specs to latest, clean reinstall |
106
128
  | `pnpm` | package-manager | pnpm packageManager / lockfile | self-update, bump specs to latest, clean reinstall |
@@ -121,7 +143,7 @@ Path-scoped overrides. Running in an unknown repo auto-detects everything and pe
121
143
  "repos": {
122
144
  "/absolute/path/to/repository": {
123
145
  "mode": "auto", // auto = re-detect; manual = respect stored toggles
124
- "exclude": ["packages/vendored-pkg"], // repo-relative dirs skipped in workspace ops
146
+ "exclude": ["packages/vendored-pkg"], // repo-relative paths skipped everywhere (see below)
125
147
  "modules": { "docker": false }, // explicit per-module on/off overrides, keyed by module id
126
148
  },
127
149
  },
@@ -135,3 +157,25 @@ bumper config set /path/to/repo exclude packages/a,packages/b
135
157
  bumper config set /path/to/repo modules.docker false
136
158
  bumper config set /path/to/repo mode manual
137
159
  ```
160
+
161
+ ### Excludes
162
+
163
+ `exclude` is a list of repo-relative paths (an exact dir/file or any descendant) that bumper
164
+ leaves alone. It applies **uniformly**:
165
+
166
+ - workspace members under an excluded path are dropped from every workspace operation, and
167
+ - every file-discovering module (e.g. `docker`, which globs `**/Dockerfile*`) skips matches under
168
+ an excluded path — so vendored packages, fixtures, or example projects used for testing are
169
+ never rewritten.
170
+
171
+ Persist it with `config set … exclude`, or pass `--exclude <path>` on a single `update` run to add
172
+ paths for that run only (repeatable, comma-separated, merged with the stored list, not saved). The
173
+ common case: a repo whose own `examples/` are self-applied test fixtures —
174
+
175
+ ```sh
176
+ bumper config set /path/to/repo exclude examples # always skip
177
+ bumper update --exclude examples # skip just this run
178
+ ```
179
+
180
+ > Modules that read a single fixed file at the repo root (`github-actions`, the package managers)
181
+ > are unaffected — `exclude` targets subpaths, and the root is never excluded.
package/dist/cli.mjs CHANGED
@@ -1993,17 +1993,11 @@ var configCommand = {
1993
1993
  };
1994
1994
 
1995
1995
  // src/commands/detect/detect.command.ts
1996
- import { resolve as resolve2 } from "node:path";
1997
-
1998
- // src/context/context.ts
1999
- import { relative as relative2 } from "node:path";
2000
-
2001
- // src/context/detectors/package-manager.detector.ts
2002
- import { join as join3 } from "node:path";
1996
+ import { resolve as resolve3 } from "node:path";
2003
1997
 
2004
1998
  // src/utils/fs.utils.ts
2005
1999
  import { access, glob, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
2006
- import { join as join2 } from "node:path";
2000
+ import { join as join2, relative as relativePath, resolve as resolve2, sep } from "node:path";
2007
2001
  async function pathExists(path) {
2008
2002
  try {
2009
2003
  await access(path);
@@ -2022,6 +2016,14 @@ async function globFiles(cwd, pattern) {
2022
2016
  const files = await Promise.all(absolute.map(async (path) => (await stat(path)).isFile() ? path : null));
2023
2017
  return files.filter((path) => path !== null);
2024
2018
  }
2019
+ function isExcluded(cwd, path, exclude) {
2020
+ const rel = relativePath(cwd, resolve2(cwd, path));
2021
+ return exclude.some((entry) => rel === entry || rel.startsWith(`${entry}/`));
2022
+ }
2023
+ async function collectFiles(cwd, pattern, exclude = []) {
2024
+ const matches = await globFiles(cwd, pattern);
2025
+ return matches.filter((match) => !match.split(sep).includes("node_modules") && !isExcluded(cwd, match, exclude));
2026
+ }
2025
2027
  async function readPackageJson(dir) {
2026
2028
  try {
2027
2029
  return JSON.parse(await readFile2(join2(dir, "package.json"), "utf8"));
@@ -2043,6 +2045,7 @@ function allDependencies(pkg) {
2043
2045
  }
2044
2046
 
2045
2047
  // src/context/detectors/package-manager.detector.ts
2048
+ import { join as join3 } from "node:path";
2046
2049
  async function detectPackageManager(cwd) {
2047
2050
  const pkg = await readPackageJson(cwd);
2048
2051
  const field = pkg?.packageManager ?? "";
@@ -2088,7 +2091,7 @@ async function exec(cmd, opts = {}) {
2088
2091
  if (file === undefined) {
2089
2092
  throw new Error("exec called with an empty command");
2090
2093
  }
2091
- return new Promise((resolve2) => {
2094
+ return new Promise((resolve3) => {
2092
2095
  const proc = spawn(file, args, {
2093
2096
  ...opts.cwd ? { cwd: opts.cwd } : {},
2094
2097
  env: opts.env ? { ...process.env, ...opts.env } : process.env,
@@ -2098,8 +2101,8 @@ async function exec(cmd, opts = {}) {
2098
2101
  let stderr = "";
2099
2102
  proc.stdout.on("data", (chunk) => stdout += chunk);
2100
2103
  proc.stderr.on("data", (chunk) => stderr += chunk);
2101
- proc.on("error", (error) => resolve2({ exitCode: 1, stdout, stderr: String(error) }));
2102
- proc.on("close", (code) => resolve2({ exitCode: code ?? 0, stdout, stderr }));
2104
+ proc.on("error", (error) => resolve3({ exitCode: 1, stdout, stderr: String(error) }));
2105
+ proc.on("close", (code) => resolve3({ exitCode: code ?? 0, stdout, stderr }));
2103
2106
  });
2104
2107
  }
2105
2108
  async function execOk(cmd, opts = {}) {
@@ -2194,12 +2197,10 @@ async function detectWorkspaces(cwd, pm) {
2194
2197
  }
2195
2198
 
2196
2199
  // src/context/context.ts
2197
- function isExcluded(cwd, dir, exclude) {
2198
- const rel = relative2(cwd, dir);
2199
- return exclude.some((entry) => rel === entry || rel.startsWith(`${entry}/`));
2200
- }
2201
2200
  async function buildContext(cwd, options = {}) {
2202
- const { config, created } = await resolveForPath(cwd);
2201
+ const { config: stored, created } = await resolveForPath(cwd);
2202
+ const exclude = [...new Set([...stored.exclude, ...options.exclude ?? []])];
2203
+ const config = { ...stored, exclude };
2203
2204
  const [runtime, packageManager] = await Promise.all([
2204
2205
  detectRuntime(cwd),
2205
2206
  detectPackageManager(cwd)
@@ -2211,7 +2212,7 @@ async function buildContext(cwd, options = {}) {
2211
2212
  runtime,
2212
2213
  packageManager,
2213
2214
  isMonorepo,
2214
- workspaces: workspaces.filter((dir) => !isExcluded(cwd, dir, config.exclude)),
2215
+ workspaces: workspaces.filter((dir) => !isExcluded(cwd, dir, exclude)),
2215
2216
  versionManager,
2216
2217
  config,
2217
2218
  dryRun: options.dryRun ?? false
@@ -2249,7 +2250,7 @@ function planLine(text) {
2249
2250
 
2250
2251
  // src/modules/features/docker/docker.feature.ts
2251
2252
  import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
2252
- import { relative as relative3, sep } from "node:path";
2253
+ import { relative as relative2 } from "node:path";
2253
2254
 
2254
2255
  // src/utils/npm-registry.utils.ts
2255
2256
  async function curlJson(url, run2 = execOk) {
@@ -2306,9 +2307,8 @@ async function ensureNodeLts(ctx) {
2306
2307
 
2307
2308
  // src/modules/features/docker/docker.feature.ts
2308
2309
  var DOCKER_GLOB = "**/{Dockerfile*,docker-compose*.yaml,docker-compose*.yml,compose*.yaml,compose*.yml}";
2309
- async function findDockerFiles(cwd) {
2310
- const matches = await globFiles(cwd, DOCKER_GLOB);
2311
- return matches.filter((match) => !match.split(sep).includes("node_modules"));
2310
+ function findDockerFiles(ctx) {
2311
+ return collectFiles(ctx.cwd, DOCKER_GLOB, ctx.config.exclude);
2312
2312
  }
2313
2313
  var dockerFeature = {
2314
2314
  kind: "feature" /* Feature */,
@@ -2319,13 +2319,13 @@ var dockerFeature = {
2319
2319
  if (toggle !== undefined) {
2320
2320
  return toggle;
2321
2321
  }
2322
- return (await findDockerFiles(ctx.cwd)).length > 0;
2322
+ return (await findDockerFiles(ctx)).length > 0;
2323
2323
  },
2324
2324
  async update(ctx) {
2325
2325
  const { version } = await ensureNodeLts(ctx);
2326
- const files = await findDockerFiles(ctx.cwd);
2326
+ const files = await findDockerFiles(ctx);
2327
2327
  await Promise.all(files.map(async (file) => {
2328
- const label = relative3(ctx.cwd, file);
2328
+ const label = relative2(ctx.cwd, file);
2329
2329
  if (ctx.dryRun) {
2330
2330
  planLine(`align node version → ${version} in ${label}`);
2331
2331
  return;
@@ -2368,7 +2368,7 @@ var githubActionsFeature = {
2368
2368
  };
2369
2369
 
2370
2370
  // src/modules/features/types-node/types-node.feature.ts
2371
- import { relative as relative4 } from "node:path";
2371
+ import { relative as relative3 } from "node:path";
2372
2372
 
2373
2373
  // src/utils/spec.utils.ts
2374
2374
  var import_semver = __toESM(require_semver2(), 1);
@@ -2400,17 +2400,45 @@ async function dirsWithTypesNode(ctx) {
2400
2400
  }));
2401
2401
  return dirs.filter((dir) => dir !== null);
2402
2402
  }
2403
- function pinTypesNode(pkg, major) {
2403
+ function pinTypesNode(pkg, version) {
2404
2404
  return BUCKETS.reduce((changed, bucket) => {
2405
2405
  const deps = pkg[bucket];
2406
2406
  const spec = deps?.[TYPES_NODE_PACKAGE];
2407
2407
  if (deps && spec && !isVersionRange(spec)) {
2408
- deps[TYPES_NODE_PACKAGE] = major;
2409
- return true;
2408
+ const next = `${operatorOf(spec)}${version}`;
2409
+ if (next !== spec) {
2410
+ deps[TYPES_NODE_PACKAGE] = next;
2411
+ return true;
2412
+ }
2410
2413
  }
2411
2414
  return changed;
2412
2415
  }, false);
2413
2416
  }
2417
+ async function updateTypesNode(ctx, resolveInRange = latestVersionInRange) {
2418
+ const { major } = await ensureNodeLts(ctx);
2419
+ const majorSpec = String(major);
2420
+ const dirs = await dirsWithTypesNode(ctx);
2421
+ if (dirs.length === 0) {
2422
+ return;
2423
+ }
2424
+ if (ctx.dryRun) {
2425
+ dirs.forEach((dir) => {
2426
+ const label = relative3(ctx.cwd, dir) || ".";
2427
+ planLine(`pin ${TYPES_NODE_PACKAGE} to latest ${majorSpec}.x in ${label}`);
2428
+ });
2429
+ return;
2430
+ }
2431
+ const version = await resolveInRange(TYPES_NODE_PACKAGE, majorSpec, viewTool(ctx.packageManager), ctx.cwd);
2432
+ if (!version) {
2433
+ return;
2434
+ }
2435
+ await Promise.all(dirs.map(async (dir) => {
2436
+ const pkg = await readPackageJson(dir);
2437
+ if (pkg && pinTypesNode(pkg, version)) {
2438
+ await writePackageJson(dir, pkg);
2439
+ }
2440
+ }));
2441
+ }
2414
2442
  var typesNodeFeature = {
2415
2443
  kind: "feature" /* Feature */,
2416
2444
  id: "types-node",
@@ -2421,22 +2449,7 @@ var typesNodeFeature = {
2421
2449
  async managedDependencies() {
2422
2450
  return [TYPES_NODE_PACKAGE];
2423
2451
  },
2424
- async update(ctx) {
2425
- const { major } = await ensureNodeLts(ctx);
2426
- const majorSpec = String(major);
2427
- const dirs = await dirsWithTypesNode(ctx);
2428
- await Promise.all(dirs.map(async (dir) => {
2429
- const label = relative4(ctx.cwd, dir) || ".";
2430
- if (ctx.dryRun) {
2431
- planLine(`pin ${TYPES_NODE_PACKAGE}@${majorSpec} in ${label}`);
2432
- return;
2433
- }
2434
- const pkg = await readPackageJson(dir);
2435
- if (pkg && pinTypesNode(pkg, majorSpec)) {
2436
- await writePackageJson(dir, pkg);
2437
- }
2438
- }));
2439
- }
2452
+ update: updateTypesNode
2440
2453
  };
2441
2454
 
2442
2455
  // src/modules/package-managers/bun/bun.package-manager.ts
@@ -2745,7 +2758,7 @@ async function detectModules(ctx) {
2745
2758
 
2746
2759
  // src/commands/detect/detect.command.ts
2747
2760
  async function run2({ values, positionals }) {
2748
- const cwd = resolve2(positionals[0] ?? process.cwd());
2761
+ const cwd = resolve3(positionals[0] ?? process.cwd());
2749
2762
  const json = values.json ?? false;
2750
2763
  const { ctx, configCreated } = await buildContext(cwd);
2751
2764
  const modules = await detectModules(ctx);
@@ -2843,11 +2856,12 @@ function commandHelp(stream = process.stdout) {
2843
2856
  }
2844
2857
 
2845
2858
  // src/commands/update/update.command.ts
2846
- import { resolve as resolve3 } from "node:path";
2859
+ import { resolve as resolve4 } from "node:path";
2847
2860
  async function run4({ values, positionals }) {
2848
- const cwd = resolve3(positionals[0] ?? process.cwd());
2861
+ const cwd = resolve4(positionals[0] ?? process.cwd());
2849
2862
  const dryRun = values["dry-run"] ?? false;
2850
- const { ctx, configCreated } = await buildContext(cwd, { dryRun });
2863
+ const exclude = (values.exclude ?? []).flatMap((entry) => entry.split(",")).map((entry) => entry.trim()).filter(Boolean);
2864
+ const { ctx, configCreated } = await buildContext(cwd, { dryRun, exclude });
2851
2865
  if (configCreated) {
2852
2866
  process.stdout.write(`${DIM}Discovered new repo, wrote entry to ${configPath()}${RESET}
2853
2867
  `);
@@ -2860,12 +2874,13 @@ var updateCommand = {
2860
2874
  name: "update",
2861
2875
  run: run4,
2862
2876
  help: () => ({
2863
- usage: ["bumper update [path] [--dry-run] [--only id]... [--skip id]..."],
2877
+ usage: ["bumper update [path] [--dry-run] [--only id]... [--skip id]... [--exclude path]..."],
2864
2878
  summary: "Run every applicable module in order",
2865
2879
  options: [
2866
2880
  "--dry-run Print intended steps without changing anything",
2867
2881
  "--only id Module id to run exclusively (repeat for several)",
2868
- "--skip id Module id to skip (repeat for several)"
2882
+ "--skip id Module id to skip (repeat for several)",
2883
+ "--exclude path Repo-relative path skipped this run, not persisted (repeat or comma-separate)"
2869
2884
  ]
2870
2885
  })
2871
2886
  };
@@ -2882,6 +2897,7 @@ var cliOptions = {
2882
2897
  "dry-run": { type: "boolean" },
2883
2898
  only: { type: "string", multiple: true },
2884
2899
  skip: { type: "string", multiple: true },
2900
+ exclude: { type: "string", multiple: true },
2885
2901
  help: { type: "boolean", short: "h" }
2886
2902
  };
2887
2903
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enke.dev/bumper",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",