| 1 | --- |
| 2 | title: Repositories |
| 3 | description: Manage GitHub repositories as Stack resources — create or adopt a repo, converge its settings on every deploy, rename in place, and opt into deletion explicitly. |
| 4 | --- |
| 5 | |
| 6 | `GitHub.Repository` manages the lifecycle of a repository owned by a |
| 7 | user or organization. The repository is created on first deploy and |
| 8 | its settings are converged on every subsequent deploy — description, |
| 9 | visibility, tabs, merge policy, topics — so repo configuration lives |
| 10 | in code next to the infrastructure it hosts. |
| 11 | |
| 12 | Repositories are treated as precious: destroying the stack retains |
| 13 | the repository by default, and deleting it is an explicit opt-in. |
| 14 | New here? [Set up credentials](/github/setup) first. |
| 15 | |
| 16 | ## Create a repository |
| 17 | |
| 18 | ```typescript |
| 19 | // alchemy.run.ts |
| 20 | import * as Alchemy from "alchemy"; |
| 21 | import * as GitHub from "alchemy/GitHub"; |
| 22 | import * as Effect from "effect/Effect"; |
| 23 | |
| 24 | export default Alchemy.Stack( |
| 25 | "MyRepos", |
| 26 | { |
| 27 | providers: GitHub.providers(), |
| 28 | state: Alchemy.localState(), |
| 29 | }, |
| 30 | Effect.gen(function* () { |
| 31 | const repo = yield* GitHub.Repository("api", { |
| 32 | owner: "my-org", |
| 33 | name: "api", |
| 34 | description: "API service", |
| 35 | autoInit: true, |
| 36 | }); |
| 37 | |
| 38 | return { url: repo.htmlUrl, clone: repo.cloneUrl }; |
| 39 | }), |
| 40 | ); |
| 41 | ``` |
| 42 | |
| 43 | `owner` is a user or organization login — alchemy picks the matching |
| 44 | create endpoint automatically. Outputs include the stable numeric |
| 45 | `repoId`, `fullName`, `htmlUrl`, the `sshUrl`/`cloneUrl`/`gitUrl` |
| 46 | clone URLs, and the resolved `defaultBranch`. |
| 47 | |
| 48 | ## Adopt an existing repository |
| 49 | |
| 50 | Pointing `Repository` at a name that already exists under the owner |
| 51 | doesn't fail with a name conflict. The deploy observes the live |
| 52 | repository and converges its settings in place, so you can bring an |
| 53 | existing repo under management just by declaring it: |
| 54 | |
| 55 | ```typescript |
| 56 | yield* GitHub.Repository("api", { |
| 57 | owner: "my-org", |
| 58 | name: "api", // already exists — settings below are applied to it |
| 59 | hasWiki: false, |
| 60 | deleteBranchOnMerge: true, |
| 61 | }); |
| 62 | ``` |
| 63 | |
| 64 | ## Settings alchemy manages |
| 65 | |
| 66 | Every deploy converges the declared settings against the live |
| 67 | repository: |
| 68 | |
| 69 | ```typescript |
| 70 | const repo = yield* GitHub.Repository("internal-tools", { |
| 71 | owner: "my-org", |
| 72 | name: "internal-tools", |
| 73 | visibility: "private", |
| 74 | hasWiki: false, |
| 75 | hasProjects: false, |
| 76 | deleteBranchOnMerge: true, |
| 77 | }); |
| 78 | ``` |
| 79 | |
| 80 | - **`description`, `homepage`** — the repository page blurb and link. |
| 81 | - **`visibility`** — `"public"` (default), `"private"`, or |
| 82 | `"internal"` (organization repos on GitHub Enterprise only). |
| 83 | - **Tabs** — `hasIssues`, `hasProjects`, `hasWiki` (default `true`) |
| 84 | and `hasDiscussions` (default `false`). |
| 85 | - **Merge policy** — `allowSquashMerge`, `allowMergeCommit`, |
| 86 | `allowRebaseMerge` (default `true`), `allowAutoMerge` and |
| 87 | `deleteBranchOnMerge` (default `false`). |
| 88 | - **`isTemplate`** — mark the repository as a template. |
| 89 | - **`defaultBranch`** — only applied once the branch actually exists; |
| 90 | on an empty repository it's dropped for that deploy and applied on |
| 91 | a later one. |
| 92 | |
| 93 | ## Topics |
| 94 | |
| 95 | The `topics` list fully replaces whatever is on the repository — |
| 96 | removing the prop on a later deploy clears them: |
| 97 | |
| 98 | ```typescript |
| 99 | yield* GitHub.Repository("sdk", { |
| 100 | owner: "my-org", |
| 101 | name: "sdk", |
| 102 | topics: ["typescript", "effect", "sdk"], |
| 103 | }); |
| 104 | ``` |
| 105 | |
| 106 | ## Seed the first commit |
| 107 | |
| 108 | `autoInit`, `gitignoreTemplate`, and `licenseTemplate` initialize the |
| 109 | repository at create time — an empty README, a `.gitignore` template, |
| 110 | and a license. They are only honored on creation; changing them on a |
| 111 | later deploy has no effect on an existing repository: |
| 112 | |
| 113 | ```typescript |
| 114 | yield* GitHub.Repository("service", { |
| 115 | owner: "my-org", |
| 116 | name: "service", |
| 117 | autoInit: true, |
| 118 | gitignoreTemplate: "Node", |
| 119 | licenseTemplate: "mit", |
| 120 | }); |
| 121 | ``` |
| 122 | |
| 123 | ## Rename, don't replace |
| 124 | |
| 125 | Keep the same logical ID and change `name` to rename the live |
| 126 | repository in place — history, issues, and pull requests are |
| 127 | preserved, and the numeric `repoId` stays the same: |
| 128 | |
| 129 | ```typescript |
| 130 | // First deploy creates "api"; this later deploy (same logical ID) |
| 131 | // renames it to "gateway". |
| 132 | const repo = yield* GitHub.Repository("api", { |
| 133 | owner: "my-org", |
| 134 | name: "gateway", |
| 135 | }); |
| 136 | ``` |
| 137 | |
| 138 | Only changing `owner` triggers a replacement — a repository can't be |
| 139 | moved between owners by an update. |
| 140 | |
| 141 | ## Archive a repository |
| 142 | |
| 143 | `archived: true` makes the repository read-only; set it back to |
| 144 | `false` on a later deploy to un-archive: |
| 145 | |
| 146 | ```typescript |
| 147 | yield* GitHub.Repository("legacy", { |
| 148 | owner: "my-org", |
| 149 | name: "legacy-service", |
| 150 | archived: true, |
| 151 | }); |
| 152 | ``` |
| 153 | |
| 154 | ## Deleting a repository |
| 155 | |
| 156 | Destroying the stack does **not** delete the repository — the default |
| 157 | removal policy is retain, protecting irreplaceable history. Opt into |
| 158 | actual deletion with `destroy()`: |
| 159 | |
| 160 | ```typescript |
| 161 | import { destroy } from "alchemy/RemovalPolicy"; |
| 162 | |
| 163 | yield* GitHub.Repository("ephemeral", { |
| 164 | owner: "my-org", |
| 165 | name: "ephemeral-preview", |
| 166 | }).pipe(destroy()); |
| 167 | ``` |
| 168 | |
| 169 | The token needs the `delete_repo` scope for this (plain `repo` scope |
| 170 | covers everything else). Deletion resolves the repository by its |
| 171 | stable `repoId`, so it targets the right repo even after a rename. |
| 172 | |
| 173 | ## Escape hatch: Octokit |
| 174 | |
| 175 | For anything the resource model doesn't cover, the |
| 176 | `GitHubCredentials` service hands you an authenticated |
| 177 | [Octokit](https://github.com/octokit/rest.js) client — the same one |
| 178 | every GitHub resource uses: |
| 179 | |
| 180 | ```typescript |
| 181 | import * as GitHub from "alchemy/GitHub"; |
| 182 | import * as Effect from "effect/Effect"; |
| 183 | |
| 184 | const program = Effect.gen(function* () { |
| 185 | const creds = yield* yield* GitHub.GitHubCredentials; |
| 186 | const octokit = creds.octokit(); |
| 187 | |
| 188 | const { data } = yield* Effect.tryPromise(() => |
| 189 | octokit.rest.repos.get({ owner: "my-org", repo: "api" }), |
| 190 | ); |
| 191 | return data; |
| 192 | }); |
| 193 | ``` |
| 194 | |
| 195 | (`GitHubCredentials` resolves to an effect that yields the service, |
| 196 | hence the double `yield*`.) |
| 197 | |
| 198 | Inside a stack, `GitHub.providers()` already carries |
| 199 | `GitHubCredentials`, resolved via [the login flow](/github/setup). |
| 200 | Outside one, build the layer directly: |
| 201 | |
| 202 | - **`GitHub.fromEnv()`** — reads `GITHUB_ACCESS_TOKEN` or |
| 203 | `GITHUB_TOKEN`. |
| 204 | - **`GitHub.fromToken(token)`** — a literal token, plain string or |
| 205 | `Redacted` — useful in tests. |
| 206 | |
| 207 | ```typescript |
| 208 | program.pipe(Effect.provide(GitHub.fromEnv())); |
| 209 | ``` |
| 210 | |
| 211 | ## Where next |
| 212 | |
| 213 | Related: |
| 214 | |
| 215 | - [Actions secrets & variables](/github/actions-config) — seed the |
| 216 | CI configuration your repository's workflows read. |
| 217 | - [Repository events](/github/events) — subscribe a Worker to the |
| 218 | repository's webhooks with typed payloads. |
| 219 | - [CI/CD guide](/environments/ci) — deploy the stack itself from GitHub |
| 220 | Actions. |
| 221 | |
| 222 | Reference: |
| 223 | |
| 224 | - [Repository API reference](/providers/github/repository) |