🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@eggjs/core

Package Overview
Dependencies
Maintainers
9
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@eggjs/core - npm Package Compare versions

Comparing version
7.0.2-beta.6
to
7.0.2-beta.7
+64
dist/loader/manifest.d.ts
//#region src/loader/manifest.d.ts
interface ManifestInvalidation {
lockfileFingerprint: string;
configFingerprint: string;
serverEnv: string;
serverScope: string;
typescriptEnabled: boolean;
}
interface StartupManifest {
version: number;
generatedAt: string;
invalidation: ManifestInvalidation;
/** Plugin-specific manifest data, keyed by plugin name */
extensions: Record<string, unknown>;
/** resolveModule cache: relative filepath -> resolved relative path | null */
resolveCache: Record<string, string | null>;
/** relative directory path -> file relative paths */
fileDiscovery: Record<string, string[]>;
}
declare class ManifestStore {
#private;
readonly data: StartupManifest;
readonly baseDir: string;
private constructor();
/**
* Load and validate manifest from `.egg/manifest.json`.
* Returns null if manifest doesn't exist or is invalid.
*/
static load(baseDir: string, serverEnv: string, serverScope: string): ManifestStore | null;
/**
* Create a collector-only ManifestStore (no cached data).
* Used during normal startup to collect data for future manifest generation.
*/
static createCollector(baseDir: string): ManifestStore;
/**
* Resolve a module path. Checks cache first, falls back to resolver, collects result.
*/
resolveModule(filepath: string, fallback: () => string | undefined): string | undefined;
/**
* Get file list for a directory. Checks cache first, falls back to globber, collects result.
*/
globFiles(directory: string, fallback: () => string[]): string[];
/**
* Look up a plugin extension by name.
*/
getExtension(name: string): unknown;
/**
* Register plugin extension data for manifest generation.
*/
setExtension(name: string, data: unknown): void;
/**
* Generate a StartupManifest from collected data.
*/
generateManifest(options: ManifestGenerateOptions): StartupManifest;
static write(baseDir: string, manifest: StartupManifest): Promise<void>;
static clean(baseDir: string): void;
}
interface ManifestGenerateOptions {
serverEnv: string;
serverScope: string;
typescriptEnabled: boolean;
}
//#endregion
export { ManifestGenerateOptions, ManifestInvalidation, ManifestStore, StartupManifest };
import fs from "node:fs";
import fsp from "node:fs/promises";
import path from "node:path";
import { debuglog } from "node:util";
import { isSupportTypeScript } from "@eggjs/utils";
import { createHash } from "node:crypto";
//#region src/loader/manifest.ts
const debug = debuglog("egg/core/loader/manifest");
const MANIFEST_VERSION = 1;
const LOCKFILE_NAMES = [
"pnpm-lock.yaml",
"package-lock.json",
"yarn.lock"
];
var ManifestStore = class ManifestStore {
data;
baseDir;
#resolveCacheCollector = {};
#fileDiscoveryCollector = {};
#extensionCollector = {};
constructor(data, baseDir) {
this.data = data;
this.baseDir = baseDir;
}
/**
* Load and validate manifest from `.egg/manifest.json`.
* Returns null if manifest doesn't exist or is invalid.
*/
static load(baseDir, serverEnv, serverScope) {
if (serverEnv === "local" && process.env.EGG_MANIFEST !== "true") {
debug("skip manifest in local env (set EGG_MANIFEST=true to enable)");
return null;
}
const manifestPath = path.join(baseDir, ".egg", "manifest.json");
let raw;
try {
raw = fs.readFileSync(manifestPath, "utf-8");
} catch {
debug("manifest not found at %s", manifestPath);
return null;
}
let data;
try {
data = JSON.parse(raw);
} catch (e) {
debug("failed to parse manifest: %s", e);
return null;
}
if (!ManifestStore.#validate(data, baseDir, serverEnv, serverScope)) return null;
debug("manifest loaded successfully");
return new ManifestStore(data, baseDir);
}
/**
* Create a collector-only ManifestStore (no cached data).
* Used during normal startup to collect data for future manifest generation.
*/
static createCollector(baseDir) {
return new ManifestStore({
version: MANIFEST_VERSION,
generatedAt: "",
invalidation: {
lockfileFingerprint: "",
configFingerprint: "",
serverEnv: "",
serverScope: "",
typescriptEnabled: false
},
extensions: {},
resolveCache: {},
fileDiscovery: {}
}, baseDir);
}
static #validate(data, baseDir, serverEnv, serverScope) {
if (data.version !== MANIFEST_VERSION) {
debug("manifest version mismatch: expected %d, got %d", MANIFEST_VERSION, data.version);
return false;
}
const inv = data.invalidation;
if (!inv) {
debug("manifest missing invalidation data");
return false;
}
if (inv.serverEnv !== serverEnv) {
debug("manifest serverEnv mismatch: expected %s, got %s", serverEnv, inv.serverEnv);
return false;
}
if (inv.serverScope !== serverScope) {
debug("manifest serverScope mismatch: expected %s, got %s", serverScope, inv.serverScope);
return false;
}
const currentTypescriptEnabled = isSupportTypeScript();
if (inv.typescriptEnabled !== currentTypescriptEnabled) {
debug("manifest typescriptEnabled mismatch: expected %s, got %s", currentTypescriptEnabled, inv.typescriptEnabled);
return false;
}
const currentLockfileFingerprint = ManifestStore.#lockfileFingerprint(baseDir);
if (inv.lockfileFingerprint !== currentLockfileFingerprint) {
debug("manifest lockfileFingerprint mismatch");
return false;
}
const currentConfigFingerprint = ManifestStore.#directoryFingerprint(path.join(baseDir, "config"));
if (inv.configFingerprint !== currentConfigFingerprint) {
debug("manifest configFingerprint mismatch");
return false;
}
return true;
}
/**
* Resolve a module path. Checks cache first, falls back to resolver, collects result.
*/
resolveModule(filepath, fallback) {
const relKey = this.#toRelative(filepath);
const cache = this.data.resolveCache;
if (cache && relKey in cache) {
const cached = cache[relKey];
debug("[resolveModule:manifest] %o => %o", filepath, cached);
return cached !== null ? this.#toAbsolute(cached) : void 0;
}
const result = fallback();
this.#resolveCacheCollector[relKey] = result !== void 0 ? this.#toRelative(result) : null;
return result;
}
/**
* Get file list for a directory. Checks cache first, falls back to globber, collects result.
*/
globFiles(directory, fallback) {
const relKey = this.#toRelative(directory);
const cache = this.data.fileDiscovery;
if (cache && relKey in cache) {
const cached = cache[relKey];
debug("[globFiles:manifest] using cached files for %o, count: %d", directory, cached.length);
return cached;
}
const result = fallback();
this.#fileDiscoveryCollector[relKey] = result;
return result;
}
/**
* Look up a plugin extension by name.
*/
getExtension(name) {
return this.data.extensions?.[name];
}
/**
* Register plugin extension data for manifest generation.
*/
setExtension(name, data) {
this.#extensionCollector[name] = data;
}
/**
* Generate a StartupManifest from collected data.
*/
generateManifest(options) {
return {
version: MANIFEST_VERSION,
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
invalidation: {
lockfileFingerprint: ManifestStore.#lockfileFingerprint(this.baseDir),
configFingerprint: ManifestStore.#directoryFingerprint(path.join(this.baseDir, "config")),
serverEnv: options.serverEnv,
serverScope: options.serverScope,
typescriptEnabled: options.typescriptEnabled
},
extensions: this.#extensionCollector,
resolveCache: this.#resolveCacheCollector,
fileDiscovery: this.#fileDiscoveryCollector
};
}
static async write(baseDir, manifest) {
const dir = path.join(baseDir, ".egg");
await fsp.mkdir(dir, { recursive: true });
const manifestPath = path.join(dir, "manifest.json");
await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
debug("manifest written to %s", manifestPath);
}
static clean(baseDir) {
const manifestPath = path.join(baseDir, ".egg", "manifest.json");
try {
fs.unlinkSync(manifestPath);
debug("manifest removed: %s", manifestPath);
} catch (err) {
if (err.code !== "ENOENT") throw err;
}
}
#toRelative(absPath) {
return (path.isAbsolute(absPath) ? path.relative(this.baseDir, absPath) : absPath).replaceAll(path.sep, "/");
}
#toAbsolute(relPath) {
if (path.isAbsolute(relPath)) return relPath;
return path.join(this.baseDir, relPath);
}
static #statFingerprint(filepath) {
try {
const stat$1 = fs.statSync(filepath);
return `${stat$1.mtimeMs}:${stat$1.size}`;
} catch {
return null;
}
}
static #lockfileFingerprint(baseDir) {
for (const name of LOCKFILE_NAMES) {
const fp = ManifestStore.#statFingerprint(path.join(baseDir, name));
if (fp) return `${name}:${fp}`;
}
return "";
}
static #directoryFingerprint(dirpath) {
const hash = createHash("md5");
const visited = /* @__PURE__ */ new Set();
ManifestStore.#fingerprintRecursive(dirpath, hash, visited);
return hash.digest("hex");
}
static #fingerprintRecursive(dirpath, hash, visited) {
let realPath;
try {
realPath = fs.realpathSync(dirpath);
} catch {
return;
}
if (visited.has(realPath)) return;
visited.add(realPath);
let entries;
try {
entries = fs.readdirSync(dirpath, { withFileTypes: true });
} catch {
return;
}
entries.sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
if (entry.isSymbolicLink()) continue;
const fullPath = path.join(dirpath, entry.name);
if (entry.isDirectory()) {
hash.update(`dir:${entry.name}\n`);
ManifestStore.#fingerprintRecursive(fullPath, hash, visited);
} else if (entry.isFile()) {
const fp = ManifestStore.#statFingerprint(fullPath);
hash.update(`file:${entry.name}:${fp ?? "missing"}\n`);
}
}
}
};
//#endregion
export { ManifestStore };
+2
-0

