feat(FN-3882): document clearStaleBlockedBy() in SelfHealingManager section
Added a single line documenting `clearStaleBlockedBy()` in the SelfHealingManager section of the architecture docs. Fusion-Task-Id: FN-3882
This commit is contained in:
@@ -158,15 +158,24 @@ describe("CLI bundle output", () => {
|
||||
expect(existsSync(join(stagedRoot, "src", "process-manager.ts"))).toBe(true);
|
||||
});
|
||||
|
||||
it("dist/plugins/fusion-plugin-dependency-graph/ is staged with a valid manifest", () => {
|
||||
it("dist/plugins/fusion-plugin-dependency-graph/ is staged as bundled runtime output", () => {
|
||||
const stagedRoot = join(cliRoot, "dist", "plugins", "fusion-plugin-dependency-graph");
|
||||
const manifestPath = join(stagedRoot, "manifest.json");
|
||||
const packageJsonPath = join(stagedRoot, "package.json");
|
||||
|
||||
expect(existsSync(manifestPath)).toBe(true);
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as { id?: string; name?: string };
|
||||
expect(manifest.id).toBe("fusion-plugin-dependency-graph");
|
||||
expect(typeof manifest.name).toBe("string");
|
||||
expect(manifest.name?.length).toBeGreaterThan(0);
|
||||
|
||||
expect(existsSync(join(stagedRoot, "bundled.js"))).toBe(true);
|
||||
expect(existsSync(join(stagedRoot, "src"))).toBe(false);
|
||||
|
||||
const stagedPkg = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
|
||||
exports?: { "."?: { import?: string } };
|
||||
};
|
||||
expect(stagedPkg.exports?.["."]?.import).toBe("./bundled.js");
|
||||
});
|
||||
|
||||
it("dist/plugins/fusion-plugin-whatsapp-chat/ is staged with a valid manifest", () => {
|
||||
|
||||
@@ -98,17 +98,20 @@ describe("CLI package.json publishing config", () => {
|
||||
);
|
||||
|
||||
function extractStringArray(name: string): string[] {
|
||||
const m = tsupRaw.match(new RegExp(`${name}:\\s*\\[([\\s\\S]*?)\\]`, "m"));
|
||||
if (!m) return [];
|
||||
return [...m[1].matchAll(/["']([^"']+)["']/g)].map((mm) => mm[1]);
|
||||
const matches = [...tsupRaw.matchAll(new RegExp(`${name}:\\s*\\[([\\s\\S]*?)\\]`, "gm"))];
|
||||
const values = matches.flatMap((m) =>
|
||||
[...m[1].matchAll(/["']([^"']+)["']/g)].map((mm) => mm[1]),
|
||||
);
|
||||
return [...new Set(values)];
|
||||
}
|
||||
|
||||
function extractRegexes(name: string): RegExp[] {
|
||||
const m = tsupRaw.match(new RegExp(`${name}:\\s*\\[([\\s\\S]*?)\\]`, "m"));
|
||||
if (!m) return [];
|
||||
const matches = [...tsupRaw.matchAll(new RegExp(`${name}:\\s*\\[([\\s\\S]*?)\\]`, "gm"))];
|
||||
// Match `/PATTERN/flags` where PATTERN may contain escaped slashes (`\/`).
|
||||
return [...m[1].matchAll(/\/((?:\\\/|[^/\n])+)\/[gimsuy]*/g)].map(
|
||||
(mm) => new RegExp(mm[1].replace(/\\\//g, "/")),
|
||||
return matches.flatMap((m) =>
|
||||
[...m[1].matchAll(/\/((?:\\\/|[^/\n])+)\/[gimsuy]*/g)].map(
|
||||
(mm) => new RegExp(mm[1].replace(/\\\//g, "/")),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,6 +128,8 @@ describe("CLI package.json publishing config", () => {
|
||||
"cpu-features": "transitive dep of dockerode (via ssh2)",
|
||||
"@homebridge/node-pty-prebuilt-multiarch":
|
||||
"aliased as node-pty in dependencies; the alias entry satisfies the import",
|
||||
"@fusion/core": "plugin-entry bundling external only; not a runtime dep of the CLI bin",
|
||||
"@fusion/engine": "plugin-entry bundling external only; not a runtime dep of the CLI bin",
|
||||
};
|
||||
|
||||
it("parses externals from tsup.config.ts", () => {
|
||||
|
||||
@@ -195,9 +195,9 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("resolvePluginEntryPath", () => {
|
||||
it("prefers src/index.ts over bundled.js when both exist in workspace contexts", () => {
|
||||
it("prefers bundled.js when both bundled and source entries exist", () => {
|
||||
mockExistsSync.mockImplementation((p: string) => p.endsWith("/src/index.ts") || p.endsWith("/bundled.js"));
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/src/index.ts");
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/bundled.js");
|
||||
});
|
||||
|
||||
it("prefers bundled.js when source entry is unavailable", () => {
|
||||
@@ -205,14 +205,14 @@ describe("resolvePluginEntryPath", () => {
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/bundled.js");
|
||||
});
|
||||
|
||||
it("prefers src/index.ts over dist/index.js in workspace contexts", () => {
|
||||
it("prefers dist/index.js when bundled.js is unavailable", () => {
|
||||
mockExistsSync.mockImplementation((p: string) => p.endsWith("/src/index.ts") || p.endsWith("/dist/index.js"));
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/src/index.ts");
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/dist/index.js");
|
||||
});
|
||||
|
||||
it("falls back to dist/index.js when source entry is unavailable", () => {
|
||||
mockExistsSync.mockImplementation((p: string) => p.endsWith("/dist/index.js"));
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/dist/index.js");
|
||||
it("falls back to src/index.ts for workspace-dev plugins without build outputs", () => {
|
||||
mockExistsSync.mockImplementation((p: string) => p.endsWith("/src/index.ts"));
|
||||
expect(resolvePluginEntryPath("/tmp/plugin")).toBe("/tmp/plugin/src/index.ts");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -412,10 +412,11 @@ describe("ensureBundledDependencyGraphPluginInstalled", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("registers Hermes from source entry when both src and dist entries exist", async () => {
|
||||
it("registers Hermes from bundled.js when bundled, src, and dist entries all exist", async () => {
|
||||
const manifest = makeManifest({ id: HERMES_PLUGIN_ID, name: "Hermes Runtime" });
|
||||
mockExistsSync.mockImplementation((p: string) => {
|
||||
if (p.endsWith("manifest.json") && p.includes(HERMES_PLUGIN_ID)) return true;
|
||||
if (p.endsWith("/bundled.js") && p.includes(HERMES_PLUGIN_ID)) return true;
|
||||
if (p.endsWith("/src/index.ts") && p.includes(HERMES_PLUGIN_ID)) return true;
|
||||
if (p.endsWith("/dist/index.js") && p.includes(HERMES_PLUGIN_ID)) return true;
|
||||
return false;
|
||||
@@ -434,6 +435,6 @@ describe("ensureBundledDependencyGraphPluginInstalled", () => {
|
||||
|
||||
expect(result).toBe("installed");
|
||||
const registerCall = store.registerPlugin.mock.calls[0]?.[0] as { path: string };
|
||||
expect(registerCall.path).toContain(`${HERMES_PLUGIN_ID}/src/index.ts`);
|
||||
expect(registerCall.path).toContain(`${HERMES_PLUGIN_ID}/bundled.js`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,16 +64,16 @@ function resolveBundledPluginDir(pluginId: string): string | null {
|
||||
* Resolve the actual loadable entry FILE path for a plugin directory. Node ESM
|
||||
* does not allow directory imports, so we must register the explicit file the
|
||||
* loader will dynamic-import. Preference order:
|
||||
* 1. ./src/index.ts (workspace/dev source of truth)
|
||||
* 2. ./bundled.js (esbuild-bundled, ships in npm tarball)
|
||||
* 3. ./dist/index.js
|
||||
* 1. ./bundled.js (esbuild-bundled, shipped in npm tarball)
|
||||
* 2. ./dist/index.js (legacy prebuilt fallback)
|
||||
* 3. ./src/index.ts (workspace/dev fallback when no bundle exists)
|
||||
* 4. fall back to the directory itself
|
||||
*/
|
||||
export function resolvePluginEntryPath(pluginDir: string): string {
|
||||
const candidates = [
|
||||
join(pluginDir, "src", "index.ts"),
|
||||
join(pluginDir, "bundled.js"),
|
||||
join(pluginDir, "dist", "index.js"),
|
||||
join(pluginDir, "src", "index.ts"),
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
|
||||
Reference in New Issue
Block a user