@savvy-web/github-action-builder 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/config.ts
4
+ /**
5
+ * Configuration schemas for GitHub Action Builder using Effect Schema.
6
+ *
7
+ * @remarks
8
+ * This module defines the schemas for validating configuration
9
+ * and provides the {@link defineConfig} helper for type-safe configuration files.
10
+ *
11
+ * @internal
12
+ */
13
+ /**
14
+ * Schema for entry point paths.
15
+ *
16
+ * @remarks
17
+ * GitHub Actions support three entry points:
18
+ * - `main`: The primary action entry point (required)
19
+ * - `pre`: Runs before the main action (optional)
20
+ * - `post`: Runs after the main action for cleanup (optional)
21
+ *
22
+ * @internal
23
+ */
24
+ const EntriesSchema = Schema.Struct({
25
+ /** Path to the main action entry point. Defaults to "src/main.ts". */
26
+ main: Schema.optionalWith(Schema.String, { default: () => "src/main.ts" }),
27
+ /** Path to the pre-action hook entry point. */
28
+ pre: Schema.optional(Schema.String),
29
+ /** Path to the post-action hook entry point. */
30
+ post: Schema.optional(Schema.String)
31
+ });
32
+ /**
33
+ * Schema for build options.
34
+ *
35
+ * @remarks
36
+ * Build options control how the TypeScript source is bundled using rsbuild.
37
+ * The bundler creates a single JavaScript file with all dependencies inlined.
38
+ *
39
+ * @internal
40
+ */
41
+ const BuildOptionsSchema = Schema.Struct({
42
+ /** Enable minification to reduce bundle size. Defaults to true. */
43
+ minify: Schema.optionalWith(Schema.Boolean, { default: () => true }),
44
+ /** Generate source maps for debugging. Defaults to false. */
45
+ sourceMap: Schema.optionalWith(Schema.Boolean, { default: () => false }),
46
+ /** Packages to exclude from the bundle (in addition to node: builtins). Defaults to []. */
47
+ externals: Schema.optionalWith(Schema.Array(Schema.String), { default: () => [] }),
48
+ /** Packages to exclude from the bundle and replace with a stub that throws if loaded at runtime. Use for optional transitive dependencies the action never exercises (e.g. native modules). Defaults to []. */
49
+ ignore: Schema.optionalWith(Schema.Array(Schema.String), { default: () => [] })
50
+ });
51
+ /**
52
+ * Schema for validation options.
53
+ *
54
+ * @remarks
55
+ * Validation options control how strictly the build process validates
56
+ * the project structure and configuration before building.
57
+ *
58
+ * @internal
59
+ */
60
+ const ValidationOptionsSchema = Schema.Struct({
61
+ /** Require action.yml to exist and be valid. Defaults to true. */
62
+ requireActionYml: Schema.optionalWith(Schema.Boolean, { default: () => true }),
63
+ /** Maximum bundle size before warning/error (e.g., "5mb", "500kb"). */
64
+ maxBundleSize: Schema.optional(Schema.String),
65
+ /** Treat warnings as errors. Auto-detects from CI when undefined. */
66
+ strict: Schema.optional(Schema.Boolean)
67
+ });
68
+ /**
69
+ * Schema for persist-local options.
70
+ *
71
+ * @remarks
72
+ * Controls automatic copying of build output to a local action directory
73
+ * for testing with nektos/act.
74
+ *
75
+ * @internal
76
+ */
77
+ const PersistLocalOptionsSchema = Schema.Struct({
78
+ /** Enable persisting build output locally. Defaults to true. */
79
+ enabled: Schema.optionalWith(Schema.Boolean, { default: () => true }),
80
+ /** Path for the local action directory, relative to cwd. Defaults to ".github/actions/local". */
81
+ path: Schema.optionalWith(Schema.String, { default: () => ".github/actions/local" }),
82
+ /** Generate act boilerplate files (.actrc, act-test.yml) if they don't exist. Defaults to true. */
83
+ actTemplate: Schema.optionalWith(Schema.Boolean, { default: () => true })
84
+ });
85
+ /**
86
+ * User-provided configuration input (all fields optional).
87
+ *
88
+ * @remarks
89
+ * This schema is used for parsing user-provided configuration.
90
+ * All sections are optional; defaults are applied via {@link defineConfig}.
91
+ *
92
+ * @internal
93
+ */
94
+ const ConfigInputSchema = Schema.Struct({
95
+ entries: Schema.optional(Schema.Struct({
96
+ main: Schema.optional(Schema.String),
97
+ pre: Schema.optional(Schema.String),
98
+ post: Schema.optional(Schema.String)
99
+ })),
100
+ build: Schema.optional(Schema.Struct({
101
+ minify: Schema.optional(Schema.Boolean),
102
+ sourceMap: Schema.optional(Schema.Boolean),
103
+ externals: Schema.optional(Schema.Array(Schema.String)),
104
+ ignore: Schema.optional(Schema.Array(Schema.String))
105
+ })),
106
+ validation: Schema.optional(Schema.Struct({
107
+ requireActionYml: Schema.optional(Schema.Boolean),
108
+ maxBundleSize: Schema.optional(Schema.String),
109
+ strict: Schema.optional(Schema.Boolean)
110
+ })),
111
+ persistLocal: Schema.optional(Schema.Struct({
112
+ enabled: Schema.optional(Schema.Boolean),
113
+ path: Schema.optional(Schema.String),
114
+ actTemplate: Schema.optional(Schema.Boolean)
115
+ }))
116
+ });
117
+ /**
118
+ * Fully resolved configuration with all defaults applied.
119
+ *
120
+ * @internal
121
+ */
122
+ const ConfigSchema = Schema.Struct({
123
+ entries: EntriesSchema,
124
+ build: BuildOptionsSchema,
125
+ validation: ValidationOptionsSchema,
126
+ persistLocal: PersistLocalOptionsSchema
127
+ });
128
+ /**
129
+ * Define a configuration with full TypeScript support.
130
+ *
131
+ * @remarks
132
+ * This function validates the configuration and applies all defaults.
133
+ * Use it in your `action.config.ts` file for autocomplete and type checking.
134
+ *
135
+ * @param config - Partial configuration object
136
+ * @returns Fully resolved configuration with defaults applied
137
+ *
138
+ * @example Basic configuration file
139
+ * ```typescript
140
+ * // action.config.ts
141
+ * import { defineConfig } from "@savvy-web/github-action-builder";
142
+ *
143
+ * export default defineConfig({
144
+ * entries: {
145
+ * main: "src/main.ts",
146
+ * },
147
+ * build: {
148
+ * minify: true,
149
+ * },
150
+ * });
151
+ * ```
152
+ *
153
+ * @example Full configuration with all options
154
+ * ```typescript
155
+ * // action.config.ts
156
+ * import { defineConfig } from "@savvy-web/github-action-builder";
157
+ *
158
+ * export default defineConfig({
159
+ * entries: {
160
+ * main: "src/action.ts",
161
+ * pre: "src/setup.ts",
162
+ * post: "src/cleanup.ts",
163
+ * },
164
+ * build: {
165
+ * minify: true,
166
+ * sourceMap: true,
167
+ * externals: ["@aws-sdk/client-s3"],
168
+ * ignore: ["libxmljs2"],
169
+ * },
170
+ * validation: {
171
+ * requireActionYml: true,
172
+ * maxBundleSize: "10mb",
173
+ * strict: true,
174
+ * },
175
+ * });
176
+ * ```
177
+ *
178
+ * @public
179
+ */
180
+ function defineConfig(config = {}) {
181
+ return Schema.decodeUnknownSync(ConfigSchema)({
182
+ entries: config.entries ?? {},
183
+ build: config.build ?? {},
184
+ validation: config.validation ?? {},
185
+ persistLocal: config.persistLocal ?? {}
186
+ });
187
+ }
188
+
189
+ //#endregion
190
+ export { BuildOptionsSchema, ConfigInputSchema, ConfigSchema, EntriesSchema, PersistLocalOptionsSchema, ValidationOptionsSchema, defineConfig };
@@ -0,0 +1,43 @@
1
+ import { Schema } from "effect";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ //#region src/schemas/path.ts
5
+ /**
6
+ * Convert a PathLike value to a string.
7
+ *
8
+ * @param pathLike - A string, Buffer, or URL path
9
+ * @returns The path as a string
10
+ *
11
+ * @internal
12
+ */
13
+ /* v8 ignore start - edge case branches for Buffer/URL paths */
14
+ function pathLikeToString(pathLike) {
15
+ if (typeof pathLike === "string") return pathLike;
16
+ if (Buffer.isBuffer(pathLike)) return pathLike.toString("utf8");
17
+ if (pathLike instanceof URL) return fileURLToPath(pathLike);
18
+ return String(pathLike);
19
+ }
20
+ /* v8 ignore stop */
21
+ /**
22
+ * Schema that accepts PathLike (string, Buffer, or URL) and normalizes to string.
23
+ *
24
+ * @remarks
25
+ * This schema is designed for user-facing inputs where flexibility is important.
26
+ * Internally, paths are stored as strings for serialization compatibility.
27
+ *
28
+ * @internal
29
+ */
30
+ const PathLikeSchema = Schema.transform(Schema.Union(Schema.String, Schema.instanceOf(Buffer), Schema.instanceOf(URL)), Schema.String, {
31
+ strict: true,
32
+ decode: (pathLike) => pathLikeToString(pathLike),
33
+ /* v8 ignore next */
34
+ encode: (s) => s
35
+ });
36
+ /**
37
+ * Optional PathLike schema.
38
+ * @internal
39
+ */
40
+ const OptionalPathLikeSchema = Schema.optional(PathLikeSchema);
41
+
42
+ //#endregion
43
+ export { OptionalPathLikeSchema };
@@ -0,0 +1,223 @@
1
+ import { BundleFailed, CleanError, WriteError } from "../errors.js";
2
+ import { BuildService } from "./build.js";
3
+ import { ConfigService } from "./config.js";
4
+ import { Effect, Layer } from "effect";
5
+ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { createRsbuild } from "@rsbuild/core";
8
+
9
+ //#region src/services/build-live.ts
10
+ /* v8 ignore start - build service requires actual bundling for integration testing */
11
+ /**
12
+ * BuildService Layer implementation.
13
+ *
14
+ */
15
+ /**
16
+ * Source of the stub module that replaces packages listed in `build.ignore`.
17
+ * It is bundled in place of the real module and throws if ever loaded.
18
+ */
19
+ const IGNORE_STUB_SOURCE = `throw new Error("A module excluded via the build 'ignore' option was loaded at runtime.");\n`;
20
+ /**
21
+ * Format bytes as a human-readable string.
22
+ */
23
+ function formatBytes(bytes) {
24
+ if (bytes < 1024) return `${bytes} B`;
25
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
26
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
27
+ }
28
+ /**
29
+ * Format build result for terminal output.
30
+ */
31
+ /* v8 ignore start - formatting function tested via integration */
32
+ function formatBuildResult(result) {
33
+ const lines = [];
34
+ if (result.success) {
35
+ lines.push("Build Summary:");
36
+ for (const entry of result.entries) if (entry.success && entry.stats) {
37
+ const { entry: name, size, duration, outputPath } = entry.stats;
38
+ lines.push(` ✓ ${name}: ${formatBytes(size)} (${duration}ms) → ${outputPath}`);
39
+ }
40
+ lines.push(`\nTotal time: ${result.duration}ms`);
41
+ } else {
42
+ lines.push("Build Failed:");
43
+ for (const entry of result.entries) if (!entry.success) lines.push(` ✗ ${entry.error}`);
44
+ }
45
+ return lines.join("\n");
46
+ }
47
+ /* v8 ignore stop */
48
+ /**
49
+ * Clean output directory.
50
+ */
51
+ function cleanDirectory(dir) {
52
+ return Effect.try({
53
+ try: () => {
54
+ if (existsSync(dir)) rmSync(dir, {
55
+ recursive: true,
56
+ force: true
57
+ });
58
+ },
59
+ /* v8 ignore next 5 - error branch requires fs permission failures */
60
+ catch: (error) => new CleanError({
61
+ directory: dir,
62
+ cause: error
63
+ })
64
+ });
65
+ }
66
+ /**
67
+ * Write file with directory creation.
68
+ */
69
+ function writeFile(path, content) {
70
+ return Effect.try({
71
+ try: () => {
72
+ mkdirSync(resolve(path, ".."), { recursive: true });
73
+ writeFileSync(path, content, "utf8");
74
+ },
75
+ /* v8 ignore next 5 - error branch requires fs permission failures */
76
+ catch: (error) => new WriteError({
77
+ path,
78
+ cause: error
79
+ })
80
+ });
81
+ }
82
+ /**
83
+ * Bundle a single entry with rsbuild.
84
+ */
85
+ /* v8 ignore start - bundling requires actual rsbuild execution */
86
+ function bundleEntry(entry, config, cwd) {
87
+ return Effect.gen(function* () {
88
+ const startTime = Date.now();
89
+ const outputDir = resolve(cwd, "dist");
90
+ const externalsSet = new Set(config.build.externals);
91
+ const ignoreSet = new Set(config.build.ignore);
92
+ const ignoreAlias = {};
93
+ if (config.build.ignore.length > 0) {
94
+ const stubPath = resolve(cwd, "node_modules", ".cache", "github-action-builder", "ignore-stub.mjs");
95
+ yield* writeFile(stubPath, IGNORE_STUB_SOURCE);
96
+ for (const moduleName of config.build.ignore) ignoreAlias[`${moduleName}$`] = stubPath;
97
+ }
98
+ const rsbuild = yield* Effect.tryPromise({
99
+ try: () => createRsbuild({ rsbuildConfig: {
100
+ source: { entry: { [entry.type]: entry.path } },
101
+ resolve: { alias: ignoreAlias },
102
+ output: {
103
+ target: "node",
104
+ module: true,
105
+ distPath: { root: outputDir },
106
+ filename: { js: "[name].js" },
107
+ externals: (data) => {
108
+ const request = data.request;
109
+ if (!request) return false;
110
+ if (request.startsWith("node:")) return `node-commonjs ${request}`;
111
+ if (externalsSet.has(request) && !ignoreSet.has(request)) return request;
112
+ return false;
113
+ },
114
+ cleanDistPath: false,
115
+ legalComments: "inline",
116
+ minify: config.build.minify,
117
+ sourceMap: config.build.sourceMap ? { js: "source-map" } : false
118
+ },
119
+ performance: { chunkSplit: { strategy: "all-in-one" } },
120
+ tools: { rspack: {
121
+ node: {
122
+ __dirname: "node-module",
123
+ __filename: "node-module"
124
+ },
125
+ output: { asyncChunks: false }
126
+ } }
127
+ } }),
128
+ catch: (error) => new BundleFailed({
129
+ entry: entry.path,
130
+ cause: error
131
+ })
132
+ });
133
+ const buildResult = yield* Effect.tryPromise({
134
+ try: () => rsbuild.build(),
135
+ catch: (error) => new BundleFailed({
136
+ entry: entry.path,
137
+ cause: error
138
+ })
139
+ });
140
+ yield* Effect.tryPromise({
141
+ try: () => buildResult.close(),
142
+ catch: (error) => new BundleFailed({
143
+ entry: entry.path,
144
+ cause: /* @__PURE__ */ new Error(`rsbuild close() failed: ${error}`)
145
+ })
146
+ });
147
+ const outputPath = resolve(outputDir, `${entry.type}.js`);
148
+ const size = yield* Effect.try({
149
+ try: () => statSync(outputPath).size,
150
+ catch: (error) => new BundleFailed({
151
+ entry: entry.path,
152
+ cause: error
153
+ })
154
+ });
155
+ const duration = Date.now() - startTime;
156
+ return {
157
+ success: true,
158
+ stats: {
159
+ entry: entry.type,
160
+ size,
161
+ duration,
162
+ outputPath: entry.output
163
+ }
164
+ };
165
+ });
166
+ }
167
+ /* v8 ignore stop */
168
+ /**
169
+ * Live implementation of BuildService.
170
+ *
171
+ * @remarks
172
+ * Uses rsbuild for bundling.
173
+ */
174
+ const BuildServiceLive = Layer.effect(BuildService, Effect.gen(function* () {
175
+ const configService = yield* ConfigService;
176
+ return {
177
+ /* v8 ignore start - build execution requires actual rsbuild bundling */
178
+ build: (config, options = {}) => Effect.gen(function* () {
179
+ const cwd = options.cwd ?? process.cwd();
180
+ const shouldClean = options.clean ?? true;
181
+ const startTime = Date.now();
182
+ const entriesConfig = { main: config.entries.main };
183
+ if (config.entries.pre) entriesConfig.pre = config.entries.pre;
184
+ if (config.entries.post) entriesConfig.post = config.entries.post;
185
+ const entriesResult = yield* configService.detectEntries(cwd, entriesConfig);
186
+ if (shouldClean) yield* cleanDirectory(resolve(cwd, "dist"));
187
+ const entryResults = [];
188
+ for (const entry of entriesResult.entries) {
189
+ const result = yield* Effect.either(bundleEntry(entry, config, cwd));
190
+ if (result._tag === "Left") {
191
+ const err = result.left;
192
+ entryResults.push({
193
+ success: false,
194
+ error: err.cause instanceof Error ? err.cause.message : String(err.cause)
195
+ });
196
+ } else entryResults.push(result.right);
197
+ }
198
+ yield* writeFile(resolve(cwd, "dist/package.json"), "{ \"type\": \"module\" }");
199
+ const duration = Date.now() - startTime;
200
+ const success = entryResults.every((r) => r.success);
201
+ if (!success) return {
202
+ success,
203
+ entries: entryResults,
204
+ duration,
205
+ error: "One or more entries failed to build"
206
+ };
207
+ return {
208
+ success,
209
+ entries: entryResults,
210
+ duration
211
+ };
212
+ }),
213
+ /* v8 ignore stop */
214
+ bundle: (entry, config) => bundleEntry(entry, config, process.cwd()),
215
+ clean: (outputDir) => cleanDirectory(outputDir),
216
+ formatResult: formatBuildResult,
217
+ formatBytes
218
+ };
219
+ }));
220
+ /* v8 ignore stop */
221
+
222
+ //#endregion
223
+ export { BuildServiceLive };
@@ -0,0 +1,63 @@
1
+ import { OptionalPathLikeSchema } from "../schemas/path.js";
2
+ import { Context, Schema } from "effect";
3
+
4
+ //#region src/services/build.ts
5
+ /**
6
+ * Options for the build process.
7
+ * @internal
8
+ */
9
+ const BuildRunnerOptionsSchema = Schema.Struct({
10
+ /** Working directory for the build. Accepts string, Buffer, or URL. */
11
+ cwd: OptionalPathLikeSchema,
12
+ /** Clean output directory before building. Defaults to true. */
13
+ clean: Schema.optional(Schema.Boolean)
14
+ });
15
+ /**
16
+ * Statistics for a single bundled entry.
17
+ * @internal
18
+ */
19
+ const BundleStatsSchema = Schema.Struct({
20
+ /** Entry type (main, pre, or post). */
21
+ entry: Schema.String,
22
+ /** Bundle size in bytes. */
23
+ size: Schema.Number,
24
+ /** Build duration in milliseconds. */
25
+ duration: Schema.Number,
26
+ /** Output path relative to working directory. */
27
+ outputPath: Schema.String
28
+ });
29
+ /**
30
+ * Result of bundling a single entry.
31
+ * @internal
32
+ */
33
+ const BundleResultSchema = Schema.Struct({
34
+ /** Whether bundling succeeded. */
35
+ success: Schema.Boolean,
36
+ /** Bundle statistics if successful. */
37
+ stats: Schema.optional(BundleStatsSchema),
38
+ /** Error message if failed. */
39
+ error: Schema.optional(Schema.String)
40
+ });
41
+ /**
42
+ * Result of the complete build process.
43
+ * @internal
44
+ */
45
+ const BuildResultSchema = Schema.Struct({
46
+ /** Whether the overall build succeeded. */
47
+ success: Schema.Boolean,
48
+ /** Results for each entry that was built. */
49
+ entries: Schema.Array(BundleResultSchema),
50
+ /** Total build duration in milliseconds. */
51
+ duration: Schema.Number,
52
+ /** Error message if build failed. */
53
+ error: Schema.optional(Schema.String)
54
+ });
55
+ /**
56
+ * BuildService tag for dependency injection.
57
+ *
58
+ * @public
59
+ */
60
+ const BuildService = Context.GenericTag("BuildService");
61
+
62
+ //#endregion
63
+ export { BuildResultSchema, BuildRunnerOptionsSchema, BuildService, BundleResultSchema, BundleStatsSchema };
@@ -0,0 +1,111 @@
1
+ import { ConfigInvalid, ConfigLoadFailed, ConfigNotFound, MainEntryMissing } from "../errors.js";
2
+ import { defineConfig } from "../schemas/config.js";
3
+ import { ConfigService } from "./config.js";
4
+ import { Effect, Layer } from "effect";
5
+ import { existsSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { createJiti } from "jiti";
8
+
9
+ //#region src/services/config-live.ts
10
+ /**
11
+ * ConfigService Layer implementation.
12
+ *
13
+ */
14
+ const CONFIG_FILENAMES = [
15
+ "action.config.ts",
16
+ "action.config.js",
17
+ "action.config.mjs"
18
+ ];
19
+ const DEFAULT_ENTRIES = {
20
+ main: "src/main.ts",
21
+ pre: "src/pre.ts",
22
+ post: "src/post.ts"
23
+ };
24
+ /**
25
+ * Find config file in the given directory.
26
+ */
27
+ function findConfigFile(cwd) {
28
+ for (const filename of CONFIG_FILENAMES) {
29
+ const configPath = resolve(cwd, filename);
30
+ if (existsSync(configPath)) return configPath;
31
+ }
32
+ }
33
+ /**
34
+ * Detect a single optional entry.
35
+ */
36
+ function detectOptionalEntry(cwd, type, explicitPath) {
37
+ const defaultPath = DEFAULT_ENTRIES[type];
38
+ const absolutePath = resolve(cwd, explicitPath ?? defaultPath);
39
+ if (existsSync(absolutePath)) return {
40
+ type,
41
+ path: absolutePath,
42
+ output: `dist/${type}.js`
43
+ };
44
+ }
45
+ /**
46
+ * Live implementation of ConfigService.
47
+ */
48
+ const ConfigServiceLive = Layer.succeed(ConfigService, {
49
+ load: (options = {}) => Effect.gen(function* () {
50
+ const cwd = options.cwd ?? process.cwd();
51
+ const configPath = options.configPath ?? findConfigFile(cwd);
52
+ if (!configPath) return {
53
+ config: defineConfig({}),
54
+ usingDefaults: true
55
+ };
56
+ /* v8 ignore start - requires explicit configPath to non-existent file */
57
+ if (!existsSync(configPath)) return yield* Effect.fail(new ConfigNotFound({
58
+ path: configPath,
59
+ message: "Specified config file does not exist"
60
+ }));
61
+ /* v8 ignore stop */
62
+ const absolutePath = resolve(cwd, configPath);
63
+ /* v8 ignore stop */
64
+ const configInput = (yield* Effect.tryPromise({
65
+ try: async () => {
66
+ if (absolutePath.endsWith(".ts")) return createJiti(absolutePath, { interopDefault: true }).import(absolutePath);
67
+ return import(absolutePath);
68
+ },
69
+ catch: (error) => new ConfigLoadFailed({
70
+ path: configPath,
71
+ cause: error
72
+ })
73
+ })).default;
74
+ /* v8 ignore start - requires config file with non-object default export */
75
+ if (!configInput || typeof configInput !== "object") return yield* Effect.fail(new ConfigInvalid({
76
+ path: configPath,
77
+ errors: ["Config file must export a default configuration object"]
78
+ }));
79
+ return {
80
+ config: defineConfig(configInput),
81
+ configPath,
82
+ usingDefaults: false
83
+ };
84
+ }),
85
+ resolve: (input = {}) => Effect.succeed(defineConfig(input)),
86
+ detectEntries: (cwd, entries) => Effect.gen(function* () {
87
+ const detected = [];
88
+ const mainPath = entries?.main ?? DEFAULT_ENTRIES.main;
89
+ const absoluteMainPath = resolve(cwd, mainPath);
90
+ if (!existsSync(absoluteMainPath)) return yield* Effect.fail(new MainEntryMissing({
91
+ expectedPath: mainPath,
92
+ cwd
93
+ }));
94
+ detected.push({
95
+ type: "main",
96
+ path: absoluteMainPath,
97
+ output: "dist/main.js"
98
+ });
99
+ const preEntry = detectOptionalEntry(cwd, "pre", entries?.pre);
100
+ if (preEntry) detected.push(preEntry);
101
+ const postEntry = detectOptionalEntry(cwd, "post", entries?.post);
102
+ if (postEntry) detected.push(postEntry);
103
+ return {
104
+ success: true,
105
+ entries: detected
106
+ };
107
+ })
108
+ });
109
+
110
+ //#endregion
111
+ export { ConfigServiceLive };
@@ -0,0 +1,63 @@
1
+ import { OptionalPathLikeSchema } from "../schemas/path.js";
2
+ import { ConfigSchema } from "../schemas/config.js";
3
+ import { Context, Schema } from "effect";
4
+
5
+ //#region src/services/config.ts
6
+ /**
7
+ * Options for loading configuration.
8
+ * @internal
9
+ */
10
+ const LoadConfigOptionsSchema = Schema.Struct({
11
+ /** Working directory to search for config. Accepts string, Buffer, or URL. */
12
+ cwd: OptionalPathLikeSchema,
13
+ /** Explicit path to config file. Accepts string, Buffer, or URL. */
14
+ configPath: OptionalPathLikeSchema
15
+ });
16
+ /**
17
+ * Entry point type.
18
+ * @internal
19
+ */
20
+ const EntryTypeSchema = Schema.Literal("main", "pre", "post");
21
+ /**
22
+ * Detected entry point information.
23
+ * @internal
24
+ */
25
+ const DetectedEntrySchema = Schema.Struct({
26
+ /** Entry type (main, pre, or post). */
27
+ type: EntryTypeSchema,
28
+ /** Absolute path to the entry file. */
29
+ path: Schema.String,
30
+ /** Output path for the bundled file. */
31
+ output: Schema.String
32
+ });
33
+ /**
34
+ * Result of entry detection.
35
+ * @internal
36
+ */
37
+ const DetectEntriesResultSchema = Schema.Struct({
38
+ /** Whether detection was successful. */
39
+ success: Schema.Boolean,
40
+ /** Detected entries. */
41
+ entries: Schema.Array(DetectedEntrySchema)
42
+ });
43
+ /**
44
+ * Result of configuration loading.
45
+ * @internal
46
+ */
47
+ const LoadConfigResultSchema = Schema.Struct({
48
+ /** The resolved configuration. */
49
+ config: ConfigSchema,
50
+ /** Path to the config file that was loaded, if any. */
51
+ configPath: Schema.optional(Schema.String),
52
+ /** Whether defaults were used (no config file found). */
53
+ usingDefaults: Schema.Boolean
54
+ });
55
+ /**
56
+ * ConfigService tag for dependency injection.
57
+ *
58
+ * @public
59
+ */
60
+ const ConfigService = Context.GenericTag("ConfigService");
61
+
62
+ //#endregion
63
+ export { ConfigService, DetectEntriesResultSchema, DetectedEntrySchema, LoadConfigOptionsSchema };