Skip to content

Commit 871e596

Browse files
authored
fix(core): include plugin dynamic files in manifest (#5922)
## Summary - Stack on top of PR #5921 / EGG-65 runtime mapping commit because that dependency is not yet in origin/next. - Complete generated startup manifests with convention-based plugin dynamic files (agent, app, app/extend/*, and app/middleware) so bundled single-mode workers can load files skipped by metadataOnly agent startup. - Add a security-like @eggjs/security fixture verifying agent, app, app/extend/agent, app/extend/application, and app/middleware/securities are present in the manifest. ## Validation - pnpm exec vitest run packages/core/test/loader/manifest_coverage.test.ts - pnpm exec vitest run packages/core/test/loader/manifest.test.ts packages/core/test/loader/manifest_roundtrip.test.ts packages/core/test/loader/manifest_fingerprint.test.ts packages/core/test/loader/manifest_query.test.ts packages/core/test/loader/manifest_coverage.test.ts - pnpm --filter @eggjs/egg-bundler test -- EntryGenerator.test.ts - pnpm --filter @eggjs/core typecheck - pnpm --filter @eggjs/egg-bundler typecheck - pnpm exec oxfmt --check packages/core/src/loader/egg_loader.ts packages/core/test/loader/manifest_coverage.test.ts tools/egg-bundler/src/lib/EntryGenerator.ts tools/egg-bundler/test/EntryGenerator.test.ts - git diff --check origin/next...HEAD Notes: targeted oxlint on changed files exits 0 with existing warnings in pre-existing egg_loader.ts lines outside this patch. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Manifest post-processing now caches convention-based module resolution and records directory-discovered middleware lists for metadata-only runs. * Bundler/runtime treats framework as a package specifier, validates specifiers, and precomputes output↔original-app aliasing for deterministic module lookup. * **Documentation** * Updated bundler docs, CLI help, and wiki to explain runtime path mapping, output-dir semantics, and framework-specifier behavior. * **Tests & Chores** * Added fixtures and tests for manifest resolve-cache, file discovery, deterministic worker generation, and framework-specifier validation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 22fc588 commit 871e596

15 files changed

Lines changed: 263 additions & 3 deletions

File tree

packages/core/src/loader/egg_loader.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Request, Response, Application, Context as KoaContext } from '@eggjs/ko
88
import { pathMatching, type PathMatchingOptions } from '@eggjs/path-matching';
99
import { isESM, isSupportTypeScript } from '@eggjs/utils';
1010
import type { Logger } from 'egg-logger';
11+
import globby from 'globby';
1112
import { isAsyncFunction, isClass, isGeneratorFunction, isObject, isPromise } from 'is-type-of';
1213
import { homedir } from 'node-homedir';
1314
import { now, diff } from 'performance-ms';
@@ -26,6 +27,12 @@ import { type FileLoaderOptions, CaseStyle, FULLPATH, FileLoader } from './file_
2627
import { ManifestStore, type StartupManifest } from './manifest.ts';
2728

2829
const debug = debuglog('egg/core/loader/egg_loader');
30+
const CONVENTIONAL_MANIFEST_LOADS = [
31+
{ type: 'resolve', path: ['agent'] },
32+
{ type: 'resolve', path: ['app'] },
33+
{ type: 'discover', path: ['app', 'extend'], extensionlessResolve: true },
34+
{ type: 'discover', path: ['app', 'middleware'] },
35+
] as const;
2936