@@ -21,2 +21,4 @@ import { Fun } from "./utils/index.js";

env?: string;
/** Skip lifecycle hooks, only trigger loadMetadata for manifest generation */
metadataOnly?: boolean;
}

@@ -23,0 +25,0 @@ type EggCoreInitOptions = Partial<EggCoreOptions>;

+2
-1

@@ -133,3 +133,4 @@ import utils_default from "./utils/index.js";

env: options.env ?? "",
EggCoreClass: EggCore
EggCoreClass: EggCore,
metadataOnly: options.metadataOnly
});

@@ -136,0 +137,0 @@ }

@@ -6,2 +6,3 @@ import { utils } from "./utils/index.js";

import { CustomLoaderConfigItem, EggAppConfig, EggAppInfo, EggPluginInfo } from "./types.js";
import { ManifestGenerateOptions, ManifestInvalidation, ManifestStore, StartupManifest } from "./loader/manifest.js";
import { CaseStyle, CaseStyleFunction, EXPORTS, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem } from "./loader/file_loader.js";

@@ -13,2 +14,2 @@ import { ClassLoader, ClassLoaderOptions, ContextLoader, ContextLoaderOptions } from "./loader/context_loader.js";

import { SequencifyResult, SequencifyTask, sequencify } from "./utils/sequencify.js";
export { BaseContextClass, BootImplClass, CaseStyle, CaseStyleFunction, ClassLoader, ClassLoaderOptions, Context, ContextLoader, ContextLoaderOptions, CustomLoaderConfigItem, EGG_LOADER, EXPORTS, EggAppConfig, EggAppInfo, EggCore, EggCoreInitOptions, EggCoreOptions, EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions, EggPluginInfo, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem, FunWithFullPath, ILifecycleBoot, KoaApplication, KoaContext, KoaMiddlewareFunc, KoaRequest, KoaResponse, Lifecycle, LifecycleOptions, MiddlewareFunc, Next, Request, Response, Router, SequencifyResult, SequencifyTask, Singleton, SingletonCreateMethod, SingletonOptions, Timing, TimingItem, sequencify, utils };
export { BaseContextClass, BootImplClass, CaseStyle, CaseStyleFunction, ClassLoader, ClassLoaderOptions, Context, ContextLoader, ContextLoaderOptions, CustomLoaderConfigItem, EGG_LOADER, EXPORTS, EggAppConfig, EggAppInfo, EggCore, EggCoreInitOptions, EggCoreOptions, EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions, EggPluginInfo, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem, FunWithFullPath, ILifecycleBoot, KoaApplication, KoaContext, KoaMiddlewareFunc, KoaRequest, KoaResponse, Lifecycle, LifecycleOptions, ManifestGenerateOptions, ManifestInvalidation, ManifestStore, MiddlewareFunc, Next, Request, Response, Router, SequencifyResult, SequencifyTask, Singleton, SingletonCreateMethod, SingletonOptions, StartupManifest, Timing, TimingItem, sequencify, utils };

