Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
# Bundle Size Development Workflow
This package contains the internal bundle-size tooling used to measure Effect
entrypoints with Rollup, minification, and gzip.
## AI Agent Instructions
Use this workflow when a user wants to quickly understand how a local source
change affects bundle size for one or more explicitly selected fixture files.
Do not scan `scratchpad/` or `packages/tools/bundle/fixtures/` for this workflow.
Only measure files that the user explicitly names, or files you create
specifically for the current investigation.
Before running a comparison, ask the user which mode they want:
- default cleanup mode for a single check or one-off investigation;
- keep-base mode for many repeated measurements, followed by explicit cleanup at
the end.
Use cleanup mode unless the user says they expect to run multiple trials.
If the user asks what a generated bundle is made of, use the bundle composition
workflow instead of the size comparison workflow.
### Analyze Bundle Composition
Use this workflow when the user gives an explicit fixture and wants to
understand which modules make up the produced bundle.
Agent procedure:
1. Confirm the exact fixture path or paths to analyze. Do not scan directories.
2. Run `pnpm bundle-analyze` with only those explicit paths.
3. Read the printed Markdown table and identify the generated `*.raw-data.json`
path for each fixture.
4. Analyze `*.raw-data.json` first. Use `*.treemap.html` only when the user wants
a visual artifact to open.
5. Report the largest modules, notable dependency groups, and any surprising
inclusions. Mention the generated artifact paths in the answer.
Prefer the repository-level wrapper:
```sh
pnpm bundle-analyze scratchpad/my-fixture.ts
```
You can pass multiple explicit files:
```sh
pnpm bundle-analyze \
scratchpad/schema-codec.ts \
scratchpad/schema-arbitrary.ts
```
The wrapper builds the current checkout with `pnpm build` before generating
analysis artifacts, so the bundle is not produced from stale `dist` files.
By default, artifacts are written to `tmp/bundle-analysis`. Use `--output-dir`
to choose another directory:
```sh
pnpm bundle-analyze --output-dir tmp/schema-analysis scratchpad/my-fixture.ts
```
For each selected fixture, the tool writes:
- `<name>.min.js`: the generated Rollup output for inspection;
- `<name>.treemap.html`: an interactive treemap for human inspection;
- `<name>.raw-data.json`: raw visualizer data suitable for AI analysis.
The analysis build disables identifier mangling so module and export names stay
readable in the visualizer output. Use `bundle-compare-selected` or `report`
when exact size measurement is the goal.
After running the command, inspect the Markdown table it prints to find the exact
paths. For an AI analysis, read the `*.raw-data.json` file first and summarize
the largest modules and dependency groups. Use the `*.treemap.html` file when
the user wants to inspect the bundle visually.
The raw data contains a tree plus module metadata. For a quick size-oriented
summary, rank module parts by `renderedLength` or `gzipLength`, then map each
part through `metaUid` to `nodeMetas[metaUid].id`.
Example inspection command:
```sh
node -e 'const data = JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")); console.log(Object.entries(data.nodeParts).map(([uid, part]) => ({ uid, id: data.nodeMetas[part.metaUid]?.id, rendered: part.renderedLength, gzip: part.gzipLength })).sort((a, b) => b.rendered - a.rendered).slice(0, 20))' tmp/bundle-analysis/my-fixture.raw-data.json
```
Do not treat the generated `.min.js` size from this workflow as the exact bundle
size comparison number. The analysis build keeps names readable for the
visualizer. Use `bundle-compare-selected` or `report` for exact size reporting.
Do not use this workflow to compare against a base ref. Use
`bundle-compare-selected` for size impact and `bundle-analyze` for current bundle
composition.
### Compare Explicit Scratchpad Fixtures
Prefer the repository-level wrapper:
```sh
pnpm bundle-compare-selected --base main scratchpad/my-fixture.ts
```
You can pass multiple explicit files:
```sh
pnpm bundle-compare-selected --base main \
scratchpad/schema-codec.ts \
scratchpad/schema-arbitrary.ts
```
If `--base` is omitted, the script compares against `main`.
By default, the wrapper removes `tmp/bundle-base` before exiting, including on
failure. This avoids leaving extra git worktree state after a one-off
measurement.
For repeated measurements where the user explicitly wants a faster feedback loop,
pass `--keep-base`:
```sh
pnpm bundle-compare-selected --base main --keep-base scratchpad/my-fixture.ts
```
When using `--keep-base`, clean up `tmp/bundle-base` after the last measurement.
The explicit final cleanup command is:
```sh
git worktree remove --force tmp/bundle-base
```
The wrapper:
- builds the current checkout with `pnpm build`;
- creates or reuses `tmp/bundle-base` at the requested base ref;
- builds the base checkout when needed and writes
`tmp/bundle-base/.bundle-build-stamp` only after a successful build;
- reuses a kept base checkout only when its HEAD and `.bundle-build-stamp`
both match the requested base ref;
- copies only the selected files into the base checkout at the same relative
paths;
- invokes the internal bundle CLI to compare current vs base sizes.
The selected files are copied into the base checkout so the report isolates
source changes instead of changes to the fixture text.
Expected output is a Markdown table:
```md
| File Name | Current Size | Previous Size | Difference |
| :------------------------- | :----------: | :-----------: | :---------------: |
| `scratchpad/my-fixture.ts` | 42.10 KB | 40.80 KB | +1.30 KB (+3.19%) |
```
### Internal CLI
Use the internal CLI directly only when the base checkout is already prepared
and built:
```sh
node packages/tools/bundle/src/bin.ts compare-selected \
--base-dir tmp/bundle-base \
scratchpad/my-fixture.ts
```
The `--base-dir` value must be the root of the base checkout, not the base
fixture directory.
### Current Size Only
If the user only asks for the current bundled size, use:
```sh
pnpm --dir packages/tools/bundle report ../../../scratchpad/my-fixture.ts
```
This does not compare against a base ref.
### Fixture Guidance
Keep temporary fixtures in `scratchpad/`.
Good temporary fixtures are small, focused entrypoints that import public
package APIs:
```ts
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
name: Schema.String
})
Schema.decodeUnknownEffect(schema)({ name: "effect" }).pipe(Effect.runFork)
```
Prefer self-contained fixtures. The compare-selected workflow copies only the
explicit entry files into the base checkout. If a fixture imports local relative
helpers, either avoid that shape or make the entry fixture self-contained before
measuring.
Do not add temporary fixtures to `packages/tools/bundle/fixtures/`. That
directory is for stable comparison fixtures used by the regular bundle-size
workflow.
### Cleanup
The wrapper cleans up `tmp/bundle-base` by default. If `--keep-base` was used for
a multi-run investigation, remove the worktree when the user is done.
To remove it:
```sh
git worktree remove --force tmp/bundle-base
```
To verify that cleanup succeeded:
```sh
git worktree list
```
Only the main repository worktree should remain.

