@savvy-web/github-action-builder 0.1.4 → 0.2.1

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 CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Console, Context, Data, Effect, Layer, ManagedRuntime, Option, ParseResult, Schema } from "effect";
2
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
- import { resolve } from "node:path";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { createHash } from "node:crypto";
6
7
  import { parse } from "yaml";
7
8
  const ConfigNotFoundBase = Data.TaggedError("ConfigNotFound");
8
9
  class ConfigNotFound extends ConfigNotFoundBase {
@@ -43,6 +44,12 @@ class CleanError extends CleanErrorBase {
43
44
  const BuildFailedBase = Data.TaggedError("BuildFailed");
44
45
  class BuildFailed extends BuildFailedBase {
45
46
  }
47
+ const PersistLocalErrorBase = Data.TaggedError("PersistLocalError");
48
+ class PersistLocalError extends PersistLocalErrorBase {
49
+ }
50
+ const ActionYmlPathErrorBase = Data.TaggedError("ActionYmlPathError");
51
+ class ActionYmlPathError extends ActionYmlPathErrorBase {
52
+ }
46
53
  function pathLikeToString(pathLike) {
47
54
  if ("string" == typeof pathLike) return pathLike;
48
55
  if (Buffer.isBuffer(pathLike)) return pathLike.toString("utf8");
@@ -109,6 +116,17 @@ const ValidationOptionsSchema = Schema.Struct({
109
116
  maxBundleSize: Schema.optional(Schema.String),
110
117
  strict: Schema.optional(Schema.Boolean)
111
118
  });
119
+ const PersistLocalOptionsSchema = Schema.Struct({
120
+ enabled: Schema.optionalWith(Schema.Boolean, {
121
+ default: ()=>true
122
+ }),
123
+ path: Schema.optionalWith(Schema.String, {
124
+ default: ()=>".github/actions/local"
125
+ }),
126
+ actTemplate: Schema.optionalWith(Schema.Boolean, {
127
+ default: ()=>true
128
+ })
129
+ });
112
130
  const ConfigInputSchema = Schema.Struct({
113
131
  entries: Schema.optional(Schema.Struct({
114
132
  main: Schema.optional(Schema.String),
@@ -126,18 +144,25 @@ const ConfigInputSchema = Schema.Struct({
126
144
  requireActionYml: Schema.optional(Schema.Boolean),
127
145
  maxBundleSize: Schema.optional(Schema.String),
128
146
  strict: Schema.optional(Schema.Boolean)
147
+ })),
148
+ persistLocal: Schema.optional(Schema.Struct({
149
+ enabled: Schema.optional(Schema.Boolean),
150
+ path: Schema.optional(Schema.String),
151
+ actTemplate: Schema.optional(Schema.Boolean)
129
152
  }))
130
153
  });
131
154
  const ConfigSchema = Schema.Struct({
132
155
  entries: EntriesSchema,
133
156
  build: BuildOptionsSchema,
134
- validation: ValidationOptionsSchema
157
+ validation: ValidationOptionsSchema,
158
+ persistLocal: PersistLocalOptionsSchema
135
159
  });
136
160
  function defineConfig(config = {}) {
137
161
  return Schema.decodeUnknownSync(ConfigSchema)({
138
162
  entries: config.entries ?? {},
139
163
  build: config.build ?? {},
140
- validation: config.validation ?? {}
164
+ validation: config.validation ?? {},
165
+ persistLocal: config.persistLocal ?? {}
141
166
  });
142
167
  }
143
168
  const LoadConfigOptionsSchema = Schema.Struct({
@@ -394,6 +419,213 @@ const ConfigServiceLive = Layer.succeed(ConfigService, {
394
419
  };
395
420
  })
396
421
  });
422
+ const PersistLocalRunnerOptionsSchema = Schema.Struct({
423
+ cwd: Schema.optional(Schema.String)
424
+ });
425
+ const PersistLocalResultSchema = Schema.Struct({
426
+ success: Schema.Boolean,
427
+ filesCopied: Schema.Number,
428
+ filesSkipped: Schema.Number,
429
+ actTemplateGenerated: Schema.Boolean,
430
+ outputPath: Schema.String,
431
+ error: Schema.optional(Schema.String)
432
+ });
433
+ const PersistLocalService = Context.GenericTag("PersistLocalService");
434
+ function fileHash(filePath) {
435
+ const content = readFileSync(filePath);
436
+ return createHash("sha256").update(content).digest("hex");
437
+ }
438
+ function syncFile(src, dest) {
439
+ if (existsSync(dest)) {
440
+ const srcHash = fileHash(src);
441
+ const destHash = fileHash(dest);
442
+ if (srcHash === destHash) return false;
443
+ }
444
+ mkdirSync(dirname(dest), {
445
+ recursive: true
446
+ });
447
+ copyFileSync(src, dest);
448
+ return true;
449
+ }
450
+ function walkDirectory(dir, base = dir) {
451
+ const files = [];
452
+ if (!existsSync(dir)) return files;
453
+ for (const entry of readdirSync(dir, {
454
+ withFileTypes: true
455
+ })){
456
+ const fullPath = join(dir, entry.name);
457
+ if (entry.isDirectory()) files.push(...walkDirectory(fullPath, base));
458
+ else files.push(relative(base, fullPath));
459
+ }
460
+ return files;
461
+ }
462
+ function syncDirectory(srcDir, destDir) {
463
+ const stats = {
464
+ copied: 0,
465
+ skipped: 0
466
+ };
467
+ const srcFiles = walkDirectory(srcDir);
468
+ for (const relPath of srcFiles){
469
+ const copied = syncFile(join(srcDir, relPath), join(destDir, relPath));
470
+ if (copied) stats.copied++;
471
+ else stats.skipped++;
472
+ }
473
+ const srcFileSet = new Set(srcFiles);
474
+ const destFiles = walkDirectory(destDir);
475
+ for (const relPath of destFiles)if (!srcFileSet.has(relPath)) {
476
+ rmSync(join(destDir, relPath), {
477
+ force: true
478
+ });
479
+ let parent = dirname(join(destDir, relPath));
480
+ while(parent !== destDir && existsSync(parent)){
481
+ const entries = readdirSync(parent);
482
+ if (0 === entries.length) {
483
+ rmSync(parent, {
484
+ recursive: true
485
+ });
486
+ parent = dirname(parent);
487
+ } else break;
488
+ }
489
+ }
490
+ return stats;
491
+ }
492
+ function validateActionYmlPaths(actionYmlPath, destDir) {
493
+ return Effect.gen(function*() {
494
+ if (!existsSync(actionYmlPath)) return;
495
+ const content = readFileSync(actionYmlPath, "utf8");
496
+ const parsed = parse(content);
497
+ if (!parsed?.runs) return;
498
+ for (const entryType of [
499
+ "main",
500
+ "pre",
501
+ "post"
502
+ ]){
503
+ const specifiedPath = parsed.runs[entryType];
504
+ if (!specifiedPath) continue;
505
+ const expectedPath = resolve(destDir, specifiedPath);
506
+ if (!existsSync(expectedPath)) return yield* Effect.fail(new ActionYmlPathError({
507
+ entryType,
508
+ specifiedPath,
509
+ expectedPath
510
+ }));
511
+ }
512
+ });
513
+ }
514
+ const ACTRC_CONTENT = `--container-architecture linux/amd64
515
+ -W .github/workflows/act-test.yml
516
+ `;
517
+ const ACT_WORKFLOW_CONTENT = `name: Local Test
518
+ on: push
519
+
520
+ jobs:
521
+ test:
522
+ runs-on: ubuntu-latest
523
+ steps:
524
+ - uses: actions/checkout@v4
525
+ - uses: ./.github/actions/local
526
+ `;
527
+ function formatPersistResult(result) {
528
+ const lines = [];
529
+ if (result.success) {
530
+ lines.push("Persist Local Summary:");
531
+ lines.push(` Output: ${result.outputPath}`);
532
+ lines.push(` Files copied: ${result.filesCopied}`);
533
+ lines.push(` Files skipped (unchanged): ${result.filesSkipped}`);
534
+ if (result.actTemplateGenerated) lines.push(" Act template files generated");
535
+ } else lines.push(`Persist Local Failed: ${result.error}`);
536
+ return lines.join("\n");
537
+ }
538
+ const PersistLocalServiceLive = Layer.succeed(PersistLocalService, {
539
+ persist: (config, options = {})=>Effect.gen(function*() {
540
+ const cwd = options.cwd ?? process.cwd();
541
+ const outputPath = resolve(cwd, config.persistLocal.path);
542
+ if (!config.persistLocal.enabled) return {
543
+ success: true,
544
+ filesCopied: 0,
545
+ filesSkipped: 0,
546
+ actTemplateGenerated: false,
547
+ outputPath
548
+ };
549
+ yield* Effect["try"]({
550
+ try: ()=>mkdirSync(outputPath, {
551
+ recursive: true
552
+ }),
553
+ catch: (error)=>new PersistLocalError({
554
+ path: outputPath,
555
+ cause: error instanceof Error ? error.message : String(error)
556
+ })
557
+ });
558
+ let totalCopied = 0;
559
+ let totalSkipped = 0;
560
+ const actionYmlSrc = resolve(cwd, "action.yml");
561
+ const actionYmlDest = resolve(outputPath, "action.yml");
562
+ if (existsSync(actionYmlSrc)) {
563
+ const copied = yield* Effect["try"]({
564
+ try: ()=>syncFile(actionYmlSrc, actionYmlDest),
565
+ catch: (error)=>new PersistLocalError({
566
+ path: actionYmlSrc,
567
+ cause: error instanceof Error ? error.message : String(error)
568
+ })
569
+ });
570
+ if (copied) totalCopied++;
571
+ else totalSkipped++;
572
+ } else if (existsSync(actionYmlDest)) rmSync(actionYmlDest, {
573
+ force: true
574
+ });
575
+ const distSrc = resolve(cwd, "dist");
576
+ if (existsSync(distSrc) && statSync(distSrc).isDirectory()) {
577
+ const distStats = yield* Effect["try"]({
578
+ try: ()=>syncDirectory(distSrc, resolve(outputPath, "dist")),
579
+ catch: (error)=>new PersistLocalError({
580
+ path: distSrc,
581
+ cause: error instanceof Error ? error.message : String(error)
582
+ })
583
+ });
584
+ totalCopied += distStats.copied;
585
+ totalSkipped += distStats.skipped;
586
+ }
587
+ const destActionYml = resolve(outputPath, "action.yml");
588
+ yield* validateActionYmlPaths(destActionYml, outputPath);
589
+ let actTemplateGenerated = false;
590
+ if (config.persistLocal.actTemplate) {
591
+ const actrcPath = resolve(cwd, ".actrc");
592
+ const actWorkflowPath = resolve(cwd, ".github/workflows/act-test.yml");
593
+ if (!existsSync(actrcPath)) {
594
+ yield* Effect["try"]({
595
+ try: ()=>writeFileSync(actrcPath, ACTRC_CONTENT, "utf8"),
596
+ catch: (error)=>new PersistLocalError({
597
+ path: actrcPath,
598
+ cause: error instanceof Error ? error.message : String(error)
599
+ })
600
+ });
601
+ actTemplateGenerated = true;
602
+ }
603
+ if (!existsSync(actWorkflowPath)) {
604
+ yield* Effect["try"]({
605
+ try: ()=>{
606
+ mkdirSync(dirname(actWorkflowPath), {
607
+ recursive: true
608
+ });
609
+ writeFileSync(actWorkflowPath, ACT_WORKFLOW_CONTENT, "utf8");
610
+ },
611
+ catch: (error)=>new PersistLocalError({
612
+ path: actWorkflowPath,
613
+ cause: error instanceof Error ? error.message : String(error)
614
+ })
615
+ });
616
+ actTemplateGenerated = true;
617
+ }
618
+ }
619
+ return {
620
+ success: true,
621
+ filesCopied: totalCopied,
622
+ filesSkipped: totalSkipped,
623
+ actTemplateGenerated,
624
+ outputPath
625
+ };
626
+ }),
627
+ formatResult: formatPersistResult
628
+ });
397
629
  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
630
  const ActionInput = Schema.Struct({
399
631
  description: Schema.String,
@@ -642,5 +874,6 @@ const ValidationServiceLive = Layer.effect(ValidationService, Effect.gen(functio
642
874
  const ConfigLayer = ConfigServiceLive;
643
875
  const ValidationLayer = ValidationServiceLive.pipe(Layer.provide(ConfigServiceLive));
644
876
  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 };
877
+ const PersistLocalLayer = PersistLocalServiceLive;
878
+ const AppLayer = Layer.mergeAll(ConfigServiceLive, ValidationLayer, BuildLayer, PersistLocalLayer);
879
+ export { ActionYmlMissing, ActionYmlMissingBase, ActionYmlPathError, ActionYmlPathErrorBase, 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, PersistLocalError, PersistLocalErrorBase, PersistLocalLayer, PersistLocalOptionsSchema, PersistLocalResultSchema, PersistLocalRunnerOptionsSchema, PersistLocalService, Schema, ValidateOptionsSchema, ValidationErrorSchema, ValidationFailed, ValidationFailedBase, ValidationLayer, ValidationOptionsSchema, ValidationResultSchema, ValidationService, ValidationWarningSchema, WriteError, WriteErrorBase, defineConfig, existsSync, mkdirSync, resolve, writeFileSync };
package/README.md CHANGED
@@ -14,6 +14,8 @@ production-ready Node.js 24 actions.
14
14
  - **Schema validation** - Validates `action.yml` against GitHub's official
15
15
  metadata specification
16
16
  - **Single-file bundles** - All dependencies inlined using @vercel/ncc
17
+ - **Local testing** - Auto-persists build output for testing with
18
+ [nektos/act](https://github.com/nektos/act)
17
19
  - **CI-aware** - Strict validation in CI, warnings-only locally
18
20
 
19
21
  ## Quick Start
@@ -126,6 +128,7 @@ runs:
126
128
 
127
129
  - [Getting Started](./docs/getting-started.md) - Installation and first build
128
130
  - [Configuration](./docs/configuration.md) - All configuration options
131
+ - [Local Testing](./docs/local-testing.md) - Testing with nektos/act
129
132
  - [CLI Reference](./docs/cli-reference.md) - Complete command reference
130
133
  - [Architecture](./docs/architecture.md) - How it works internally
131
134
  - [Troubleshooting](./docs/troubleshooting.md) - Common issues and solutions
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { Args, Command, Options } from "@effect/cli";
3
3
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
- import { Layer, AppLayer, BuildService, ValidationService, ConfigService, Effect, existsSync, resolve, mkdirSync, writeFileSync, Console, Option } from "../123.js";
4
+ import { Layer, writeFileSync, AppLayer, BuildService, ConfigService, ValidationService, Effect, existsSync, resolve, mkdirSync, PersistLocalService, Console, Option } from "../123.js";
5
5
  const configOption = Options.file("config").pipe(Options.withAlias("c"), Options.withDescription("Path to configuration file"), Options.optional);
6
6
  const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Suppress non-error output"), Options.withDefault(false));
7
7
  const noValidateOption = Options.boolean("no-validate").pipe(Options.withDescription("Skip validation step"), Options.withDefault(false));
8
- const buildHandler = ({ config, quiet, noValidate })=>Effect.gen(function*() {
8
+ const noPersistOption = Options.boolean("no-persist").pipe(Options.withDescription("Skip persisting build output to local action directory"), Options.withDefault(false));
9
+ const buildHandler = ({ config, quiet, noValidate, noPersist })=>Effect.gen(function*() {
9
10
  const configService = yield* ConfigService;
10
11
  const validationService = yield* ValidationService;
11
12
  const buildService = yield* BuildService;
13
+ const persistLocalService = yield* PersistLocalService;
12
14
  const cwd = process.cwd();
13
15
  if (!quiet) yield* Console.log("Loading configuration...");
14
16
  const loadOptions = Option.isSome(config) ? {
@@ -44,17 +46,25 @@ const buildHandler = ({ config, quiet, noValidate })=>Effect.gen(function*() {
44
46
  yield* Console.log(`\n${buildService.formatResult(buildResult)}`);
45
47
  yield* Console.log("\nBuild completed successfully!");
46
48
  }
49
+ if (!noPersist && configResult.config.persistLocal.enabled) {
50
+ if (!quiet) yield* Console.log("\nPersisting to local action directory...");
51
+ const persistResult = yield* persistLocalService.persist(configResult.config, {
52
+ cwd
53
+ });
54
+ if (!quiet && persistResult.success) yield* Console.log(persistLocalService.formatResult(persistResult));
55
+ }
47
56
  });
48
57
  const buildCommand = Command.make("build", {
49
58
  config: configOption,
50
59
  quiet: quietOption,
51
- noValidate: noValidateOption
60
+ noValidate: noValidateOption,
61
+ noPersist: noPersistOption
52
62
  }, buildHandler);
53
63
  const actionNameArg = Args.text({
54
64
  name: "action-name"
55
65
  }).pipe(Args.withDescription("Name of the GitHub Action (also the output directory)"));
56
66
  const forceOption = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite existing files"), Options.withDefault(false));
57
- const getPackageVersion = ()=>"0.1.4";
67
+ const getPackageVersion = ()=>"0.2.1";
58
68
  const generatePackageJson = (name)=>{
59
69
  const version = getPackageVersion();
60
70
  const pkg = {
@@ -287,7 +297,7 @@ const rootCommand = Command.make("github-action-builder").pipe(Command.withSubco
287
297
  ]));
288
298
  const cli = Command.run(rootCommand, {
289
299
  name: "github-action-builder",
290
- version: "0.1.4"
300
+ version: "0.2.1"
291
301
  });
292
302
  const CliLayer = Layer.merge(AppLayer, NodeContext.layer);
293
303
  const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(CliLayer));
package/index.d.ts CHANGED
@@ -83,6 +83,41 @@ export declare const ActionYmlMissingBase: new <A extends Record<string, any> =
83
83
  readonly _tag: "ActionYmlMissing";
84
84
  } & Readonly<A>;
85
85
 
86
+ /**
87
+ * Error when action.yml runs paths don't resolve correctly in destination.
88
+ *
89
+ * @public
90
+ */
91
+ export declare class ActionYmlPathError extends ActionYmlPathErrorBase<{
92
+ /**
93
+ * The entry type whose path failed validation (main, pre, post).
94
+ */
95
+ readonly entryType: string;
96
+ /**
97
+ * The path specified in action.yml.
98
+ */
99
+ readonly specifiedPath: string;
100
+ /**
101
+ * The expected resolved path.
102
+ */
103
+ readonly expectedPath: string;
104
+ }> {
105
+ }
106
+
107
+ /**
108
+ * Base class for ActionYmlPathError error.
109
+ *
110
+ * @privateRemarks
111
+ * This export is required for api-extractor documentation generation.
112
+ * Effect's Data.TaggedError creates an anonymous base class that must be
113
+ * explicitly exported to avoid "forgotten export" warnings. Do not delete.
114
+ *
115
+ * @internal
116
+ */
117
+ export declare const ActionYmlPathErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
118
+ readonly _tag: "ActionYmlPathError";
119
+ } & Readonly<A>;
120
+
86
121
  /**
87
122
  * Result of action.yml validation.
88
123
  * @public
@@ -200,13 +235,14 @@ export declare const ActionYmlSyntaxErrorBase: new <A extends Record<string, any
200
235
  *
201
236
  * @public
202
237
  */
203
- export declare type AppError = ConfigError | ValidationError | BuildError;
238
+ export declare type AppError = ConfigError | ValidationError | BuildError | PersistError;
204
239
 
205
240
  /**
206
241
  * Combined layer providing all services.
207
242
  *
208
243
  * @remarks
209
- * This layer composes ConfigService, ValidationService, and BuildService.
244
+ * This layer composes ConfigService, ValidationService, BuildService,
245
+ * and PersistLocalService.
210
246
  * Use this when you need access to all services in your Effect program.
211
247
  *
212
248
  * @example Using AppLayer with Effect
@@ -229,7 +265,7 @@ export declare type AppError = ConfigError | ValidationError | BuildError;
229
265
  *
230
266
  * @public
231
267
  */
232
- export declare const AppLayer: Layer.Layer<BuildService | ConfigService | ValidationService, never, never>;
268
+ export declare const AppLayer: Layer.Layer<BuildService | ConfigService | PersistLocalService | ValidationService, never, never>;
233
269
 
234
270
  /**
235
271
  * Union of all build-related errors.
@@ -618,6 +654,11 @@ export declare const ConfigInputSchema: Schema.Struct<{
618
654
  maxBundleSize: Schema.optional<typeof Schema.String>;
619
655
  strict: Schema.optional<typeof Schema.Boolean>;
620
656
  }>>;
657
+ persistLocal: Schema.optional<Schema.Struct<{
658
+ enabled: Schema.optional<typeof Schema.Boolean>;
659
+ path: Schema.optional<typeof Schema.String>;
660
+ actTemplate: Schema.optional<typeof Schema.Boolean>;
661
+ }>>;
621
662
  }>;
622
663
 
623
664
  /**
@@ -771,6 +812,20 @@ export declare const ConfigSchema: Schema.Struct<{
771
812
  /** Treat warnings as errors. Auto-detects from CI when undefined. */
772
813
  strict: Schema.optional<typeof Schema.Boolean>;
773
814
  }>;
815
+ persistLocal: Schema.Struct<{
816
+ /** Enable persisting build output locally. Defaults to true. */
817
+ enabled: Schema.optionalWith<typeof Schema.Boolean, {
818
+ default: () => true;
819
+ }>;
820
+ /** Path for the local action directory, relative to cwd. Defaults to ".github/actions/local". */
821
+ path: Schema.optionalWith<typeof Schema.String, {
822
+ default: () => string;
823
+ }>;
824
+ /** Generate act boilerplate files (.actrc, act-test.yml) if they don't exist. Defaults to true. */
825
+ actTemplate: Schema.optionalWith<typeof Schema.Boolean, {
826
+ default: () => true;
827
+ }>;
828
+ }>;
774
829
  }>;
775
830
 
776
831
  /**
@@ -1221,6 +1276,15 @@ export declare const GitHubActionBuildResultSchema: Schema.Struct<{
1221
1276
  suggestion: Schema.optional<typeof Schema.String>;
1222
1277
  }>>;
1223
1278
  }>>;
1279
+ /** Persist-local result if persist was performed. */
1280
+ persistLocal: Schema.optional<Schema.Struct<{
1281
+ success: typeof Schema.Boolean;
1282
+ filesCopied: typeof Schema.Number;
1283
+ filesSkipped: typeof Schema.Number;
1284
+ actTemplateGenerated: typeof Schema.Boolean;
1285
+ outputPath: typeof Schema.String;
1286
+ error: Schema.optional<typeof Schema.String>;
1287
+ }>>;
1224
1288
  /** Error message if the build or validation failed. */
1225
1289
  error: Schema.optional<typeof Schema.String>;
1226
1290
  }>;
@@ -1271,7 +1335,7 @@ export declare interface GitHubActionOptions {
1271
1335
  * @remarks
1272
1336
  * Advanced option for testing or customizing service implementations.
1273
1337
  */
1274
- layer?: Layer.Layer<ConfigService | ValidationService | BuildService>;
1338
+ layer?: Layer.Layer<ConfigService | ValidationService | BuildService | PersistLocalService>;
1275
1339
  }
1276
1340
 
1277
1341
  /**
@@ -1335,6 +1399,163 @@ export declare const MainEntryMissingBase: new <A extends Record<string, any> =
1335
1399
  readonly _tag: "MainEntryMissing";
1336
1400
  } & Readonly<A>;
1337
1401
 
1402
+ /**
1403
+ * Union of all persist-local-related errors.
1404
+ *
1405
+ * @public
1406
+ */
1407
+ export declare type PersistError = PersistLocalError | ActionYmlPathError;
1408
+
1409
+ /**
1410
+ * Error when persisting build output to local action directory fails.
1411
+ *
1412
+ * @public
1413
+ */
1414
+ export declare class PersistLocalError extends PersistLocalErrorBase<{
1415
+ /**
1416
+ * The path involved in the failure.
1417
+ */
1418
+ readonly path: string;
1419
+ /**
1420
+ * The underlying error message.
1421
+ */
1422
+ readonly cause: string;
1423
+ }> {
1424
+ }
1425
+
1426
+ /**
1427
+ * Base class for PersistLocalError error.
1428
+ *
1429
+ * @privateRemarks
1430
+ * This export is required for api-extractor documentation generation.
1431
+ * Effect's Data.TaggedError creates an anonymous base class that must be
1432
+ * explicitly exported to avoid "forgotten export" warnings. Do not delete.
1433
+ *
1434
+ * @internal
1435
+ */
1436
+ export declare const PersistLocalErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
1437
+ readonly _tag: "PersistLocalError";
1438
+ } & Readonly<A>;
1439
+
1440
+ /**
1441
+ * Layer providing PersistLocalService (no dependencies).
1442
+ *
1443
+ * @remarks
1444
+ * Use this layer when you only need persist-local functionality.
1445
+ *
1446
+ * @public
1447
+ */
1448
+ export declare const PersistLocalLayer: Layer.Layer<PersistLocalService, never, never>;
1449
+
1450
+ /**
1451
+ * Persist-local options for copying build output.
1452
+ *
1453
+ * @public
1454
+ */
1455
+ export declare type PersistLocalOptions = typeof PersistLocalOptionsSchema.Type;
1456
+
1457
+ /**
1458
+ * Schema for persist-local options.
1459
+ *
1460
+ * @remarks
1461
+ * Controls automatic copying of build output to a local action directory
1462
+ * for testing with nektos/act.
1463
+ *
1464
+ * @internal
1465
+ */
1466
+ export declare const PersistLocalOptionsSchema: Schema.Struct<{
1467
+ /** Enable persisting build output locally. Defaults to true. */
1468
+ enabled: Schema.optionalWith<typeof Schema.Boolean, {
1469
+ default: () => true;
1470
+ }>;
1471
+ /** Path for the local action directory, relative to cwd. Defaults to ".github/actions/local". */
1472
+ path: Schema.optionalWith<typeof Schema.String, {
1473
+ default: () => string;
1474
+ }>;
1475
+ /** Generate act boilerplate files (.actrc, act-test.yml) if they don't exist. Defaults to true. */
1476
+ actTemplate: Schema.optionalWith<typeof Schema.Boolean, {
1477
+ default: () => true;
1478
+ }>;
1479
+ }>;
1480
+
1481
+ /**
1482
+ * Result of the persist-local operation.
1483
+ * @public
1484
+ */
1485
+ export declare type PersistLocalResult = typeof PersistLocalResultSchema.Type;
1486
+
1487
+ /**
1488
+ * Result of the persist-local operation.
1489
+ * @internal
1490
+ */
1491
+ export declare const PersistLocalResultSchema: Schema.Struct<{
1492
+ /** Whether the operation completed successfully. */
1493
+ success: typeof Schema.Boolean;
1494
+ /** Number of files copied (changed or new). */
1495
+ filesCopied: typeof Schema.Number;
1496
+ /** Number of files skipped (unchanged). */
1497
+ filesSkipped: typeof Schema.Number;
1498
+ /** Whether act template files were generated. */
1499
+ actTemplateGenerated: typeof Schema.Boolean;
1500
+ /** Output path where files were persisted. */
1501
+ outputPath: typeof Schema.String;
1502
+ /** Error message if failed. */
1503
+ error: Schema.optional<typeof Schema.String>;
1504
+ }>;
1505
+
1506
+ /**
1507
+ * Options for the persist operation.
1508
+ * @public
1509
+ */
1510
+ export declare type PersistLocalRunnerOptions = typeof PersistLocalRunnerOptionsSchema.Type;
1511
+
1512
+ /**
1513
+ * Options for the persist operation.
1514
+ * @internal
1515
+ */
1516
+ export declare const PersistLocalRunnerOptionsSchema: Schema.Struct<{
1517
+ /** Working directory. Accepts string. */
1518
+ cwd: Schema.optional<typeof Schema.String>;
1519
+ }>;
1520
+
1521
+ /**
1522
+ * PersistLocalService interface for copying build output locally.
1523
+ *
1524
+ * @remarks
1525
+ * This service handles:
1526
+ * - Smart-syncing action.yml and dist/ to a local action directory
1527
+ * - Hash-based comparison to avoid unnecessary copies
1528
+ * - Removing stale files in the destination
1529
+ * - Validating action.yml runs paths resolve in the destination
1530
+ * - Generating act boilerplate files
1531
+ *
1532
+ * @public
1533
+ */
1534
+ export declare interface PersistLocalService {
1535
+ /**
1536
+ * Persist build output to the local action directory.
1537
+ *
1538
+ * @param config - Configuration with persistLocal options
1539
+ * @param options - Runner options (cwd, etc.)
1540
+ * @returns Effect that resolves to persist result
1541
+ */
1542
+ readonly persist: (config: Config, options?: PersistLocalRunnerOptions) => Effect.Effect<PersistLocalResult, PersistLocalError | ActionYmlPathError>;
1543
+ /**
1544
+ * Format persist result for display.
1545
+ *
1546
+ * @param result - Persist result to format
1547
+ * @returns Formatted string for terminal output
1548
+ */
1549
+ readonly formatResult: (result: PersistLocalResult) => string;
1550
+ }
1551
+
1552
+ /**
1553
+ * PersistLocalService tag for dependency injection.
1554
+ *
1555
+ * @public
1556
+ */
1557
+ export declare const PersistLocalService: Context.Tag<PersistLocalService, PersistLocalService>;
1558
+
1338
1559
  /**
1339
1560
  * Options for validation.
1340
1561
  * @public
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { Schema, AppLayer, BuildService, ConfigService, ValidationService, Effect, BuildResultSchema, ValidationResultSchema, ManagedRuntime, defineConfig } from "./123.js";
1
+ import { Schema, AppLayer, BuildService, ConfigService, ValidationService, Effect, BuildResultSchema, PersistLocalResultSchema, ValidationResultSchema, ManagedRuntime, defineConfig, PersistLocalService } from "./123.js";
2
2
  const GitHubActionBuildResultSchema = Schema.Struct({
3
3
  success: Schema.Boolean,
4
4
  build: Schema.optional(BuildResultSchema),
5
5
  validation: Schema.optional(ValidationResultSchema),
6
+ persistLocal: Schema.optional(PersistLocalResultSchema),
6
7
  error: Schema.optional(Schema.String)
7
8
  });
8
9
  class GitHubAction {
@@ -94,14 +95,26 @@ class GitHubAction {
94
95
  error: buildResult.error ?? "Build failed"
95
96
  };
96
97
  }
98
+ let persistLocalResult;
99
+ if (config.persistLocal.enabled) {
100
+ const persistProgram = Effect.gen(function*() {
101
+ const persistLocalService = yield* PersistLocalService;
102
+ return yield* persistLocalService.persist(config, {
103
+ cwd
104
+ });
105
+ });
106
+ persistLocalResult = await this.runtime.runPromise(persistProgram);
107
+ }
97
108
  if (validationResult) return {
98
109
  success: true,
99
110
  build: buildResult,
100
- validation: validationResult
111
+ validation: validationResult,
112
+ persistLocal: persistLocalResult
101
113
  };
102
114
  return {
103
115
  success: true,
104
- build: buildResult
116
+ build: buildResult,
117
+ persistLocal: persistLocalResult
105
118
  };
106
119
  } catch (error) {
107
120
  return {
@@ -114,5 +127,5 @@ class GitHubAction {
114
127
  await this.runtime.dispose();
115
128
  }
116
129
  }
117
- 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, DetectEntriesResultSchema, DetectedEntrySchema, EntriesSchema, EntryFileMissing, EntryFileMissingBase, LoadConfigOptionsSchema, MainEntryMissing, MainEntryMissingBase, ValidateOptionsSchema, ValidationErrorSchema, ValidationFailed, ValidationFailedBase, ValidationLayer, ValidationOptionsSchema, ValidationResultSchema, ValidationService, ValidationWarningSchema, WriteError, WriteErrorBase, defineConfig } from "./123.js";
130
+ export { ActionYmlMissing, ActionYmlMissingBase, ActionYmlPathError, ActionYmlPathErrorBase, 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, DetectEntriesResultSchema, DetectedEntrySchema, EntriesSchema, EntryFileMissing, EntryFileMissingBase, LoadConfigOptionsSchema, MainEntryMissing, MainEntryMissingBase, PersistLocalError, PersistLocalErrorBase, PersistLocalLayer, PersistLocalOptionsSchema, PersistLocalResultSchema, PersistLocalRunnerOptionsSchema, PersistLocalService, ValidateOptionsSchema, ValidationErrorSchema, ValidationFailed, ValidationFailedBase, ValidationLayer, ValidationOptionsSchema, ValidationResultSchema, ValidationService, ValidationWarningSchema, WriteError, WriteErrorBase, defineConfig } from "./123.js";
118
131
  export { GitHubAction, GitHubActionBuildResultSchema };
package/package.json CHANGED
@@ -1,86 +1,86 @@
1
1
  {
2
- "name": "@savvy-web/github-action-builder",
3
- "version": "0.1.4",
4
- "private": false,
5
- "description": "A zero-config build tool for creating GitHub Actions from TypeScript. Bundles with @vercel/ncc, validates action.yml against GitHub's schema, and outputs production-ready Node.js 24 actions.",
6
- "keywords": [
7
- "github-actions",
8
- "github",
9
- "actions",
10
- "ncc",
11
- "vercel-ncc",
12
- "bundler",
13
- "typescript",
14
- "node24",
15
- "cli",
16
- "build-tool",
17
- "action-builder",
18
- "effect-ts",
19
- "esm"
20
- ],
21
- "homepage": "https://github.com/savvy-web/github-action-builder#readme",
22
- "bugs": {
23
- "url": "https://github.com/savvy-web/github-action-builder/issues"
24
- },
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/savvy-web/github-action-builder.git"
28
- },
29
- "license": "MIT",
30
- "author": {
31
- "name": "C. Spencer Beggs",
32
- "email": "spencer@savvyweb.systems",
33
- "url": "https://savvyweb.systems"
34
- },
35
- "type": "module",
36
- "exports": {
37
- ".": {
38
- "types": "./index.d.ts",
39
- "import": "./index.js"
40
- }
41
- },
42
- "bin": {
43
- "github-action-builder": "./bin/github-action-builder.js"
44
- },
45
- "dependencies": {
46
- "@effect/cli": "^0.73.1",
47
- "@effect/platform": "^0.94.2",
48
- "@effect/platform-node": "^0.104.1",
49
- "@effect/printer": "^0.47.0",
50
- "@effect/printer-ansi": "^0.47.0",
51
- "@effect/typeclass": "^0.38.0",
52
- "@vercel/ncc": "^0.38.4",
53
- "effect": "^3.19.15",
54
- "picocolors": "^1.1.1",
55
- "yaml": "^2.8.2"
56
- },
57
- "peerDependencies": {
58
- "@types/node": "^25.2.0",
59
- "@typescript/native-preview": "^7.0.0-dev.20260124.1",
60
- "typescript": "^5.9.3"
61
- },
62
- "peerDependenciesMeta": {
63
- "@types/node": {
64
- "optional": false
65
- },
66
- "@typescript/native-preview": {
67
- "optional": false
68
- },
69
- "typescript": {
70
- "optional": false
71
- }
72
- },
73
- "files": [
74
- "!github-action-builder.api.json",
75
- "!tsconfig.json",
76
- "!tsdoc.json",
77
- "123.js",
78
- "LICENSE",
79
- "README.md",
80
- "bin/github-action-builder.js",
81
- "index.d.ts",
82
- "index.js",
83
- "package.json",
84
- "tsdoc-metadata.json"
85
- ]
86
- }
2
+ "name": "@savvy-web/github-action-builder",
3
+ "version": "0.2.1",
4
+ "private": false,
5
+ "description": "A zero-config build tool for creating GitHub Actions from TypeScript. Bundles with @vercel/ncc, validates action.yml against GitHub's schema, and outputs production-ready Node.js 24 actions.",
6
+ "keywords": [
7
+ "github-actions",
8
+ "github",
9
+ "actions",
10
+ "ncc",
11
+ "vercel-ncc",
12
+ "bundler",
13
+ "typescript",
14
+ "node24",
15
+ "cli",
16
+ "build-tool",
17
+ "action-builder",
18
+ "effect-ts",
19
+ "esm"
20
+ ],
21
+ "homepage": "https://github.com/savvy-web/github-action-builder#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/savvy-web/github-action-builder/issues"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/savvy-web/github-action-builder.git"
28
+ },
29
+ "license": "MIT",
30
+ "author": {
31
+ "name": "C. Spencer Beggs",
32
+ "email": "spencer@savvyweb.systems",
33
+ "url": "https://savvyweb.systems"
34
+ },
35
+ "type": "module",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./index.d.ts",
39
+ "import": "./index.js"
40
+ }
41
+ },
42
+ "bin": {
43
+ "github-action-builder": "./bin/github-action-builder.js"
44
+ },
45
+ "dependencies": {
46
+ "@effect/cli": "^0.73.1",
47
+ "@effect/platform": "^0.94.2",
48
+ "@effect/platform-node": "^0.104.1",
49
+ "@effect/printer": "^0.47.0",
50
+ "@effect/printer-ansi": "^0.47.0",
51
+ "@effect/typeclass": "^0.38.0",
52
+ "@vercel/ncc": "^0.38.4",
53
+ "effect": "^3.19.19",
54
+ "picocolors": "^1.1.1",
55
+ "yaml": "^2.8.2"
56
+ },
57
+ "peerDependencies": {
58
+ "@types/node": "^25.2.0",
59
+ "@typescript/native-preview": "^7.0.0-dev.20260124.1",
60
+ "typescript": "^5.9.3"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@types/node": {
64
+ "optional": false
65
+ },
66
+ "@typescript/native-preview": {
67
+ "optional": false
68
+ },
69
+ "typescript": {
70
+ "optional": false
71
+ }
72
+ },
73
+ "files": [
74
+ "!github-action-builder.api.json",
75
+ "!tsconfig.json",
76
+ "!tsdoc.json",
77
+ "123.js",
78
+ "LICENSE",
79
+ "README.md",
80
+ "bin/github-action-builder.js",
81
+ "index.d.ts",
82
+ "index.js",
83
+ "package.json",
84
+ "tsdoc-metadata.json"
85
+ ]
86
+ }
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.56.3"
8
+ "packageVersion": "7.57.6"
9
9
  }
10
10
  ]
11
11
  }