Skip to content

fix(installer): seed schema-declared core keys on --yes installs and propagate core --set overrides to module config spreads - #2633

Open
will-ai-m wants to merge 2 commits into
bmad-code-org:mainfrom
will-ai-m:fix/installer-core-key-seeding
Open

fix(installer): seed schema-declared core keys on --yes installs and propagate core --set overrides to module config spreads#2633
will-ai-m wants to merge 2 commits into
bmad-code-org:mainfrom
will-ai-m:fix/installer-core-key-seeding

Conversation

@will-ai-m

Copy link
Copy Markdown

What

Two related installer fixes around core config keys, prototyped and requested by @jheyworth in the review of #2609. This PR gates #2609, which will declare the first core key that the --yes literal doesn't know about.

  1. --yes installs now seed schema-declared core keys. Fresh --yes installs seed core from a hardcoded five-key literal in ui.js without reading core's schema, and --yes updates skip core once seeded — so any newly declared core key was silently dropped on every --yes install, and a later --set core.<key> mis-filed to team scope because config.user.toml never learned the key.
  2. --set core.<key> now refreshes the spread copies. Core values are spread into every module's config.yaml at generate time and skills read their own module's copy — but --set core.<key> --action update only patched the central tomls, leaving every spread copy stale until a later install regenerated it. This half is live on main today with existing core keys (e.g. --set core.user_name).

How

  • tools/installer/ui.js — after the --yes core seed, backfill any core keys declared in src/core-skills/module.yaml but absent from the assembled config. Value precedence per key: prior [core] answer → prior answer under a module section (key promoted module → core) → schema default. The legacy _hoistCoreKeysFromLegacyModuleConfigs() cannot cover the promotion case: it only runs on the pre-central-toml load path, never for v6 installs.
  • tools/installer/set-overrides.js — a core --set now patches the spread copy in every installed module's config.yaml, not just core's own. Non-module dirs (_config, docs, etc.) are excluded; module-scoped --set behavior is unchanged.

Testing

New Test Suite 49 in test/test-installation-components.js with the three regression cases requested in the #2609 review, plus edge guards:

  • fresh --yes install seeds a schema-declared core key with its schema default
  • --yes update preserves a prior [core] answer instead of resetting to the default
  • --yes update migrates a prior module-section value for a key promoted to core (and a prior [core] answer wins over a module-section one)
  • core --set routes to config.user.toml and immediately refreshes the spread copies in core/, bmm/, and an external module's config.yaml, preserving the generated-file banner header
  • non-module dirs are untouched, and a module-scoped --set does not propagate to other modules

npm test run locally: refs, install (403/403), urls, channels, skills, eslint, markdownlint, and prettier all pass. The test:renderer step fails on this machine on pristine main as well (local Python is 3.9; render.py imports tomllib, stdlib only in 3.11+) — environmental, unrelated to this change; CI should confirm.

🤖 Generated with Claude Code

…propagate core --set overrides to module config spreads

Fresh --yes installs seed core config from a hardcoded five-key literal
without reading core's schema, and --yes updates skip core once seeded —
so any core key declared after that literal was written is silently
dropped on every --yes install, and a subsequent --set core.<key> then
mis-files to team scope because the user toml never learned the key.

Separately, --set core.<key> --action update patches the central tomls
but leaves every module's spread copy of the value stale in
_bmad/<module>/config.yaml, so skills keep reading the old value until
some later install regenerates the spread. This half is live on main
today with existing core keys (e.g. --set core.user_name).

- ui.js: after the --yes core seed, backfill any schema-declared core
  keys missing from the assembled config. Value precedence per key:
  prior [core] answer, then a prior answer under a module section (key
  promoted module -> core), then the schema default. The legacy
  _hoistCoreKeysFromLegacyModuleConfigs cannot cover this: it only runs
  on the pre-central-toml load path, never for v6 installs.
- set-overrides.js: a core --set now also refreshes the spread copies in
  every installed module's config.yaml, not just core's own.
- test: new suite covering --yes seeding of a schema-declared key, prior
  [core] answer preservation, module -> core promotion keeping the
  user's prior value, [core]-beats-module precedence, and core --set
  propagation to spread copies (plus non-module dirs and module-scoped
  --set staying untouched).

