{"id":44461053,"url":"https://github.com/dodok8/gaji","last_synced_at":"2026-02-18T08:00:45.341Z","repository":{"id":337875517,"uuid":"1155503995","full_name":"dodok8/gaji","owner":"dodok8","description":"Type-safe GitHub Actions workflows in TypeScript, GitHub Actions Justified Improvements","archived":false,"fork":false,"pushed_at":"2026-02-16T05:10:31.000Z","size":3039,"stargazers_count":18,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-17T10:54:31.943Z","etag":null,"topics":["cd","ci","cli","github-actions","rust","typescript"],"latest_commit_sha":null,"homepage":"https://gaji.gaebalgom.work","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dodok8.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-11T15:34:28.000Z","updated_at":"2026-02-17T05:48:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dodok8/gaji","commit_stats":null,"previous_names":["dodok8/gha-ts","dodok8/gaji"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/dodok8/gaji","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dodok8%2Fgaji","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dodok8%2Fgaji/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dodok8%2Fgaji/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dodok8%2Fgaji/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dodok8","download_url":"https://codeload.github.com/dodok8/gaji/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dodok8%2Fgaji/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29573392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T07:57:19.261Z","status":"ssl_error","status_checked_at":"2026-02-18T07:57:18.820Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cd","ci","cli","github-actions","rust","typescript"],"created_at":"2026-02-12T19:04:56.360Z","updated_at":"2026-02-18T08:00:45.290Z","avatar_url":"https://github.com/dodok8.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"logo.png\" alt=\"gaji logo\" width=\"200\"/\u003e\n  \u003ch1\u003egaji\u003c/h1\u003e\n  \u003cp\u003eType-safe GitHub Actions workflows in TypeScript\u003c/p\u003e\n  \u003cp\u003e\u003cem\u003eGitHub Actions Justified Improvements\u003c/em\u003e\u003c/p\u003e\n  \u003cp\u003e🍆 Named after the Korean word \"가지\" (gaji, eggplant) - a versatile vegetable\u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://crates.io/crates/gaji\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/gaji\" alt=\"crates.io\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/gaji\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/gaji\" alt=\"npm\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n## Overview\n\n`gaji` is a CLI tool that allows developers to write GitHub Actions workflows in TypeScript with full type safety, then compile them to YAML. It automatically fetches `action.yml` definitions and generates typed wrappers, providing autocomplete and type checking for action inputs and outputs.\n\n## Features\n\n- TypeScript-based workflow authoring with full type safety\n- Automatic type generation from `action.yml` files\n- Composite action and reusable workflow support\n- File watching for development (`--watch`)\n- Built-in QuickJS execution with `npx tsx` fallback\n- GitHub Enterprise support\n- Single binary distribution (Rust)\n\n## Installation\n\n### From npm\n\n```bash\nnpm install -D gaji\n```\n\n### From cargo\n\n```bash\ncargo install gaji\n```\n\n## Quick Start\n\n```bash\n# Initialize a new project (creates workflows/ and generated/ directories)\ngaji init\n\n# Add actions and generate types\ngaji add actions/checkout@v5\ngaji add actions/setup-node@v4\n\n# Run a one-time dev scan to generate types\ngaji dev\n\n# Build workflows to YAML\ngaji build\n```\n\n## Usage\n\n### Writing Workflows\n\nCreate TypeScript files in the `workflows/` directory:\n\n```typescript\nimport { getAction, Job, Workflow } from \"../generated/index.js\";\n\nconst checkout = getAction(\"actions/checkout@v5\");\nconst setupNode = getAction(\"actions/setup-node@v4\");\n\nconst build = new Job(\"ubuntu-latest\")\n  .addStep(checkout({\n    name: \"Checkout code\",\n    with: { \"fetch-depth\": 1 },\n  }))\n  .addStep(setupNode({\n    with: { \"node-version\": \"22\" },\n  }))\n  .addStep({ name: \"Install dependencies\", run: \"npm ci\" })\n  .addStep({ name: \"Run tests\", run: \"npm test\" });\n\nconst workflow = new Workflow({\n  name: \"CI\",\n  on: {\n    push: { branches: [\"main\"] },\n    pull_request: { branches: [\"main\"] },\n  },\n}).addJob(\"build\", build);\n\nworkflow.build(\"ci\");\n```\n\nRun `gaji build` and it outputs `.github/workflows/ci.yml`.\n\n### Recommended Development Workflow\n\n1. **Start watch mode**:\n   ```bash\n   gaji dev --watch\n   ```\n   Leave this running in a terminal. It will automatically generate types when you add new actions.\n\n2. **Edit your TypeScript workflows** in `workflows/*.ts`:\n   - Add or modify steps\n   - Use `getAction()` with full type safety\n   - Types are automatically generated for new actions\n\n3. **Build to YAML**:\n   ```bash\n   gaji build\n   ```\n\n4. **Review the generated YAML** in `.github/workflows/`:\n   - Verify commands are correct\n   - Check that step order is as expected\n   - Ensure all required fields are present\n\n5. **Commit both TypeScript and YAML**:\n   ```bash\n   git add workflows/ .github/workflows/\n   git commit -m \"Update workflows\"\n   ```\n\n#### Why Commit Both?\n\nYou should commit **both** the TypeScript source (`workflows/*.ts`) and the generated YAML (`.github/workflows/*.yml`):\n\n- **TypeScript**: Source of truth for your workflows\n- **YAML**: What GitHub Actions actually executes\n\n#### ⚠️ Important: Auto-compilation in CI\n\nWhile you can create a workflow that auto-compiles TypeScript to YAML on push, **this is NOT recommended**. Always compile and review workflows locally before committing.\n\nIf you're willing to handle the complexity of GitHub Actions triggers (e.g., filtering `paths`, managing PAT tokens, avoiding infinite loops), you can set up an auto-compilation workflow. See [`workflows/update-workflows.ts`](https://github.com/dodok8/gaji/blob/main/workflows/update-workflows.ts) for a working example.\n\n### Composite Actions\n\nDefine reusable composite actions and reference them in workflows:\n\n```typescript\nimport { CompositeAction, CallAction, Job, Workflow } from \"../generated/index.js\";\n\nconst action = new CompositeAction({\n  name: \"Setup\",\n  description: \"Setup the project environment\",\n  inputs: {\n    \"node-version\": { description: \"Node.js version\", required: false, default: \"20\" },\n  },\n});\naction.addStep({ name: \"Install deps\", run: \"npm ci\", shell: \"bash\" });\naction.build(\"setup\");\n\n// Reference the composite action in a workflow\nconst job = new Job(\"ubuntu-latest\")\n  .addStep(CallAction.from(action).toJSON());\n\nconst workflow = new Workflow({\n  name: \"CI\",\n  on: { push: {} },\n}).addJob(\"build\", job);\n\nworkflow.build(\"ci\");\n```\n\n### Reusable Workflows\n\nCall reusable workflows using `CallJob`:\n\n```typescript\nimport { CallJob, Workflow } from \"../generated/index.js\";\n\nconst deploy = new CallJob(\"./.github/workflows/deploy.yml\")\n  .with({ environment: \"production\" })\n  .secrets(\"inherit\")\n  .needs([\"build\"]);\n\nconst workflow = new Workflow({\n  name: \"Release\",\n  on: { push: { tags: [\"v*\"] } },\n}).addJob(\"deploy\", deploy);\n\nworkflow.build(\"release\");\n```\n\n### Job Options\n\nThe `Job` constructor accepts an optional second argument for additional configuration:\n\n```typescript\nconst job = new Job(\"ubuntu-latest\", {\n  needs: [\"setup\"],\n  env: { NODE_ENV: \"test\" },\n  \"timeout-minutes\": 30,\n  \"continue-on-error\": true,\n  permissions: { contents: \"read\" },\n  strategy: {\n    matrix: { node: [\"18\", \"20\", \"22\"] },\n    \"fail-fast\": false,\n  },\n});\n```\n\nBuilder methods are also available:\n\n```typescript\nconst job = new Job(\"ubuntu-latest\")\n  .addStep({ name: \"Test\", run: \"npm test\" })\n  .needs([\"setup\"])\n  .env({ CI: \"true\" })\n  .when(\"github.event_name == 'push'\")\n  .permissions({ contents: \"read\" })\n  .outputs({ result: \"${{ steps.test.outputs.result }}\" })\n  .strategy({ matrix: { os: [\"ubuntu-latest\", \"macos-latest\"] } })\n  .continueOnError(true)\n  .timeoutMinutes(30);\n```\n\n## Commands\n\n### `gaji init`\n\nInitialize a new gaji project. Detects the project state (empty, existing project, or has YAML workflows) and sets up accordingly.\n\n```\ngaji init [OPTIONS]\n```\n\n| Option | Description |\n|---|---|\n| `--force` | Overwrite existing files |\n| `--skip-examples` | Skip example workflow creation |\n| `--migrate` | Migrate existing YAML workflows to TypeScript |\n| `-i, --interactive` | Interactive mode |\n\n### `gaji dev`\n\nStart development mode. Scans workflow files for action references and generates types.\n\n```\ngaji dev [OPTIONS]\n```\n\n| Option | Description |\n|---|---|\n| `-i, --input \u003cPATH\u003e...` | Workflow directories or individual `.ts` files (default: `workflows`) |\n| `--watch` | Keep watching for changes after the initial scan |\n\n### `gaji build`\n\nBuild TypeScript workflows to YAML.\n\n```\ngaji build [OPTIONS]\n```\n\n| Option | Description |\n|---|---|\n| `-i, --input \u003cPATH\u003e...` | Workflow directories or individual `.ts` files (default: `workflows`) |\n| `-o, --output \u003cDIR\u003e` | Output directory for YAML files (default: `.github`) |\n| `--dry-run` | Preview YAML output without writing files |\n\nOutput files are placed in subdirectories based on type:\n- Workflows: `.github/workflows/\u003cid\u003e.yml`\n- Composite actions: `.github/actions/\u003cid\u003e/action.yml`\n\n### `gaji add \u003caction\u003e`\n\nAdd a new action and generate types.\n\n```bash\ngaji add actions/checkout@v5\ngaji add actions/setup-node@v4\n```\n\n### `gaji clean`\n\nClean generated files.\n\n```\ngaji clean [OPTIONS]\n```\n\n| Option | Description |\n|---|---|\n| `--cache` | Also clean cache |\n\n## Configuration\n\n### `.gaji.toml`\n\nProject-level configuration file. Created automatically by `gaji init`.\n\n```toml\n[project]\nworkflows_dir = \"workflows\"    # TypeScript workflow source directory\noutput_dir = \".github\"         # Output directory for generated YAML\ngenerated_dir = \"generated\"    # Directory for generated type definitions\n\n[watch]\ndebounce_ms = 300              # Debounce delay for file watcher\nignored_patterns = [\"node_modules\", \".git\", \"generated\"]\n\n[build]\nvalidate = true                # Validate workflow YAML (requires 'on' and 'jobs')\nformat = true                  # Format YAML output\n\n[github]\ntoken = \"ghp_...\"              # GitHub token (prefer .gaji.local.toml for this)\napi_url = \"https://github.example.com\"  # GitHub Enterprise URL\n```\n\n### `.gaji.local.toml`\n\nLocal overrides for sensitive values. Add this to `.gitignore`.\n\n```toml\n[github]\ntoken = \"ghp_your_token_here\"\napi_url = \"https://github.example.com\"  # for GitHub Enterprise\n```\n\nToken resolution priority: `GITHUB_TOKEN` env var \u003e `.gaji.local.toml` \u003e `.gaji.toml`\n\n## Documentation\n\n📚 **[Full Documentation](https://gaji.gaebalgom.work)** (English \u0026 한국어)\n\n- [Getting Started](https://gaji.gaebalgom.work/guide/getting-started)\n- [Writing Workflows](https://gaji.gaebalgom.work/guide/writing-workflows)\n- [CLI Reference](https://gaji.gaebalgom.work/reference/cli)\n- [API Reference](https://gaji.gaebalgom.work/reference/api)\n- [Examples](examples/)\n\n## Examples\n\nCheck out the [examples/](examples/) directory for complete working examples:\n\n- **[ts-package](examples/ts-package/)** - TypeScript package with gaji CI workflow using pnpm\n\n## License\n\nMIT License\n\n## Special Thanks\n\n### gaji Brand\n\n- Name suggestions: [kiwiyou](https://github.com/kiwiyou), [RanolP](https://github.com/ranolp)\n- Logo design: [sij411](https://github.com/sij411)\n\n### Inspiration\n\n- Client Devops Team@Toss: Without the experience on this team, I would never have thought deeply about YAML and GitHub Actions. The product below was also introduced to me through a teammate.\n- [emmanuelnk/github-actions-workflow-ts](https://github.com/emmanuelnk/github-actions-workflow-ts): The idea of writing GitHub Actions in TypeScript came from here.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdodok8%2Fgaji","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdodok8%2Fgaji","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdodok8%2Fgaji/lists"}