@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 @@
1
+ export { };
@@ -1,317 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import { Args, Command, Options } from "@effect/cli";
2
+ import { AppLayer } from "../layers/app.js";
3
+ import { buildCommand } from "../cli/commands/build.js";
4
+ import { initCommand } from "../cli/commands/init.js";
5
+ import { validateCommand } from "../cli/commands/validate.js";
6
+ import "../cli/commands/index.js";
7
+ import { Cause, Console, Effect, Layer } from "effect";
8
+ import { Command } from "@effect/cli";
3
9
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
- import { Cause, Console, Effect, Layer, Option } from "effect";
5
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
6
- import { resolve } from "node:path";
7
- import { PersistLocalService, ValidationService, AppLayer, BuildService, ConfigService, ValidationFailed, BuildFailed } from "../948.js";
8
- const configOption = Options.file("config").pipe(Options.withAlias("c"), Options.withDescription("Path to configuration file"), Options.optional);
9
- const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Suppress non-error output"), Options.withDefault(false));
10
- const noValidateOption = Options.boolean("no-validate").pipe(Options.withDescription("Skip validation step"), Options.withDefault(false));
11
- const noPersistOption = Options.boolean("no-persist").pipe(Options.withDescription("Skip persisting build output to local action directory"), Options.withDefault(false));
12
- const buildHandler = ({ config, quiet, noValidate, noPersist })=>Effect.gen(function*() {
13
- const configService = yield* ConfigService;
14
- const validationService = yield* ValidationService;
15
- const buildService = yield* BuildService;
16
- const persistLocalService = yield* PersistLocalService;
17
- const cwd = process.cwd();
18
- if (!quiet) yield* Console.log("Loading configuration...");
19
- const loadOptions = Option.isSome(config) ? {
20
- cwd,
21
- configPath: config.value
22
- } : {
23
- cwd
24
- };
25
- const configResult = yield* configService.load(loadOptions);
26
- if (!quiet) if (configResult.usingDefaults) yield* Console.log(" Using default configuration");
27
- else yield* Console.log(` Found ${configResult.configPath}`);
28
- if (!noValidate) {
29
- if (!quiet) yield* Console.log("\nValidating...");
30
- const validationResult = yield* validationService.validate(configResult.config, {
31
- cwd
32
- });
33
- if (!validationResult.valid) {
34
- yield* Console.error(`\n${validationService.formatResult(validationResult)}`);
35
- return yield* new ValidationFailed({
36
- errorCount: validationResult.errors.length,
37
- warningCount: validationResult.warnings.length,
38
- message: "Validation failed"
39
- });
40
- }
41
- if (!quiet && validationResult.warnings.length > 0) yield* Console.log(validationService.formatResult(validationResult));
42
- else if (!quiet) yield* Console.log(" All checks passed");
43
- }
44
- if (!quiet) yield* Console.log("\nBuilding...");
45
- const buildResult = yield* buildService.build(configResult.config, {
46
- cwd
47
- });
48
- if (!buildResult.success) {
49
- yield* Console.error(`\nBuild failed: ${buildResult.error}`);
50
- return yield* new BuildFailed({
51
- message: buildResult.error ?? "One or more entries failed to build",
52
- failedEntries: buildResult.entries.filter((e)=>!e.success).length
53
- });
54
- }
55
- if (!quiet) {
56
- yield* Console.log(`\n${buildService.formatResult(buildResult)}`);
57
- yield* Console.log("\nBuild completed successfully!");
58
- }
59
- if (!noPersist && configResult.config.persistLocal.enabled) {
60
- if (!quiet) yield* Console.log("\nPersisting to local action directory...");
61
- const persistResult = yield* persistLocalService.persist(configResult.config, {
62
- cwd
63
- });
64
- if (!quiet && persistResult.success) yield* Console.log(persistLocalService.formatResult(persistResult));
65
- }
66
- });
67
- const buildCommand = Command.make("build", {
68
- config: configOption,
69
- quiet: quietOption,
70
- noValidate: noValidateOption,
71
- noPersist: noPersistOption
72
- }, buildHandler);
73
- const actionNameArg = Args.text({
74
- name: "action-name"
75
- }).pipe(Args.withDescription("Name of the GitHub Action (also the output directory)"));
76
- const forceOption = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite existing files"), Options.withDefault(false));
77
- const getPackageVersion = ()=>"0.7.3";
78
- const generatePackageJson = (name)=>{
79
- const version = getPackageVersion();
80
- const pkg = {
81
- name,
82
- version: "0.0.0",
83
- private: true,
84
- type: "module",
85
- scripts: {
86
- build: "github-action-builder build",
87
- validate: "github-action-builder validate",
88
- typecheck: "tsc --noEmit"
89
- },
90
- devDependencies: {
91
- "@savvy-web/github-action-builder": `^${version}`,
92
- typescript: "^5.9.3"
93
- },
94
- dependencies: {
95
- "@actions/core": "^1.11.1",
96
- "@actions/github": "^6.0.0"
97
- }
98
- };
99
- return `${JSON.stringify(pkg, null, 2)}\n`;
100
- };
101
- const generateTsConfig = ()=>{
102
- const config = {
103
- compilerOptions: {
104
- target: "ES2022",
105
- module: "NodeNext",
106
- moduleResolution: "NodeNext",
107
- strict: true,
108
- esModuleInterop: true,
109
- skipLibCheck: true,
110
- forceConsistentCasingInFileNames: true,
111
- declaration: false,
112
- outDir: "dist",
113
- rootDir: "src"
114
- },
115
- include: [
116
- "src/**/*.ts",
117
- "action.config.ts"
118
- ],
119
- exclude: [
120
- "node_modules",
121
- "dist"
122
- ]
123
- };
124
- return `${JSON.stringify(config, null, 2)}\n`;
125
- };
126
- const generateActionYml = (name)=>`name: "${name}"
127
- description: "A GitHub Action built with @savvy-web/github-action-builder"
128
- author: ""
129
10
 
130
- inputs:
131
- example-input:
132
- description: "An example input"
133
- required: false
134
- default: "hello"
135
-
136
- outputs:
137
- example-output:
138
- description: "An example output"
139
-
140
- runs:
141
- using: "node24"
142
- main: "dist/main.js"
143
- pre: "dist/pre.js"
144
- post: "dist/post.js"
145
-
146
- branding:
147
- icon: "zap"
148
- color: "blue"
149
- `;
150
- const defaultConfig = `import { GitHubAction } from "@savvy-web/github-action-builder";
151
-
152
- export default GitHubAction.create({
153
- // Entry points are auto-detected from src/main.ts, src/pre.ts, src/post.ts
154
- // Uncomment to customize:
155
- // entries: {
156
- // main: "src/main.ts",
157
- // pre: "src/pre.ts",
158
- // post: "src/post.ts",
159
- // },
160
-
161
- // Build options
162
- // build: {
163
- // minify: true,
164
- // sourceMap: false,
165
- // },
166
-
167
- // Validation options
168
- // validation: {
169
- // strict: undefined, // Auto-detects CI environment
170
- // },
171
- });
172
- `;
173
- const mainTemplate = `import * as core from "@actions/core";
174
-
175
- async function run(): Promise<void> {
176
- try {
177
- const input = core.getInput("example-input");
178
- core.info(\`Running main action with input: \${input}\`);
179
-
180
- // Your main action logic goes here
181
-
182
- core.setOutput("example-output", "success");
183
- } catch (error) {
184
- if (error instanceof Error) {
185
- core.setFailed(error.message);
186
- } else {
187
- core.setFailed("An unexpected error occurred");
188
- }
189
- }
190
- }
191
-
192
- run();
193
- `;
194
- const preTemplate = `import * as core from "@actions/core";
195
-
196
- async function run(): Promise<void> {
197
- try {
198
- core.info("Running pre action...");
199
-
200
- // Your pre-action setup logic goes here
201
- // This runs before the main action
202
- } catch (error) {
203
- if (error instanceof Error) {
204
- core.setFailed(error.message);
205
- } else {
206
- core.setFailed("An unexpected error occurred");
207
- }
208
- }
209
- }
210
-
211
- run();
212
- `;
213
- const postTemplate = `import * as core from "@actions/core";
214
-
215
- async function run(): Promise<void> {
216
- try {
217
- core.info("Running post action...");
218
-
219
- // Your post-action cleanup logic goes here
220
- // This runs after the main action, even if it fails
221
- } catch (error) {
222
- if (error instanceof Error) {
223
- core.warning(error.message);
224
- } else {
225
- core.warning("An unexpected error occurred during cleanup");
226
- }
227
- }
228
- }
229
-
230
- run();
231
- `;
232
- const writeFile = (path, content, force, createdFiles, skippedFiles)=>{
233
- if (existsSync(path) && !force) return void skippedFiles.push(path);
234
- writeFileSync(path, content, "utf-8");
235
- createdFiles.push(path);
236
- };
237
- const initHandler = ({ actionName, force })=>Effect.gen(function*() {
238
- const cwd = process.cwd();
239
- const projectDir = resolve(cwd, actionName);
240
- const createdFiles = [];
241
- const skippedFiles = [];
242
- if (existsSync(projectDir) && !force) {
243
- yield* Console.error(`Directory already exists: ${actionName}`);
244
- yield* Console.error("Use --force to overwrite existing files.");
245
- return yield* Effect.fail(new Error("Directory exists"));
246
- }
247
- if (!existsSync(projectDir)) mkdirSync(projectDir, {
248
- recursive: true
249
- });
250
- const srcDir = resolve(projectDir, "src");
251
- if (!existsSync(srcDir)) mkdirSync(srcDir, {
252
- recursive: true
253
- });
254
- writeFile(resolve(projectDir, "package.json"), generatePackageJson(actionName), force, createdFiles, skippedFiles);
255
- writeFile(resolve(projectDir, "tsconfig.json"), generateTsConfig(), force, createdFiles, skippedFiles);
256
- writeFile(resolve(projectDir, "action.yml"), generateActionYml(actionName), force, createdFiles, skippedFiles);
257
- writeFile(resolve(projectDir, "action.config.ts"), defaultConfig, force, createdFiles, skippedFiles);
258
- writeFile(resolve(srcDir, "main.ts"), mainTemplate, force, createdFiles, skippedFiles);
259
- writeFile(resolve(srcDir, "pre.ts"), preTemplate, force, createdFiles, skippedFiles);
260
- writeFile(resolve(srcDir, "post.ts"), postTemplate, force, createdFiles, skippedFiles);
261
- yield* Console.log(`Created ${actionName}/`);
262
- if (createdFiles.length > 0) for (const file of createdFiles)yield* Console.log(` ${file.replace(`${projectDir}/`, "")}`);
263
- if (skippedFiles.length > 0) {
264
- yield* Console.log("\nSkipped existing files (use --force to overwrite):");
265
- for (const file of skippedFiles)yield* Console.log(` ${file.replace(`${projectDir}/`, "")}`);
266
- }
267
- yield* Console.log("\nNext steps:");
268
- yield* Console.log(` cd ${actionName}`);
269
- yield* Console.log(" npm install");
270
- yield* Console.log(" npm run build");
271
- });
272
- const initCommand = Command.make("init", {
273
- actionName: actionNameArg,
274
- force: forceOption
275
- }, initHandler);
276
- const validateHandler = ({ config, quiet })=>Effect.gen(function*() {
277
- const configService = yield* ConfigService;
278
- const validationService = yield* ValidationService;
279
- const cwd = process.cwd();
280
- if (!quiet) yield* Console.log("Loading configuration...");
281
- const loadOptions = Option.isSome(config) ? {
282
- cwd,
283
- configPath: config.value
284
- } : {
285
- cwd
286
- };
287
- const configResult = yield* configService.load(loadOptions);
288
- if (!quiet) if (configResult.usingDefaults) yield* Console.log(" Using default configuration");
289
- else yield* Console.log(` Found ${configResult.configPath}`);
290
- if (!quiet) yield* Console.log("\nValidating...");
291
- const validationResult = yield* validationService.validate(configResult.config, {
292
- cwd
293
- });
294
- yield* Console.log(`\n${validationService.formatResult(validationResult)}`);
295
- if (!validationResult.valid) return yield* Effect.fail(new Error("Validation failed"));
296
- if (!quiet) yield* Console.log("\nValidation completed successfully!");
297
- });
298
- const validateCommand = Command.make("validate", {
299
- config: configOption,
300
- quiet: quietOption
301
- }, validateHandler);
11
+ //#region src/cli/index.ts
12
+ /* v8 ignore start - CLI entry point requires integration testing */
13
+ /**
14
+ * GitHub Action Builder CLI entry point.
15
+ */
16
+ /**
17
+ * Root command for the CLI.
18
+ */
302
19
  const rootCommand = Command.make("github-action-builder").pipe(Command.withSubcommands([
303
- buildCommand,
304
- validateCommand,
305
- initCommand
20
+ buildCommand,
21
+ validateCommand,
22
+ initCommand
306
23
  ]));
