Skip to content

feat(tegg): declarative module plugin mechanism#6021

Open
gxkl wants to merge 71 commits into
nextfrom
feat/module-plugin-core
Open

feat(tegg): declarative module plugin mechanism#6021
gxkl wants to merge 71 commits into
nextfrom
feat/module-plugin-core

Conversation

@gxkl

@gxkl gxkl commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

Tegg's framework-level extensions (AOP, DAL, controller registration, config source, ...) all rely on the HOST hand-registering lifecycle hooks in its boot code. A module cannot declare "run this when a load unit is created" by itself, so:

  • the same registration logic is duplicated between the egg plugin boot and the standalone Runner;
  • every new runtime shape (e.g. a service worker runtime) has to copy the whole assembly again;
  • modules are not self-contained.

This PR ports the declarative module plugin design from eggjs/tegg#325 (merged to the stale standalone-next branch, never reached master) to the next architecture, and completes the two parts #325 left open: egg-host wiring (inner object protos used to be silently ignored in app mode) and bootstrapping the built-in hooks themselves (the #handleCompatibility TODO).

Design

A plain eggModule package can now provide framework extensions declaratively:

  • @InnerObjectProto — framework inner object (a SingletonProto with EGG_INNER_OBJECT_PROTO_IMPL_TYPE), diverted by the loader into ModuleDescriptor.innerObjectClazzList, never into business load units.
  • @EggLifecycleProto five typed variants (LoadUnit / LoadUnitInstance / EggPrototype / EggObject / EggContext) — a DI-capable hook object, auto-registered into the matching scope-aware LifecycleUtil and deregistered symmetrically.

Two-phase ordering (the load-bearing constraint). GlobalGraph.create() only adds nodes; build() adds inject edges and consumes registerBuildHook hooks once at its end (late registration is silently lost). Both hosts now boot as:

  1. scan modules → GlobalGraph.create (nodes only)
  2. create AND instantiate the InnerObjectLoadUnit (own topologically sorted proto graph, cycle detection, hard error on missing non-optional deps) — hooks register here, including graph build hooks from @LifecyclePostInject (see AopGraphHookRegistrar)
  3. build() / sort() → business load units → business instances
  4. destroy in reverse creation order (inner unit last)

Commits

commit scope
feat(core): add module plugin core mechanism decorators + metadata (EggInnerObjectPrototypeImpl, InjectObjectPrototypeFinder / ProtoGraphUtils extracted with a proto-name index replacing the O(n·m·n) scan) + runtime (EggInnerObjectImpl runs only decorator-declared self lifecycle; host-agnostic InnerObjectLoadUnit(Builder/Instance)) + loader diversion (manifest getDecoratedFiles covers the new list so bundle mode still re-imports those files)
feat(standalone): two-phase StandaloneApp with module plugin support BREAKING: Runner renamed to StandaloneApp (no alias; main()/preLoad() unchanged). Explicit boot phases, frameworkDeps option, bundle-mode manifest/loaderFS consumption + StandaloneApp.loadMetadata(), tegg#325 test suite ported
feat(tegg-plugin): instantiate InnerObjectLoadUnit in app mode ModuleHandler.init() phases via EggModuleLoader.initGraph()/load() split; the same module plugin package behaves identically under both hosts
feat(tegg): convert built-in framework hooks to module plugins AOP (incl. AopGraphHookRegistrar for the crossCut/pointCut build hooks), DAL (constructor args → @Inject of host-provided moduleConfigs/runtimeConfig/logger inner objects, PRIVATE on the egg host), ConfigSource; vestigial LoadUnitMultiInstanceProtoHook registration dropped (empty preCreate, unconsumed static set); builder dedupes by class since a package may be both hard-fed and scanned as an eggModule (e.g. @eggjs/dal-plugin)
docs(wiki): record tegg module plugin architecture concept page + log

Test evidence

  • New coverage: decorator metadata (7), loader diversion (3), InnerObjectLoadUnit integration incl. decorator-only-lifecycle semantics / auto register+deregister / cycle detection / missing-dep hard error (4), standalone module plugin suite ported from tegg#325 — five lifecycle proto types + inner object PUBLIC/PRIVATE semantics (7), app-mode module plugin fixture (2).
  • Regression: 15 tegg projects (core + plugins + standalone) — 76 files / 296 tests green, MultiApp isolation green; full-repo run has zero failures related to this change (remaining ones verified as pre-existing/env: dns-cache needs a fresh ut install, multipart fails on clean next too, onerror/development are flaky-on-rerun).

Notes

  • Independent of fix(controller): register middlewareGraphHook before global graph build #6020 (no overlapping files; git merge-tree clean; combined-tree run of both test suites green).
  • TeggScope rules kept: no new process-global mutable state; lifecycle registration happens inside the host scope via the scope-aware utils; type-keyed creator registries stay global per the multi-app guidelines.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added declarative support for inner objects and lifecycle prototypes, including scope-aware lifecycle hook registration.
    • Introduced a new standalone app bootstrap flow with module/manifest caching and loaderFS support.
    • Exposed AOP runtime registrars to manage context advice for request-scoped weaving.
  • Bug Fixes
    • Improved module resolution and manifest generation with package-aware matching and clearer optional/missing behavior.
    • Fixed startup/shutdown hook registration to avoid silent non-weaving and ensure proper teardown.
  • Documentation
    • Updated concept and usage docs for the module plugin mechanism and the standalone app API.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds declarative inner-object and lifecycle decorators, routes prototype resolution through new metadata and graph utilities, introduces inner-object runtime/load-unit support, migrates AOP/config/DAL and tegg boot wiring to the new model, replaces the standalone Runner with StandaloneApp, and updates supporting tests and docs.

Changes

Tegg Module Plugin Mechanism