Prerequisite for bmad-code-org#2609, which declares the first core key outside the
--yes literal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The installer now backfills missing core configuration keys during --yes installs, centralizes reserved-directory filtering, and propagates core --set overrides to module config.yaml spread copies. Test Suite 49 covers precedence, fallback behavior, propagation, and isolation.

Changes

Installer core configuration

Layer / File(s) Summary
Shared module-directory filtering
tools/installer/non-module-dirs.js, tools/installer/core/installer.js, tools/installer/modules/official-modules.js
A shared reserved-directory set is used when discovering modules, generating configs, merging help catalogs, and loading legacy configuration.
Core schema backfill
tools/installer/ui.js, test/test-installation-components.js
--yes installs backfill missing core keys from prior core values, module values, or schema defaults, while schema read failures produce a warning and do not abort installation.
Core override spread and validation
tools/installer/set-overrides.js, test/test-installation-components.js
Core overrides update each module’s config.yaml while excluding reserved directories; tests verify header preservation, non-module isolation, and module-scoped override behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InstallerUI
  participant ExistingConfig
  participant CoreSchema
  InstallerUI->>ExistingConfig: load prior core and module values
  InstallerUI->>CoreSchema: read core-skills/module.yaml
  CoreSchema-->>InstallerUI: return schema keys and defaults
  InstallerUI->>InstallerUI: backfill missing core values
Loading

Possibly related PRs

Suggested reviewers: bmadcode

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the installer fixes for --yes core seeding and core --set propagation.
Description check ✅ Passed The description is directly related to the changes and explains both installer fixes in enough detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tools/installer/ui.js (2)

879-907: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the try to schema loading, and prefer the public existingConfig accessor.

The catch currently swallows any failure in the seeding loop too (not just an unreadable schema), so a real defect in the backfill silently degrades to "no core keys seeded" with no signal. Consider wrapping only the read/parse and letting loop errors surface (or at least logging a warning). Separately, configCollector._existingConfig reaches into a private field of OfficialModules; the public existingConfig getter (exercised in test/test-installation-components.js around lines 2986-3003) is the stable contract.

