| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- import { afterEach, expect } from "bun:test"
- import { existsSync } from "node:fs"
- import path from "node:path"
- import { pathToFileURL } from "node:url"
- import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
- import { CrossSpawnSpawner } from "@kirincode-ai/core/cross-spawn-spawner"
- import { Cause, Effect, Exit, Fiber } from "effect"
- import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
- import { InstanceBootstrap } from "../../src/project/bootstrap"
- import { InstanceStore } from "../../src/project/instance-store"
- import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
- import { testEffect } from "../lib/effect"
- import { waitGlobalBusEvent } from "../server/global-bus"
- const it = testEffect(
- LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node]), [
- [InstanceStore.bootstrapNode, InstanceBootstrap.node],
- ]),
- )
- // InstanceBootstrap must run before any code touches the instance —
- // originally tracked by PRs #25389 and #25449, now a permanent
- // invariant. The plugin config hook writes a marker file; the test
- // bodies deliberately avoid Plugin/config directly. The marker only
- // appears if InstanceBootstrap ran at the instance boundary.
- //
- // The boundaries below are transport-agnostic and stay.
- afterEach(async () => {
- await disposeAllInstances()
- })
- const bootstrapFixture = Effect.gen(function* () {
- const dir = yield* tmpdirScoped({ git: true })
- const marker = path.join(dir, "config-hook-fired")
- const pluginFile = path.join(dir, "plugin.ts")
- yield* Effect.promise(() =>
- Bun.write(
- pluginFile,
- [
- `const MARKER = ${JSON.stringify(marker)}`,
- "export default async () => ({",
- " config: async () => {",
- ' await Bun.write(MARKER, "ran")',
- " },",
- "})",
- "",
- ].join("\n"),
- ),
- )
- yield* Effect.promise(() =>
- Bun.write(
- path.join(dir, "kirincode.json"),
- JSON.stringify({
- $schema: "https://kirincode.ai/config.json",
- plugin: [pathToFileURL(pluginFile).href],
- }),
- ),
- )
- return { directory: dir, marker }
- })
- function waitDisposed(directory: string) {
- return waitGlobalBusEvent({
- message: "timed out waiting for CLI bootstrap instance disposal",
- predicate: (event) => event.payload.type === "server.instance.disposed" && event.directory === directory,
- })
- }
- it.live("InstanceStore.provide runs InstanceBootstrap before effect", () =>
- Effect.gen(function* () {
- const tmp = yield* bootstrapFixture
- const store = yield* InstanceStore.Service
- yield* store.provide({ directory: tmp.directory }, Effect.succeed("ok"))
- expect(existsSync(tmp.marker)).toBe(true)
- }),
- )
- it.live("CLI bootstrap runs InstanceBootstrap before callback", () =>
- Effect.gen(function* () {
- const tmp = yield* bootstrapFixture
- yield* Effect.promise(() => cliBootstrap(tmp.directory, async () => "ok"))
- expect(existsSync(tmp.marker)).toBe(true)
- }),
- )
- it.live("CLI bootstrap disposes the instance when the callback rejects", () =>
- Effect.gen(function* () {
- const tmp = yield* bootstrapFixture
- const disposed = yield* waitDisposed(tmp.directory).pipe(Effect.forkScoped({ startImmediately: true }))
- const exit = yield* Effect.promise(() =>
- cliBootstrap(tmp.directory, async () => Promise.reject(new Error("boom"))),
- ).pipe(Effect.exit)
- expect(Exit.isFailure(exit)).toBe(true)
- if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toMatchObject({ message: "boom" })
- yield* Fiber.join(disposed)
- }),
- )
- it.live("InstanceStore.reload runs InstanceBootstrap", () =>
- Effect.gen(function* () {
- const tmp = yield* bootstrapFixture
- const store = yield* InstanceStore.Service
- yield* store.reload({ directory: tmp.directory })
- expect(existsSync(tmp.marker)).toBe(true)
- }),
- )
|