Layer / File(s) Summary
Core decorators and metadata contracts
tegg/core/core-decorator/src/decorator/*, tegg/core/core-decorator/src/util/PrototypeUtil.ts, tegg/core/types/src/core-decorator/*, tests
Adds InnerObjectProto, EggLifecycleProto, and DefineModuleQualifier decorators, plus lifecycle/inner-object metadata and public type exports.
Prototype metadata and dependency graph
tegg/core/metadata/src/impl/*, tegg/core/metadata/src/model/*
Adds inner-object prototype implementations, inject-object resolution helpers, descriptor shape updates, and graph lookup utilities.
Loader and manifest classification
tegg/core/loader/src/LoaderFactory.ts, tegg/core/test-util/src/LoaderUtil.ts, loader tests/fixtures
Diverts inner-object classes into innerObjectClazzList, extends manifest building, and updates loader test scaffolding.
Inner object runtime and load-unit execution
tegg/core/runtime/src/impl/*, runtime tests
Implements inner-object object creation, load units, lifecycle-instance registration, and host-provided object support.
Plugin migrations
tegg/core/aop-runtime/*, tegg/plugin/aop/*; tegg/plugin/config/*; tegg/plugin/dal/*
Converts AOP/config/DAL hooks and managers to decorator-driven module-plugin wiring.
Tegg boot wiring and standalone bootstrap
tegg/plugin/tegg/*, tegg/standalone/standalone/*
Reworks app/module loading order, replaces Runner with StandaloneApp, and adds standalone fixtures/tests for the new flow.
Wiki docs
wiki/*
Adds the module-plugin concept page and related wiki updates.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • eggjs/egg#5846: Both PRs affect manifest and decorated-file collection for inner-object-aware module loading.
  • eggjs/egg#5973: Both PRs adjust tegg/plugin/config/src/app.ts module-reference resolution and config loading around manifest-derived paths.
  • eggjs/egg#5993: Both PRs touch AOP graph-hook registration and the timing of build-hook wiring in app boot.

Suggested reviewers: jerryliang64, killagu, fengmk2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the PR’s main change: adding a declarative module plugin mechanism for Tegg.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/module-plugin-core

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 4, 2026

Copy link
Copy Markdown

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: d49c104
Status: ✅  Deploy successful!
Preview URL: https://9faaeeff.egg-cci.pages.dev
Branch Preview URL: https://feat-module-plugin-core.egg-cci.pages.dev

View logs

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.88%. Comparing base (767ac19) to head (facb7f1).
⚠️ Report is 2 commits behind head on next.

❗ There is a different number of reports uploaded between BASE (767ac19) and HEAD (facb7f1). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (767ac19) HEAD (facb7f1)
3 2
Additional details and impacted files
@@             Coverage Diff             @@
##             next    #6021       +/-   ##
===========================================
- Coverage   81.95%   70.88%   -11.08%     
===========================================
  Files         678       18      -660     
  Lines       20800      735    -20065     
  Branches     4147      181     -3966     
===========================================
- Hits        17047      521    -16526     
+ Misses       3235      181     -3054     
+ Partials      518       33      -485     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@socket-security

socket-security Bot commented Jul 4, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 4, 2026

Copy link
Copy Markdown

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: d49c104
Status: ✅  Deploy successful!
Preview URL: https://c813e992.egg-v3.pages.dev
Branch Preview URL: https://feat-module-plugin-core.egg-v3.pages.dev

View logs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements a declarative module plugin mechanism using @InnerObjectProto and @EggLifecycleProto decorators, enabling framework extensions to be loaded into an InnerObjectLoadUnit before the business graph is built for both standalone and egg hosts. It also converts AOP, DAL, and ConfigSource hooks into module plugins and renames Runner to StandaloneApp. The review feedback suggests maintaining consistency by using .ts extensions instead of .js in import/export paths within aop-runtime, and defensively checking if caught exceptions are Error instances before accessing their message property in StandaloneApp.ts and main.ts to avoid runtime type errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +5 to +6
import { crossCutGraphHook } from './CrossCutGraphHook.js';
import { pointCutGraphHook } from './PointCutGraphHook.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository's general rules, TypeScript source file imports should use the .ts extension instead of .js to maintain consistency across the codebase.

Suggested change
import { crossCutGraphHook } from './CrossCutGraphHook.js';
import { pointCutGraphHook } from './PointCutGraphHook.js';
import { crossCutGraphHook } from './CrossCutGraphHook.ts';
import { pointCutGraphHook } from './PointCutGraphHook.ts';
References
  1. In this repository, use '.ts' extensions in import/export paths for TypeScript source files to maintain consistency with the existing convention across source and test files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

不采纳这里的 .ts import 建议。tegg 源码当前按 ESM 输出规范使用 .js import specifier,同包其它 TS 源码也是这个模式;改成 .ts 反而会和现有构建/类型检查约定不一致。

Comment on lines +4 to +7
import { AopGraphHookRegistrar } from './AopGraphHookRegistrar.js';
import { EggObjectAopHook } from './EggObjectAopHook.js';
import { EggPrototypeCrossCutHook } from './EggPrototypeCrossCutHook.js';
import { LoadUnitAopHook } from './LoadUnitAopHook.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository's general rules, TypeScript source file imports should use the .ts extension instead of .js to maintain consistency across the codebase.

Suggested change
import { AopGraphHookRegistrar } from './AopGraphHookRegistrar.js';
import { EggObjectAopHook } from './EggObjectAopHook.js';
import { EggPrototypeCrossCutHook } from './EggPrototypeCrossCutHook.js';
import { LoadUnitAopHook } from './LoadUnitAopHook.js';
import { AopGraphHookRegistrar } from './AopGraphHookRegistrar.ts';
import { EggObjectAopHook } from './EggObjectAopHook.ts';
import { EggPrototypeCrossCutHook } from './EggPrototypeCrossCutHook.ts';
import { LoadUnitAopHook } from './LoadUnitAopHook.ts';
References
  1. In this repository, use '.ts' extensions in import/export paths for TypeScript source files to maintain consistency with the existing convention across source and test files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

不改,原因同上:tegg TS 源码使用 .js import specifier 才符合当前 ESM 输出约定。这个 thread 现在也已经是 outdated。

Comment thread tegg/core/aop-runtime/src/index.ts Outdated
Comment on lines +7 to +8
export * from './AopGraphHookRegistrar.js';
export * from './AopInnerObjectClazzList.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository's general rules, TypeScript source file exports should use the .ts extension instead of .js to maintain consistency across the codebase.

Suggested change
export * from './AopGraphHookRegistrar.js';
export * from './AopInnerObjectClazzList.js';
export * from './AopGraphHookRegistrar.ts';
export * from './AopInnerObjectClazzList.ts';
References
  1. In this repository, use '.ts' extensions in import/export paths for TypeScript source files to maintain consistency with the existing convention across source and test files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

不改,原因同上:这里的 export specifier 也应保持 .js,和 tegg 现有 ESM 源码约定一致。这个 thread 现在已 outdated。

Comment on lines +321 to +324
ctx.destroy(lifecycle).catch((e) => {
e.message = `[tegg/standalone] destroy tegg context failed: ${e.message}`;
console.warn(e);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When handling a caught exception, defensively check if the caught value is an Error instance before accessing or mutating its message property. Mutating e.message directly can throw a TypeError in strict mode if e is a primitive value. Use String(e) as a fallback to safely format the error message.

Suggested change
ctx.destroy(lifecycle).catch((e) => {
e.message = `[tegg/standalone] destroy tegg context failed: ${e.message}`;
console.warn(e);
});
ctx.destroy(lifecycle).catch((e) => {
const msg = e instanceof Error ? e.message : String(e);
console.warn('[tegg/standalone] destroy tegg context failed: ' + msg);
});
References
  1. When creating a new Error from a caught exception, check if the caught value is an 'Error' instance before accessing its 'message' property. Use 'String(err)' as a fallback for non-Error values to ensure a meaningful error message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已按这个方向处理:destroy catch 里不再直接假设 caught value 一定是 Error;会先做 Error 判断,非 Error 用 String(e) 兜底格式化,避免 primitive throw 时二次抛错。这个 thread 目前已 outdated。

Comment thread tegg/standalone/standalone/src/main.ts Outdated
Comment on lines 32 to 35
app.destroy().catch((e) => {
e.message = `[tegg/standalone] destroy tegg failed: ${e.message}`;
console.warn(e);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When handling a caught exception, defensively check if the caught value is an Error instance before accessing or mutating its message property. Mutating e.message directly can throw a TypeError in strict mode if e is a primitive value. Use String(e) as a fallback to safely format the error message.

Suggested change
app.destroy().catch((e) => {
e.message = `[tegg/standalone] destroy tegg failed: ${e.message}`;
console.warn(e);
});
app.destroy().catch((e) => {
const msg = e instanceof Error ? e.message : String(e);
console.warn('[tegg/standalone] destroy tegg failed: ' + msg);
});
References
  1. When creating a new Error from a caught exception, check if the caught value is an 'Error' instance before accessing its 'message' property. Use 'String(err)' as a fallback for non-Error values to ensure a meaningful error message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已处理:main 成功/失败路径里都会等待 app destroy,并且 destroy catch 对非 Error 值做 String(e) 兜底,不再直接改 e.message。这个 thread 目前已 outdated。

@gxkl gxkl changed the title [WIP] feat(tegg): declarative module plugin mechanism feat(tegg): declarative module plugin mechanism Jul 6, 2026
@gxkl gxkl requested a review from killagu July 6, 2026 00:20
@gxkl gxkl marked this pull request as ready for review July 6, 2026 01:38
Copilot AI review requested due to automatic review settings July 6, 2026 01:38
gxkl and others added 18 commits July 6, 2026 09:39
Introduce the declarative framework-extension (module plugin) core so a
plain tegg module can provide lifecycle hooks and framework inner
objects via decorators, instead of the host hand-registering every hook
in its boot code. Ported from eggjs/tegg#325 (standalone-next) and
adapted to the next-branch architecture.

- core-decorator/types: @InnerObjectProto (framework inner object,
  a SingletonProto with EGG_INNER_OBJECT_PROTO_IMPL_TYPE) and
  @EggLifecycleProto with the five typed variants
  (LoadUnit/LoadUnitInstance/EggPrototype/EggObject/EggContext);
  PrototypeUtil metadata accessors.
- loader: divert inner object classes into
  ModuleDescriptor.innerObjectClazzList; getDecoratedFiles now covers
  the new list so bundle/manifest mode still re-imports those files.
- metadata: EggInnerObjectPrototypeImpl (resolves injects at create
  time); InjectObjectPrototypeFinder extracted from EggPrototypeBuilder
  (behavior unchanged); ProtoGraphUtils extracted from GlobalGraph with
  a proto-name index replacing the O(n*m*n) scan;
  ProtoDescriptorHelper.createByInstanceClazz supports define/instance
  module separation.
- runtime: EggInnerObjectImpl runs only decorator-declared self
  lifecycle methods (hook-callback names never double as self
  lifecycle); host-agnostic InnerObjectLoadUnit(+Instance/Builder)
  instantiates inner objects on a dedicated topologically-sorted proto
  graph (cycle detection, hard error on missing non-optional deps) and
  auto registers/deregisters lifecycle protos by declared type via the
  scope-aware lifecycle utils.

The InnerObjectLoadUnit is designed to be instantiated before the
business GlobalGraph build so registered hooks (including future
declarative graph build hooks) land inside their consumption windows;
host wiring for standalone/app follows in separate PRs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename Runner to StandaloneApp (no Runner alias kept — 4.x breaking
change; main()/preLoad() entries are unchanged) and restructure boot
into explicit phases so module plugins work end to end:

1. scan modules + create the business GlobalGraph (nodes only)
2. create AND instantiate the InnerObjectLoadUnit — host-provided inner
   objects plus every scanned @InnerObjectProto/@XxxLifecycleProto class
   (topologically ordered); lifecycle protos auto-register
3. build()/sort() the business graph and create module load units
4. instantiate module load units

Deferring build/sort until after the inner load unit is instantiated
restores the tegg#325 two-phase ordering: hooks registered by lifecycle
protos (including graph build hooks registered in @LifecyclePostInject)
land before their consumption windows. Destroy now runs in reverse
creation order so lifecycle protos stay registered until every object
they may hook is torn down.

Also:
- new frameworkDeps option: framework module plugins scanned ahead of
  the app's own modules (service worker runtime packages plug in here)
- bundle-mode support: EggModuleLoader accepts a tegg manifest +
  loaderFS (reuses precomputed decorated files instead of globbing) and
  StandaloneApp.loadMetadata() provides the scan-only manifest
  generation entry for bundlers
- replace StandaloneLoadUnit / StandaloneInnerObjectProto /
  StandaloneInnerObject with the host-agnostic core implementations
  (InnerObjectLoadUnit / ProvidedInnerObjectProto)
- port the tegg#325 module plugin test suite (five lifecycle proto
  types, inner object DI, PUBLIC/PRIVATE access semantics)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire the module plugin mechanism into the egg host, the part tegg#325
left out (inner object / lifecycle protos were silently ignored in app
mode). ModuleHandler.init() now runs in phases:

1. EggModuleLoader.initGraph(): scan modules, create the business
   GlobalGraph (nodes only) and flush buffered build hooks
2. create AND instantiate the InnerObjectLoadUnit from every scanned
   module's innerObjectClazzList — lifecycle protos auto-register with
   DI wired, before any business load unit exists
3. EggModuleLoader.load(): APP load unit + build()/sort() + module load
   units — declarative graph build hooks registered in step 2 land
   before build() consumes them
4. instantiate business load units (inner unit skips egg compatible
   mounting)

destroy() runs in reverse creation order so lifecycle protos stay
registered until every object they may hook is torn down.

The same module plugin package now behaves identically under the egg
host and the standalone host. Covered by a fixture app whose module
provides @InnerObjectProto/@LoadUnitLifecycleProto/@EggObjectLifecycleProto
classes; MultiApp isolation regression stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete the module plugin bootstrap (the #handleCompatibility TODO from
tegg#325): every hook the standalone host used to hand-register is now a
declarative module plugin class, shared verbatim by both hosts.

- aop: LoadUnitAopHook / EggPrototypeCrossCutHook / EggObjectAopHook are
  @XxxLifecycleProto classes injecting an @InnerObjectProto
  CrosscutAdviceFactory (optional constructor kept for manual paths);
  the crossCut/pointCut graph build hooks are registered by the new
  AopGraphHookRegistrar from @LifecyclePostInject — instantiated after
  graph creation and before build(), the only valid window. Exported as
  AOP_INNER_OBJECT_CLAZZ_LIST.
- dal: the three hooks are @XxxLifecycleProto classes injecting the
  host-provided moduleConfigs / runtimeConfig / logger inner objects
  instead of constructor args. Exported as DAL_INNER_OBJECT_CLAZZ_LIST.
- config source: both ConfigSourceLoadUnitHook copies decorated with
  @LoadUnitLifecycleProto.
- LoadUnitMultiInstanceProtoHook registration dropped on both hosts —
  its preCreate is empty and the static set has no consumers.

Host wiring:
- ModuleHandler.registerInnerObjectClazzList() buffers plugin-provided
  classes (aop/dal boots now register one list in configDidLoad instead
  of constructing and registering hooks); the egg host provides
  moduleConfigs/runtimeConfig/logger as PRIVATE inner objects so they
  stay visible to inner objects only and never pollute cross-unit
  resolution (InnerObject/ProvidedInnerObjectProto gain accessLevel).
- StandaloneApp feeds the built-in lists, always provides a logger inner
  object, and drops all manual register/delete bookkeeping — lifecycle
  protos deregister with the InnerObjectLoadUnit.
- InnerObjectLoadUnitBuilder dedupes by class: hosts hard-feed built-in
  lists while the owning package may also be scanned as an eggModule
  (e.g. @eggjs/dal-plugin as a module dependency) — first add wins.
- test-util LoaderUtil mirrors the production loader and diverts inner
  object classes out of module load units.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inner unit

Review follow-ups on the module plugin PR:

- ConfigSourceLoadUnitHook existed twice (standalone + egg plugin copy) —
  exactly the duplication the module plugin mechanism is meant to end. Move
  the single host-agnostic class to @eggjs/metadata (hook/); both hosts now
  feed the same import into their inner-object clazz lists.
- ModuleHandler tracked the inner-object load unit inside the business
  loadUnits list, forcing INNER_OBJECT_LOAD_UNIT_TYPE filters in the init
  loop and contextModuleCompatible. Track it in its own field: business
  loops need no filtering, destroy still tears it down last (its lifecycle
  protos must outlive every hooked object).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up: hand-fed inner-object class lists defeated the module
plugin's purpose — the packages already declare eggModule metadata, so
consume them AS modules:

- StandaloneApp: aop-runtime/dal-plugin join the regular module scan as
  built-in framework modules (their @InnerObjectProto/@XxxLifecycleProto
  classes are diverted into the InnerObjectLoadUnit by loadApp); the
  AOP_/DAL_INNER_OBJECT_CLAZZ_LIST exports are gone. Module references now
  dedupe by path (an app may also depend on a built-in module directly).
- egg host: moduleHandler.registerInnerObjectModule(path) — the aop/dal
  plugins register their package as a scanned module instead of a class
  list; manifest collection includes registered framework modules.
- aop-runtime re-exports CrosscutAdviceFactory (decorated in aop-decorator)
  so the scan picks it up as a module member, and no longer needs the
  manual list file.
- LoaderUtil.filePattern: '!**/test' does not exclude descendants — add
  '!**/test/**' (and coverage) so scanning workspace packages that ship
  test dirs stays safe; built-in references also exclude test fixture
  modules from the reference scan.