As per path instructions for tools/**: "Build script/tooling. Check error handling and proper exit codes."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/installer/ui.js` around lines 879 - 907, Narrow the try/catch around
yaml loading and parsing only, allowing errors in the core seeding loop to
surface rather than being silently swallowed; preserve the fallback behavior for
unreadable schema files. In the same block, replace the private
configCollector._existingConfig access with the public
configCollector.existingConfig getter, keeping the existing core backfill logic
unchanged.

Source: Path instructions


898-902: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use replaceAll for {directory_name} to match the interactive path.

String.prototype.replace with a string pattern substitutes only the first occurrence; buildQuestion() in tools/installer/modules/official-modules.js (lines 1910-1939) uses replaceAll, so a schema default containing the placeholder twice would resolve differently under --yes.

♻️ Proposed fix
-            if (typeof def === 'string') def = def.replace('{directory_name}', path.basename(directory));
+            if (typeof def === 'string') def = def.replaceAll('{directory_name}', path.basename(directory));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/installer/ui.js` around lines 898 - 902, Update the default-value
substitution in the undefined-value branch to use replaceAll for every
`{directory_name}` placeholder, matching buildQuestion() behavior in the
interactive path; preserve the existing directory basename replacement and
non-string handling.
test/test-installation-components.js (1)

3664-3701: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the backfill's failure branch and CLI-flag precedence.

The four scenarios cover the happy paths well. Two gaps remain on the code this suite is guarding: (1) the catch in collectModuleConfigs that swallows an unreadable/absent core-skills/module.yaml — a fixture pointing getSourcePath at an empty tree would pin "install still proceeds with the seeded core config"; (2) --yes combined with --user-name/--output-folder, where the backfill must not overwrite CLI-provided values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-installation-components.js` around lines 3664 - 3701, Extend the
test coverage around collectModuleConfigs with two cases: use a fixture whose
getSourcePath points to an empty tree so the core-skills/module.yaml read enters
the catch path, then assert installation still proceeds with the seeded core
configuration; also test --yes together with --user-name and --output-folder,
asserting the backfill preserves both CLI-provided values instead of overwriting
them.
tools/installer/set-overrides.js (1)

299-307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the nonModuleDirs list instead of re-declaring it.

The same exclusion set (minus core) is hardcoded in tools/installer/core/installer.js around lines 1051-1076. Two copies will drift the moment a new reserved directory is added, and a drifted copy here means core --set starts rewriting a non-module config.yaml. Exporting one constant from a shared module and importing it in both places keeps the "what counts as a module dir" rule single-sourced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/installer/set-overrides.js` around lines 299 - 307, The
module-directory exclusion list is duplicated and can drift between installers.
Extract the shared non-module directory set into an exported constant, then
update the core `moduleCode === 'core'` logic and the corresponding logic in
`core/installer.js` to import and reuse it, preserving the existing exclusions
including `core` where required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/test-installation-components.js`:
- Around line 3664-3701: Extend the test coverage around collectModuleConfigs
with two cases: use a fixture whose getSourcePath points to an empty tree so the
core-skills/module.yaml read enters the catch path, then assert installation
still proceeds with the seeded core configuration; also test --yes together with
--user-name and --output-folder, asserting the backfill preserves both
CLI-provided values instead of overwriting them.

In `@tools/installer/set-overrides.js`:
- Around line 299-307: The module-directory exclusion list is duplicated and can
drift between installers. Extract the shared non-module directory set into an
exported constant, then update the core `moduleCode === 'core'` logic and the
corresponding logic in `core/installer.js` to import and reuse it, preserving
the existing exclusions including `core` where required.

In `@tools/installer/ui.js`:
- Around line 879-907: Narrow the try/catch around yaml loading and parsing
only, allowing errors in the core seeding loop to surface rather than being
silently swallowed; preserve the fallback behavior for unreadable schema files.
In the same block, replace the private configCollector._existingConfig access
with the public configCollector.existingConfig getter, keeping the existing core
backfill logic unchanged.
- Around line 898-902: Update the default-value substitution in the
undefined-value branch to use replaceAll for every `{directory_name}`
placeholder, matching buildQuestion() behavior in the interactive path; preserve
the existing directory basename replacement and non-string handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cf17c30e-45c6-4138-b3ad-46be5a59c2d3

📥 Commits

Reviewing files that changed from the base of the PR and between bb45db4 and 3863266.

📒 Files selected for processing (3)
  • test/test-installation-components.js
  • tools/installer/set-overrides.js
  • tools/installer/ui.js

will-ai-m added a commit to will-ai-m/BMAD-METHOD that referenced this pull request Jul 27, 2026
…rding

Review round on bmad-code-org#2609: the preference is cross-cutting ("Should agents
recommend...", wanted for any skill per bmad-code-org#2602), so it belongs in core,
where one declaration reaches every module's config.yaml via the core
spread — not just bmm skills.

- schema block moves from src/bmm-skills/module.yaml to
  src/core-skills/module.yaml unchanged (scope: user, default "true",
  string-valued single-select)
- canonical rule wording in SKILL.md, step-01, and step-04 (step-04
  keeps its parenthetical examples), adding the no-grounds clause so
  evidence-free asks don't manufacture a justification
- docs: key becomes core.always_show_recommendation (incl. the --set
  example); the what-it-does paragraph moves out of the --set mechanics
  section into the interactive-install section and says "prompted during
  interactive install"; customize-bmad names the key explicitly; French
  docs mirror both edits
- tests: assert the key lands in [core] of config.user.toml and is
  spread into a non-core module's generated config.yaml

Merges after bmad-code-org#2633, which makes --yes installs seed schema-declared
core keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR addresses two related installer regressions around --yes installs: schema-declared core keys beyond the hardcoded five-key literal were silently dropped, and a core --set override only patched the central TOML files, leaving every module's spread config.yaml stale until the next full install.

  • non-module-dirs.js — extracts the previously duplicated six-entry exclusion set into a single shared constant, imported by core/installer.js, modules/official-modules.js, and set-overrides.js (replacing three independent new Set([...]) literals).
  • ui.js backfill — after the hardcoded --yes seed, reads core-skills/module.yaml and fills any schema-declared keys not yet in collectedConfig.core, with precedence: prior [core] TOML answer → prior module-section answer (key promoted module→core migration) → schema default. A prompts.log.warn (not a silent catch) is emitted if the schema file is unreadable.
  • set-overrides.js spread refresh — a core --set now enumerates every non-excluded directory under _bmad/ and patches the config.yaml there, preserving the generated-file banner header via a comment-prefix extraction loop; the original single-file code path is unchanged for module-scoped overrides.
  • Suite 49 — six assertion groups cover fresh seed, prior-answer precedence, module→core promotion, CLI-flag coexistence, unreadable-schema fallback, non-module-dir exclusion, and module-scoped non-propagation.

Confidence Score: 5/5

Safe to merge; changes are narrowly scoped, well-guarded, and fully exercised by the new test suite.

Both new code paths — the --yes backfill in ui.js and the core --set spread in set-overrides.js — are correctly implemented. The backfill uses _existingConfig (populated by loadExistingConfig) for precedence lookup, the key-in-core guard correctly preserves already-seeded values, and the schema-unreadable path emits a visible warning rather than silently failing. The spread refresh correctly excludes NON_MODULE_DIRS plus core's own directory to avoid double-patching, and the header-preservation loop handles all standard YAML comment/blank-line arrangements. Suite 49 covers every stated regression case plus edge guards.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
tools/installer/non-module-dirs.js New file exporting NON_MODULE_DIRS constant; cleanly de-duplicates three prior inline Set literals across the codebase.
tools/installer/set-overrides.js Core --set now propagates to every non-excluded module config.yaml; header-preservation loop is correct, double-patch guard via excluded Set is sound.
tools/installer/ui.js Backfill reads core schema after --yes seed; precedence chain (prior core → prior module → default) is correct; {directory_name} substitution mirrors existing buildQuestion() behavior; warn-on-unreadable-schema is correctly scoped to just the try/parse block.
tools/installer/core/installer.js Two inline nonModuleDirs Sets replaced with shared NON_MODULE_DIRS import; no logic changes.
tools/installer/modules/official-modules.js One inline nonModuleDirs Set replaced with shared NON_MODULE_DIRS import in loadExistingConfig's legacy path; no logic changes.
test/test-installation-components.js Suite 49 comprehensively covers both new behaviors with fresh, update, promotion, CLI, unreadable-schema, non-module-dir exclusion, and module-scoped-non-propagation cases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[collectModuleConfigs called with --yes] --> B{CLI flags provided?}
    B -- Yes --> C[loadExistingConfig → _existingConfig]
    B -- No --> D[loadExistingConfig → _existingConfig]
    C --> E[Seed collectedConfig.core from CLI flags]
    D --> F{collectedConfig.core empty?}
    F -- Yes, fresh install --> G[Seed hardcoded 5-key core defaults]
    F -- No --> H[Keep existing collectedConfig.core]
    E --> I
    G --> I
    H --> I
    I[--yes backfill: read core-skills/module.yaml] --> J{Schema readable?}
    J -- No --> K[prompts.log.warn, skip backfill]
    J -- Yes --> L[For each schema key not in collectedConfig.core]
    L --> M{Prior value in _existingConfig.core?}
    M -- Yes --> N[Use prior core value]
    M -- No --> O{Prior value in any module section?}
    O -- Yes --> P[Use promoted module value]
    O -- No --> Q[Use schema default, expand directory_name]
    N --> R[Set core key]
    P --> R
    Q --> R
    R --> S[collectAllConfigurations: core skipped, modules collected]
    K --> S

    subgraph applySetOverrides [set-overrides.js: core --set]
        T[Write value to config.user.toml] --> U[Build yamlTargets: core/config.yaml first]
        U --> V[Enumerate _bmad dirs excluding NON_MODULE_DIRS + core]
        V --> W[Add each module/config.yaml to yamlTargets]
        W --> X[For each target: parse YAML, patch key, preserve header, write]
    end
Loading

Reviews (2): Last reviewed commit: "fix(installer): address review-bot findi..." | Re-trigger Greptile

Comment thread tools/installer/set-overrides.js
Comment thread tools/installer/ui.js Outdated
- ui.js: {directory_name} resolves with replaceAll, matching
  buildQuestion() on the interactive path; the try now wraps only the
  schema read/parse (a defect in the seeding loop surfaces instead of
  silently degrading) and the catch logs a warning so a broken core
  module.yaml is visible; the backfill reads the public existingConfig
  getter instead of the private field
- set-overrides.js / core/installer.js / modules/official-modules.js:
  the reserved-directory exclusion set is now a single shared
  NON_MODULE_DIRS constant (new tools/installer/non-module-dirs.js)
  instead of four independent copies that could drift
- test: two more Suite 49 cases — --yes with --user-name/--output-folder
  keeps CLI values while still seeding schema-declared keys, and an
  unreadable core module.yaml skips the backfill without failing the
  install

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@will-ai-m

Copy link
Copy Markdown
Author

Bot findings dispositioned in c6b9446 — all four CodeRabbit nitpicks fixed, plus both optional test suggestions taken:

  • Fixedui.js {directory_name}: replacereplaceAll, matching buildQuestion() on the interactive path.
  • Fixedui.js error handling: the try now wraps only the schema read/parse; the catch logs a warning via prompts.log.warn instead of staying silent, and seeding-loop errors surface.
  • Fixedui.js encapsulation: the backfill reads the public existingConfig getter instead of _existingConfig.
  • Fixedset-overrides.js duplication: new shared NON_MODULE_DIRS constant in tools/installer/non-module-dirs.js, imported by all four former copies (set-overrides.js, core/installer.js ×2, modules/official-modules.js).
  • Added — Suite 49 case: unreadable/absent core module.yaml--yes install still proceeds with the seeded config, backfill skipped.
  • Added — Suite 49 case: --yes + --user-name/--output-folder → CLI values preserved while schema-declared keys still seed.

Full npm test (renderer included) green locally: 408/408 installer component tests.

@will-ai-m

Copy link
Copy Markdown
Author

@coderabbitai review

@will-ai-m

Copy link
Copy Markdown
Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/installer/ui.js`:
- Line 908: Update the replacement in the definition handling around def so
replaceAll uses a callback returning path.basename(directory), ensuring
directory names containing replacement-pattern characters remain literal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 565caa69-62a0-4c8d-a6ff-7acb5e5a2f45

📥 Commits

Reviewing files that changed from the base of the PR and between 3863266 and c6b9446.

📒 Files selected for processing (6)
  • test/test-installation-components.js
  • tools/installer/core/installer.js
  • tools/installer/modules/official-modules.js
  • tools/installer/non-module-dirs.js
  • tools/installer/set-overrides.js
  • tools/installer/ui.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/installer/set-overrides.js

Comment thread tools/installer/ui.js
}
if (value === undefined) {
let def = item.default;
if (typeof def === 'string') def = def.replaceAll('{directory_name}', path.basename(directory));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

node - <<'NODE'
const template = 'project-{directory_name}';
const name = '$&';
const unsafe = template.replaceAll('{directory_name}', name);
const safe = template.replaceAll('{directory_name}', () => name);
if (unsafe === safe) process.exit(1);
console.log({ unsafe, safe });
NODE

Repository: bmad-code-org/BMAD-METHOD

Length of output: 222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file:"
fd -a 'ui\.js$' . | sed 's#^\./##' | grep 'tools/installer/ui.js' || true
echo

echo "Relevant lines around collectModuleConfigs:"
wc -l tools/installer/ui.js
sed -n '860,925p' tools/installer/ui.js | cat -n

echo
echo "Search for project_name / def directory_name usage:"
rg -n "project_name|directory_name|collectModuleConfigs|replaceAll\\('\\{directory_name\\}'" tools/installer/ui.js

Repository: bmad-code-org/BMAD-METHOD

Length of output: 4700


Use a replacer callback for literal directory names.

String.prototype.replaceAll('{directory_name}', path.basename(directory)) treats $&, $', $` , and $$ as replacement instructions, so a directory name like "$&" leaves {directory_name} in project_name. Use def.replaceAll('{directory_name}', () => path.basename(directory)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/installer/ui.js` at line 908, Update the replacement in the definition
handling around def so replaceAll uses a callback returning
path.basename(directory), ensuring directory names containing
replacement-pattern characters remain literal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant