{"id":49203895,"url":"https://github.com/ekaone/annota","last_synced_at":"2026-04-23T17:00:39.709Z","repository":{"id":353360629,"uuid":"1219046359","full_name":"ekaone/annota","owner":"ekaone","description":"Header parser and polyglot script runner. Inspect, validate, and run any script by its shebang","archived":false,"fork":false,"pushed_at":"2026-04-23T15:12:10.000Z","size":42,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T16:30:15.161Z","etag":null,"topics":["cli","metadata","parser","polyglot","runner","shebang"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/ekaone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T13:27:52.000Z","updated_at":"2026-04-23T15:12:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ekaone/annota","commit_stats":null,"previous_names":["ekaone/annota"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ekaone/annota","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekaone%2Fannota","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekaone%2Fannota/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekaone%2Fannota/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekaone%2Fannota/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ekaone","download_url":"https://codeload.github.com/ekaone/annota/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekaone%2Fannota/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32189656,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T15:28:30.493Z","status":"ssl_error","status_checked_at":"2026-04-23T15:28:29.972Z","response_time":53,"last_error":"SSL_read: 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":["cli","metadata","parser","polyglot","runner","shebang"],"created_at":"2026-04-23T17:00:24.724Z","updated_at":"2026-04-23T17:00:39.697Z","avatar_url":"https://github.com/ekaone.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @ekaone/annota\n\n\u003e Script header parser and polyglot script runner. It also inspects, validates, and runs any script by reading its shebang.\n\n```bash\nnpx @ekaone/annota run ./build.ts\nnpx @ekaone/annota run ./analyze.py\nnpx @ekaone/annota run ./deploy.sh\n```\n\nOne command. Any language. No configuration.\n\n---\n\n## Install\n\n```bash\n# npm\nnpm install @ekaone/annota\n\n# pnpm\npnpm add @ekaone/annota\n\n# bun\nbun add @ekaone/annota\n```\n\n---\n\n## How it works\n\n`annota` reads the **shebang line** (`#!`) at the top of a script to determine which runtime to use, then delegates execution to that runtime. It also parses the full script header, including JSDoc, TypeScript directives, and custom hints,exposing everything as structured data.\n\n```\n#!/usr/bin/env bun        →  bun ./build.ts\n#!/usr/bin/env python3    →  python3 ./analyze.py\n#!/usr/bin/env deno run   →  deno run ./server.ts\n#!/usr/bin/env tsx        →  tsx ./transform.ts\n#!/bin/bash               →  bash ./deploy.sh\n```\n\n---\n\n## Supported executors\n\n`annota run` works with any executor reachable on `PATH`. Common examples:\n\n| Shebang | Executor |\n|---------|----------|\n| `#!/usr/bin/env bun` | [Bun](https://bun.sh) |\n| `#!/usr/bin/env tsx` | [tsx](https://github.com/privatenumber/tsx) |\n| `#!/usr/bin/env ts-node` | [ts-node](https://typestrong.org/ts-node/) |\n| `#!/usr/bin/env -S deno run` | [Deno](https://deno.land) |\n| `#!/usr/bin/env node` | [Node.js](https://nodejs.org) |\n| `#!/usr/bin/env python3` | [Python 3](https://www.python.org) |\n| `#!/usr/bin/env ruby` | [Ruby](https://www.ruby-lang.org) |\n| `#!/bin/bash` | [Bash](https://www.gnu.org/software/bash/) |\n| `#!/bin/sh` | [sh](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html) |\n\n---\n\n## CLI\n\n### `annota run \u003cfile\u003e` or use alias `an \u003cfile\u003e`\n\nRun a script using the executor declared in its shebang.\n\n```bash\nannota run ./build.ts\nannota run ./scripts/migrate.py\nannota run ./deploy.sh\n```\n\n**Flags**\n\n| Flag | Description |\n|------|-------------|\n| `--dry-run` | Print the resolved command without executing it |\n| `--fallback=\u003cexecutor\u003e` | Use this executor if no shebang is found |\n| `-- \u003cargs\u003e` | Forward arguments to the executor |\n\n```bash\n# See what command would be run\nannota run ./build.ts --dry-run\n# → bun /home/user/project/build.ts\n\n# Use a fallback executor when shebang is absent\nannota run ./legacy.ts --fallback=tsx\n\n# Pass arguments through to the script\nannota run ./server.ts -- --port 4000 --watch\n```\n\n\u003e `run` is intentionally single-file only. For multi-script orchestration, use `json-cli` with `annota` as the executor layer.\n\n---\n\n### `annota inspect \u003cfile...\u003e`\n\nParse and display the script header metadata.\n\n```bash\nannota inspect ./build.ts\nannota inspect ./scripts/*.ts\nannota inspect ./build.ts --format json\nannota inspect ./build.ts --format table\nannota inspect ./build.ts --field executor\n```\n\n**Flags**\n\n| Flag | Description |\n|------|-------------|\n| `--format pretty` | Human-readable output (default) |\n| `--format json` | JSON output, pipeable |\n| `--format table` | Tabular summary for multiple files |\n| `--field \u003ckey\u003e` | Print a single field: `executor`, `version`, `author`, `description`, `shebang` |\n\n**Pretty output**\n\n```\n✔ scripts/build.ts\n  executor  : bun\n  shebang   : #!/usr/bin/env bun\n  ts-check  : true\n  ts-nocheck: false\n  description: Build script for the project\n  version   : 2.0.0\n  author    : ekaone\n  env       : {\"NODE_ENV\":\"production\"}\n  requires  : node\u003e=18\n```\n\n**Table output** (`--format table`)\n\n```\nFILE              EXECUTOR   TS-CHECK   VERSION   VALID\nbuild.ts          bun        ✔          2.0.0     ✔\ndeploy.ts         ts-node    ✔          —         ✔\nanalyze.py        python3    —          —         ✔\nsetup.sh          bash       —          —         ✔\nbroken.ts         —          —          —         ✘\n```\n\n**JSON output** (`--format json`)\n\n```json\n[\n  {\n    \"file\": \"build.ts\",\n    \"shebang\": { \"raw\": \"#!/usr/bin/env bun\", \"executor\": \"bun\", \"args\": [] },\n    \"directives\": { \"tsCheck\": true, \"tsNoCheck\": false },\n    \"jsdoc\": { \"description\": \"Build script\", \"version\": \"2.0.0\", \"author\": \"ekaone\", \"tags\": {} },\n    \"env\": { \"NODE_ENV\": \"production\" },\n    \"requires\": [\"node\u003e=18\"],\n    \"headerLines\": 9\n  }\n]\n```\n\n---\n\n### `annota validate \u003cfile...\u003e`\n\nLike `inspect` but CI-focused. Exits with code `1` if any file fails.\n\n```bash\nannota validate ./scripts/*.ts --require shebang\nannota validate ./scripts/*.ts --require executor=bun\nannota validate ./scripts/*.ts --require ts-check\nannota validate ./scripts/*.ts --require version\n```\n\n**Available rules**\n\n| Rule | Passes when |\n|------|-------------|\n| `shebang` | File has any shebang line |\n| `ts-check` | File has `// @ts-check` |\n| `version` | File has `@version` in JSDoc |\n| `executor=\u003cname\u003e` | Shebang executor matches `\u003cname\u003e` |\n\nMultiple `--require` flags are supported and all must pass:\n\n```bash\nannota validate ./scripts/*.ts \\\n  --require shebang \\\n  --require executor=bun \\\n  --require version\n```\n\n**Output**\n\n```\n  ✔ scripts/build.ts\n  ✔ scripts/lint.ts\n  ✘ scripts/deploy.ts\n      → executor mismatch: expected \"bun\", got \"ts-node\"\n  ✘ scripts/old.ts\n      → missing shebang\n\n  2 passed, 2 failed\n```\n\n**JSON output** (`--format json`)\n\n```json\n[\n  { \"file\": \"build.ts\", \"passed\": true, \"failures\": [] },\n  { \"file\": \"deploy.ts\", \"passed\": false, \"failures\": [\"executor mismatch: expected \\\"bun\\\", got \\\"ts-node\\\"\"] }\n]\n```\n\n---\n\n### `annota which \u003cfile\u003e`\n\nPrint only the resolved executor. Unix-friendly and composable.\n\n```bash\nannota which ./build.ts\n# bun\n\n# Compose with shell\n$(annota which ./build.ts) ./build.ts\n# Equivalent to: bun ./build.ts\n\n# Use in scripts\nEXECUTOR=$(annota which ./build.ts)\necho \"Running with: $EXECUTOR\"\n```\n\n---\n\n## CI / GitHub Actions\n\n**Validate all scripts have a shebang**\n\n```yaml\n- name: Validate script headers\n  run: |\n    find ./scripts -name \"*.ts\" -o -name \"*.py\" -o -name \"*.sh\" | \\\n    xargs npx @ekaone/annota validate --require shebang\n```\n\n**Enforce TypeScript scripts use bun**\n\n```yaml\n- name: Enforce executor\n  run: npx @ekaone/annota validate ./scripts/*.ts --require executor=bun\n```\n\n**Pre-commit hook (husky)**\n\n```bash\n# .husky/pre-commit\nnpx @ekaone/annota validate \\\n  $(git diff --cached --name-only | grep -E \"\\.(ts|py|sh)$\") \\\n  --require shebang\n```\n\n**Export report as JSON artifact**\n\n```yaml\n- name: Inspect scripts\n  run: |\n    npx @ekaone/annota inspect ./scripts/* --format json \u003e script-report.json\n\n- uses: actions/upload-artifact@v4\n  with:\n    name: script-report\n    path: script-report.json\n```\n\n---\n\n## Programmatic API\n\n`annota` is also a full library. The CLI is a thin shell over the same primitives.\n\n### `parseMeta(source)`\n\nParse all header metadata from a source string.\n\n```ts\nimport { readFileSync } from 'node:fs'\nimport { parseMeta } from '@ekaone/annota'\n\nconst source = readFileSync('./build.ts', 'utf8')\nconst meta = parseMeta(source)\n\nconsole.log(meta.shebang?.executor)   // 'bun'\nconsole.log(meta.jsdoc.version)       // '2.0.0'\nconsole.log(meta.directives.tsCheck)  // true\nconsole.log(meta.env)                 // { NODE_ENV: 'production' }\nconsole.log(meta.requires)            // ['node\u003e=18']\n```\n\n**Returns: `ScriptMeta`**\n\n```ts\ninterface ScriptMeta {\n  shebang:     ShebangResult | null\n  directives:  DirectivesResult\n  jsdoc:       JSDocResult\n  env:         Record\u003cstring, string\u003e\n  requires:    string[]\n  headerLines: number\n}\n```\n\n---\n\n### `parseShebang(source)`\n\nParse only the shebang line.\n\n```ts\nimport { parseShebang } from '@ekaone/annota'\n\nparseShebang('#!/usr/bin/env bun\\n')\n// { raw: '#!/usr/bin/env bun', executor: 'bun', args: [] }\n\nparseShebang('#!/usr/bin/env -S deno run --allow-net\\n')\n// { raw: '...', executor: 'deno', args: ['run', '--allow-net'] }\n\nparseShebang('console.log(1)')\n// null\n```\n\n**Returns: `ShebangResult | null`**\n\n```ts\ninterface ShebangResult {\n  raw:      string    // full shebang line including #!\n  executor: string    // e.g. 'bun', 'python3', 'bash'\n  args:     string[]  // arguments after the executor\n}\n```\n\n---\n\n### `parseDirectives(source)`\n\nDetect TypeScript directives.\n\n```ts\nimport { parseDirectives } from '@ekaone/annota'\n\nparseDirectives('// @ts-check\\nconsole.log(1)')\n// { tsCheck: true, tsNoCheck: false }\n```\n\n**Returns: `DirectivesResult`**\n\n```ts\ninterface DirectivesResult {\n  tsCheck:   boolean\n  tsNoCheck: boolean\n}\n```\n\n---\n\n### `parseJSDoc(source)`\n\nParse the leading JSDoc block.\n\n```ts\nimport { parseJSDoc } from '@ekaone/annota'\n\nconst source = `\n/**\n * Deploy script for production\n * @version 3.0.0\n * @author ekaone\n * @license MIT\n */\n`\n\nparseJSDoc(source)\n// {\n//   description: 'Deploy script for production',\n//   version:     '3.0.0',\n//   author:      'ekaone',\n//   tags:        { license: 'MIT' }\n// }\n```\n\n**Returns: `JSDocResult`**\n\n```ts\ninterface JSDocResult {\n  description: string | null\n  version:     string | null\n  author:      string | null\n  tags:        Record\u003cstring, string\u003e  // all other @tags\n}\n```\n\n---\n\n### `parseEnvHints(source)` / `parseRequiresHints(source)`\n\nParse custom inline hints.\n\n```ts\nimport { parseEnvHints, parseRequiresHints } from '@ekaone/annota'\n\nconst source = `\n// @env NODE_ENV=production\n// @env PORT=3000\n// @requires node\u003e=18\n// @requires bun\n`\n\nparseEnvHints(source)\n// { NODE_ENV: 'production', PORT: '3000' }\n\nparseRequiresHints(source)\n// ['node\u003e=18', 'bun']\n```\n\nHints are declared as single-line comments so they're valid in any language that uses `//` comments (TypeScript, JavaScript, Rust, Go, etc.).\n\n---\n\n### `validate(source, file, rules)`\n\nValidate a source string against a set of rules.\n\n```ts\nimport { validate } from '@ekaone/annota'\nimport type { ValidateRule } from '@ekaone/annota'\n\nconst source = readFileSync('./build.ts', 'utf8')\n\nconst result = validate(source, 'build.ts', [\n  'shebang',\n  'ts-check',\n  'version',\n  'executor=bun',\n])\n\nconsole.log(result.passed)    // true | false\nconsole.log(result.failures)  // string[]\nconsole.log(result.meta)      // full ScriptMeta\n```\n\n**Returns: `ValidationResult`**\n\n```ts\ninterface ValidationResult {\n  file:     string\n  passed:   boolean\n  failures: string[]\n  meta:     ScriptMeta\n}\n```\n\n**Available rules**\n\n```ts\ntype ValidateRule =\n  | 'shebang'\n  | 'ts-check'\n  | 'version'\n  | `executor=${string}`\n```\n\n---\n\n## Script header conventions\n\nA fully annotated script header looks like this:\n\n```ts\n#!/usr/bin/env bun\n// @ts-check\n// @env NODE_ENV=production\n// @env PORT=3000\n// @requires node\u003e=18\n/**\n * Build and bundle the project for production.\n * @version 2.1.0\n * @author Eka Prasetia\n * @license MIT\n */\nimport { build } from './build.js'\nawait build()\n```\n\nNone of these are required. `annota` gracefully handles missing sections and returns `null` for absent fields. Use only what's useful for your project.\n\n---\n\n## License\n\nMIT © [Eka Prasetia](./LICENSE)\n\n## Links\n\n- [npm Package](https://www.npmjs.com/package/@ekaone/annota)\n- [GitHub Repository](https://github.com/ekaone/annota)\n- [Issue Tracker](https://github.com/ekaone/annota/issues)\n\n---\n\n⭐ If this library helps you, please consider giving it a star on GitHub!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekaone%2Fannota","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fekaone%2Fannota","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekaone%2Fannota/lists"}