Skip to content

Commit 0313705

Browse files
killaguclaude
andcommitted
refactor(core): replace tegg-specific types with generic extensions
Remove ManifestTegg, ManifestModuleReference, ManifestModuleDescriptor from core. Replace `tegg` field with `extensions: Record<string, unknown>` so plugins can store arbitrary manifest data without core knowing the shape. - ManifestStore.tegg getter → getExtension(name) method - generateManifest(tegg?) → generateManifest(extensions?) - Remove teggManifestCollector from EggLoader - Add expected-manifest.json fixture showing full manifest structure Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d2b52c6 commit 0313705

File tree

6 files changed

+110
-51
lines changed

6 files changed

+110
-51
lines changed

packages/core/src/loader/egg_loader.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { sequencify } from '../utils/sequencify.ts';
2323
import { Timing } from '../utils/timing.ts';
2424
import { type ContextLoaderOptions, ContextLoader } from './context_loader.ts';
2525
import { type FileLoaderOptions, CaseStyle, FULLPATH, FileLoader } from './file_loader.ts';
26-
import { ManifestStore, type ManifestTegg, type StartupManifest } from './manifest.ts';
26+
import { ManifestStore, type StartupManifest } from './manifest.ts';
2727

2828
const debug = debuglog('egg/core/loader/egg_loader');
2929