@@ -8,2 +8,3 @@ import utils_default from "./utils/index.js";

import { ClassLoader, ContextLoader } from "./loader/context_loader.js";
import { ManifestStore } from "./loader/manifest.js";
import { EggLoader } from "./loader/egg_loader.js";

@@ -13,2 +14,2 @@ import { Singleton } from "./singleton.js";

export { BaseContextClass, CaseStyle, ClassLoader, Context, ContextLoader, EGG_LOADER, EXPORTS, EggCore, EggLoader, FULLPATH, FileLoader, KoaApplication, KoaContext, KoaRequest, KoaResponse, Lifecycle, Request, Response, Router, Singleton, Timing, sequencify, utils_default as utils };
export { BaseContextClass, CaseStyle, ClassLoader, Context, ContextLoader, EGG_LOADER, EXPORTS, EggCore, EggLoader, FULLPATH, FileLoader, KoaApplication, KoaContext, KoaRequest, KoaResponse, Lifecycle, ManifestStore, Request, Response, Router, Singleton, Timing, sequencify, utils_default as utils };

@@ -43,2 +43,8 @@ import { Fun } from "./utils/index.js";

beforeClose?(): Promise<void>;
/**
* Collect metadata for manifest generation (metadataOnly mode).
* Called instead of configWillLoad/configDidLoad/didLoad/willReady
* when the application is started with metadataOnly: true.
*/
loadMetadata?(): Promise<void> | void;
}