View File

@@ -0,0 +1,4 @@
{
"$schema": "../../../node_modules/@effect/docgen/schema.json",
"exclude": ["src/bin.ts"]
}

View File

@@ -0,0 +1,3 @@
import * as Effect from "effect/Effect"
Effect.succeed(123).pipe(Effect.runFork)

View File

@@ -0,0 +1,25 @@
import * as Array from "effect/Array"
import * as Effect from "effect/Effect"
import * as Exit from "effect/Exit"
import * as Request from "effect/Request"
import * as Resolver from "effect/RequestResolver"
class GetNameById extends Request.TaggedClass("GetNameById")<{
readonly id: number
}, string> {}
const UserResolver = Resolver.make<GetNameById>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`))
}
})
)
const effect = Effect.forEach(
Array.range(1, 100_000),
(id) => Effect.request(new GetNameById({ id }), UserResolver),
{ concurrency: "unbounded" }
)
Effect.runFork(effect)

View File

@@ -0,0 +1,5 @@
import * as Brand from "effect/Brand"
import * as Schema from "effect/Schema"
type Positive = number & Brand.Brand<"Positive">
const Positive = Brand.check<Positive>(Schema.isGreaterThan(0))

View File

@@ -0,0 +1,10 @@
import * as Cache from "effect/Cache"
import * as Effect from "effect/Effect"
Cache.make({
capacity: 1024,
lookup: (key: string) => Effect.succeed(key)
}).pipe(
Effect.flatMap(Cache.get("1")),
Effect.runFork
)

View File

@@ -0,0 +1,13 @@
import * as Config from "effect/Config"
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
API_KEY: Schema.String,
PORT: Schema.Int,
LOCALHOST: Schema.URL
})
const config = Config.schema(schema)
Effect.runFork(config)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
id: Schema.Number,
name: Schema.String,
price: Schema.Number
})
Schema.toDifferJsonPatch(schema)

View File

@@ -0,0 +1,12 @@
import * as Effect from "effect/Effect"
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
import * as HttpClient from "effect/unstable/http/HttpClient"
Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const res = yield* client.get("https://jsonplaceholder.typicode.com/posts/1")
yield* res.json
}).pipe(
Effect.provide(FetchHttpClient.layer),
Effect.runPromise
)

View File

@@ -0,0 +1,5 @@
import * as Effect from "effect/Effect"
Effect.log("hello").pipe(
Effect.runFork
)

View File

@@ -0,0 +1,13 @@
import * as Effect from "effect/Effect"
import * as Metric from "effect/Metric"
const program = Effect.gen(function*() {
yield* Effect.succeed(1).pipe(
Effect.forkChild({ startImmediately: true })
)
})
program.pipe(
Metric.enableRuntimeMetrics,
Effect.runFork
)

View File

@@ -0,0 +1,6 @@
import * as Optic from "effect/Optic"
type S = { readonly a: number }
const optic = Optic.id<S>().key("a")
optic.getResult({ a: 1 })

View File

@@ -0,0 +1,22 @@
import * as Effect from "effect/Effect"
import * as PubSub from "effect/PubSub"
const program = Effect.gen(function*() {
const pubsub = yield* PubSub.unbounded<number>()
yield* Effect.gen(function*() {
const subscription = yield* PubSub.subscribe(pubsub)
while (true) {
const element = yield* PubSub.take(subscription)
console.log(element)
}
}).pipe(Effect.forkScoped({ startImmediately: true }))
yield* PubSub.publishAll(pubsub, [1, 2])
yield* PubSub.publishAll(pubsub, [3, 4]).pipe(Effect.delay("100 millis"), Effect.forkScoped)
yield* PubSub.publishAll(pubsub, [5, 6, 7, 8]).pipe(Effect.delay("200 millis"), Effect.forkScoped)
yield* Effect.sleep("500 millis")
})
Effect.runFork(Effect.scoped(program))

View File

@@ -0,0 +1,18 @@
import * as Effect from "effect/Effect"
import * as Queue from "effect/Queue"
const program = Effect.gen(function*() {
const queue = yield* Queue.make<number>()
yield* Effect.gen(function*() {
yield* Queue.takeN(queue, 3)
}).pipe(Effect.forever, Effect.forkScoped)
yield* Queue.offerAll(queue, [1, 2])
yield* Queue.offerAll(queue, [3, 4]).pipe(Effect.delay("100 millis"), Effect.forkScoped)
yield* Queue.offerAll(queue, [5, 6, 7, 8]).pipe(Effect.delay("200 millis"), Effect.forkScoped)
yield* Effect.sleep("500 millis")
})
Effect.runFork(Effect.scoped(program))

View File

@@ -0,0 +1,9 @@
import * as Effect from "effect/Effect"
import * as Schedule from "effect/Schedule"
Effect.succeed(123).pipe(
Effect.repeat({
schedule: Schedule.spaced("100 millis")
}),
Effect.runFork
)

View File

@@ -0,0 +1,12 @@
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
class A extends Schema.Class<A>("A")({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
}) {}
Schema.decodeUnknownEffect(A)({ a: "a", b: 1, c: ["c"] }).pipe(
Effect.runFork
)

View File

@@ -0,0 +1,16 @@
import * as SchemaRepresentation from "effect/SchemaRepresentation"
const doc = SchemaRepresentation.fromJsonSchemaDocument({
"dialect": "draft-2020-12",
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
"definitions": {}
})
console.dir(doc, { depth: null })

View File

@@ -0,0 +1,16 @@
import * as Schema from "effect/Schema"
import * as SchemaRepresentation from "effect/SchemaRepresentation"
const schema = Schema.toCodecJson(Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
}))
const json = Schema.encodeSync(SchemaRepresentation.DocumentFromJson)(
SchemaRepresentation.fromAST(schema.ast)
)
SchemaRepresentation.toSchema(
Schema.decodeSync(SchemaRepresentation.DocumentFromJson)(JSON.parse(JSON.stringify(json)))
)

View File

@@ -0,0 +1,20 @@
import * as Duration from "effect/Duration"
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
import * as SchemaTransformation from "effect/SchemaTransformation"
const schema = Schema.String.pipe(Schema.decodeTo(
Schema.String,
SchemaTransformation.transformOrFail({
decode: (s) =>
Effect.gen(function*() {
yield* Effect.clockWith((clock) => clock.sleep(Duration.millis(300)))
return s
}),
encode: (_) => Effect.succeed(_)
})
))
Schema.decodeUnknownEffect(schema)({ a: "a", b: 1, c: ["c"] }).pipe(
Effect.runFork
)

View File

@@ -0,0 +1,8 @@
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
const schema = Schema.String
Schema.decodeUnknownEffect(schema)("a").pipe(
Effect.runFork
)

View File

@@ -0,0 +1,8 @@
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
const schema = Schema.TemplateLiteral(["a", Schema.String])
Schema.decodeUnknownEffect(schema)("abc").pipe(
Effect.runFork
)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toArbitraryLazy(schema)

View File

@@ -0,0 +1,12 @@
import * as Schema from "effect/Schema"
import * as SchemaRepresentation from "effect/SchemaRepresentation"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
const representation = Schema.toRepresentation(schema)
SchemaRepresentation.toCodeDocument(SchemaRepresentation.toMultiDocument(representation))

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toCodecJson(schema)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toEquivalence(schema)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toFormatter(schema)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toJsonSchemaDocument(schema)

View File

@@ -0,0 +1,9 @@
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.toRepresentation(schema)

View File

@@ -0,0 +1,12 @@
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.optional(Schema.FiniteFromString),
c: Schema.Array(Schema.String)
})
Schema.decodeUnknownEffect(schema)({ a: "a", b: 1, c: ["c"] }).pipe(
Effect.runFork
)

View File

@@ -0,0 +1,21 @@
import * as Effect from "effect/Effect"
import * as TxRef from "effect/TxRef"
const program = Effect.gen(function*() {
const ref = yield* TxRef.make(0)
yield* Effect.forkChild(Effect.forever(
TxRef.update(ref, (n) => n + 1).pipe(Effect.delay("100 millis"))
))
yield* Effect.tx(Effect.gen(function*() {
const value = yield* TxRef.get(ref)
if (value < 10) {
yield* Effect.log(`retry due to value: ${value}`)
return yield* Effect.txRetry
}
yield* Effect.log(`transaction done with value: ${value}`)
}))
})
Effect.runPromise(program).catch(console.error)

View File

@@ -0,0 +1,7 @@
import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"
Stream.range(1, 100_000).pipe(
Stream.runDrain,
Effect.runSync
)

View File

@@ -0,0 +1,51 @@
{
"name": "@effect/bundle",
"version": "0.0.0",
"type": "module",
"private": true,
"license": "MIT",
"description": "Bundle size testing infrastructure for Effect packages",
"homepage": "https://effect.website",
"repository": {
"type": "git",
"url": "https://github.com/Effect-TS/effect.git",
"directory": "packages/tools/bundle"
},
"sideEffects": [],
"bin": {
"effect-bundle": "./src/bin.ts"
},
"exports": {
"./package.json": "./package.json",
"./*": "./src/*.ts"
},
"files": [
"src/**/*.ts",
"dist/**/*.js",
"dist/**/*.js.map",
"dist/**/*.d.ts",
"dist/**/*.d.ts.map"
],
"scripts": {
"build": "tsc -b tsconfig.src.json && pnpm babel",
"babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
"check": "tsc -b tsconfig.json",
"compare": "node src/bin.ts compare",
"report": "node src/bin.ts report",
"visualize": "node src/bin.ts visualize"
},
"dependencies": {
"@effect/platform-node": "workspace:^",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"@rollup/plugin-terser": "^1.0.0",
"effect": "workspace:^",
"glob": "^13.0.6",
"rollup": "^4.62.2",
"rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-visualizer": "^7.0.1"
},
"devDependencies": {
"@types/node": "^26.1.1"
}
}

View File

@@ -0,0 +1,123 @@
/**
* Command definitions for the `effect-bundle` bundle-size CLI.
*
* This module wires the top-level `bundle` command to the reporting service and
* exposes the workflows used when maintaining fixture bundle sizes. `compare`
* builds the package's local fixtures and compares them with matching fixture
* files from another checkout, `report` bundles an explicit list of entrypoints
* and prints a Markdown table, `compare-selected` compares explicit entrypoints
* against a base checkout, `visualize-selected` analyzes explicit entrypoints,
* and `visualize` prompts for local fixtures before producing visualization
* output for inspection.
*
* Command output is intentionally split by workflow. `compare` requires an
* existing `--base-dir` (`-b`) and writes its Markdown report to `--output-path`
* (`-o`), defaulting to `stats.txt` resolved from the current working directory.
* `report` accepts one or more existing files and writes to stdout. `visualize`
* uses `--output-dir` (`-o`) for generated bundle artifacts, so `-o` names a
* file for `compare` but a directory for `visualize`.
*
* @since 4.0.0
*/
import * as Console from "effect/Console"
import * as Effect from "effect/Effect"
import * as FileSystem from "effect/FileSystem"
import * as Path from "effect/Path"
import * as Argument from "effect/unstable/cli/Argument"
import * as Command from "effect/unstable/cli/Command"
import * as Flag from "effect/unstable/cli/Flag"
import * as Prompt from "effect/unstable/cli/Prompt"
import { Fixtures } from "./Fixtures.ts"
import { Reporter } from "./Reporter.ts"
const baseDirectory = Flag.directory("base-dir", { mustExist: true }).pipe(
Flag.withAlias("b"),
Flag.withDescription("The base directory to use for bundle size comparisons")
)
const outputPath = Flag.file("output-path").pipe(
Flag.withAlias("o"),
Flag.withDescription("The name of the file to write the bundle size report to"),
Flag.withDefault("stats.txt"),
Flag.mapEffect(Effect.fnUntraced(function*(outputPath) {
const path = yield* Path.Path
return path.resolve(outputPath)
}))
)
const compare = Command.make("compare", { baseDirectory, outputPath }).pipe(
Command.withHandler(Effect.fnUntraced(function*({ baseDirectory, outputPath }) {
const fs = yield* FileSystem.FileSystem
const reporter = yield* Reporter
const report = yield* reporter.report({ baseDirectory })
yield* fs.writeFileString(outputPath, report)
yield* Effect.log(`Bundle size report written to: '${outputPath}'`)
}))
)
const outputDirectory = Flag.directory("output-dir").pipe(
Flag.withAlias("o"),
Flag.withDescription("The name of the directory to write the bundle size visualizations to"),
Flag.mapEffect(Effect.fnUntraced(function*(outputPath) {
const path = yield* Path.Path
return path.resolve(outputPath)
}))
)
const visualize = Command.make("visualize", { outputDirectory }).pipe(
Command.withHandler(Effect.fnUntraced(function*({ outputDirectory }) {
const path = yield* Path.Path
const { fixtures, fixturesDir } = yield* Fixtures
const reporter = yield* Reporter
const paths = yield* Prompt.multiSelect({
message: "Select files whose bundle size you would like to visualize",
choices: fixtures.map((fixture) => ({
title: fixture,
value: path.join(fixturesDir, fixture)
}))
})
const report = yield* reporter.visualize({ paths, outputDirectory })
yield* Console.log(report)
}))
)
const reportPaths = Argument.file("paths", { mustExist: true }).pipe(
Argument.withDescription("Fixture files to include in the report"),
Argument.variadic({ min: 1 })
)
const report = Command.make("report", { paths: reportPaths }).pipe(
Command.withHandler(Effect.fnUntraced(function*({ paths }) {
const reporter = yield* Reporter
const report = yield* reporter.reportSelected({ paths })
yield* Console.log(report)
}))
)
const compareSelected = Command.make("compare-selected", { baseDirectory, paths: reportPaths }).pipe(
Command.withHandler(Effect.fnUntraced(function*({ baseDirectory, paths }) {
const reporter = yield* Reporter
const report = yield* reporter.reportSelectedComparison({ baseDirectory, paths })
yield* Console.log(report)
}))
)
const visualizeSelected = Command.make("visualize-selected", { outputDirectory, paths: reportPaths }).pipe(
Command.withHandler(Effect.fnUntraced(function*({ outputDirectory, paths }) {
const reporter = yield* Reporter
const report = yield* reporter.visualize({ outputDirectory, paths })
yield* Console.log(report)
}))
)
/**
* Bundle analysis CLI command with subcommands for comparing fixture bundle sizes, reporting selected fixtures, and generating visualizations.
*
* @category commands
* @since 4.0.0
*/
export const cli = Command.make("bundle").pipe(
Command.withSubcommands([compare, compareSelected, report, visualize, visualizeSelected])
)

View File

@@ -0,0 +1,51 @@
/**
* Discovers the TypeScript entrypoint fixtures used by the bundle-size tools.
*
* The bundle CLI uses these fixture names to build current bundle reports,
* compare them against a base directory, and populate the visualization
* selector. Fixtures are intentionally discovered from the package's local
* `fixtures` directory as top-level `.ts` files and sorted by name so reports
* are deterministic.
*
* When adding or renaming fixtures, keep in mind that comparison reports match
* files by basename between the current fixtures directory and the provided
* base directory. New fixtures without a matching base file are reported as
* unchanged. Each fixture is bundled as its own Rollup entrypoint, so it should
* represent the import shape being measured and avoid depending on incidental
* fixture discovery order.
*
* @since 4.0.0
*/
import * as Array from "effect/Array"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Order from "effect/Order"
import * as Glob from "glob"
/**
* Context service that discovers and sorts TypeScript fixture files used by the bundle size tooling.
*
* @category services
* @since 4.0.0
*/
export class Fixtures extends Context.Service<Fixtures>()(
"@effect/bundle/Fixtures",
{
make: Effect.gen(function*() {
const fixturesDir = new URL("../fixtures/", import.meta.url).pathname
const fixtures = yield* Effect.promise(() => Glob.glob("*.ts", { cwd: fixturesDir })).pipe(
Effect.map(Array.sort(Order.String)),
Effect.orDie
)
return {
fixtures,
fixturesDir
} as const
})
}
) {
static readonly layer = Layer.effect(this, this.make)
}

View File

@@ -0,0 +1,176 @@
/**
* Utilities for assembling the Rollup plugin pipeline used by the Effect
* bundle-size tooling.
*
* This module is responsible for the bundler-specific work that turns local
* fixture entrypoints into comparable ESM output: resolving Effect package
* imports against each package's built `dist` files, replacing production
* environment checks, lowering TypeScript with esbuild, minifying with terser,
* and optionally adding a bundle visualizer. It is primarily used by the
* Rollup service when measuring gzipped fixture sizes or opening a
* visualization for bundle inspection.
*
* Keep plugin ordering intentional when changing this module. Local package
* resolution must run before normal node resolution so workspace imports are
* measured from built artifacts, esbuild must emit ESM for Rollup to continue
* tree-shaking, and terser mangling is disabled while visualizing so reported
* module names stay readable.
*
* @since 4.0.0
*/
import { nodeResolve } from "@rollup/plugin-node-resolve"
import replace from "@rollup/plugin-replace"
import terser from "@rollup/plugin-terser"
import type * as Path from "effect/Path"
import * as Predicate from "effect/Predicate"
import type { Plugin } from "rollup"
import esbuild from "rollup-plugin-esbuild"
import { type PluginVisualizerOptions, visualizer } from "rollup-plugin-visualizer"
const EFFECT_PACKAGE_REGEX = /^(@effect\/[\w-]+|effect)(\/.*)?$/
const TYPE_SCRIPT_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"])
const toLocalDistPath = (pathService: Path.Path, packageDir: string, resolvedId: string): string => {
const srcDir = pathService.join(packageDir, "src")
const relative = pathService.relative(srcDir, resolvedId)
if (relative === "" || relative.startsWith("..") || pathService.isAbsolute(relative)) {
return resolvedId
}
const extension = pathService.extname(relative)
if (!TYPE_SCRIPT_EXTENSIONS.has(extension)) {
return resolvedId
}
return pathService.join(packageDir, "dist", relative.slice(0, -extension.length) + ".js")
}
/**
* Options for configuring Rollup plugins.
*
* @category options
* @since 4.0.0
*/
export interface PluginOptions {
readonly nodeTarget?: string | undefined
readonly minify?: boolean | undefined
readonly mangle?: boolean | undefined
readonly visualize?: boolean | undefined
readonly visualizations?: ReadonlyArray<VisualizationOutput> | undefined
}
/**
* Output generated by the Rollup visualizer plugin.
*
* @category options
* @since 4.0.0
*/
export interface VisualizationOutput {
readonly filename: string
readonly template: NonNullable<PluginVisualizerOptions["template"]>
readonly title?: string | undefined
}
interface ResolvedPluginOptions {
readonly nodeTarget: string
readonly minify: boolean
readonly mangle: boolean
readonly visualize: boolean
readonly visualizations: ReadonlyArray<VisualizationOutput>
}
const defaultPluginOptions: ResolvedPluginOptions = {
nodeTarget: "node20",
minify: true,
mangle: true,
visualize: false,
visualizations: []
}
/**
* Merges provided options with defaults.
*/
const resolvePluginOptions = (options: PluginOptions): ResolvedPluginOptions => ({
nodeTarget: options.nodeTarget ?? defaultPluginOptions.nodeTarget,
minify: options.minify ?? defaultPluginOptions.minify,
mangle: options.mangle ?? defaultPluginOptions.mangle,
visualize: options.visualize ?? defaultPluginOptions.visualize,
visualizations: options.visualizations ?? defaultPluginOptions.visualizations
})
/**
* Creates a custom Rollup plugin that resolves Effect package imports to their
* local dist directories.
*
* @category constructors
* @since 4.0.0
*/
export const createResolveLocalPackageImports = (pathService: Path.Path): Plugin => ({
name: "rollup-plugin-resolve-imports",
async resolveId(source, importer) {
const match = source.match(EFFECT_PACKAGE_REGEX)
if (Predicate.isNotNull(match)) {
const packageName = match[1]
const packageJson = await this.resolve(`${packageName}/package.json`, importer, { skipSelf: true })
if (packageJson === null) return null
const resolved = await this.resolve(source, importer, { skipSelf: true })
if (resolved === null) return null
return {
...resolved,
id: toLocalDistPath(pathService, pathService.dirname(packageJson.id), resolved.id),
external: false
}
}
return null
}
})
/**
* Creates the full Rollup plugin pipeline for bundling.
*
* @category constructors
* @since 4.0.0
*/
export const createPlugins = (pathService: Path.Path, options: PluginOptions = {}): Array<Plugin> => {
const resolved = resolvePluginOptions(options)
const plugins: Array<Plugin> = [
createResolveLocalPackageImports(pathService),
nodeResolve(),
// @ts-expect-error see https://github.com/rollup/plugins/issues/1662
replace({
"process.env.NODE_ENV": JSON.stringify("production"),
preventAssignment: true
}),
esbuild({
target: resolved.nodeTarget,
format: "esm",
treeShaking: true
}),
// @ts-expect-error see https://github.com/rollup/plugins/issues/1662
terser({
format: { comments: false },
compress: resolved.minify,
mangle: resolved.mangle && !resolved.visualize
})
]
if (resolved.visualizations.length > 0) {
for (const output of resolved.visualizations) {
const visualizerOptions: PluginVisualizerOptions = {
filename: output.filename,
gzipSize: true,
open: false,
template: output.template
}
if (output.title !== undefined) {
visualizerOptions.title = output.title
}
plugins.push(visualizer(visualizerOptions))
}
} else if (resolved.visualize) {
plugins.push(visualizer({
open: true,
gzipSize: true
}))
}
return plugins
}

View File

@@ -0,0 +1,299 @@
/**
* Bundle report generation for the Effect bundle-size tooling.
*
* The reporter coordinates fixture discovery with the Rollup service to turn
* measured fixture bundles into Markdown tables or visualization output. It is
* used by the bundle CLI to compare the current workspace against a checked-out
* base directory, to print a one-off report for selected entry files, and to
* generate visualizations when a size change needs inspection.
*
* Reports compare files by basename and display gzipped Rollup output sizes in
* decimal kilobytes. Base fixtures are bundled only when the matching file
* exists; if a current fixture has no matching basename in the base directory it
* is reported as unchanged. Visualization artifacts are named from entry file
* stems in the requested output directory, so duplicate names can make the
* output misleading.
*
* @since 4.0.0
*/
import * as Context from "effect/Context"
import * as Data from "effect/Data"
import * as Effect from "effect/Effect"
import * as FileSystem from "effect/FileSystem"
import { constFalse } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Path from "effect/Path"
import { fileURLToPath } from "node:url"
import { Fixtures } from "./Fixtures.ts"
import type { BundleStats } from "./Rollup.ts"
import { Rollup } from "./Rollup.ts"
/**
* Error raised when generating a bundle size report or visualization fails.
*
* @category errors
* @since 4.0.0
*/
export class ReporterError extends Data.TaggedError("ReporterError")<{
readonly cause: unknown
}> {}
/**
* Options for generating a bundle size comparison report against fixture files from a base directory.
*
* @category options
* @since 4.0.0
*/
export interface ReportOptions {
readonly baseDirectory: string
}
/**
* Options for generating bundle visualizations for selected entry files into an output directory.
*
* @category options
* @since 4.0.0
*/
export interface VisualizeOptions {
readonly paths: ReadonlyArray<string>
readonly outputDirectory: string
}
/**
* Options for generating a bundle size report for an explicit list of entry files.
*
* @category options
* @since 4.0.0
*/
export interface ReportSelectedOptions {
readonly paths: ReadonlyArray<string>
}
/**
* Options for generating a bundle size comparison report for explicit entry files against a base checkout.
*
* @category options
* @since 4.0.0
*/
export interface ReportSelectedComparisonOptions {
readonly baseDirectory: string
readonly paths: ReadonlyArray<string>
}
/**
* Context service for producing bundle size reports and visualizations from Rollup-generated fixture stats.
*
* @category services
* @since 4.0.0
*/
export class Reporter extends Context.Service<Reporter>()(
"@effect/bundle/Reporter",
{
make: Effect.gen(function*() {
const fs = yield* FileSystem.FileSystem
const path = yield* Path.Path
const { fixtures, fixturesDir } = yield* Fixtures
const rollup = yield* Rollup
const currentDirectory = path.resolve(fileURLToPath(new URL("../../../../", import.meta.url)))
const calculateDifference = (current: BundleStats, previous: BundleStats) => {
const currSize = current.sizeInBytes
const prevSize = previous.sizeInBytes
const diff = currSize - prevSize
const diffPct = prevSize === 0 ? 0 : (Math.abs(diff) / prevSize) * 100
const currKb = (currSize / 1000).toFixed(2)
const prevKb = (prevSize / 1000).toFixed(2)
const diffKb = (Math.abs(diff) / 1000).toFixed(2)
const filename = path.basename(current.path)
return {
diff,
diffPct,
currKb,
prevKb,
diffKb,
filename
}
}
const createComparisonReport = (
entries: ReadonlyArray<{
readonly current: BundleStats
readonly previous: BundleStats
readonly filename: string
}>
): string => {
const lines: Array<string> = [
"| File Name | Current Size | Previous Size | Difference |",
"|:----------|:------------:|:-------------:|:----------:|"
]
for (const { current, previous, filename } of entries) {
const comparison = calculateDifference(current, previous)
const currKb = `${comparison.currKb} KB`
const prevKb = `${comparison.prevKb} KB`
const diffKb = `${comparison.diffKb} KB`
const diffPct = `${comparison.diffPct.toFixed(2)}%`
const sign = comparison.diff === 0 ? "" : comparison.diff > 0 ? "+" : "-"
const line = `| \`${filename}\` | ${currKb} | ${prevKb} | ${sign}${diffKb} (${sign}${diffPct}) |`
lines.push(line)
}
return lines.join("\n") + "\n"
}
const createReport = (curr: ReadonlyArray<BundleStats>, prev: ReadonlyArray<BundleStats>): string => {
const entries: Array<{
readonly current: BundleStats
readonly previous: BundleStats
readonly filename: string
}> = []
for (const current of curr) {
const previous = prev.find((previous) => {
return path.basename(previous.path) === path.basename(current.path)
}) ?? current
entries.push({
current,
previous,
filename: path.basename(current.path)
})
}
return createComparisonReport(entries)
}
const createSelectedReport = (stats: ReadonlyArray<BundleStats>): string => {
const lines: Array<string> = [
"| File Name | Current Size |",
"|:----------|:------------:|"
]
for (const current of stats) {
const filename = `\`${path.basename(current.path)}\``
const currKb = `${(current.sizeInBytes / 1000).toFixed(2)} KB`
const line = `| ${filename} | ${currKb} |`
lines.push(line)
}
return lines.join("\n") + "\n"
}
const createVisualizationReport = (paths: ReadonlyArray<string>, outputDirectory: string): string => {
const lines: Array<string> = [
"| File Name | Generated Bundle | Treemap | Raw Data |",
"|:----------|:----------------|:--------|:---------|"
]
for (const entryPath of paths) {
const name = path.parse(entryPath).name
const filename = path.relative(currentDirectory, path.resolve(entryPath))
const minified = path.join(outputDirectory, `${name}.min.js`)
const treemap = path.join(outputDirectory, `${name}.treemap.html`)
const rawData = path.join(outputDirectory, `${name}.raw-data.json`)
lines.push(`| \`${filename}\` | \`${minified}\` | \`${treemap}\` | \`${rawData}\` |`)
}
return lines.join("\n") + "\n"
}
const report = Effect.fn("Reporter.report")(
function*(options: ReportOptions) {
yield* Effect.logInfo(`Found ${fixtures.length} files to bundle`)
const currentPaths = fixtures.map((fixture) => path.join(fixturesDir, fixture))
const previousPaths = yield* Effect.filter(
fixtures.map((fixture) => path.join(options.baseDirectory, fixture)),
(previousPath) => fs.exists(previousPath).pipe(Effect.orElseSucceed(constFalse)),
{ concurrency: fixtures.length }
)
const [currentStats, previousStats] = yield* Effect.all([
rollup.bundleAll({
paths: currentPaths
}),
rollup.bundleAll({
paths: previousPaths
})
], { concurrency: 2 })
yield* Effect.logInfo("Bundling complete! Generating bundle size report...")
return createReport(currentStats, previousStats)
}
)
const visualize = Effect.fn("Reporter.visualize")(
function*(options: VisualizeOptions) {
yield* fs.makeDirectory(options.outputDirectory, { recursive: true })
yield* rollup.bundleAll({
paths: options.paths,
outputDirectory: options.outputDirectory,
visualize: true
})
return createVisualizationReport(options.paths, options.outputDirectory)
}
)
const reportSelected = Effect.fn("Reporter.reportSelected")(
function*(options: ReportSelectedOptions) {
yield* Effect.logInfo(`Found ${options.paths.length} files to bundle`)
const stats = yield* rollup.bundleAll({ paths: options.paths })
yield* Effect.logInfo("Bundling complete! Generating bundle size report...")
return createSelectedReport(stats)
}
)
const reportSelectedComparison = Effect.fn("Reporter.reportSelectedComparison")(
function*(options: ReportSelectedComparisonOptions) {
yield* Effect.logInfo(`Found ${options.paths.length} files to compare`)
const baseDirectory = path.resolve(options.baseDirectory)
const currentPaths = options.paths.map((currentPath) => path.resolve(currentPath))
const previousPaths = yield* Effect.forEach(
currentPaths,
Effect.fnUntraced(function*(currentPath) {
const relativePath = path.relative(currentDirectory, currentPath)
if (relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
return yield* Effect.fail(
new ReporterError({
cause: `Selected bundle entry must be inside ${currentDirectory}: ${currentPath}`
})
)
}
const previousPath = path.join(baseDirectory, relativePath)
yield* fs.makeDirectory(path.dirname(previousPath), { recursive: true })
yield* fs.copy(currentPath, previousPath, { overwrite: true })
return previousPath
}),
{ concurrency: currentPaths.length }
)
const [currentStats, previousStats] = yield* Effect.all([
rollup.bundleAll({ paths: currentPaths }),
rollup.bundleAll({ paths: previousPaths })
], { concurrency: 2 })
const entries: Array<{
readonly current: BundleStats
readonly previous: BundleStats
readonly filename: string
}> = []
for (let i = 0; i < currentStats.length; i++) {
entries.push({
current: currentStats[i]!,
previous: previousStats[i]!,
filename: path.relative(currentDirectory, currentStats[i]!.path)
})
}
yield* Effect.logInfo("Bundling complete! Generating bundle size report...")
return createComparisonReport(entries)
}
)
return {
report,
reportSelectedComparison,
reportSelected,
visualize
} as const
})
}
) {
static readonly layer = Layer.effect(this, this.make).pipe(
Layer.provide(Fixtures.layer),
Layer.provide(Rollup.layer)
)
}