- Drop the test-only optional constructor params from LoadUnitAopHook /
  EggPrototypeCrossCutHook; the aop-runtime tests wire the dependency via
  Reflect.set on the DI property instead.

registerInnerObjectClazzList stays for host-boot wiring (e.g. the shared
ConfigSourceLoadUnitHook) and edge hosts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the contract

Second review round:

- Drop registerInnerObjectModule (added one commit ago): a central register
  is exactly what module plugins should not need. The egg host resolves
  module plugins from what already exists: tegg-config's ModuleScanner picks
  eggModule-declaring packages up from app/framework dependencies, and
  EggModuleLoader.resolveModuleReferences() fills the remaining gap by
  auto-including every ENABLED plugin whose package declares eggModule
  (ModuleConfigUtil.hasEggModule). Enabling the plugin is the whole contract.
- plugin/aop is now itself the AOP module (eggModule: teggAop) and
  re-exports the aop-runtime/aop-decorator hook classes for the scan;
  plugin/dal already declared eggModule - both app.ts registrations are
  gone. aop-runtime keeps its own eggModule for the standalone built-in.
- InnerObjectLoadUnitBuilder: remove the #seenClazzSet dual-arrival dedupe -
  reference-level path dedupe covers the legitimate case now that hand-fed
  lists are gone; a genuine double registration fails loud. Host-provided
  instances now resolve through the SAME selectProto rules as graph protos
  (name + qualifiers + access level) instead of a bare name whitelist; they
  still skip the topological sort - an already-constructed instance has no
  construction order and no outgoing edges.
