1/**
2 * Command definitions for the `effect-bundle` bundle-size CLI.
3 *
4 * This module wires the top-level `bundle` command to the reporting service and
5 * exposes the workflows used when maintaining fixture bundle sizes. `compare`
6 * builds the package's local fixtures and compares them with matching fixture
7 * files from another checkout, `report` bundles an explicit list of entrypoints
8 * and prints a Markdown table, `compare-selected` compares explicit entrypoints
9 * against a base checkout, `visualize-selected` analyzes explicit entrypoints,
10 * and `visualize` prompts for local fixtures before producing visualization
11 * output for inspection.
12 *
13 * Command output is intentionally split by workflow. `compare` requires an
14 * existing `--base-dir` (`-b`) and writes its Markdown report to `--output-path`
15 * (`-o`), defaulting to `stats.txt` resolved from the current working directory.
16 * `report` accepts one or more existing files and writes to stdout. `visualize`
17 * uses `--output-dir` (`-o`) for generated bundle artifacts, so `-o` names a
18 * file for `compare` but a directory for `visualize`.
19 *
20 * @since 4.0.0
21 */
22import * as Argument from "effect/cli/Argument"
23import * as Command from "effect/cli/Command"
24import * as Flag from "effect/cli/Flag"
25import * as Prompt from "effect/cli/Prompt"
26import * as Console from "effect/Console"
27import * as Effect from "effect/Effect"
28import * as FileSystem from "effect/FileSystem"
29import * as Path from "effect/Path"
30import { Fixtures } from "./Fixtures.ts"
31import { Reporter } from "./Reporter.ts"
32
33const baseDirectory = Flag.Directory("base-dir", { mustExist: true }).pipe(
34 Flag.withAlias("b"),
35 Flag.withDescription("The base directory to use for bundle size comparisons")
36)
37
38const outputPath = Flag.File("output-path").pipe(
39 Flag.withAlias("o"),
40 Flag.withDescription("The name of the file to write the bundle size report to"),
41 Flag.withDefault("stats.txt"),
42 Flag.mapEffect(Effect.fnUntraced(function*(outputPath) {
43 const path = yield* Path.Path
44 return path.resolve(outputPath)
45 }))
46)
47
48const compare = Command.make("compare", { baseDirectory, outputPath }).pipe(
49 Command.withHandler(Effect.fnUntraced(function*({ baseDirectory, outputPath }) {
50 const fs = yield* FileSystem.FileSystem
51 const reporter = yield* Reporter
52 const report = yield* reporter.report({ baseDirectory })
53 yield* fs.writeFileString(outputPath, report)
54 yield* Effect.log(`Bundle size report written to: '${outputPath}'`)
55 }))
56)
57
58const outputDirectory = Flag.Directory("output-dir").pipe(
59 Flag.withAlias("o"),
60 Flag.withDescription("The name of the directory to write the bundle size visualizations to"),
61 Flag.mapEffect(Effect.fnUntraced(function*(outputPath) {
62 const path = yield* Path.Path
63 return path.resolve(outputPath)
64 }))
65)
66
67const visualize = Command.make("visualize", { outputDirectory }).pipe(
68 Command.withHandler(Effect.fnUntraced(function*({ outputDirectory }) {
69 const path = yield* Path.Path
70 const { fixtures, fixturesDir } = yield* Fixtures
71 const reporter = yield* Reporter
72
73 const paths = yield* Prompt.MultiSelect({
74 message: "Select files whose bundle size you would like to visualize",
75 choices: fixtures.map((fixture) => ({
76 title: fixture,
77 value: path.join(fixturesDir, fixture)
78 }))
79 })
80
81 const report = yield* reporter.visualize({ paths, outputDirectory })
82 yield* Console.log(report)
83 }))
84)
85
86const reportPaths = Argument.File("paths", { mustExist: true }).pipe(
87 Argument.withDescription("Fixture files to include in the report"),
88 Argument.variadic({ min: 1 })
89)
90
91const report = Command.make("report", { paths: reportPaths }).pipe(
92 Command.withHandler(Effect.fnUntraced(function*({ paths }) {
93 const reporter = yield* Reporter
94 const report = yield* reporter.reportSelected({ paths })
95 yield* Console.log(report)
96 }))
97)
98
99const compareSelected = Command.make("compare-selected", { baseDirectory, paths: reportPaths }).pipe(
100 Command.withHandler(Effect.fnUntraced(function*({ baseDirectory, paths }) {
101 const reporter = yield* Reporter
102 const report = yield* reporter.reportSelectedComparison({ baseDirectory, paths })
103 yield* Console.log(report)
104 }))
105)
106
107const visualizeSelected = Command.make("visualize-selected", { outputDirectory, paths: reportPaths }).pipe(
108 Command.withHandler(Effect.fnUntraced(function*({ outputDirectory, paths }) {
109 const reporter = yield* Reporter
110 const report = yield* reporter.visualize({ outputDirectory, paths })
111 yield* Console.log(report)
112 }))
113)
114
115/**
116 * Bundle analysis CLI command with subcommands for comparing fixture bundle sizes, reporting selected fixtures, and generating visualizations.
117 *
118 * @category commands
119 * @since 4.0.0
120 */
121export const cli = Command.make("bundle").pipe(
122 Command.withSubcommands([compare, compareSelected, report, visualize, visualizeSelected])
123)