@@ -82,4 +88,5 @@ type BootImplClass<T = ILifecycleBoot> = new (...args: any[]) => T;

triggerServerDidReady(): Promise<void>;
triggerLoadMetadata(): Promise<void>;
}
//#endregion
export { BootImplClass, FunWithFullPath, ILifecycleBoot, Lifecycle, LifecycleOptions };

@@ -18,2 +18,3 @@ import utils_default from "./utils/index.js";

#isClosed;
#metadataOnly;
#closeFunctionSet;

@@ -33,2 +34,3 @@ loadReady;

this.#isClosed = false;
this.#metadataOnly = false;
this.#init = false;

@@ -46,3 +48,3 @@ this.timing.start(`${this.options.app.type} Start`);

this.ready((err) => {
this.triggerDidReady(err);
if (!this.#metadataOnly) this.triggerDidReady(err);
debug("app ready");

@@ -227,2 +229,20 @@ this.timing.end(`${this.options.app.type} Start`);

}
async triggerLoadMetadata() {
this.#metadataOnly = true;
debug("trigger loadMetadata start");
let firstError;
for (const boot of this.#boots) if (typeof boot.loadMetadata === "function") {
debug("trigger loadMetadata at %o", boot.fullPath);
try {
await boot.loadMetadata();
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
if (!firstError) firstError = error;
debug("trigger loadMetadata error at %o, error: %s", boot.fullPath, error);
this.emit("error", error);
}
}
debug("trigger loadMetadata end");
this.ready(firstError ?? true);
}
#initReady() {

@@ -229,0 +249,0 @@ debug("loadReady init");

import { Timing } from "../utils/timing.js";
import { Lifecycle } from "../lifecycle.js";
import { EggAppConfig, EggAppInfo, EggPluginInfo } from "../types.js";
import { ManifestStore, StartupManifest } from "./manifest.js";
import { FileLoader, FileLoaderOptions } from "./file_loader.js";

@@ -24,2 +25,4 @@ import { ContextLoader, ContextLoaderOptions } from "./context_loader.js";

plugins?: Record<string, EggPluginInfo>;
/** Skip lifecycle hooks, only trigger loadMetadata for manifest generation */
metadataOnly?: boolean;
}

@@ -42,2 +45,4 @@ type EggDirInfoType = "app" | "plugin" | "framework";

dirs?: EggDirInfo[];
/** Startup manifest — loaded from cache or collecting for generation */
readonly manifest: ManifestStore;
/**

@@ -374,4 +379,9 @@ * @class

resolveModule(filepath: string): string | undefined;
/**
* Generate startup manifest from collected data.
* Should be called after all loading phases complete.
*/
generateManifest(): StartupManifest;
}
//#endregion
export { EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions };

@@ -6,2 +6,3 @@ import utils_default from "../utils/index.js";

import { ContextLoader } from "./context_loader.js";
import { ManifestStore } from "./manifest.js";
import fs from "node:fs";