- test-util buildGlobalGraph reuses LoaderFactory.loadApp for
  classification instead of re-implementing the inner-object diversion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in the graph

Third review round:

- Only plugins are modules: aop-runtime drops its eggModule declaration
  (pure library again); @eggjs/aop-plugin (eggModule: teggAop) is THE aop
  module for BOTH hosts - its egg imports are all type-only, so the
  standalone built-in reference now points at the plugin package, same as
  the egg host resolves it from the enabled-plugin list. One module name,
  one carrier. resolveModuleReferences locates the real package root from
  plugin.path (which points inside the package, through symlinks).
- Host-provided inner objects are now REAL graph nodes: same descriptors,
  same vertices, same edge resolution and topological sort as hook protos
  (ProtoNode ids include qualifiers, so multi-entry names like per-module
  moduleConfig coexist). Only the instantiation list excludes them - the
  instances already exist. The selectProto fallback branch is gone; one
  resolution path.
- aop-runtime tests no longer feed the package root as a module (hooks are
  registered manually there); fixture .egg manifest caches are stale-prone
  local artifacts, not repo files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore the pre-refactor design (StandaloneInnerObjectProto) inside the new
graph: a provided instance's descriptor is a regular ClassProtoDescriptor
whose clazz is the factory `() => obj`, with protoImplType
PROVIDED_INNER_OBJECT dispatched through the standard creator registry (the
same polymorphism point as COMPATIBLE/INNER_OBJECT proto types).
"Constructing" one returns the instance.

One uniform channel: the builder feeds every descriptor - hooks and
provided alike - through the same vertices, edge resolution, topological
sort, createProtoByDescriptor loop and instantiation loop. The sort-output
filter and the load unit's separate #innerObjects registration channel are
both gone; InnerObjectLoadUnitOptions.innerObjects is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vestige of the intermediate round where aop-runtime itself was the scanned
module; plugin/aop (the actual aop module) re-exports
CrosscutAdviceFactory from aop-decorator directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed-plugin fallback

Fourth review round, closing the discovery question:

The enabled-plugin auto-include (resolveModuleReferences + hasEggModule)
was solving a fixture problem with a mechanism. The pre-existing contract
already covers plugins-as-modules on BOTH declaration styles:

- explicit config/module.json: an npm-form reference to the plugin package
  (the dal fixture has carried `{"package": "../../../../"}` all along -
  that is HOW its plugin module loaded before this PR);
- scan mode: eggModule-declaring packages in app/framework dependencies
  (the standalone dal fixture declares `@eggjs/dal-plugin` in deps).

What broke was never the mechanism: the aop/dal/controller egg fixtures
enable plugins whose hooks now ride the module scan, without declaring the
plugin package as a module. Before the refactor that omission was invisible
(hooks were hand-registered). Fix the fixtures to follow the dal precedent
(`{"package": "@eggjs/aop-plugin"}` in module.json) and revert the
discovery machinery: EggModuleLoader is back to app.moduleReferences,
hasEggModule is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProvidedInnerObjectProto stored the `() => obj` factory in a field named
`clazz` typed EggProtoImplClass (inherited from the old
StandaloneInnerObjectProto trick), making constructEggObject read like an
instantiation. Rename to `objFactory: () => object` and call it directly -
the only casts left sit at the EggPrototypeLifecycleContext boundary, whose
`clazz` slot is the carrier, with comments saying so.

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Core tegg semantics with no declaring app: scan discovery would leak host
internals into user module.json. One shared class, one line per host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the config module

Review suggestion: the config-domain hook belongs to the config package, and
that package should simply BE a module. This kills the last clazz-list
registration and with it the whole registerInnerObjectClazzList API.

- @eggjs/tegg-config declares eggModule (teggConfig) and hosts the hook in
  src/lib (host-safe: its egg imports are type-only). Both hosts consume it
  as a scanned module: standalone via the third built-in framework module
  reference; the egg host via the framework-dependency channel.
- ModuleScanner now takes the RUNTIME framework dir (app.options.framework,
  what egg/mm actually resolved) over re-deriving from appPkg.egg.framework
  - the gap that made framework-deps discovery invisible in test harnesses.
  With it, packages/egg's own dependencies (aop/dal/config plugins) reach
  every fixture as OPTIONAL modules, promoted by the existing enabled loop;
  the fixture module.json declarations added earlier are retired.
