@savvy-web/github-action-builder 0.1.0

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.
package/123.js ADDED
@@ -0,0 +1,646 @@
1
+ import { Console, Context, Data, Effect, Layer, ManagedRuntime, Option, ParseResult, Schema } from "effect";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parse } from "yaml";
7
+ const ConfigNotFoundBase = Data.TaggedError("ConfigNotFound");
8
+ class ConfigNotFound extends ConfigNotFoundBase {
9
+ }
10
+ const ConfigInvalidBase = Data.TaggedError("ConfigInvalid");
11
+ class ConfigInvalid extends ConfigInvalidBase {
12
+ }
13
+ const ConfigLoadFailedBase = Data.TaggedError("ConfigLoadFailed");
14
+ class ConfigLoadFailed extends ConfigLoadFailedBase {
15
+ }
16
+ const MainEntryMissingBase = Data.TaggedError("MainEntryMissing");
17
+ class MainEntryMissing extends MainEntryMissingBase {
18
+ }
19
+ const EntryFileMissingBase = Data.TaggedError("EntryFileMissing");
20
+ class EntryFileMissing extends EntryFileMissingBase {
21
+ }
22
+ const ActionYmlMissingBase = Data.TaggedError("ActionYmlMissing");
23
+ class ActionYmlMissing extends ActionYmlMissingBase {
24
+ }
25
+ const ActionYmlSyntaxErrorBase = Data.TaggedError("ActionYmlSyntaxError");
26
+ class ActionYmlSyntaxError extends ActionYmlSyntaxErrorBase {
27
+ }
28
+ const ActionYmlSchemaErrorBase = Data.TaggedError("ActionYmlSchemaError");
29
+ class ActionYmlSchemaError extends ActionYmlSchemaErrorBase {
30
+ }
31
+ const ValidationFailedBase = Data.TaggedError("ValidationFailed");
32
+ class ValidationFailed extends ValidationFailedBase {
33
+ }
34
+ const BundleFailedBase = Data.TaggedError("BundleFailed");
35
+ class BundleFailed extends BundleFailedBase {
36
+ }
37
+ const WriteErrorBase = Data.TaggedError("WriteError");
38
+ class WriteError extends WriteErrorBase {
39
+ }
40
+ const CleanErrorBase = Data.TaggedError("CleanError");
41
+ class CleanError extends CleanErrorBase {
42
+ }
43
+ const BuildFailedBase = Data.TaggedError("BuildFailed");
44
+ class BuildFailed extends BuildFailedBase {
45
+ }
46
+ function pathLikeToString(pathLike) {
47
+ if ("string" == typeof pathLike) return pathLike;
48
+ if (Buffer.isBuffer(pathLike)) return pathLike.toString("utf8");
49
+ if (pathLike instanceof URL) return fileURLToPath(pathLike);
50
+ return String(pathLike);
51
+ }
52
+ const PathLikeSchema = Schema.transform(Schema.Union(Schema.String, Schema.instanceOf(Buffer), Schema.instanceOf(URL)), Schema.String, {
53
+ strict: true,
54
+ decode: (pathLike)=>pathLikeToString(pathLike),
55
+ encode: (s)=>s
56
+ });
57
+ const OptionalPathLikeSchema = Schema.optional(PathLikeSchema);
58
+ const BuildRunnerOptionsSchema = Schema.Struct({
59
+ cwd: OptionalPathLikeSchema,
60
+ clean: Schema.optional(Schema.Boolean)
61
+ });
62
+ const BundleStatsSchema = Schema.Struct({
63
+ entry: Schema.String,
64
+ size: Schema.Number,
65
+ duration: Schema.Number,
66
+ outputPath: Schema.String
67
+ });
68
+ const BundleResultSchema = Schema.Struct({
69
+ success: Schema.Boolean,
70
+ stats: Schema.optional(BundleStatsSchema),
71
+ error: Schema.optional(Schema.String)
72
+ });
73
+ const BuildResultSchema = Schema.Struct({
74
+ success: Schema.Boolean,
75
+ entries: Schema.Array(BundleResultSchema),
76
+ duration: Schema.Number,
77
+ error: Schema.optional(Schema.String)
78
+ });
79
+ const BuildService = Context.GenericTag("BuildService");
80
+ const EntriesSchema = Schema.Struct({
81
+ main: Schema.optionalWith(Schema.String, {
82
+ default: ()=>"src/main.ts"
83
+ }),
84
+ pre: Schema.optional(Schema.String),
85
+ post: Schema.optional(Schema.String)
86
+ });
87
+ const EsTarget = Schema.Literal("es2020", "es2021", "es2022", "es2023", "es2024");
88
+ const BuildOptionsSchema = Schema.Struct({
89
+ minify: Schema.optionalWith(Schema.Boolean, {
90
+ default: ()=>true
91
+ }),
92
+ target: Schema.optionalWith(EsTarget, {
93
+ default: ()=>"es2022"
94
+ }),
95
+ sourceMap: Schema.optionalWith(Schema.Boolean, {
96
+ default: ()=>false
97
+ }),
98
+ externals: Schema.optionalWith(Schema.Array(Schema.String), {
99
+ default: ()=>[]
100
+ }),
101
+ quiet: Schema.optionalWith(Schema.Boolean, {
102
+ default: ()=>false
103
+ })
104
+ });
105
+ const ValidationOptionsSchema = Schema.Struct({
106
+ requireActionYml: Schema.optionalWith(Schema.Boolean, {
107
+ default: ()=>true
108
+ }),
109
+ maxBundleSize: Schema.optional(Schema.String),
110
+ strict: Schema.optional(Schema.Boolean)
111
+ });
112
+ const ConfigInputSchema = Schema.Struct({
113
+ entries: Schema.optional(Schema.Struct({
114
+ main: Schema.optional(Schema.String),
115
+ pre: Schema.optional(Schema.String),
116
+ post: Schema.optional(Schema.String)
117
+ })),
118
+ build: Schema.optional(Schema.Struct({
119
+ minify: Schema.optional(Schema.Boolean),
120
+ target: Schema.optional(EsTarget),
121
+ sourceMap: Schema.optional(Schema.Boolean),
122
+ externals: Schema.optional(Schema.Array(Schema.String)),
123
+ quiet: Schema.optional(Schema.Boolean)
124
+ })),
125
+ validation: Schema.optional(Schema.Struct({
126
+ requireActionYml: Schema.optional(Schema.Boolean),
127
+ maxBundleSize: Schema.optional(Schema.String),
128
+ strict: Schema.optional(Schema.Boolean)
129
+ }))
130
+ });
131
+ const ConfigSchema = Schema.Struct({
132
+ entries: EntriesSchema,
133
+ build: BuildOptionsSchema,
134
+ validation: ValidationOptionsSchema
135
+ });
136
+ function defineConfig(config = {}) {
137
+ return Schema.decodeUnknownSync(ConfigSchema)({
138
+ entries: config.entries ?? {},
139
+ build: config.build ?? {},
140
+ validation: config.validation ?? {}
141
+ });
142
+ }
143
+ const LoadConfigOptionsSchema = Schema.Struct({
144
+ cwd: OptionalPathLikeSchema,
145
+ configPath: OptionalPathLikeSchema
146
+ });
147
+ const EntryTypeSchema = Schema.Literal("main", "pre", "post");
148
+ const DetectedEntrySchema = Schema.Struct({
149
+ type: EntryTypeSchema,
150
+ path: Schema.String,
151
+ output: Schema.String
152
+ });
153
+ const DetectEntriesResultSchema = Schema.Struct({
154
+ success: Schema.Boolean,
155
+ entries: Schema.Array(DetectedEntrySchema)
156
+ });
157
+ Schema.Struct({
158
+ config: ConfigSchema,
159
+ configPath: Schema.optional(Schema.String),
160
+ usingDefaults: Schema.Boolean
161
+ });
162
+ const ConfigService = Context.GenericTag("ConfigService");
163
+ const build_live_require = createRequire(import.meta.url);
164
+ const ncc = build_live_require("@vercel/ncc");
165
+ async function bundle(entryPath, options = {}) {
166
+ return ncc(entryPath, {
167
+ minify: options.minify ?? true,
168
+ sourceMap: options.sourceMap ?? false,
169
+ target: options.target ?? "es2022",
170
+ quiet: options.quiet ?? true,
171
+ externals: options.externals ?? [],
172
+ ...options
173
+ });
174
+ }
175
+ function getBundleSize(code) {
176
+ return Buffer.byteLength(code, "utf8");
177
+ }
178
+ function formatBytes(bytes) {
179
+ if (bytes < 1024) return `${bytes} B`;
180
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
181
+ return `${(bytes / 1048576).toFixed(1)} MB`;
182
+ }
183
+ function formatBuildResult(result) {
184
+ const lines = [];
185
+ if (result.success) {
186
+ lines.push("Build Summary:");
187
+ for (const entry of result.entries)if (entry.success && entry.stats) {
188
+ const { entry: name, size, duration, outputPath } = entry.stats;
189
+ lines.push(` ✓ ${name}: ${formatBytes(size)} (${duration}ms) → ${outputPath}`);
190
+ }
191
+ lines.push(`\nTotal time: ${result.duration}ms`);
192
+ } else {
193
+ lines.push("Build Failed:");
194
+ for (const entry of result.entries)if (!entry.success) lines.push(` ✗ ${entry.error}`);
195
+ }
196
+ return lines.join("\n");
197
+ }
198
+ function cleanDirectory(dir) {
199
+ return Effect["try"]({
200
+ try: ()=>{
201
+ if (existsSync(dir)) rmSync(dir, {
202
+ recursive: true,
203
+ force: true
204
+ });
205
+ },
206
+ catch: (error)=>new CleanError({
207
+ directory: dir,
208
+ cause: error instanceof Error ? error.message : String(error)
209
+ })
210
+ });
211
+ }
212
+ function writeFile(path, content) {
213
+ return Effect["try"]({
214
+ try: ()=>{
215
+ const dir = resolve(path, "..");
216
+ mkdirSync(dir, {
217
+ recursive: true
218
+ });
219
+ writeFileSync(path, content, "utf8");
220
+ },
221
+ catch: (error)=>new WriteError({
222
+ path,
223
+ cause: error instanceof Error ? error.message : String(error)
224
+ })
225
+ });
226
+ }
227
+ function bundleEntry(entry, config, cwd) {
228
+ return Effect.gen(function*() {
229
+ const startTime = Date.now();
230
+ const nccResult = yield* Effect.tryPromise({
231
+ try: ()=>bundle(entry.path, {
232
+ minify: config.build.minify,
233
+ sourceMap: config.build.sourceMap,
234
+ target: config.build.target,
235
+ externals: [
236
+ ...config.build.externals
237
+ ],
238
+ quiet: config.build.quiet
239
+ }),
240
+ catch: (error)=>new BundleFailed({
241
+ entry: entry.path,
242
+ cause: error instanceof Error ? error.message : String(error)
243
+ })
244
+ });
245
+ const outputPath = resolve(cwd, entry.output);
246
+ yield* writeFile(outputPath, nccResult.code);
247
+ if (config.build.sourceMap && nccResult.map) yield* writeFile(`${outputPath}.map`, nccResult.map);
248
+ if (nccResult.assets) {
249
+ const outputDir = resolve(outputPath, "..");
250
+ for (const [assetName, assetData] of Object.entries(nccResult.assets)){
251
+ const assetPath = resolve(outputDir, assetName);
252
+ const content = "string" == typeof assetData.source ? assetData.source : assetData.source.toString("utf8");
253
+ yield* writeFile(assetPath, content);
254
+ }
255
+ }
256
+ const duration = Date.now() - startTime;
257
+ const size = getBundleSize(nccResult.code);
258
+ return {
259
+ success: true,
260
+ stats: {
261
+ entry: entry.type,
262
+ size,
263
+ duration,
264
+ outputPath: entry.output
265
+ }
266
+ };
267
+ });
268
+ }
269
+ const BuildServiceLive = Layer.effect(BuildService, Effect.gen(function*() {
270
+ const configService = yield* ConfigService;
271
+ return {
272
+ build: (config, options = {})=>Effect.gen(function*() {
273
+ const cwd = options.cwd ?? process.cwd();
274
+ const shouldClean = options.clean ?? true;
275
+ const startTime = Date.now();
276
+ const entriesConfig = {
277
+ main: config.entries.main
278
+ };
279
+ if (config.entries.pre) entriesConfig.pre = config.entries.pre;
280
+ if (config.entries.post) entriesConfig.post = config.entries.post;
281
+ const entriesResult = yield* configService.detectEntries(cwd, entriesConfig);
282
+ if (shouldClean) yield* cleanDirectory(resolve(cwd, "dist"));
283
+ const entryResults = [];
284
+ for (const entry of entriesResult.entries){
285
+ const result = yield* Effect.either(bundleEntry(entry, config, cwd));
286
+ if ("Left" === result._tag) entryResults.push({
287
+ success: false,
288
+ error: result.left.cause
289
+ });
290
+ else entryResults.push(result.right);
291
+ }
292
+ yield* writeFile(resolve(cwd, "dist/package.json"), '{ "type": "module" }');
293
+ const duration = Date.now() - startTime;
294
+ const success = entryResults.every((r)=>r.success);
295
+ if (!success) return {
296
+ success,
297
+ entries: entryResults,
298
+ duration,
299
+ error: "One or more entries failed to build"
300
+ };
301
+ return {
302
+ success,
303
+ entries: entryResults,
304
+ duration
305
+ };
306
+ }),
307
+ bundle: (entry, config)=>bundleEntry(entry, config, process.cwd()),
308
+ clean: (outputDir)=>cleanDirectory(outputDir),
309
+ formatResult: formatBuildResult,
310
+ formatBytes: formatBytes
311
+ };
312
+ }));
313
+ const CONFIG_FILENAMES = [
314
+ "action.config.ts",
315
+ "action.config.js",
316
+ "action.config.mjs"
317
+ ];
318
+ const DEFAULT_ENTRIES = {
319
+ main: "src/main.ts",
320
+ pre: "src/pre.ts",
321
+ post: "src/post.ts"
322
+ };
323
+ function findConfigFile(cwd) {
324
+ for (const filename of CONFIG_FILENAMES){
325
+ const configPath = resolve(cwd, filename);
326
+ if (existsSync(configPath)) return configPath;
327
+ }
328
+ }
329
+ function detectOptionalEntry(cwd, type, explicitPath) {
330
+ const defaultPath = DEFAULT_ENTRIES[type];
331
+ const entryPath = explicitPath ?? defaultPath;
332
+ const absolutePath = resolve(cwd, entryPath);
333
+ if (existsSync(absolutePath)) return {
334
+ type,
335
+ path: absolutePath,
336
+ output: `dist/${type}.js`
337
+ };
338
+ }
339
+ const ConfigServiceLive = Layer.succeed(ConfigService, {
340
+ load: (options = {})=>Effect.gen(function*() {
341
+ const cwd = options.cwd ?? process.cwd();
342
+ const configPath = options.configPath ?? findConfigFile(cwd);
343
+ if (!configPath) return {
344
+ config: defineConfig({}),
345
+ usingDefaults: true
346
+ };
347
+ if (!existsSync(configPath)) return yield* Effect.fail(new ConfigNotFound({
348
+ path: configPath,
349
+ message: "Specified config file does not exist"
350
+ }));
351
+ const absolutePath = resolve(cwd, configPath);
352
+ const configModule = yield* Effect.tryPromise({
353
+ try: async ()=>import(absolutePath),
354
+ catch: (error)=>new ConfigLoadFailed({
355
+ path: configPath,
356
+ cause: error instanceof Error ? error.message : String(error)
357
+ })
358
+ });
359
+ const configInput = configModule.default;
360
+ if (!configInput || "object" != typeof configInput) return yield* Effect.fail(new ConfigInvalid({
361
+ path: configPath,
362
+ errors: [
363
+ "Config file must export a default configuration object"
364
+ ]
365
+ }));
366
+ const config = defineConfig(configInput);
367
+ return {
368
+ config,
369
+ configPath,
370
+ usingDefaults: false
371
+ };
372
+ }),
373
+ resolve: (input = {})=>Effect.succeed(defineConfig(input)),
374
+ detectEntries: (cwd, entries)=>Effect.gen(function*() {
375
+ const detected = [];
376
+ const mainPath = entries?.main ?? DEFAULT_ENTRIES.main;
377
+ const absoluteMainPath = resolve(cwd, mainPath);
378
+ if (!existsSync(absoluteMainPath)) return yield* Effect.fail(new MainEntryMissing({
379
+ expectedPath: mainPath,
380
+ cwd
381
+ }));
382
+ detected.push({
383
+ type: "main",
384
+ path: absoluteMainPath,
385
+ output: "dist/main.js"
386
+ });
387
+ const preEntry = detectOptionalEntry(cwd, "pre", entries?.pre);
388
+ if (preEntry) detected.push(preEntry);
389
+ const postEntry = detectOptionalEntry(cwd, "post", entries?.post);
390
+ if (postEntry) detected.push(postEntry);
391
+ return {
392
+ success: true,
393
+ entries: detected
394
+ };
395
+ })
396
+ });
397
+ const BrandingIcon = Schema.Literal("activity", "airplay", "alert-circle", "alert-octagon", "alert-triangle", "align-center", "align-justify", "align-left", "align-right", "anchor", "aperture", "archive", "arrow-down-circle", "arrow-down-left", "arrow-down-right", "arrow-down", "arrow-left-circle", "arrow-left", "arrow-right-circle", "arrow-right", "arrow-up-circle", "arrow-up-left", "arrow-up-right", "arrow-up", "at-sign", "award", "bar-chart-2", "bar-chart", "battery-charging", "battery", "bell-off", "bell", "bluetooth", "bold", "book-open", "book", "bookmark", "box", "briefcase", "calendar", "camera-off", "camera", "cast", "check-circle", "check-square", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "chevrons-down", "chevrons-left", "chevrons-right", "chevrons-up", "circle", "clipboard", "clock", "cloud-drizzle", "cloud-lightning", "cloud-off", "cloud-rain", "cloud-snow", "cloud", "code", "command", "compass", "copy", "corner-down-left", "corner-down-right", "corner-left-down", "corner-left-up", "corner-right-down", "corner-right-up", "corner-up-left", "corner-up-right", "cpu", "credit-card", "crop", "crosshair", "database", "delete", "disc", "dollar-sign", "download-cloud", "download", "droplet", "edit-2", "edit-3", "edit", "external-link", "eye-off", "eye", "fast-forward", "feather", "file-minus", "file-plus", "file-text", "file", "film", "filter", "flag", "folder-minus", "folder-plus", "folder", "gift", "git-branch", "git-commit", "git-merge", "git-pull-request", "globe", "grid", "hard-drive", "hash", "headphones", "heart", "help-circle", "home", "image", "inbox", "info", "italic", "layers", "layout", "life-buoy", "link-2", "link", "list", "loader", "lock", "log-in", "log-out", "mail", "map-pin", "map", "maximize-2", "maximize", "menu", "message-circle", "message-square", "mic-off", "mic", "minimize-2", "minimize", "minus-circle", "minus-square", "minus", "monitor", "moon", "more-horizontal", "more-vertical", "move", "music", "navigation-2", "navigation", "octagon", "package", "paperclip", "pause-circle", "pause", "percent", "phone-call", "phone-forwarded", "phone-incoming", "phone-missed", "phone-off", "phone-outgoing", "phone", "pie-chart", "play-circle", "play", "plus-circle", "plus-square", "plus", "pocket", "power", "printer", "radio", "refresh-ccw", "refresh-cw", "repeat", "rewind", "rotate-ccw", "rotate-cw", "rss", "save", "scissors", "search", "send", "server", "settings", "share-2", "share", "shield-off", "shield", "shopping-bag", "shopping-cart", "shuffle", "sidebar", "skip-back", "skip-forward", "slash", "sliders", "smartphone", "speaker", "square", "star", "stop-circle", "sun", "sunrise", "sunset", "table", "tablet", "tag", "target", "terminal", "thermometer", "thumbs-down", "thumbs-up", "toggle-left", "toggle-right", "trash-2", "trash", "trending-down", "trending-up", "triangle", "truck", "tv", "type", "umbrella", "underline", "unlock", "upload-cloud", "upload", "user-check", "user-minus", "user-plus", "user-x", "user", "users", "video-off", "video", "voicemail", "volume-1", "volume-2", "volume-x", "volume", "watch", "wifi-off", "wifi", "wind", "x-circle", "x-square", "x", "zap-off", "zap", "zoom-in", "zoom-out");
398
+ const ActionInput = Schema.Struct({
399
+ description: Schema.String,
400
+ required: Schema.optional(Schema.Boolean),
401
+ default: Schema.optional(Schema.String),
402
+ deprecationMessage: Schema.optional(Schema.String)
403
+ });
404
+ const ActionOutput = Schema.Struct({
405
+ description: Schema.String
406
+ });
407
+ const Runs = Schema.Struct({
408
+ using: Schema.Literal("node24"),
409
+ main: Schema.String,
410
+ pre: Schema.optional(Schema.String),
411
+ "pre-if": Schema.optional(Schema.String),
412
+ post: Schema.optional(Schema.String),
413
+ "post-if": Schema.optional(Schema.String)
414
+ });
415
+ const BrandingColor = Schema.Literal("white", "black", "yellow", "blue", "green", "orange", "red", "purple", "gray-dark");
416
+ const Branding = Schema.Struct({
417
+ icon: Schema.optional(BrandingIcon),
418
+ color: Schema.optional(BrandingColor)
419
+ });
420
+ const ActionYml = Schema.Struct({
421
+ name: Schema.String,
422
+ description: Schema.String,
423
+ author: Schema.optional(Schema.String),
424
+ inputs: Schema.optional(Schema.Record({
425
+ key: Schema.String,
426
+ value: ActionInput
427
+ })),
428
+ outputs: Schema.optional(Schema.Record({
429
+ key: Schema.String,
430
+ value: ActionOutput
431
+ })),
432
+ runs: Runs,
433
+ branding: Schema.optional(Branding)
434
+ });
435
+ const ValidateOptionsSchema = Schema.Struct({
436
+ cwd: OptionalPathLikeSchema,
437
+ strict: Schema.optional(Schema.Boolean)
438
+ });
439
+ const ValidationErrorSchema = Schema.Struct({
440
+ code: Schema.String,
441
+ message: Schema.String,
442
+ file: Schema.optional(Schema.String),
443
+ suggestion: Schema.optional(Schema.String)
444
+ });
445
+ const ValidationWarningSchema = Schema.Struct({
446
+ code: Schema.String,
447
+ message: Schema.String,
448
+ file: Schema.optional(Schema.String),
449
+ suggestion: Schema.optional(Schema.String)
450
+ });
451
+ const ValidationResultSchema = Schema.Struct({
452
+ valid: Schema.Boolean,
453
+ errors: Schema.Array(ValidationErrorSchema),
454
+ warnings: Schema.Array(ValidationWarningSchema)
455
+ });
456
+ const ActionYmlResultSchema = Schema.Struct({
457
+ valid: Schema.Boolean,
458
+ content: Schema.optional(Schema.Any),
459
+ errors: Schema.Array(ValidationErrorSchema),
460
+ warnings: Schema.Array(ValidationWarningSchema)
461
+ });
462
+ const ValidationService = Context.GenericTag("ValidationService");
463
+ const isCI = ()=>"true" === process.env.CI || "1" === process.env.CI || "true" === process.env.GITHUB_ACTIONS;
464
+ const resolveStrict = (configStrict)=>configStrict ?? isCI();
465
+ const makeWarning = (code, message, suggestion, file)=>void 0 !== file ? {
466
+ code,
467
+ message,
468
+ suggestion,
469
+ file
470
+ } : {
471
+ code,
472
+ message,
473
+ suggestion
474
+ };
475
+ const formatSchemaErrors = (error, filePath)=>[
476
+ {
477
+ path: filePath,
478
+ message: ParseResult.TreeFormatter.formatErrorSync(error)
479
+ }
480
+ ];
481
+ const ValidationServiceLive = Layer.effect(ValidationService, Effect.gen(function*() {
482
+ const configService = yield* ConfigService;
483
+ const readActionYml = (path)=>Effect.gen(function*() {
484
+ if (!existsSync(path)) return yield* new ActionYmlMissing({
485
+ cwd: path
486
+ });
487
+ const content = yield* Effect["try"]({
488
+ try: ()=>readFileSync(path, "utf8"),
489
+ catch: ()=>new ActionYmlSyntaxError({
490
+ path,
491
+ message: "Failed to read file"
492
+ })
493
+ });
494
+ const parsed = yield* Effect["try"]({
495
+ try: ()=>parse(content),
496
+ catch: (error)=>new ActionYmlSyntaxError({
497
+ path,
498
+ message: error instanceof Error ? error.message : "Invalid YAML syntax"
499
+ })
500
+ });
501
+ if (!parsed || "object" != typeof parsed) return yield* new ActionYmlSyntaxError({
502
+ path,
503
+ message: "action.yml must be an object"
504
+ });
505
+ return parsed;
506
+ });
507
+ const validateSchema = (parsed, path)=>Effect.gen(function*() {
508
+ const result = Schema.decodeUnknownEither(ActionYml)(parsed);
509
+ if ("Left" === result._tag) return yield* new ActionYmlSchemaError({
510
+ path,
511
+ errors: formatSchemaErrors(result.left, path)
512
+ });
513
+ return result.right;
514
+ });
515
+ const checkRecommendations = (content, filePath)=>{
516
+ const warnings = [];
517
+ if (content.branding) {
518
+ const branding = content.branding;
519
+ if (!branding.icon) warnings.push(makeWarning("ACTION_YML_NO_BRANDING_ICON", "Branding icon not specified", "Add branding.icon for better marketplace visibility", filePath));
520
+ if (!branding.color) warnings.push(makeWarning("ACTION_YML_NO_BRANDING_COLOR", "Branding color not specified", "Add branding.color for better marketplace visibility", filePath));
521
+ } else warnings.push(makeWarning("ACTION_YML_NO_BRANDING", "No branding configuration found", "Add branding.icon and branding.color for better marketplace visibility", filePath));
522
+ if (content.inputs) {
523
+ const inputs = content.inputs;
524
+ for (const [name, input] of Object.entries(inputs))if (!input.description) warnings.push(makeWarning("ACTION_YML_INPUT_NO_DESCRIPTION", `Input '${name}' has no description`, `Add a description for the '${name}' input`, filePath));
525
+ }
526
+ if (content.outputs) {
527
+ const outputs = content.outputs;
528
+ for (const [name, output] of Object.entries(outputs))if (!output.description) warnings.push(makeWarning("ACTION_YML_OUTPUT_NO_DESCRIPTION", `Output '${name}' has no description`, `Add a description for the '${name}' output`, filePath));
529
+ }
530
+ return warnings;
531
+ };
532
+ const validateActionYml = (path)=>Effect.gen(function*() {
533
+ const parsed = yield* readActionYml(path);
534
+ const content = yield* validateSchema(parsed, path);
535
+ const warnings = checkRecommendations(parsed, path);
536
+ return {
537
+ valid: true,
538
+ content,
539
+ errors: [],
540
+ warnings
541
+ };
542
+ });
543
+ const checkEntries = (config, cwd)=>Effect.gen(function*() {
544
+ const errors = [];
545
+ const entriesConfig = {
546
+ main: config.entries.main
547
+ };
548
+ if (config.entries.pre) entriesConfig.pre = config.entries.pre;
549
+ if (config.entries.post) entriesConfig.post = config.entries.post;
550
+ const result = yield* Effect.either(configService.detectEntries(cwd, entriesConfig));
551
+ if ("Left" === result._tag && result.left instanceof MainEntryMissing) errors.push({
552
+ code: "MAIN_ENTRY_MISSING",
553
+ message: `Main entry point not found: ${result.left.expectedPath}`,
554
+ file: result.left.expectedPath,
555
+ suggestion: "Create src/main.ts or specify a different path in config"
556
+ });
557
+ return errors;
558
+ });
559
+ const checkActionYml = (config, cwd)=>Effect.gen(function*() {
560
+ const errors = [];
561
+ const warnings = [];
562
+ if (!config.validation.requireActionYml) return {
563
+ errors,
564
+ warnings
565
+ };
566
+ const actionYmlPath = resolve(cwd, "action.yml");
567
+ const result = yield* Effect.either(validateActionYml(actionYmlPath));
568
+ if ("Left" === result._tag) {
569
+ const error = result.left;
570
+ if (error instanceof ActionYmlMissing) warnings.push({
571
+ code: "ACTION_YML_MISSING",
572
+ message: "action.yml not found",
573
+ file: actionYmlPath,
574
+ suggestion: "Create action.yml to define your action metadata"
575
+ });
576
+ else if (error instanceof ActionYmlSyntaxError) errors.push({
577
+ code: "ACTION_YML_SYNTAX_ERROR",
578
+ message: error.message,
579
+ file: error.path
580
+ });
581
+ else if (error instanceof ActionYmlSchemaError) for (const schemaError of error.errors)errors.push({
582
+ code: "ACTION_YML_SCHEMA_ERROR",
583
+ message: schemaError.message,
584
+ file: error.path
585
+ });
586
+ } else warnings.push(...result.right.warnings);
587
+ return {
588
+ errors,
589
+ warnings
590
+ };
591
+ });
592
+ return {
593
+ validate: (config, options = {})=>Effect.gen(function*() {
594
+ const cwd = options.cwd ?? process.cwd();
595
+ const strict = resolveStrict(options.strict ?? config.validation.strict);
596
+ const entryErrors = yield* checkEntries(config, cwd);
597
+ const actionYmlResult = yield* checkActionYml(config, cwd);
598
+ const errors = [
599
+ ...entryErrors,
600
+ ...actionYmlResult.errors
601
+ ];
602
+ const warnings = [
603
+ ...actionYmlResult.warnings
604
+ ];
605
+ const valid = 0 === errors.length && (!strict || 0 === warnings.length);
606
+ if (strict && warnings.length > 0 && 0 === errors.length) return yield* new ValidationFailed({
607
+ errorCount: 0,
608
+ warningCount: warnings.length,
609
+ message: "Warnings treated as errors in strict mode"
610
+ });
611
+ return {
612
+ valid,
613
+ errors,
614
+ warnings
615
+ };
616
+ }),
617
+ validateActionYml,
618
+ formatResult: (result)=>{
619
+ const lines = [];
620
+ if (result.errors.length > 0) {
621
+ lines.push("Errors:");
622
+ for (const error of result.errors){
623
+ lines.push(` \u2717 ${error.message}`);
624
+ if (error.suggestion) lines.push(` \u2192 ${error.suggestion}`);
625
+ }
626
+ }
627
+ if (result.warnings.length > 0) {
628
+ if (lines.length > 0) lines.push("");
629
+ lines.push("Warnings:");
630
+ for (const warning of result.warnings){
631
+ lines.push(` \u26A0 ${warning.message}`);
632
+ if (warning.suggestion) lines.push(` \u2192 ${warning.suggestion}`);
633
+ }
634
+ }
635
+ if (result.valid && 0 === result.errors.length && 0 === result.warnings.length) lines.push("\u2713 All checks passed");
636
+ return lines.join("\n");
637
+ },
638
+ isCI: ()=>Effect.succeed(isCI()),
639
+ isStrict: (configStrict)=>Effect.succeed(resolveStrict(configStrict))
640
+ };
641
+ }));
642
+ const ConfigLayer = ConfigServiceLive;
643
+ const ValidationLayer = ValidationServiceLive.pipe(Layer.provide(ConfigServiceLive));
644
+ const BuildLayer = BuildServiceLive.pipe(Layer.provide(ConfigServiceLive));
645
+ const AppLayer = Layer.mergeAll(ConfigServiceLive, ValidationLayer, BuildLayer);
646
+ export { ActionYmlMissing, ActionYmlMissingBase, ActionYmlResultSchema, ActionYmlSchemaError, ActionYmlSchemaErrorBase, ActionYmlSyntaxError, ActionYmlSyntaxErrorBase, AppLayer, BuildFailed, BuildFailedBase, BuildLayer, BuildOptionsSchema, BuildResultSchema, BuildRunnerOptionsSchema, BuildService, BundleFailed, BundleFailedBase, BundleResultSchema, BundleStatsSchema, CleanError, CleanErrorBase, ConfigInputSchema, ConfigInvalid, ConfigInvalidBase, ConfigLayer, ConfigLoadFailed, ConfigLoadFailedBase, ConfigNotFound, ConfigNotFoundBase, ConfigSchema, ConfigService, Console, DetectEntriesResultSchema, DetectedEntrySchema, Effect, EntriesSchema, EntryFileMissing, EntryFileMissingBase, Layer, LoadConfigOptionsSchema, MainEntryMissing, MainEntryMissingBase, ManagedRuntime, Option, Schema, ValidateOptionsSchema, ValidationErrorSchema, ValidationFailed, ValidationFailedBase, ValidationLayer, ValidationOptionsSchema, ValidationResultSchema, ValidationService, ValidationWarningSchema, WriteError, WriteErrorBase, defineConfig, existsSync, mkdirSync, resolve, writeFileSync };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Savvy Web Strategy, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.