@@ -40,2 +41,4 @@ import path from "node:path";

dirs;
/** Startup manifest — loaded from cache or collecting for generation */
manifest;
/**

@@ -109,2 +112,3 @@ * @class

this.appInfo = this.getAppInfo();
this.manifest = ManifestStore.load(this.options.baseDir, this.serverEnv, this.serverScope) ?? ManifestStore.createCollector(this.options.baseDir);
}

@@ -747,3 +751,4 @@ get app() {

await this.#loadBootHook("app");
this.lifecycle.triggerConfigWillLoad();
if (this.options.metadataOnly) await this.lifecycle.triggerLoadMetadata();
else this.lifecycle.triggerConfigWillLoad();
}

@@ -755,3 +760,4 @@ /**

await this.#loadBootHook("agent");
this.lifecycle.triggerConfigWillLoad();
if (this.options.metadataOnly) await this.lifecycle.triggerLoadMetadata();
else this.lifecycle.triggerConfigWillLoad();
}

@@ -1049,3 +1055,4 @@ loadBootHook() {}

target,
inject: this.app
inject: this.app,
manifest: this.manifest
};

@@ -1069,3 +1076,4 @@ const timingKey = `Load "${String(property)}" to Application`;

property,
inject: this.app
inject: this.app,
manifest: this.manifest
};

@@ -1100,2 +1108,5 @@ const timingKey = `Load "${String(property)}" to Context`;

resolveModule(filepath) {
return this.manifest.resolveModule(filepath, () => this.#doResolveModule(filepath));
}
#doResolveModule(filepath) {
let fullPath;

@@ -1135,2 +1146,13 @@ try {

}
/**
* Generate startup manifest from collected data.
* Should be called after all loading phases complete.
*/
generateManifest() {
return this.manifest.generateManifest({
serverEnv: this.serverEnv,
serverScope: this.serverScope,
typescriptEnabled: isSupportTypeScript()
});
}
};

@@ -1137,0 +1159,0 @@ function depCompatible(plugin) {

import { Fun } from "../utils/index.js";
import { ManifestStore } from "./manifest.js";

@@ -40,2 +41,4 @@ //#region src/loader/file_loader.d.ts

lowercaseFirst?: boolean;
/** Startup manifest for caching globby scans and collecting results */
manifest?: ManifestStore;
}

@@ -42,0 +45,0 @@ interface FileLoaderParseItem {

@@ -130,4 +130,5 @@ import utils_default from "../utils/index.js";

for (const directory of directories) {
const filepaths = globby.sync(files, { cwd: directory });
debug("[parse] globby files: %o, cwd: %o => %o", files, directory, filepaths);
const manifest = this.options.manifest;
const filepaths = manifest ? manifest.globFiles(directory, () => globby.sync(files, { cwd: directory })) : globby.sync(files, { cwd: directory });
debug("[parse] files: %o, cwd: %o => %o", files, directory, filepaths);
for (const filepath of filepaths) {

@@ -134,0 +135,0 @@ const fullpath = path.join(directory, filepath);

{
"name": "@eggjs/core",
"version": "7.0.2-beta.6",
"version": "7.0.2-beta.7",
"description": "A core plugin framework based on @eggjs/koa",

@@ -44,7 +44,7 @@ "keywords": [

"utility": "^2.5.0",
"@eggjs/extend2": "5.0.2-beta.6",
"@eggjs/router": "4.0.2-beta.6",
"@eggjs/utils": "5.0.2-beta.6",
"@eggjs/koa": "3.1.2-beta.6",
"@eggjs/path-matching": "3.0.2-beta.6"
"@eggjs/extend2": "5.0.2-beta.7",
"@eggjs/koa": "3.1.2-beta.7",
"@eggjs/path-matching": "3.0.2-beta.7",
"@eggjs/router": "4.0.2-beta.7",
"@eggjs/utils": "5.0.2-beta.7"
},

@@ -61,5 +61,5 @@ "devDependencies": {

"urllib": "^4.8.2",
"@eggjs/tsconfig": "3.1.2-beta.6",
"@eggjs/mock": "7.0.2-beta.6",
"@eggjs/supertest": "9.0.2-beta.6"
"@eggjs/mock": "7.0.2-beta.7",
"@eggjs/tsconfig": "3.1.2-beta.7",
"@eggjs/supertest": "9.0.2-beta.7"
},

@@ -66,0 +66,0 @@ "engines": {