- ModuleHandler.registerInnerObjectClazzList and its buffer are gone - every
  hook now arrives through the module scan; ReadModule asserts the
  production shape (business refs exact, framework refs optional).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up from asking whether standalone can reuse ModuleScanner (it can't:
the scanner's increment over readModuleReference is egg-only semantics -
single frameworkDir + optional marking gated by plugin enablement, and
standalone has no plugin system; the shared primitives already live in
common-util):

- Graph sort only gates optional modules' BUSINESS protos; their inner
  hooks were instantiated regardless, so a disabled plugin's hooks silently
  loaded. instantiateInnerObjectLoadUnit now skips unpromoted optional
  descriptors - symmetric gating.
- That gate exposed a long-dormant bug: buildAppGraph's enabled-promotion
  matched plugin.path (inside the package, through symlinks) against
  reference paths (real package roots) and never fired. Resolve the real
  package root before matching.
- StandaloneApp switches its hand-rolled path dedupe to the shared
  ModuleConfigUtil.deduplicateModules (same first-wins semantics, plus
  duplicate-name conflict detection).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gxkl added 25 commits July 9, 2026 20:31
@gxkl gxkl force-pushed the feat/module-plugin-core branch from facb7f1 to 80b9d49 Compare July 9, 2026 12:33
Copilot AI review requested due to automatic review settings July 9, 2026 12:39
@gxkl

gxkl commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

回复 @killagu 这条汇总 review: #6021 (comment)

这条漏回了,补一个逐项说明。当前 head 已更新到 d49c104fc

  1. ModuleScanner 多层自定义框架丢 hook:已改。现在从 app framework 开始沿 pkg.egg.framework 继续向上解析父框架,显式处理 framework: true / 非 string 终止和环;跨层同名 module 采用 first-wins,并对不同 path 的同名项打 warn。framework 自己的 config/module.json 里的 { "package": ... } 也改成按 framework cwd 解析。补了 framework chain、同名不同 path、同 path 去重、boolean root、cycle、framework module.json package 解析等回归。

  2. bundle/manifest 下 framework module plugin promote 失败:已改。manifest 里的 moduleReferences 现在保留 package,运行时 buildAppGraph() promote enabled plugin 时先按 package 比较,只有旧数据/缺 package 时才 fallback 到原来的 path 比较。bundle restore 仍复用 manifest 的 moduleReferencesmoduleDescriptors[].decoratedFiles,避免对不存在的 bundle 目录做 globby。另把 buildTeggManifestData() 抽到 @eggjs/tegg-loader,防止 standalone / egg 两边 manifest 字段继续漂移。

  3. disabled plugin hook:按来源拆分处理。framework 扫描出来的 module plugin 都是 optional: true,只有对应 egg plugin enable: true 时才会 promote 成非 optional;disabled 的会继续保持 optional,并在 ModuleHandler 创建 inner object load unit 时跳过。对于 app 自己通过 dependencies / config/module.json 明确声明的非 optional module,本 PR 不再用 plugin.enable 做二次过滤:这里的语义是“显式 module 声明就是加载契约”,不是 egg plugin enable 的影子开关。

  4. inner object proto id 跨模块冲突:已改。inner object descriptor 现在记录 defineModuleName / defineUnitPath,并默认加 DefineModuleQualifier;同时新增 DefineModuleQualifier 装饰器用于重名注入时显式指定来源 module。host provided object 默认 define module qualifier 为 app,因此和各 module 定义的同名 inner object 不再共用同一个 proto id;重名注入时按 tegg 标准方式用 qualifier 区分。

  5. 自定义 lifecycle type:已改成类型收窄。EggLifecycleType 现在只暴露 5 个内置 lifecycle 字面量;运行时仍保留 unknown type guard,防止 any/旧编译产物绕过类型后静默注册失败。

  6. RunnerOptions.innerObjects 静默丢弃:已改。高层 main() 里如果传旧的 innerObjects 会直接抛明确错误,提示改用 innerObjectHandlers,不再静默吞掉。

  7. standalone framework placeholder 覆盖优先级:已改。innerObjectHandlers / low-level innerObjects 中用户提供的 loggerruntimeConfigmoduleConfigs 会覆盖 framework placeholder,并打 warning;moduleConfig 是追加/扩展语义,也会打 warning。这样保留覆盖能力,同时把危险覆盖显式暴露出来。

  8. 启动中途失败泄漏 inner LoadUnitInstance:已改。ModuleHandler 现在在创建 inner object instance 后立即保存到 loadUnitInstances,inner load unit 本身也单独保存;destroy 按创建逆序执行,业务 load units 先销毁,inner object load unit 最后销毁,确保 AOP/DAL/config lifecycle protos 在业务对象销毁期间仍可用。destroy 过程改成逐项 safe destroy,最后用 AggregateError 汇总,避免前一个失败导致后续不清理。

前 8 外的几项也处理了:

  • wiki/PR 描述与代码不符:已重写 wiki 相关内容,并补充 EggModuleLoader source_files。
  • 启动开销:framework scan 是 module plugin 发现的必要路径,不再用 pkg.egg.framework 有无作为开关;但扫描范围限制在 framework chain。standalone bundle consume 端现在读取 manifest 的 moduleReferences,不再重复扫模块目录。
  • 重复代码:本 PR 已抽公共 buildTeggManifestData(),并让 inner object prototype 复用 prototype builder;EggInnerObjectImpl 暂不抽公共基类,因为 inner object lifecycle 不能 fallback 到接口同名方法,这个差异需要单独设计和回归。
  • 多 app 覆盖:补了 inner object 多 app 隔离回归。
  • LoaderUtil !**/test/**:实测 globby 11 下旧 !**/test 已经会剪掉子树,所以新增 exclude 属于冗余,已删除。
  • teggAop barrel 重导出:维持不改,验证过 loader 会把 FILE_PATH 盖到实际扫描到的 barrel 文件,这个候选不成立。

本地验证过的关键项包括:ModuleScanner.test.tsEggModuleLoader.test.ts(非沙箱重跑,避免 listen EPERM)、相关 typecheck,以及 standalone / 五插件矩阵中除本地 MySQL 127.0.0.1:3306 环境依赖外的可跑部分。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tegg/standalone/standalone/README.md (1)

28-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the README example to use StandaloneApp tegg/standalone/standalone/README.md:28-30 still imports Runner from @eggjs/tegg/standalone, but that entrypoint now exports StandaloneApp/main instead. Update the snippet so copy-paste examples don’t break.

🤖 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 `@tegg/standalone/standalone/README.md` around lines 28 - 30, The README
example is using the old standalone entrypoint API, so update the snippet to
match the current exports from `@eggjs/tegg/standalone` by replacing
Runner/MainRunner usage with StandaloneApp and main. Adjust the example so it
reflects the new startup pattern and remains copy-pasteable for readers using
the standalone package.
🧹 Nitpick comments (1)
tegg/standalone/standalone/README.md (1)

46-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language specifier to the fenced code block.

The code block at line 48 lacks a language identifier, which triggers markdownlint MD040. Since the content was modified in this PR, consider adding ts to the fence.