@@ -76,8 +76,6 @@ export class EggLoader {
7676
readonly resolveCacheCollector: Record<string, string | null> = {};
7777
/** Collected file discovery results for manifest generation */
7878
readonly fileDiscoveryCollector: Record<string, string[]> = {};
79-
/** Collected tegg manifest data (populated by tegg plugin) */
80-
teggManifestCollector?: ManifestTegg;
8179

8280
/**
8381
* @class
@@ -1781,13 +1779,13 @@ export class EggLoader {
17811779
* Generate startup manifest from collected data.
17821780
* Should be called after all loading phases complete.
17831781
*/
1784-
generateManifest(tegg?: ManifestTegg): StartupManifest {
1782+
generateManifest(extensions?: Record<string, unknown>): StartupManifest {
17851783
return ManifestStore.generate({
17861784
baseDir: this.options.baseDir,
17871785
serverEnv: this.serverEnv,
17881786
serverScope: this.serverScope,
17891787
typescriptEnabled: isSupportTypeScript(),
1790-
tegg,
1788+
extensions,
17911789
resolveCache: this.resolveCacheCollector,
17921790
fileDiscovery: this.fileDiscoveryCollector,
17931791
});

packages/core/src/loader/manifest.ts

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,6 @@ const MANIFEST_VERSION = 1;
1010

1111
const LOCKFILE_NAMES = ['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock'] as const;
1212

13-
export interface ManifestModuleReference {
14-
name: string;
15-
path: string;
16-
optional?: boolean;
17-
}
18-
19-
export interface ManifestModuleDescriptor {
20-
name: string;
21-
unitPath: string;
22-
optional?: boolean;
23-
/** Files containing decorated classes, relative to unitPath */
24-
decoratedFiles: string[];
25-
}
26-
27-
export interface ManifestTegg {
28-
moduleReferences: ManifestModuleReference[];
29-
moduleDescriptors: ManifestModuleDescriptor[];
30-
}
31-
3213
export interface ManifestInvalidation {
3314
lockfileFingerprint: string;
3415
configFingerprint: string;
@@ -42,7 +23,8 @@ export interface StartupManifest {
4223
version: number;
4324
generatedAt: string;
4425
invalidation: ManifestInvalidation;
45-
tegg: ManifestTegg;
26+
/** Plugin-specific manifest data, keyed by plugin name */
27+
extensions: Record<string, unknown>;
4628
/** resolveModule cache: filepath -> resolved path | null */
4729
resolveCache: Record<string, string | null>;
4830
/** directory path -> file relative paths (filtered) */
@@ -147,8 +129,8 @@ export class ManifestStore {
147129
return this.data.fileDiscovery?.[directory];
148130
}
149131

150-
get tegg(): ManifestTegg | undefined {
151-
return this.data.tegg;
132+
getExtension(name: string): unknown {
133+
return this.data.extensions?.[name];
152134
}
153135

154136
// --- Generation APIs ---
@@ -165,7 +147,7 @@ export class ManifestStore {
165147
baseDir: options.baseDir,
166148
typescriptEnabled: options.typescriptEnabled,
167149
},
168-
tegg: options.tegg ?? { moduleReferences: [], moduleDescriptors: [] },
150+
extensions: options.extensions ?? {},
169151
resolveCache: options.resolveCache ?? {},
170152
fileDiscovery: options.fileDiscovery ?? {},
171153
};
@@ -256,7 +238,7 @@ export interface ManifestGenerateOptions {
256238
serverEnv: string;
257239
serverScope: string;
258240
typescriptEnabled: boolean;
259-
tegg?: ManifestTegg;
241+
extensions?: Record<string, unknown>;
260242
resolveCache?: Record<string, string | null>;
261243
fileDiscovery?: Record<string, string[]>;
262244
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"version": 1,
3+
"generatedAt": "2026-03-28T04:00:00.000Z",
4+
"invalidation": {
5+
"lockfileFingerprint": "pnpm-lock.yaml:1711584000000:128",
6+
"configFingerprint": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
7+
"serverEnv": "prod",
8+
"serverScope": "",
9+
"baseDir": "/app/my-egg-project",
10+
"typescriptEnabled": true
11+
},
12+
"extensions": {
13+
"tegg": {
14+
"moduleReferences": [
15+
{ "name": "app", "path": "/app/my-egg-project/app" },
16+
{ "name": "my-plugin", "path": "/app/my-egg-project/node_modules/my-plugin", "optional": true }
17+
],
18+
"moduleDescriptors": [
19+
{
20+
"name": "app",
21+
"unitPath": "/app/my-egg-project/app",
22+
"decoratedFiles": ["controller/home.ts", "service/user.ts"]
23+
}
24+
]
25+
}
26+
},
27+
"resolveCache": {
28+
"/app/my-egg-project/config/plugin.default": "/app/my-egg-project/config/plugin.default.ts",
29+
"/app/my-egg-project/config/plugin": null,
30+
"/app/my-egg-project/config/config.default": "/app/my-egg-project/config/config.default.ts",
31+
"/app/my-egg-project/config/config.prod": "/app/my-egg-project/config/config.prod.ts",
32+
"/app/my-egg-project/app/router": "/app/my-egg-project/app/router.ts",
33+
"/app/my-egg-project/node_modules/egg-security/config/config.default": "/app/my-egg-project/node_modules/egg-security/config/config.default.js",
34+
"/app/my-egg-project/node_modules/egg-security/app": "/app/my-egg-project/node_modules/egg-security/app.js",
35+
"/app/my-egg-project/node_modules/egg-session/config/config.default": null,
36+
"/app/my-egg-project/app/app": null
37+
},
38+
"fileDiscovery": {
39+
"/app/my-egg-project/app/controller": ["home.ts", "user.ts", "admin/dashboard.ts"],
40+
"/app/my-egg-project/app/service": ["user.ts", "auth.ts"],
41+
"/app/my-egg-project/app/middleware": ["auth.ts", "error_handler.ts"],
42+
"/app/my-egg-project/node_modules/egg-security/app/middleware": ["csrf.js", "xframe.js"]
43+
}
44+
}

packages/core/test/loader/manifest.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,18 @@ describe('ManifestStore', () => {
5555
typescriptEnabled: false,
5656
});
5757

58-
assert.deepStrictEqual(manifest.tegg, { moduleReferences: [], moduleDescriptors: [] });
58+
assert.deepStrictEqual(manifest.extensions, {});
5959
assert.deepStrictEqual(manifest.resolveCache, {});
6060
assert.deepStrictEqual(manifest.fileDiscovery, {});
6161
} finally {
6262
fs.rmSync(baseDir, { recursive: true, force: true });
6363
}
6464
});
6565

66-
it('should preserve provided tegg, resolveCache, fileDiscovery', () => {
66+
it('should preserve provided extensions, resolveCache, fileDiscovery', () => {
6767
const baseDir = setupBaseDir();
6868
try {
69-
const tegg = {
70-
moduleReferences: [{ name: 'mod', path: '/tmp/mod' }],
71-
moduleDescriptors: [{ name: 'mod', unitPath: '/tmp/mod', decoratedFiles: ['a.ts'] }],
72-
};
69+
const extensions = { tegg: { moduleReferences: [{ name: 'mod', path: '/tmp/mod' }] } };
7370
const resolveCache = { '/foo/bar': '/resolved/bar', '/foo/missing': null };
7471
const fileDiscovery = { '/app/service': ['user.ts', 'post.ts'] };
7572

@@ -78,12 +75,12 @@ describe('ManifestStore', () => {
7875
serverEnv: 'prod',
7976
serverScope: '',
8077
typescriptEnabled: true,
81-
tegg,
78+
extensions,
8279
resolveCache,
8380
fileDiscovery,
8481
});
8582

86-
assert.deepStrictEqual(manifest.tegg, tegg);
83+
assert.deepStrictEqual(manifest.extensions, extensions);
8784
assert.deepStrictEqual(manifest.resolveCache, resolveCache);
8885
assert.deepStrictEqual(manifest.fileDiscovery, fileDiscovery);
8986
} finally {

packages/core/test/loader/manifest_query.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,25 @@ describe('ManifestStore query APIs', () => {
8282
});
8383
});
8484

85-
describe('tegg getter', () => {
86-
it('should return tegg data', async () => {
85+
describe('getExtension()', () => {
86+
it('should return extension data by name', async () => {
8787
const baseDir = setupBaseDir();
88-
const tegg = {
89-
moduleReferences: [{ name: 'myModule', path: '/tmp/myModule' }],
90-
moduleDescriptors: [],
91-
};
88+
const extensions = { tegg: { moduleReferences: [{ name: 'myModule', path: '/tmp/myModule' }] } };
9289
try {
93-
await generateAndWrite(baseDir, { tegg });
90+
await generateAndWrite(baseDir, { extensions });
9491
const store = ManifestStore.load(baseDir, 'prod', '')!;
95-
assert.deepStrictEqual(store.tegg, tegg);
92+
assert.deepStrictEqual(store.getExtension('tegg'), extensions.tegg);
93+
} finally {
94+
fs.rmSync(baseDir, { recursive: true, force: true });
95+
}
96+
});
97+
98+
it('should return undefined for unknown extension', async () => {
99+
const baseDir = setupBaseDir();
100+
try {
101+
await generateAndWrite(baseDir, { extensions: {} });
102+
const store = ManifestStore.load(baseDir, 'prod', '')!;
103+
assert.equal(store.getExtension('nonexistent'), undefined);
96104
} finally {
97105
fs.rmSync(baseDir, { recursive: true, force: true });
98106
}

packages/core/test/loader/manifest_roundtrip.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ describe('ManifestStore roundtrip: generate → write → load', () => {
2323
try {
2424
const resolveCache = { '/some/path': '/resolved/path', '/missing': null };
2525
const fileDiscovery = { '/app/controller': ['home.ts', 'user.ts'] };
26-
const tegg = {
27-
moduleReferences: [{ name: 'foo', path: '/tmp/foo', optional: true }],
28-
moduleDescriptors: [{ name: 'foo', unitPath: '/tmp/foo', decoratedFiles: ['service.ts'] }],
29-
};
26+
const extensions = { tegg: { moduleReferences: [{ name: 'foo', path: '/tmp/foo' }] } };
3027

3128
const original = ManifestStore.generate({
3229
baseDir,
@@ -35,15 +32,15 @@ describe('ManifestStore roundtrip: generate → write → load', () => {
3532
typescriptEnabled: true,
3633
resolveCache,
3734
fileDiscovery,
38-
tegg,
35+
extensions,
3936
});
4037
await ManifestStore.write(baseDir, original);
4138

4239
const store = ManifestStore.load(baseDir, 'prod', '')!;
4340
assert.ok(store);
4441
assert.deepStrictEqual(store.data.resolveCache, resolveCache);
4542
assert.deepStrictEqual(store.data.fileDiscovery, fileDiscovery);
46-
assert.deepStrictEqual(store.data.tegg, tegg);
43+
assert.deepStrictEqual(store.data.extensions, extensions);
4744
assert.equal(store.data.version, original.version);
4845
assert.equal(store.data.generatedAt, original.generatedAt);
4946
} finally {
@@ -89,4 +86,37 @@ describe('ManifestStore roundtrip: generate → write → load', () => {
8986
fs.rmSync(baseDir, { recursive: true, force: true });
9087
}
9188
});
89+
90+
it('should match expected manifest fixture structure', async () => {
91+
const fixturePath = path.join(__dirname, '../fixtures/manifest/expected-manifest.json');
92+
const expected = JSON.parse(fs.readFileSync(fixturePath, 'utf-8'));
93+
94+
// Verify the fixture conforms to StartupManifest structure
95+
assert.equal(expected.version, 1);
96+
assert.ok(expected.generatedAt);
97+
assert.ok(expected.invalidation);
98+
assert.equal(expected.invalidation.serverEnv, 'prod');
99+
assert.ok(typeof expected.invalidation.lockfileFingerprint === 'string');
100+
assert.ok(typeof expected.invalidation.configFingerprint === 'string');
101+
assert.ok(typeof expected.invalidation.typescriptEnabled === 'boolean');
102+
103+
// extensions is a generic Record<string, unknown>
104+
assert.ok(typeof expected.extensions === 'object');
105+
assert.ok(expected.extensions.tegg);
106+
assert.ok(Array.isArray(expected.extensions.tegg.moduleReferences));
107+
108+
// resolveCache: string -> string | null
109+
assert.ok(typeof expected.resolveCache === 'object');
110+
for (const [key, value] of Object.entries(expected.resolveCache)) {
111+
assert.ok(typeof key === 'string');
112+
assert.ok(value === null || typeof value === 'string', `resolveCache[${key}] should be string | null`);
113+
}
114+
115+
// fileDiscovery: string -> string[]
116+
assert.ok(typeof expected.fileDiscovery === 'object');
117+
for (const [key, value] of Object.entries(expected.fileDiscovery)) {
118+
assert.ok(typeof key === 'string');
119+
assert.ok(Array.isArray(value), `fileDiscovery[${key}] should be string[]`);
120+
}
121+
});
92122
});

0 commit comments

Comments
 (0)