diff --git a/.changeset/tidy-shrimps-shop.md b/.changeset/tidy-shrimps-shop.md new file mode 100644 index 00000000000..8a50319cd4d --- /dev/null +++ b/.changeset/tidy-shrimps-shop.md @@ -0,0 +1,5 @@ +--- +"@module-federation/node": patch +--- + +Fix node chunk parsing and align node webpack-path lint handling. diff --git a/.codex/skills/changeset-pr/SKILL.md b/.codex/skills/changeset-pr/SKILL.md index e7d9dade8a4..a867530f626 100644 --- a/.codex/skills/changeset-pr/SKILL.md +++ b/.codex/skills/changeset-pr/SKILL.md @@ -9,10 +9,12 @@ description: Create or update a `.changeset/*.md` file for the current branch or Create a repo-correct changeset for the current branch, or update an existing one without widening scope unnecessarily. Verify both syntax and package scope before handoff. +Ground the changeset in the live branch diff, not stale branch intent. Always inspect the current branch state against its base before choosing package scope or release type. + ## Workflow 1. Confirm whether a changeset is needed. -2. Identify the publishable package scope from the branch diff. +2. Identify the publishable package scope from the live branch diff. 3. Create or edit one `.changeset/*.md` file. 4. Validate the file against repo config and branch scope. 5. Report the exact commands run and any ambiguity that remains. @@ -27,6 +29,16 @@ Read [references/repo-conventions.md](./references/repo-conventions.md) when you ## Determine Scope +First inspect the live branch state: + +```bash +git diff --name-status origin/main...HEAD +git diff --stat origin/main...HEAD +git log --oneline --decorate --no-merges origin/main..HEAD +``` + +Use that to separate real publishable-package behavior changes from repo-local docs, skills, tooling, or cleanup. + Start with the helper script: ```bash @@ -82,13 +94,13 @@ python3 .codex/skills/changeset-pr/scripts/inspect_changeset_scope.py --base ori 2. Validate that Changesets can parse and plan the release: ```bash -pnpm exec changeset status --verbose +python3 .codex/skills/changeset-pr/scripts/run_changeset_status.py --verbose ``` 3. When machine-readable output is useful: ```bash -pnpm exec changeset status --output /tmp/changeset-status.json +python3 .codex/skills/changeset-pr/scripts/run_changeset_status.py --output /tmp/changeset-status.json ``` Interpretation: @@ -96,6 +108,7 @@ Interpretation: - `status` verifies parseability and computed release planning. - `status` does not prove the changeset is branch-local or minimal in this repo because other pending changesets may already exist. - Fixed-group packages can cause broader or higher bumps than the frontmatter alone suggests. +- Prefer the helper script over direct `pnpm exec changeset status` in Codex runs because shell wrappers in non-TTY sessions can add `/dev/tty` noise or otherwise make the raw CLI output unreliable. ## Update Existing Changesets diff --git a/.codex/skills/changeset-pr/scripts/run_changeset_status.py b/.codex/skills/changeset-pr/scripts/run_changeset_status.py new file mode 100644 index 00000000000..821036593c0 --- /dev/null +++ b/.codex/skills/changeset-pr/scripts/run_changeset_status.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path + + +def run(cmd: list[str], cwd: Path) -> str: + result = subprocess.run( + cmd, + cwd=str(cwd), + check=True, + capture_output=True, + text=True, + ) + return result.stdout.strip() + + +def find_repo_root(start: Path) -> Path: + return Path(run(["git", "rev-parse", "--show-toplevel"], start)) + + +def find_changeset_cli(repo_root: Path) -> Path: + candidates = [ + repo_root / "node_modules" / "@changesets" / "cli" / "bin.js", + repo_root / "node_modules" / ".bin" / "changeset", + ] + for candidate in candidates: + if candidate.exists(): + return candidate + raise FileNotFoundError("Unable to locate Changesets CLI under node_modules") + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Run Changesets status without shell wrappers so output is stable in non-TTY environments." + ) + parser.add_argument( + "--repo-root", + default=".", + help="Repo root. Defaults to current working directory.", + ) + parser.add_argument( + "--output", + help="Optional path to write Changesets JSON output.", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Pass --verbose to Changesets status.", + ) + parser.add_argument( + "--json", + action="store_true", + help="Emit a small JSON wrapper with command, exit code, stdout, and stderr.", + ) + args = parser.parse_args() + + repo_root = find_repo_root(Path(args.repo_root).resolve()) + cli = find_changeset_cli(repo_root) + + cmd = ["node", str(cli), "status"] + if args.verbose: + cmd.append("--verbose") + if args.output: + cmd.extend(["--output", str(Path(args.output).resolve())]) + + proc = subprocess.run( + cmd, + cwd=str(repo_root), + capture_output=True, + text=True, + check=False, + ) + + if args.json: + payload = { + "command": cmd, + "exit_code": proc.returncode, + "stdout": proc.stdout, + "stderr": proc.stderr, + } + sys.stdout.write(json.dumps(payload, indent=2) + "\n") + else: + if proc.stdout: + sys.stdout.write(proc.stdout) + if proc.stderr: + sys.stderr.write(proc.stderr) + + return proc.returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.codex/skills/gh-pr-metadata/SKILL.md b/.codex/skills/gh-pr-metadata/SKILL.md new file mode 100644 index 00000000000..1bf4863da42 --- /dev/null +++ b/.codex/skills/gh-pr-metadata/SKILL.md @@ -0,0 +1,111 @@ +--- +name: gh-pr-metadata +description: Update the current GitHub PR title and body for this repository so they match the repo's pull request template and conventional-commit title style. Use when a PR title is vague or misformatted, when the PR body is missing required sections or checklist items, when Codex needs to normalize PR metadata before review or merge, or when GitHub comments/checks indicate the PR title or body should be corrected. +--- + +# GH PR Metadata + +## Overview + +Normalize the current branch's GitHub PR metadata to this repo's expectations. Keep the title in conventional-commit style, keep the body aligned to `.github/pull_request_template.md`, and validate before handoff. + +Ground the PR metadata in the live branch state, not stale branch names or old commit subjects. Always inspect the current branch diff versus its base before rewriting the PR title/body. + +Read [references/repo-pr-format.md](./references/repo-pr-format.md) when you need the exact section order, checklist items, or a title example. + +## Workflow + +1. Resolve the current branch PR. + +```bash +gh pr view --json number,title,body,url,headRefName,baseRefName +``` + +2. Inspect the current branch state against the PR base branch. + +At minimum, check: + +```bash +git diff --name-status origin/...HEAD +git diff --stat origin/...HEAD +git log --oneline --decorate --no-merges origin/..HEAD +``` + +Use these to answer: +- what files actually differ from the base right now +- which changes are functional versus cleanup/tooling/docs +- whether the existing PR title/body still matches the current branch after rebases, reverts, or scope narrowing + +3. Validate the current title and body. + +```bash +python3 .codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py +``` + +4. If the PR body needs a clean template scaffold, print one: + +```bash +python3 .codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py --print-template +``` + +5. Rewrite the PR title in conventional-commit style. + +Rules: +- Prefer `type(scope): summary` +- Keep the title short and direct +- Use repo-typical types such as `fix`, `feat`, `docs`, `refactor`, `chore`, `test`, `ci`, `build`, `perf`, `revert` +- Keep the scope tight to the affected package or subsystem when useful +- Do not add prefixes like `[codex]` +- Make sure the title describes the current branch diff, not the original branch intent if the branch was later narrowed or partially reverted + +6. Rewrite the PR body to preserve the repo template structure: +- `## Description` +- `## Related Issue` +- `## Types of changes` +- `## Checklist` + +7. Update the PR with `gh`. + +Prefer writing the body to a temporary file first, then: + +```bash +gh pr edit --title "" --body-file /tmp/pr-body.md +``` + +8. Re-run validation and report whether the PR metadata is now compliant. + +```bash +python3 .codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py +``` + +## Body Guidance + +- Keep `Description` prose-first and specific to the branch. +- Reflect the branch as it exists now, especially after rebases, cleanups, or partial reverts. +- Summarize the real file-level themes from the live diff instead of copying commit messages mechanically. +- Put issue references in `Related Issue`; if there is no issue, say so plainly instead of deleting the section. +- In `Types of changes`, check only the boxes that actually apply. +- In `Checklist`, preserve all repo checklist items and mark only the items that are true. +- Do not remove required sections just because the PR is small. +- Keep the body concise; do not turn it into a changelog dump. + +## Title Guidance + +Good examples: +- `fix(node): normalize remote chunk parsing` +- `chore(manifest): drop extra compat cleanup` +- `docs(agents): prefer normalized webpack path requires` + +Bad examples: +- `update pr` +- `fix stuff` +- `[codex] cleanup` + +## Validation + +Use the helper script to detect: +- non-conventional PR titles +- missing or reordered template sections +- missing repo checklist items + +The script validates either the current PR from `gh` or explicit `--title` / `--body-file` input. diff --git a/.codex/skills/gh-pr-metadata/agents/openai.yaml b/.codex/skills/gh-pr-metadata/agents/openai.yaml new file mode 100644 index 00000000000..178747d3983 --- /dev/null +++ b/.codex/skills/gh-pr-metadata/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: 'GH PR Metadata' + short_description: 'Normalize PR title and body' + default_prompt: "Use $gh-pr-metadata to update this repo's PR title and body to match the template and conventional-commit style." diff --git a/.codex/skills/gh-pr-metadata/references/repo-pr-format.md b/.codex/skills/gh-pr-metadata/references/repo-pr-format.md new file mode 100644 index 00000000000..cfe350d6ec2 --- /dev/null +++ b/.codex/skills/gh-pr-metadata/references/repo-pr-format.md @@ -0,0 +1,45 @@ +# Repo PR Format + +Source files: +- `.github/pull_request_template.md` +- `AGENTS.md` + +## Required PR Body Sections + +Keep these sections in this order: + +1. `## Description` +2. `## Related Issue` +3. `## Types of changes` +4. `## Checklist` + +## Required Checklist Items + +Types of changes: +- `- [ ] Docs change / refactoring / dependency upgrade` +- `- [ ] Bug fix (non-breaking change which fixes an issue)` +- `- [ ] New feature (non-breaking change which adds functionality)` + +Checklist: +- `- [ ] I have added tests to cover my changes.` +- `- [ ] All new and existing tests passed.` +- `- [ ] I have updated the documentation.` + +## Title Convention + +Prefer conventional-commit style: + +```text +type(scope): short summary +``` + +Examples: +- `fix(node): normalize remote chunk parsing` +- `docs(agents): prefer normalized webpack path requires` +- `chore(manifest): drop extra compat cleanup` + +Avoid: +- bracketed prefixes like `[codex]` +- vague summaries like `update pr` +- titles that do not describe the branch's actual change + diff --git a/.codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py b/.codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py new file mode 100644 index 00000000000..0ec85f28bcf --- /dev/null +++ b/.codex/skills/gh-pr-metadata/scripts/validate_pr_metadata.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path + + +TITLE_RE = ( + r"^(build|chore|ci|docs|feat|fix|perf|refactor|revert|test)" + r"(\([^)]+\))?!?: .+" +) + +DEFAULT_TEMPLATE = """## Description + + + + +## Related Issue + + + + + + +## Types of changes + +- [ ] Docs change / refactoring / dependency upgrade +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) + +## Checklist + +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. +- [ ] I have updated the documentation. +""" + + +def run(cmd: list[str], cwd: Path) -> str: + proc = subprocess.run( + cmd, + cwd=str(cwd), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + if proc.returncode != 0: + raise RuntimeError(proc.stderr.strip() or proc.stdout.strip()) + return proc.stdout + + +def read_current_pr(cwd: Path) -> tuple[str, str]: + raw = run( + ["gh", "pr", "view", "--json", "title,body", "--jq", "{title: .title, body: .body}"], + cwd, + ) + data = json.loads(raw) + return data["title"], data["body"] or "" + + +def load_body(args: argparse.Namespace, cwd: Path) -> tuple[str, str]: + if args.title or args.body or args.body_file: + title = args.title or "" + if args.body_file: + body = Path(args.body_file).read_text() + else: + body = args.body or "" + return title, body + return read_current_pr(cwd) + + +def load_repo_template(cwd: Path) -> str: + template_path = cwd / ".github" / "pull_request_template.md" + if template_path.exists(): + return template_path.read_text() + return DEFAULT_TEMPLATE + + +def parse_template_requirements(template: str) -> tuple[list[str], list[str], list[str]]: + sections: list[str] = [] + type_lines: list[str] = [] + checklist_lines: list[str] = [] + current_section = "" + + for raw_line in template.splitlines(): + line = raw_line.strip() + if line.startswith("## "): + sections.append(line) + current_section = line + continue + if not line.startswith("- [ ] "): + continue + if current_section == "## Types of changes": + type_lines.append(line) + elif current_section == "## Checklist": + checklist_lines.append(line) + + return sections, type_lines, checklist_lines + + +def validate_title(title: str) -> list[str]: + import re + + errors: list[str] = [] + if not title: + errors.append("title is empty") + return errors + if not re.match(TITLE_RE, title): + errors.append( + "title does not match conventional format " + "(expected `type(scope): summary` or `type: summary`)" + ) + if title.startswith("["): + errors.append("title should not start with a bracketed prefix") + return errors + + +def validate_body( + body: str, + required_sections: list[str], + required_type_lines: list[str], + required_checklist_lines: list[str], +) -> list[str]: + errors: list[str] = [] + positions: list[int] = [] + + for section in required_sections: + idx = body.find(section) + if idx == -1: + errors.append(f"missing section: {section}") + positions.append(idx) + + valid_positions = [p for p in positions if p != -1] + if valid_positions and valid_positions != sorted(valid_positions): + errors.append("required sections are out of order") + + for line in required_type_lines: + if line not in body: + errors.append(f"missing type checkbox: {line}") + + for line in required_checklist_lines: + if line not in body: + errors.append(f"missing checklist item: {line}") + + return errors + + +def print_template(cwd: Path) -> None: + sys.stdout.write(load_repo_template(cwd)) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Validate PR title/body against repo conventions." + ) + parser.add_argument("--title", help="Explicit PR title to validate.") + parser.add_argument("--body", help="Explicit PR body to validate.") + parser.add_argument("--body-file", help="Path to a PR body file to validate.") + parser.add_argument( + "--repo-root", + default=".", + help="Repo root. Defaults to current working directory.", + ) + parser.add_argument( + "--print-template", + action="store_true", + help="Print a repo-compliant PR body scaffold and exit.", + ) + parser.add_argument( + "--format", + choices=["text", "json"], + default="text", + help="Output format.", + ) + args = parser.parse_args() + + cwd = Path(args.repo_root).resolve() + if args.print_template: + print_template(cwd) + return 0 + + required_sections, required_type_lines, required_checklist_lines = ( + parse_template_requirements(load_repo_template(cwd)) + ) + title, body = load_body(args, cwd) + + errors = validate_title(title) + validate_body( + body, + required_sections, + required_type_lines, + required_checklist_lines, + ) + payload = { + "ok": not errors, + "title": title, + "errors": errors, + } + + if args.format == "json": + sys.stdout.write(json.dumps(payload, indent=2) + "\n") + else: + if errors: + sys.stdout.write("PR metadata validation failed:\n") + for error in errors: + sys.stdout.write(f"- {error}\n") + else: + sys.stdout.write("PR metadata validation passed.\n") + + return 0 if not errors else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.gitignore b/.gitignore index 14967688977..5addad60b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,8 @@ packages/enhanced/generated/** !/.codex/skills/ !/.codex/skills/turborepo/ !/.codex/skills/turborepo/** +.codex/skills/**/__pycache__/ +.codex/skills/**/*.pyc .pnpm-store/ apps/rsc-demo/**/build/ apps/rsc-demo/e2e/test-results/ diff --git a/packages/nextjs-mf/src/plugins/AddRuntimeRequirementToPromiseExternalPlugin.ts b/packages/nextjs-mf/src/plugins/AddRuntimeRequirementToPromiseExternalPlugin.ts index 17002c92357..094437b9bd8 100644 --- a/packages/nextjs-mf/src/plugins/AddRuntimeRequirementToPromiseExternalPlugin.ts +++ b/packages/nextjs-mf/src/plugins/AddRuntimeRequirementToPromiseExternalPlugin.ts @@ -1,8 +1,6 @@ import type { Compiler, WebpackPluginInstance } from 'webpack'; -export class AddRuntimeRequirementToPromiseExternal - implements WebpackPluginInstance -{ +export class AddRuntimeRequirementToPromiseExternal implements WebpackPluginInstance { apply(compiler: Compiler) { compiler.hooks.compilation.tap( 'AddRuntimeRequirementToPromiseExternal', diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts index ca327dabedd..3ce572676a4 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts @@ -34,21 +34,13 @@ import logger from '../../logger'; const resolveRuntimePluginPath = (): string => process.env.IS_ESM_BUILD === 'true' - ? require.resolve( - '@module-federation/nextjs-mf/dist/src/plugins/container/runtimePlugin.mjs', - ) - : require.resolve( - '@module-federation/nextjs-mf/dist/src/plugins/container/runtimePlugin.js', - ); + ? require.resolve('@module-federation/nextjs-mf/dist/src/plugins/container/runtimePlugin.mjs') + : require.resolve('@module-federation/nextjs-mf/dist/src/plugins/container/runtimePlugin.js'); const resolveNoopPath = (): string => process.env.IS_ESM_BUILD === 'true' - ? require.resolve( - '@module-federation/nextjs-mf/dist/src/federation-noop.mjs', - ) - : require.resolve( - '@module-federation/nextjs-mf/dist/src/federation-noop.js', - ); + ? require.resolve('@module-federation/nextjs-mf/dist/src/federation-noop.mjs') + : require.resolve('@module-federation/nextjs-mf/dist/src/federation-noop.js'); const resolveNodeRuntimePluginPath = (): string => { const nodePackageRoot = path.dirname( @@ -270,4 +262,3 @@ export class NextFederationPlugin { } export default NextFederationPlugin; - diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts index 964577ba9a6..642443fe5fc 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts @@ -16,21 +16,13 @@ import path from 'path'; const resolveFixImageLoaderPath = (): string => process.env.IS_ESM_BUILD === 'true' - ? require.resolve( - '@module-federation/nextjs-mf/dist/src/loaders/fixImageLoader.mjs', - ) - : require.resolve( - '@module-federation/nextjs-mf/dist/src/loaders/fixImageLoader.js', - ); + ? require.resolve('@module-federation/nextjs-mf/dist/src/loaders/fixImageLoader.mjs') + : require.resolve('@module-federation/nextjs-mf/dist/src/loaders/fixImageLoader.js'); const resolveFixUrlLoaderPath = (): string => process.env.IS_ESM_BUILD === 'true' - ? require.resolve( - '@module-federation/nextjs-mf/dist/src/loaders/fixUrlLoader.mjs', - ) - : require.resolve( - '@module-federation/nextjs-mf/dist/src/loaders/fixUrlLoader.js', - ); + ? require.resolve('@module-federation/nextjs-mf/dist/src/loaders/fixUrlLoader.mjs') + : require.resolve('@module-federation/nextjs-mf/dist/src/loaders/fixUrlLoader.js'); /** * Set up default shared values based on the environment. * @param {boolean} isServer - Boolean indicating if the code is running on the server. diff --git a/packages/node/.eslintrc.json b/packages/node/.eslintrc.json index 07fe19291a2..e7eaba61005 100644 --- a/packages/node/.eslintrc.json +++ b/packages/node/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "dist/**/*", "**/*.d.ts"], "env": { "jest": true }, @@ -11,6 +11,7 @@ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { + "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-restricted-imports": [ "error", { @@ -41,7 +42,23 @@ }, { "files": ["*.js", "*.jsx"], - "rules": {} + "env": { + "node": true, + "es2021": true + }, + "rules": { + "no-undef": "off", + "no-unused-vars": "off" + } + }, + { + "files": [ + "src/plugins/AutomaticPublicPathPlugin.ts", + "src/plugins/StreamingTargetPlugin.ts" + ], + "rules": { + "@typescript-eslint/no-empty-object-type": "off" + } } ] } diff --git a/packages/node/src/plugins/CommonJsChunkLoadingPlugin.ts b/packages/node/src/plugins/CommonJsChunkLoadingPlugin.ts index d98cbb7e2bd..c7b42e48685 100644 --- a/packages/node/src/plugins/CommonJsChunkLoadingPlugin.ts +++ b/packages/node/src/plugins/CommonJsChunkLoadingPlugin.ts @@ -7,8 +7,7 @@ const StartupChunkDependenciesPlugin = require( import ChunkLoadingRuntimeModule from './DynamicFilesystemChunkLoadingRuntimeModule'; import AutoPublicPathRuntimeModule from './RemotePublicPathRuntimeModule'; -interface DynamicFilesystemChunkLoadingOptions - extends ModuleFederationPluginOptions { +interface DynamicFilesystemChunkLoadingOptions extends ModuleFederationPluginOptions { baseURI: Compiler['options']['output']['publicPath']; promiseBaseURI?: string; remotes: Record; diff --git a/packages/node/src/utils/flush-chunks.ts b/packages/node/src/utils/flush-chunks.ts index c767a5a6d59..49729e17dc6 100644 --- a/packages/node/src/utils/flush-chunks.ts +++ b/packages/node/src/utils/flush-chunks.ts @@ -97,8 +97,19 @@ const createShareMap = () => { // @ts-ignore const processChunk = async (chunk, shareMap, hostStats) => { const chunks = new Set(); - const [remote, req] = chunk.split('/'); - const request = './' + req; + const normalizedChunk = chunk.includes('->') + ? chunk.replace('->', '/') + : chunk; + const remoteSeparatorIndex = normalizedChunk.indexOf('/'); + const remote = + remoteSeparatorIndex === -1 + ? normalizedChunk + : normalizedChunk.slice(0, remoteSeparatorIndex); + const req = + remoteSeparatorIndex === -1 + ? '' + : normalizedChunk.slice(remoteSeparatorIndex + 1); + const request = req?.startsWith('./') ? req : './' + req; const knownRemotes = getAllKnownRemotes(); //@ts-ignore if (!knownRemotes[remote]) { diff --git a/packages/node/src/utils/hot-reload.ts b/packages/node/src/utils/hot-reload.ts index caed2d3bab8..1a259b5a95a 100644 --- a/packages/node/src/utils/hot-reload.ts +++ b/packages/node/src/utils/hot-reload.ts @@ -125,6 +125,8 @@ const searchCache = function ( } }; +globalThis.moduleGraphDirty = false; + const hashmap = globalThis.mfHashMap || ({} as Record); globalThis.moduleGraphDirty = false;