📝 Proposed fix
-```
+```ts
 await main(cwd, {
🤖 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 `@tegg/standalone/standalone/README.md` around lines 46 - 58, Add a language
identifier to the fenced example in the standalone README so it satisfies
markdownlint MD040. Update the code block around the `await main(cwd, { ... })`
example to use a TypeScript fence, keeping the surrounding `innerObjectHandlers`
documentation unchanged.

Source: Linters/SAST tools

🤖 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 `@tegg/plugin/config/src/lib/ModuleScanner.ts`:
- Around line 79-86: The `readAndDeduplicateModuleReferences` flow is applying
the app-only `outDir` exclusion too broadly because
`this.readModuleOptions.extraFilePattern` is being mutated before framework
scans. Update `ModuleScanner` so the `!**/dist` exclusion is scoped only to the
app scan rooted at `this.baseDir`, either by cloning `readModuleOptions` for the
app-specific read or by adding the extra pattern only when scanning the app
directory, and keep framework package scans using the original options.

---

Outside diff comments:
In `@tegg/standalone/standalone/README.md`:
- Around line 28-30: The README example is using the old standalone entrypoint
API, so update the snippet to match the current exports from
`@eggjs/tegg/standalone` by replacing Runner/MainRunner usage with StandaloneApp
and main. Adjust the example so it reflects the new startup pattern and remains
copy-pasteable for readers using the standalone package.

---

Nitpick comments:
In `@tegg/standalone/standalone/README.md`:
- Around line 46-58: Add a language identifier to the fenced example in the
standalone README so it satisfies markdownlint MD040. Update the code block
around the `await main(cwd, { ... })` example to use a TypeScript fence, keeping
the surrounding `innerObjectHandlers` documentation unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d4032db-6fcc-4e38-b273-9efe747203b3

📥 Commits

Reviewing files that changed from the base of the PR and between facb7f1 and d49c104.

⛔ Files ignored due to path filters (23)
  • tegg/core/aop-runtime/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
  • tegg/core/core-decorator/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
  • tegg/core/metadata/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
  • tegg/core/runtime/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
  • tegg/core/tegg/test/__snapshots__/exports.test.ts.snap is excluded by !**/*.snap
  • tegg/core/tegg/test/__snapshots__/helper.test.ts.snap is excluded by !**/*.snap
  • tegg/core/types/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/base-framework/node_modules/base-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/base-framework/node_modules/shared-module-base/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/base-framework/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/chair-framework/node_modules/chair-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/chair-framework/node_modules/shared-module-chair/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-chain/app/node_modules/chair-framework/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-cycle/app/node_modules/base-framework/node_modules/base-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-cycle/app/node_modules/base-framework/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-cycle/app/node_modules/chair-framework/node_modules/chair-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-cycle/app/node_modules/chair-framework/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-module-json/app/node_modules/chair-framework/config/module.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-module-json/app/node_modules/chair-framework/node_modules/framework-config-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-module-json/app/node_modules/chair-framework/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-module-json/app/node_modules/framework-config-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-same-path/app/node_modules/chair-framework/node_modules/chair-module/package.json is excluded by !**/node_modules/**
  • tegg/plugin/config/test/fixtures/framework-same-path/app/node_modules/chair-framework/package.json is excluded by !**/node_modules/**
📒 Files selected for processing (147)
  • tegg/core/aop-decorator/src/CrosscutAdviceFactory.ts
  • tegg/core/aop-runtime/package.json
  • tegg/core/aop-runtime/src/AopContextAdviceRegistry.ts
  • tegg/core/aop-runtime/src/AopGraphHookRegistrar.ts
  • tegg/core/aop-runtime/src/EggObjectAopHook.ts
  • tegg/core/aop-runtime/src/EggPrototypeCrossCutHook.ts
  • tegg/core/aop-runtime/src/LoadUnitAopHook.ts
  • tegg/core/aop-runtime/src/index.ts
  • tegg/core/aop-runtime/test/aop-runtime.test.ts
  • tegg/core/common-util/src/ModuleConfig.ts
  • tegg/core/common-util/test/ModuleConfig.test.ts
  • tegg/core/core-decorator/src/decorator/DefineModuleQualifier.ts
  • tegg/core/core-decorator/src/decorator/EggLifecycleProto.ts
  • tegg/core/core-decorator/src/decorator/InnerObjectProto.ts
  • tegg/core/core-decorator/src/decorator/index.ts
  • tegg/core/core-decorator/src/util/PrototypeUtil.ts
  • tegg/core/core-decorator/test/decorators.test.ts
  • tegg/core/core-decorator/test/fixtures/decators/QualifierCacheService.ts
  • tegg/core/core-decorator/test/inner-object-decorators.test.ts
  • tegg/core/loader/src/LoaderFactory.ts
  • tegg/core/loader/test/LoaderInnerObject.test.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/ControllerHook.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/FetchRouter.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/HelloService.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/package.json
  • tegg/core/metadata/src/impl/EggInnerObjectPrototypeImpl.ts
  • tegg/core/metadata/src/impl/EggPrototypeBuilder.ts
  • tegg/core/metadata/src/impl/EggPrototypeImpl.ts
  • tegg/core/metadata/src/impl/InjectObjectPrototypeFinder.ts
  • tegg/core/metadata/src/impl/index.ts
  • tegg/core/metadata/src/model/ModuleDescriptor.ts
  • tegg/core/metadata/src/model/ProtoDescriptorHelper.ts
  • tegg/core/metadata/src/model/graph/GlobalGraph.ts
  • tegg/core/metadata/src/model/graph/ProtoGraphUtils.ts
  • tegg/core/metadata/src/model/graph/index.ts
  • tegg/core/metadata/test/ModuleDescriptorDumper.test.ts
  • tegg/core/runtime/src/impl/EggInnerObjectImpl.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnit.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnitBuilder.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnitInstance.ts
  • tegg/core/runtime/src/impl/ProvidedInnerObjectProto.ts
  • tegg/core/runtime/src/impl/index.ts
  • tegg/core/runtime/test/InnerObjectLoadUnit.test.ts
  • tegg/core/test-util/src/LoaderUtil.ts
  • tegg/core/types/package.json
  • tegg/core/types/src/common/ModuleConfig.ts
  • tegg/core/types/src/core-decorator/EggLifecycleProto.ts
  • tegg/core/types/src/core-decorator/InnerObjectProto.ts
  • tegg/core/types/src/core-decorator/Prototype.ts
  • tegg/core/types/src/core-decorator/enum/Qualifier.ts
  • tegg/core/types/src/core-decorator/index.ts
  • tegg/core/types/src/core-decorator/model/EggLifecycleInfo.ts
  • tegg/core/types/src/core-decorator/model/index.ts
  • tegg/core/types/src/metadata/model/ProtoDescriptor.ts
  • tegg/plugin/aop/package.json
  • tegg/plugin/aop/src/InnerObjects.ts
  • tegg/plugin/aop/src/app.ts
  • tegg/plugin/aop/src/lib/AopContextHook.ts
  • tegg/plugin/aop/test/aop.test.ts
  • tegg/plugin/aop/test/fixtures/apps/aop-app/modules/aop-module/Hello.ts
  • tegg/plugin/config/package.json
  • tegg/plugin/config/src/app.ts
  • tegg/plugin/config/src/lib/ConfigSourceLoadUnitHook.ts
  • tegg/plugin/config/src/lib/ModuleScanner.ts
  • tegg/plugin/config/test/ManifestModuleReference.test.ts
  • tegg/plugin/config/test/ModuleScanner.test.ts
  • tegg/plugin/config/test/ReadModule.test.ts
  • tegg/plugin/config/test/fixtures/framework-chain/app/package.json
  • tegg/plugin/config/test/fixtures/framework-cycle/app/package.json
  • tegg/plugin/config/test/fixtures/framework-module-json/app/package.json
  • tegg/plugin/config/test/fixtures/framework-same-path/app/config/module.json
  • tegg/plugin/config/test/fixtures/framework-same-path/app/package.json
  • tegg/plugin/dal/package.json
  • tegg/plugin/dal/src/app.ts
  • tegg/plugin/dal/src/app/extend/application.ts
  • tegg/plugin/dal/src/lib/DalModuleLoadUnitHook.ts
  • tegg/plugin/dal/src/lib/DalTableEggPrototypeHook.ts
  • tegg/plugin/dal/src/lib/DataSource.ts
  • tegg/plugin/dal/src/lib/MysqlDataSourceManager.ts
  • tegg/plugin/dal/src/lib/SqlMapManager.ts
  • tegg/plugin/dal/src/lib/TableModelManager.ts
  • tegg/plugin/dal/src/lib/TransactionPrototypeHook.ts
  • tegg/plugin/dal/test/transaction.test.ts
  • tegg/plugin/tegg/package.json
  • tegg/plugin/tegg/src/app.ts
  • tegg/plugin/tegg/src/lib/EggModuleLoader.ts
  • tegg/plugin/tegg/src/lib/ModuleConfigLoader.ts
  • tegg/plugin/tegg/src/lib/ModuleHandler.ts
  • tegg/plugin/tegg/test/BundledAppBoot.test.ts
  • tegg/plugin/tegg/test/ManifestCollection.test.ts
  • tegg/plugin/tegg/test/ModulePlugin.test.ts
  • tegg/plugin/tegg/test/MultiApp.test.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/config.default.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/module.json
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/plugin.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/HelloService.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/InnerRegistry.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/PluginHooks.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/package.json
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/package.json
  • tegg/plugin/tegg/test/fixtures/apps/multi-app-isolation/modules/counter-module/CounterService.ts
  • tegg/plugin/tegg/test/lib/EggModuleLoader.test.ts
  • tegg/plugin/tegg/test/lib/ModuleHandler.test.ts
  • tegg/standalone/standalone/README.md
  • tegg/standalone/standalone/package.json
  • tegg/standalone/standalone/src/ConfigSourceLoadUnitHook.ts
  • tegg/standalone/standalone/src/EggModuleLoader.ts
  • tegg/standalone/standalone/src/Runner.ts
  • tegg/standalone/standalone/src/StandaloneApp.ts
  • tegg/standalone/standalone/src/StandaloneInnerObject.ts
  • tegg/standalone/standalone/src/StandaloneInnerObjectProto.ts
  • tegg/standalone/standalone/src/StandaloneLoadUnit.ts
  • tegg/standalone/standalone/src/index.ts
  • tegg/standalone/standalone/src/main.ts
  • tegg/standalone/standalone/test/ModulePlugin.test.ts
  • tegg/standalone/standalone/test/fixtures/dal-manager-inject/foo.ts
  • tegg/standalone/standalone/test/fixtures/dal-manager-inject/package.json
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/FooEggContextHook.ts
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/FooEggObjectHook.ts
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/FooEggPrototypeHook.ts
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/foo.ts
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/innerBar.ts
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/package.json
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/foo.ts
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/innerBar.ts
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/package.json
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/FooLoadUnitInstanceHook.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/FooLoadUnitHook.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/Runner.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/logger-option/foo.ts
  • tegg/standalone/standalone/test/fixtures/logger-option/package.json
  • tegg/standalone/standalone/test/index.test.ts
  • tegg/standalone/standalone/tsdown.config.ts
  • tools/egg-bundler/src/lib/ManifestLoader.ts
  • wiki/concepts/tegg-module-plugin.md
  • wiki/index.md
  • wiki/log.md
💤 Files with no reviewable changes (9)
  • tegg/standalone/standalone/src/StandaloneInnerObjectProto.ts
  • tegg/standalone/standalone/src/StandaloneLoadUnit.ts
  • tegg/plugin/dal/package.json
  • tegg/plugin/dal/src/app/extend/application.ts
  • tegg/plugin/tegg/package.json
  • tegg/standalone/standalone/src/ConfigSourceLoadUnitHook.ts
  • tegg/standalone/standalone/src/Runner.ts
  • tegg/plugin/dal/src/app.ts
  • tegg/standalone/standalone/src/StandaloneInnerObject.ts
✅ Files skipped from review due to trivial changes (30)
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/package.json
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/package.json
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/package.json
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/package.json
  • tegg/plugin/config/test/fixtures/framework-cycle/app/package.json
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/ControllerHook.ts
  • tegg/core/types/src/core-decorator/model/index.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/module.json
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/package.json
  • tegg/core/metadata/src/model/graph/index.ts
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/package.json
  • wiki/index.md
  • tegg/core/metadata/test/ModuleDescriptorDumper.test.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/package.json
  • tegg/core/aop-runtime/src/index.ts
  • tegg/plugin/config/src/lib/ConfigSourceLoadUnitHook.ts
  • tegg/plugin/config/test/fixtures/framework-same-path/app/package.json
  • tegg/plugin/aop/package.json
  • tegg/standalone/standalone/test/fixtures/dal-manager-inject/package.json
  • tegg/core/aop-decorator/src/CrosscutAdviceFactory.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/FetchRouter.ts
  • tegg/core/types/src/core-decorator/EggLifecycleProto.ts
  • tegg/plugin/aop/src/InnerObjects.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/plugin.ts
  • tegg/plugin/tegg/test/BundledAppBoot.test.ts
  • tegg/plugin/config/test/ManifestModuleReference.test.ts
  • tegg/core/metadata/src/impl/index.ts
  • tegg/plugin/config/test/fixtures/framework-same-path/app/config/module.json
  • tegg/standalone/standalone/test/fixtures/logger-option/package.json
  • wiki/log.md
🚧 Files skipped from review as they are similar to previous changes (102)
  • tegg/core/core-decorator/src/decorator/DefineModuleQualifier.ts
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/foo.ts
  • tegg/core/types/src/core-decorator/index.ts
  • tegg/core/types/src/core-decorator/enum/Qualifier.ts
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/tsdown.config.ts
  • tegg/core/types/src/core-decorator/InnerObjectProto.ts
  • tegg/standalone/standalone/test/ModulePlugin.test.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/package.json
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/FooEggContextHook.ts
  • tegg/core/runtime/src/impl/index.ts
  • tegg/core/types/src/core-decorator/model/EggLifecycleInfo.ts
  • tegg/core/types/src/common/ModuleConfig.ts
  • tegg/standalone/standalone/test/fixtures/invalid-inner-object-inject/innerBar.ts
  • tegg/standalone/standalone/test/fixtures/egg-prototype-lifecycle-proto/FooEggPrototypeHook.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/FooLoadUnitInstanceHook.ts
  • tegg/core/core-decorator/src/decorator/InnerObjectProto.ts
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/foo.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/Foo.ts
  • tools/egg-bundler/src/lib/ManifestLoader.ts
  • tegg/plugin/dal/test/transaction.test.ts
  • tegg/plugin/config/package.json
  • tegg/core/types/src/metadata/model/ProtoDescriptor.ts
  • tegg/plugin/tegg/test/MultiApp.test.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/HelloService.ts
  • tegg/core/aop-runtime/src/AopContextAdviceRegistry.ts
  • tegg/core/aop-runtime/src/EggObjectAopHook.ts
  • tegg/plugin/config/test/ReadModule.test.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-instance-lifecycle-proto/Foo.ts
  • tegg/plugin/config/test/fixtures/framework-chain/app/package.json
  • tegg/plugin/aop/test/fixtures/apps/aop-app/modules/aop-module/Hello.ts
  • tegg/core/types/src/core-decorator/Prototype.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/FooLoadUnitHook.ts
  • tegg/standalone/standalone/test/fixtures/egg-context-lifecycle-proto/Foo.ts
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/FooEggObjectHook.ts
  • tegg/standalone/standalone/test/fixtures/logger-option/foo.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/Runner.ts
  • tegg/core/loader/test/fixtures/modules/module-with-inner-object/HelloService.ts
  • tegg/plugin/tegg/test/ModulePlugin.test.ts
  • tegg/core/metadata/src/impl/EggPrototypeImpl.ts
  • tegg/plugin/tegg/test/lib/EggModuleLoader.test.ts
  • tegg/standalone/standalone/test/fixtures/inner-object-proto/innerBar.ts
  • tegg/core/loader/test/LoaderInnerObject.test.ts
  • tegg/standalone/standalone/test/fixtures/load-unit-lifecycle-proto/package.json
  • tegg/standalone/standalone/test/fixtures/dal-manager-inject/foo.ts
  • tegg/plugin/aop/test/aop.test.ts
  • tegg/core/core-decorator/test/fixtures/decators/QualifierCacheService.ts
  • tegg/core/metadata/src/impl/EggInnerObjectPrototypeImpl.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/config/config.default.ts
  • tegg/standalone/standalone/test/fixtures/egg-object-lifecycle-proto/Foo.ts
  • tegg/core/core-decorator/test/decorators.test.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/PluginHooks.ts
  • tegg/standalone/standalone/package.json
  • tegg/standalone/standalone/src/index.ts
  • tegg/core/aop-runtime/src/AopGraphHookRegistrar.ts
  • tegg/plugin/tegg/test/fixtures/apps/multi-app-isolation/modules/counter-module/CounterService.ts
  • tegg/core/metadata/src/model/ProtoDescriptorHelper.ts
  • tegg/plugin/config/test/fixtures/framework-module-json/app/package.json
  • tegg/plugin/tegg/test/lib/ModuleHandler.test.ts
  • tegg/plugin/tegg/test/fixtures/apps/module-plugin-app/modules/plugin-module/InnerRegistry.ts
  • tegg/standalone/standalone/src/main.ts
  • tegg/plugin/dal/src/lib/TransactionPrototypeHook.ts
  • tegg/plugin/config/src/app.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnit.ts
  • tegg/core/test-util/src/LoaderUtil.ts
  • tegg/plugin/dal/src/lib/DataSource.ts
  • tegg/core/core-decorator/src/decorator/EggLifecycleProto.ts
  • tegg/plugin/dal/src/lib/MysqlDataSourceManager.ts
  • tegg/core/core-decorator/src/decorator/index.ts
  • tegg/plugin/tegg/test/ManifestCollection.test.ts
  • tegg/core/metadata/src/model/graph/ProtoGraphUtils.ts
  • tegg/core/metadata/src/model/graph/GlobalGraph.ts
  • tegg/plugin/aop/src/app.ts
  • tegg/plugin/dal/src/lib/DalTableEggPrototypeHook.ts
  • tegg/core/aop-runtime/test/aop-runtime.test.ts
  • tegg/core/aop-runtime/src/EggPrototypeCrossCutHook.ts
  • tegg/core/types/package.json
  • tegg/core/common-util/test/ModuleConfig.test.ts
  • tegg/core/core-decorator/test/inner-object-decorators.test.ts
  • tegg/plugin/config/test/ModuleScanner.test.ts
  • tegg/core/aop-runtime/package.json
  • tegg/plugin/dal/src/lib/TableModelManager.ts
  • tegg/plugin/dal/src/lib/DalModuleLoadUnitHook.ts
  • tegg/core/metadata/src/impl/EggPrototypeBuilder.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnitBuilder.ts
  • tegg/plugin/tegg/src/lib/ModuleConfigLoader.ts
  • tegg/core/metadata/src/impl/InjectObjectPrototypeFinder.ts
  • tegg/plugin/tegg/src/app.ts
  • tegg/core/runtime/test/InnerObjectLoadUnit.test.ts
  • tegg/core/loader/src/LoaderFactory.ts
  • tegg/core/runtime/src/impl/InnerObjectLoadUnitInstance.ts
  • tegg/core/runtime/src/impl/ProvidedInnerObjectProto.ts
  • tegg/plugin/dal/src/lib/SqlMapManager.ts
  • tegg/plugin/aop/src/lib/AopContextHook.ts
  • tegg/standalone/standalone/test/index.test.ts
  • tegg/core/metadata/src/model/ModuleDescriptor.ts
  • tegg/standalone/standalone/src/EggModuleLoader.ts
  • tegg/plugin/tegg/src/lib/EggModuleLoader.ts
  • tegg/core/runtime/src/impl/EggInnerObjectImpl.ts
  • tegg/standalone/standalone/src/StandaloneApp.ts
  • tegg/plugin/tegg/src/lib/ModuleHandler.ts

Comment on lines +79 to +86
private readAndDeduplicateModuleReferences(baseDir: string, cwd?: string): readonly ModuleReference[] {
return ModuleConfigUtil.deduplicateModules(
ModuleConfigUtil.readModuleReference(baseDir, {
...this.readModuleOptions,
...(cwd ? { cwd } : {}),
}),
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how readModuleReference consumes extraFilePattern + cwd to confirm
# whether an app outDir exclusion (e.g. !**/dist) can exclude framework
# packages scanned from node_modules.
fd -a 'ModuleConfig*.ts' tegg/core/common-util/src | while read -r f; do
  echo "== $f =="
  ast-grep outline "$f" --items all
done

rg -nP -C4 '\b(extraFilePattern|readModuleReference|cwd)\b' tegg/core/common-util/src

Repository: eggjs/egg

Length of output: 5268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ModuleScanner.ts =="
ast-grep outline tegg/plugin/config/src/lib/ModuleScanner.ts --items all

echo
echo "== nearby lines =="
cat -n tegg/plugin/config/src/lib/ModuleScanner.ts | sed -n '1,220p'

echo
echo "== scanModuleReferences callers =="
rg -n -C 4 'scanModuleReferences|extraFilePattern|outDir|readModuleOptions' tegg/plugin/config/src tegg -g '*.ts'

Repository: eggjs/egg

Length of output: 30326


Scope the outDir exclusion to the app scan only.
readModuleOptions.extraFilePattern is mutated before ModuleScanner scans framework directories, so !**/dist also applies to published framework packages. That can hide framework module plugins shipped only from compiled output; clone the options for the app scan or add the exclusion only for this.baseDir.

🤖 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 `@tegg/plugin/config/src/lib/ModuleScanner.ts` around lines 79 - 86, The
`readAndDeduplicateModuleReferences` flow is applying the app-only `outDir`
exclusion too broadly because `this.readModuleOptions.extraFilePattern` is being
mutated before framework scans. Update `ModuleScanner` so the `!**/dist`
exclusion is scoped only to the app scan rooted at `this.baseDir`, either by
cloning `readModuleOptions` for the app-specific read or by adding the extra
pattern only when scanning the app directory, and keep framework package scans
using the original options.

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.

3 participants