View File

@@ -0,0 +1,201 @@
/**
* Rollup-backed bundling and size measurement for the Effect bundle-size tools.
*
* This module provides the service used by the bundle CLI and reporter to turn
* fixture or selected TypeScript entrypoints into ESM Rollup output, optionally
* write a minified artifact, and return the gzipped byte count used in
* bundle-size comparisons.
*
* Bundles are generated in memory so the emitted code can be streamed to both
* gzip measurement and optional file output. Only Rollup `chunk` outputs are
* included; assets are ignored, and when Rollup creates multiple chunks (for
* example because of dynamic imports or shared chunks) their code is streamed
* together for measurement. The optional output file is named from the
* entrypoint stem, so it is best treated as an inspection artifact rather than
* a complete Rollup output directory.
*
* @since 4.0.0
*/
import * as NodeStream from "@effect/platform-node/NodeStream"
import * as Context from "effect/Context"
import * as Data from "effect/Data"
import * as Effect from "effect/Effect"
import * as FiberSet from "effect/FiberSet"
import * as FileSystem from "effect/FileSystem"
import * as Layer from "effect/Layer"
import * as Path from "effect/Path"
import * as Stream from "effect/Stream"
import { createGzip } from "node:zlib"
import type { RollupOptions } from "rollup"
import { rollup } from "rollup"
import { createPlugins, type VisualizationOutput } from "./Plugins.ts"
/**
* Error raised when Rollup bundling, output generation, or bundle size measurement fails.
*
* @category errors
* @since 4.0.0
*/
export class RollupError extends Data.TaggedError("RollupError")<{
readonly cause: unknown
}> {}
/**
* Bundle size statistics for an entry file, including its path and gzipped size in bytes.
*
* @category models
* @since 4.0.0
*/
export class BundleStats extends Data.TaggedClass("BundleStats")<{
readonly path: string
readonly sizeInBytes: number
}> {}
/**
* Options for bundling one entry file, optionally writing a minified output and generating a visualization.
*
* @category options
* @since 4.0.0
*/
export interface BundleOptions {
readonly path: string
readonly visualize?: boolean | undefined
readonly outputDirectory?: string | undefined
}
/**
* Options for bundling multiple entry files with shared visualization and output-directory settings.
*
* @category options
* @since 4.0.0
*/
export interface BundleAllOptions {
readonly paths: ReadonlyArray<string>
readonly visualize?: boolean | undefined
readonly outputDirectory?: string | undefined
}
/**
* Context service for bundling entry files with Rollup and measuring their gzipped output size.
*
* @category services
* @since 4.0.0
*/
export class Rollup extends Context.Service<Rollup>()(
"@effect/bundle/Rollup",
{
make: Effect.gen(function*() {
const pathService = yield* Path.Path
const fs = yield* FileSystem.FileSystem
const createVisualizationOutputs = (options: BundleOptions): ReadonlyArray<VisualizationOutput> => {
if (!options.visualize || !options.outputDirectory) {
return []
}
const name = pathService.parse(options.path).name
return [
{
filename: pathService.join(options.outputDirectory, `${name}.treemap.html`),
template: "treemap",
title: `${name} bundle treemap`
},
{
filename: pathService.join(options.outputDirectory, `${name}.raw-data.json`),
template: "raw-data",
title: `${name} bundle raw data`
}
]
}
const getRollupOptions = (options: BundleOptions): RollupOptions => ({
input: options.path,
output: {
format: "esm"
},
plugins: createPlugins(pathService, {
visualize: options.visualize,
visualizations: createVisualizationOutputs(options)
}),
onwarn: (warning, next) => {
if (warning.code === "THIS_IS_UNDEFINED") return
next(warning)
}
})
const bundle = Effect.fn("Rollup.bundle")(
function*(options: BundleOptions) {
const bundle = yield* Effect.acquireRelease(
Effect.tryPromise({
try: () => rollup(getRollupOptions(options)),
catch: (cause) => new RollupError({ cause })
}),
(bundle) => Effect.promise(() => bundle.close())
)
const fibers = yield* FiberSet.make()
const { output } = yield* Effect.tryPromise({
try: () => bundle.generate({ format: "esm" }),
catch: (cause) => new RollupError({ cause })
})
const stream = yield* Stream.fromIterable(output).pipe(
Stream.filter((output) => output.type === "chunk"),
Stream.map((chunk) => chunk.code),
Stream.encodeText,
Stream.broadcast({ capacity: 8, replay: 8 })
)
if (options.outputDirectory) {
const outputPath = pathService.join(
options.outputDirectory,
`${pathService.parse(options.path).name}.min.js`
)
yield* FiberSet.run(
fibers,
stream.pipe(
Stream.run(fs.sink(outputPath))
)
)
}
const sizeInBytes = yield* stream.pipe(
NodeStream.pipeThroughDuplex({
evaluate: () => createGzip({ level: 9 }),
onError: (cause) => new RollupError({ cause })
}),
Stream.runFold(
() => 0,
(totalBytes, chunkBytes) => chunkBytes.length + totalBytes
)
)
yield* FiberSet.awaitEmpty(fibers)
yield* Effect.log(`Bundled ${options.path}`).pipe(
Effect.annotateLogs({ size: `${(sizeInBytes / 1000).toFixed(2)} kB` })
)
return new BundleStats({ path: options.path, sizeInBytes })
},
Effect.scoped
)
const bundleAll = Effect.fn("Rollup.bundleAll")(
function*(options: BundleAllOptions) {
return yield* Effect.forEach(
options.paths,
(path) => bundle({ path, visualize: options.visualize, outputDirectory: options.outputDirectory }),
{ concurrency: options.paths.length }
)
}
)
return {
bundle,
bundleAll
} as const
})
}
) {
static readonly layer = Layer.effect(this, this.make)
}

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env node
/**
* @since 4.0.0
*/
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import * as NodeServices from "@effect/platform-node/NodeServices"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Command from "effect/unstable/cli/Command"
import PackageJson from "../package.json" with { type: "json" }
import { cli } from "./Cli.ts"
import { Fixtures } from "./Fixtures.ts"
import { Reporter } from "./Reporter.ts"
const MainLayer = Layer.mergeAll(
Fixtures.layer,
Reporter.layer
).pipe(Layer.provideMerge(NodeServices.layer))
Command.run(cli, { version: PackageJson["version"] }).pipe(
Effect.provide(MainLayer),
NodeRuntime.runMain
)