3037
const originalPrototypes: Record<string, unknown> = {
3138
request: Request.prototype,
@@ -1764,11 +1771,69 @@ export class EggLoader {
17641771
* Should be called after all loading phases complete.
17651772
*/
17661773
generateManifest(): StartupManifest {
1767-
return this.manifest.generateManifest({
1774+
const manifest = this.manifest.generateManifest({
17681775
serverEnv: this.serverEnv,
17691776
serverScope: this.serverScope,
17701777
typescriptEnabled: isSupportTypeScript(),
17711778
});
1779+
this.#collectConventionalDynamicFiles(manifest);
1780+
return manifest;
1781+
}
1782+
1783+
/**
1784+
* metadataOnly startup intentionally skips the agent process, but bundled
1785+
* single-mode workers still load agent boot hooks and agent extends later.
1786+
* Record convention-based dynamic entry points so the bundle can satisfy
1787+
* those runtime lookups without running agent lifecycle hooks at manifest
1788+
* generation time.
1789+
*/
1790+
#collectConventionalDynamicFiles(manifest: StartupManifest): void {
1791+
for (const unit of this.getLoadUnits()) {
1792+
for (const load of CONVENTIONAL_MANIFEST_LOADS) {
1793+
const target = path.join(unit.path, ...load.path);
1794+
if (load.type === 'resolve') {
1795+
this.#collectConventionResolve(manifest, target);
1796+
} else if ('extensionlessResolve' in load && load.extensionlessResolve) {
1797+
this.#collectConventionFileResolves(manifest, target);
1798+
} else {
1799+
this.#collectConventionFileDiscovery(manifest, target);
1800+
}
1801+
}
1802+
}
1803+
}
1804+
1805+
#collectConventionResolve(manifest: StartupManifest, request: string): void {
1806+
const requestKey = this.#toManifestRel(request);
1807+
if (Object.hasOwn(manifest.resolveCache, requestKey)) return;
1808+
1809+
const resolved = this.#doResolveModule(request);
1810+
manifest.resolveCache[requestKey] = resolved ? this.#toManifestRel(resolved) : null;
1811+
}
1812+
1813+
#collectConventionFileResolves(manifest: StartupManifest, directory: string): void {
1814+
const files = this.#collectConventionFileDiscovery(manifest, directory);
1815+
for (const file of files) {
1816+
const ext = path.extname(file);
1817+
if (!ext) continue;
1818+
const request = path.join(directory, file.slice(0, -ext.length));
1819+
this.#collectConventionResolve(manifest, request);
1820+
}
1821+
}
1822+
1823+
#collectConventionFileDiscovery(manifest: StartupManifest, directory: string): string[] {
1824+
const dirKey = this.#toManifestRel(directory);
1825+
if (Object.hasOwn(manifest.fileDiscovery, dirKey)) return manifest.fileDiscovery[dirKey];
1826+
1827+
manifest.fileDiscovery[dirKey] =
1828+
fs.existsSync(directory) && fs.statSync(directory).isDirectory()
1829+
? globby.sync(FileLoader.getDefaultMatch(), { cwd: directory }).sort()
1830+
: [];
1831+
return manifest.fileDiscovery[dirKey];
1832+
}
1833+
1834+
#toManifestRel(filepath: string): string {
1835+
const rel = path.isAbsolute(filepath) ? path.relative(this.options.baseDir, filepath) : filepath;
1836+
return rel.replaceAll(path.sep, '/');
17721837
}
17731838
}
17741839

packages/core/src/loader/file_loader.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export interface FileLoaderParseItem {
6060
exports: object | Fun;
6161
}
6262

63+
function getDefaultFileLoaderMatch(): string[] {
64+
return isSupportTypeScript() ? ['**/*.(js|ts)', '!**/*.d.ts'] : ['**/*.js'];
65+
}
66+
6367
/**
6468
* Load files from directory to target object.
6569
* @since 1.0.0
@@ -73,6 +77,10 @@ export class FileLoader {
7377
return EXPORTS;
7478
}
7579

80+
static getDefaultMatch(): string[] {
81+
return getDefaultFileLoaderMatch();
82+
}
83+
7684
readonly options: FileLoaderOptions & Required<Pick<FileLoaderOptions, 'caseStyle'>>;
7785

7886
/**
@@ -181,7 +189,7 @@ export class FileLoader {
181189
if (files) {
182190
files = Array.isArray(files) ? files : [files];
183191
} else {
184-
files = isSupportTypeScript() ? ['**/*.(js|ts)', '!**/*.d.ts'] : ['**/*.js'];
192+
files = FileLoader.getDefaultMatch();
185193
}
186194

187195
let ignore = this.options.ignore;

packages/core/src/loader/manifest.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ export class ManifestStore {
211211
return cached !== null ? this.#toAbsolute(cached) : undefined;
212212
}
213213

214+
const discovered = this.#resolveFromFileDiscovery(relKey);
215+
if (discovered) {
216+
debug('[resolveModule:fileDiscovery] %o => %o', filepath, discovered);
217+
return discovered;
218+
}
219+
214220
const result = fallback();
215221
this.#resolveCacheCollector[relKey] = result !== undefined ? this.#toRelative(result) : null;
216222
return result;
@@ -343,6 +349,30 @@ export class ManifestStore {
343349
return path.join(this.baseDir, relPath);
344350
}
345351

352+
#resolveFromFileDiscovery(relKey: string): string | undefined {
353+
let matchedDir: string | undefined;
354+
for (const dir of Object.keys(this.data.fileDiscovery)) {
355+
if ((relKey === dir || relKey.startsWith(dir + '/')) && (!matchedDir || dir.length > matchedDir.length)) {
356+
matchedDir = dir;
357+
}
358+
}
359+
if (!matchedDir || relKey === matchedDir) return;
360+
361+
const request = relKey.slice(matchedDir.length + 1);
362+
const files = this.data.fileDiscovery[matchedDir];
363+
const matchedFile = files.find((file) => {
364+
if (file === request) return true;
365+
366+
const ext = path.posix.extname(file);
367+
if (!ext || ext === '.map') return false;
368+
369+
const extensionlessFile = file.slice(0, -ext.length);
370+
return extensionlessFile === request || extensionlessFile === `${request}/index`;
371+
});
372+
373+
return matchedFile ? this.#toAbsolute(path.posix.join(matchedDir, matchedFile)) : undefined;
374+
}
375+
346376
// --- Fingerprint Utilities ---
347377

348378
static #statFingerprint(filepath: string): string | null {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = {
4+
security: {
5+
enable: true,
6+
package: '@eggjs/security',
7+
},
8+
};

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/agent.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/app.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/app/extend/agent.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/app/extend/application.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/app/extend/filter.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/test/fixtures/manifest-dynamic-plugin/node_modules/@eggjs/security/app/middleware/securities.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)