24
+ /**
25
+ * CLI application configuration.
26
+ */
307
27
  const cli = Command.run(rootCommand, {
308
- name: "github-action-builder",
309
- version: "0.7.3"
28
+ name: "github-action-builder",
29
+ version: process.env.__PACKAGE_VERSION__ ?? "0.0.0"
310
30
  });
31
+ /**
32
+ * Combined layer: AppLayer + NodeContext for CLI.
33
+ */
311
34
  const CliLayer = Layer.merge(AppLayer, NodeContext.layer);
312
- const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(CliLayer), Effect.catchAllCause((cause)=>{
313
- const defects = Cause.defects(cause);
314
- if (defects.length > 0) return Console.error(Cause.pretty(cause)).pipe(Effect.andThen(Effect.failCause(cause)));
315
- return Effect.failCause(cause);
35
+ /**
36
+ * Run the CLI with full error cause rendering.
37
+ *
38
+ * Expected typed errors (ValidationFailed, BuildFailed, etc.) are already
39
+ * printed by command handlers — they just need to exit with failure.
40
+ * Unexpected defects get full Cause.pretty rendering with stack traces.
41
+ */
42
+ const main = Effect.suspend(() => cli(process.argv)).pipe(Effect.provide(CliLayer), Effect.catchAllCause((cause) => {
43
+ if (Cause.defects(cause).length > 0) return Console.error(Cause.pretty(cause)).pipe(Effect.andThen(Effect.failCause(cause)));
44
+ return Effect.failCause(cause);
316
45
  }));
317
46
  NodeRuntime.runMain(main);
47
+
48
+ //#endregion
49
+ export { };
@@ -0,0 +1,91 @@
1
+ import { BuildFailed, ValidationFailed } from "../../errors.js";
2
+ import { BuildService } from "../../services/build.js";
3
+ import { ConfigService } from "../../services/config.js";
4
+ import { PersistLocalService } from "../../services/persist-local.js";
5
+ import { ValidationService } from "../../services/validation.js";
6
+ import { Console, Effect, Option } from "effect";
7
+ import { Command, Options } from "@effect/cli";
8
+
9
+ //#region src/cli/commands/build.ts
10
+ /* v8 ignore start - CLI commands require integration testing */
11
+ /**
12
+ * Build command for GitHub Action Builder CLI.
13
+ */
14
+ /**
15
+ * Config file option - shared across commands.
16
+ */
17
+ const configOption = Options.file("config").pipe(Options.withAlias("c"), Options.withDescription("Path to configuration file"), Options.optional);
18
+ /**
19
+ * Quiet mode option - suppress non-error output.
20
+ */
21
+ const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Suppress non-error output"), Options.withDefault(false));
22
+ /**
23
+ * Skip validation option.
24
+ */
25
+ const noValidateOption = Options.boolean("no-validate").pipe(Options.withDescription("Skip validation step"), Options.withDefault(false));
26
+ /**
27
+ * Skip persist-local option.
28
+ */
29
+ const noPersistOption = Options.boolean("no-persist").pipe(Options.withDescription("Skip persisting build output to local action directory"), Options.withDefault(false));
30
+ /**
31
+ * Build command handler using Effect services.
32
+ */
33
+ const buildHandler = ({ config, quiet, noValidate, noPersist }) => Effect.gen(function* () {
34
+ const configService = yield* ConfigService;
35
+ const validationService = yield* ValidationService;
36
+ const buildService = yield* BuildService;
37
+ const persistLocalService = yield* PersistLocalService;
38
+ const cwd = process.cwd();
39
+ if (!quiet) yield* Console.log("Loading configuration...");
40
+ const loadOptions = Option.isSome(config) ? {
41
+ cwd,
42
+ configPath: config.value
43
+ } : { cwd };
44
+ const configResult = yield* configService.load(loadOptions);
45
+ if (!quiet) if (configResult.usingDefaults) yield* Console.log(" Using default configuration");
46
+ else yield* Console.log(` Found ${configResult.configPath}`);
47
+ if (!noValidate) {
48
+ if (!quiet) yield* Console.log("\nValidating...");
49
+ const validationResult = yield* validationService.validate(configResult.config, { cwd });
50
+ if (!validationResult.valid) {
51
+ yield* Console.error(`\n${validationService.formatResult(validationResult)}`);
52
+ return yield* new ValidationFailed({
53
+ errorCount: validationResult.errors.length,
54
+ warningCount: validationResult.warnings.length,
55
+ message: "Validation failed"
56
+ });
57
+ }
58
+ if (!quiet && validationResult.warnings.length > 0) yield* Console.log(validationService.formatResult(validationResult));
59
+ else if (!quiet) yield* Console.log(" All checks passed");
60
+ }
61
+ if (!quiet) yield* Console.log("\nBuilding...");
62
+ const buildResult = yield* buildService.build(configResult.config, { cwd });
63
+ if (!buildResult.success) {
64
+ yield* Console.error(`\nBuild failed: ${buildResult.error}`);
65
+ return yield* new BuildFailed({
66
+ message: buildResult.error ?? "One or more entries failed to build",
67
+ failedEntries: buildResult.entries.filter((e) => !e.success).length
68
+ });
69
+ }
70
+ if (!quiet) {
71
+ yield* Console.log(`\n${buildService.formatResult(buildResult)}`);
72
+ yield* Console.log("\nBuild completed successfully!");
73
+ }
74
+ if (!noPersist && configResult.config.persistLocal.enabled) {
75
+ if (!quiet) yield* Console.log("\nPersisting to local action directory...");
76
+ const persistResult = yield* persistLocalService.persist(configResult.config, { cwd });
77
+ if (!quiet && persistResult.success) yield* Console.log(persistLocalService.formatResult(persistResult));
78
+ }
79
+ });
80
+ /**
81
+ * Build command - bundles GitHub Action entry points.
82
+ */
83
+ const buildCommand = Command.make("build", {
84
+ config: configOption,
85
+ quiet: quietOption,
86
+ noValidate: noValidateOption,
87
+ noPersist: noPersistOption
88
+ }, buildHandler);
89
+
90
+ //#endregion
91
+ export { buildCommand, configOption, noValidateOption, quietOption };
@@ -0,0 +1,5 @@
1
+ import { buildCommand, configOption, noValidateOption, quietOption } from "./build.js";
2
+ import { initCommand } from "./init.js";
3
+ import { validateCommand } from "./validate.js";
4
+
5
+ export { };