View File

@@ -0,0 +1,74 @@
import { createResolveLocalPackageImports } from "@effect/bundle/Plugins"
import { assert, describe, it } from "@effect/vitest"
import type * as EffectPath from "effect/Path"
import * as path from "node:path"
import { fileURLToPath } from "node:url"
import type { Plugin } from "rollup"
type Resolved = {
readonly id: string
readonly external: false
}
type ResolveId = (
this: {
readonly resolve: (
source: string,
importer?: string,
options?: { readonly skipSelf?: boolean }
) => Promise<Resolved | null>
},
source: string,
importer?: string
) => Promise<Resolved | null>
const packageDir = fileURLToPath(new URL("../../../effect", import.meta.url))
const pathService = path as unknown as EffectPath.Path
const getResolveId = (plugin: Plugin): ResolveId => {
assert.strictEqual(typeof plugin.resolveId, "function")
return plugin.resolveId as ResolveId
}
const resolved = (id: string): Resolved => ({
id,
external: false
})
describe("createResolveLocalPackageImports", () => {
it("resolves directory package exports to dist index files", async () => {
const resolveId = getResolveId(createResolveLocalPackageImports(pathService))
const result = await resolveId.call({
resolve: async (source) => {
switch (source) {
case "effect/package.json":
return resolved(path.join(packageDir, "package.json"))
case "effect/testing":
return resolved(path.join(packageDir, "src", "testing", "index.ts"))
default:
return null
}
}
}, "effect/testing")
assert.deepStrictEqual(result, resolved(path.join(packageDir, "dist", "testing", "index.js")))
})
it("keeps flat package exports on flat dist files", async () => {
const resolveId = getResolveId(createResolveLocalPackageImports(pathService))
const result = await resolveId.call({
resolve: async (source) => {
switch (source) {
case "effect/package.json":
return resolved(path.join(packageDir, "package.json"))
case "effect/Schema":
return resolved(path.join(packageDir, "src", "Schema.ts"))
default:
return null
}
}
}, "effect/Schema")
assert.deepStrictEqual(result, resolved(path.join(packageDir, "dist", "Schema.js")))
})
})

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": ["fixtures"],
"compilerOptions": {
"rootDir": "fixtures",
"noEmit": true
},
"references": [
{ "path": "../../effect" },
{ "path": "../../platform-node" }
]
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": [],
"references": [
{ "path": "tsconfig.fixtures.json" },
{ "path": "tsconfig.src.json" }
]
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"resolveJsonModule": true,
"types": ["node"]
},
"references": [
{ "path": "../../effect" },
{ "path": "../../platform-node" }
]
}

View File

@@ -0,0 +1,6 @@
import { mergeConfig, type ViteUserConfig } from "vitest/config"
import shared from "../../../vitest.shared.ts"
const config: ViteUserConfig = {}
export default mergeConfig(shared, config)