@rathnasgala/cli 0.0.22 → 1.1.4
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/README.md +100 -187
- package/package.json +3 -3
- package/src/api/gala.js +163 -0
- package/src/api/github.js +63 -0
- package/src/api/http.js +72 -0
- package/src/auth/gala.js +52 -0
- package/src/auth/github.js +78 -0
- package/src/auth/store.js +88 -0
- package/src/cli/args.js +56 -0
- package/src/cli/terminal.js +104 -0
- package/src/commands/auth.js +20 -0
- package/src/commands/doctor.js +98 -0
- package/src/commands/domain.js +124 -0
- package/src/commands/init.js +276 -0
- package/src/commands/new.js +76 -0
- package/src/commands/preview.js +92 -0
- package/src/commands/prism.js +360 -0
- package/src/commands/publish.js +57 -0
- package/src/commands/upgrade.js +173 -0
- package/src/commands-manifest.js +80 -0
- package/src/content.js +31 -0
- package/src/domain.js +33 -0
- package/src/git.js +160 -0
- package/src/index.js +44 -294
- package/src/publication.js +39 -0
- package/src/assign-content-ids.js +0 -1
- package/src/auth-command.js +0 -36
- package/src/configure-site.js +0 -102
- package/src/content-files.js +0 -1
- package/src/doctor-command.js +0 -214
- package/src/entitlement-client.js +0 -26
- package/src/entitlement-command.js +0 -74
- package/src/evaluation-date.js +0 -1
- package/src/gala-credential-health.js +0 -34
- package/src/gala-credential-store.js +0 -115
- package/src/gala-device-flow.js +0 -121
- package/src/git-credentials.js +0 -37
- package/src/github-auth-command.js +0 -50
- package/src/github-credential-store.js +0 -104
- package/src/github-device-flow.js +0 -153
- package/src/github-empty-repository.js +0 -89
- package/src/github-identity.js +0 -32
- package/src/github-pages-provisioning.js +0 -107
- package/src/github-repository-secret.js +0 -82
- package/src/github-repository-variable.js +0 -56
- package/src/github-template-repository.js +0 -171
- package/src/hook-command.js +0 -64
- package/src/http-failure.js +0 -55
- package/src/new-command.js +0 -54
- package/src/open-browser.js +0 -40
- package/src/preview-command.js +0 -60
- package/src/publication-creation-client.js +0 -155
- package/src/publication-state.js +0 -7
- package/src/publish-command.js +0 -37
- package/src/record-deployment-command.js +0 -147
- package/src/refresh-command.js +0 -104
- package/src/repository-limits.js +0 -94
- package/src/scaffold-git.js +0 -76
- package/src/scaffold-options.js +0 -58
- package/src/scaffold-preflight.js +0 -146
- package/src/scaffold-site.js +0 -185
- package/src/site-config-registration.js +0 -47
- package/src/site-registration-client.js +0 -138
- package/src/theme-package.js +0 -128
- package/src/topology-client.js +0 -43
- package/src/topology-command.js +0 -70
- package/src/upgrade-command.js +0 -81
- package/src/validate-command.js +0 -5
- package/src/workflow-command.js +0 -87
package/README.md
CHANGED
|
@@ -1,267 +1,180 @@
|
|
|
1
1
|
# Gala CLI
|
|
2
2
|
|
|
3
|
-
Create,
|
|
4
|
-
|
|
5
|
-
The quick start below begins with the required accounts and tools and does not assume a global CLI installation.
|
|
3
|
+
Create a publication, write posts, preview them, and publish — from your terminal.
|
|
6
4
|
|
|
7
5
|
## Requirements
|
|
8
6
|
|
|
9
7
|
- [Git](https://git-scm.com/downloads)
|
|
10
|
-
- [Node.js
|
|
8
|
+
- [Node.js 20](https://nodejs.org/en/download) or newer
|
|
11
9
|
- A [GitHub account](https://github.com/signup)
|
|
12
|
-
- The [Gala GitHub App](https://github.com/apps/gala67-app/installations/new) — `scaffold` walks you through installing it if it is not already
|
|
13
|
-
|
|
14
|
-
Check your local tools:
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
node --version
|
|
18
|
-
npm --version
|
|
19
|
-
git --version
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
You should see something like this:
|
|
23
|
-
```console
|
|
24
|
-
v22.18.0
|
|
25
|
-
10.9.3
|
|
26
|
-
git version 2.50.1 (Apple Git-155)
|
|
27
|
-
```
|
|
11
|
+
Nothing to install. Every command runs through `npx`.
|
|
28
12
|
|
|
29
|
-
##
|
|
13
|
+
## Start a publication
|
|
30
14
|
|
|
31
|
-
|
|
15
|
+
Create it in the current empty folder:
|
|
32
16
|
|
|
33
17
|
```console
|
|
34
18
|
mkdir field-notes && cd field-notes
|
|
35
|
-
npx --yes @rathnasgala/cli@latest
|
|
19
|
+
npx --yes @rathnasgala/cli@latest init
|
|
36
20
|
```
|
|
37
21
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
1. **Signs you in to Gala** if no valid token is stored, showing a code to enter in the browser.
|
|
41
|
-
2. **Signs you in to GitHub** the same way, as the Gala GitHub App. It requests no scopes: a GitHub
|
|
42
|
-
App's permissions are fixed on the app and granted when you install it, so Gala reaches only the
|
|
43
|
-
repositories you have shared with it — never every repository you can access.
|
|
44
|
-
3. **Reads your GitHub account** from that token, so there is no username to type.
|
|
45
|
-
4. **Finds the Gala GitHub App installation** for your account. If the App is not installed yet it
|
|
46
|
-
prints the installation page, waits while you install it, and carries on — the installation ID
|
|
47
|
-
is never something you have to read out of a URL.
|
|
48
|
-
5. **Names the publication** after the folder you are standing in.
|
|
49
|
-
6. **Creates the repository** from the site template, registers it, installs its one-time secret,
|
|
50
|
-
writes the publication workflow, commits, and enables GitHub Pages.
|
|
51
|
-
|
|
52
|
-
Both sign-ins are skipped when a valid credential is already stored, so re-running is cheap.
|
|
53
|
-
|
|
54
|
-
After scaffolding succeeds, open [GitHub App settings](https://github.com/settings/installations)
|
|
55
|
-
and restrict the App to the publication repository if you installed it against all of them.
|
|
56
|
-
|
|
57
|
-
### Write, preview, and publish
|
|
22
|
+
Or name a new destination directly:
|
|
58
23
|
|
|
59
24
|
```console
|
|
60
|
-
npx --yes @rathnasgala/cli@latest
|
|
61
|
-
npx --yes @rathnasgala/cli@latest preview
|
|
62
|
-
npx --yes @rathnasgala/cli@latest publish
|
|
25
|
+
npx --yes @rathnasgala/cli@latest init field-notes
|
|
63
26
|
```
|
|
64
27
|
|
|
65
|
-
|
|
66
|
-
|
|
28
|
+
The destination must be empty. An initialized Git repository with no commits and no files is also
|
|
29
|
+
accepted and its `.git` directory is preserved. To reserve a custom domain during setup, add
|
|
30
|
+
`--domain blog.example.com`; Gala still requires GitHub ownership verification and healthy DNS
|
|
31
|
+
before activating it.
|
|
67
32
|
|
|
68
|
-
|
|
33
|
+
It signs you in to Gala and to GitHub if you are not already, creates the repository, registers the
|
|
34
|
+
publication, and leaves a working checkout in the folder. When it finishes it prints the address
|
|
35
|
+
your publication will live at.
|
|
69
36
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
37
|
+
If the Gala GitHub App has not been given access to the new repository, it says so and links to the
|
|
38
|
+
one page that grants it — GitHub has no way for an app to grant itself access, so that click is
|
|
39
|
+
unavoidable. Everything else is automatic.
|
|
73
40
|
|
|
74
|
-
|
|
75
|
-
npx --yes @rathnasgala/cli@latest scaffold \
|
|
76
|
-
--owner YOUR_GITHUB_USERNAME \
|
|
77
|
-
--repository YOUR_REPOSITORY_NAME \
|
|
78
|
-
--target ./YOUR_REPOSITORY_NAME \
|
|
79
|
-
--installation-id YOUR_INSTALLATION_ID
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
`--repository` is otherwise taken from `--target`, then from `--site-name`, and only then asked
|
|
83
|
-
for. Outside a terminal — in CI — nothing is ever prompted for: a value that cannot be derived is
|
|
84
|
-
an error, so an automated run fails fast instead of waiting for an answer that will not come.
|
|
85
|
-
|
|
86
|
-
## Command reference
|
|
87
|
-
|
|
88
|
-
Run commands through `npx` without installing a global package:
|
|
41
|
+
## Write
|
|
89
42
|
|
|
90
43
|
```console
|
|
91
|
-
npx --yes @rathnasgala/cli@latest
|
|
44
|
+
npx --yes @rathnasgala/cli@latest new "The places we return to"
|
|
92
45
|
```
|
|
93
46
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
| Command | Purpose | Common options |
|
|
97
|
-
| --- | --- | --- |
|
|
98
|
-
| `gala auth` | Authenticate the author with Gala | `--api-base-url URL` for a non-production API |
|
|
99
|
-
| `gala auth github` | Authenticate the CLI with GitHub | Browser device flow; requests `repo workflow` |
|
|
100
|
-
| `gala scaffold` | Sign in if needed, then create and register a publication | All derived; override with `--owner`, `--repository`, `--target`, `--installation-id` |
|
|
101
|
-
| `gala configure` | Update author-owned site and design settings | `--root`, plus the configuration options below |
|
|
102
|
-
| `gala new` | Create a Markdown post variant | `--root`, `--title`, `--language`, `--today` |
|
|
103
|
-
| `gala validate` | Validate repository content without publishing | optional root path, `--today` |
|
|
104
|
-
| `gala preview` | Validate and run the local Eleventy preview | `--root`, `--today` |
|
|
105
|
-
| `gala publish` | Validate, commit, and push publication changes | `--root`, `--today`, `--force` |
|
|
106
|
-
| `gala doctor` | Report managed-framework drift and publication-state validity | optional root path; `--fix --source TRUSTED_ROOT` |
|
|
107
|
-
| `gala hook install` | Install the pre-push validation hook | `--root` |
|
|
108
|
-
| `gala refresh` | Refresh and commit the engagement snapshot | `--root` |
|
|
109
|
-
| `gala upgrade` | Verify and install an exact theme-package release | `--root`, `--channel`, `--yes` |
|
|
110
|
-
| `gala topology` | Switch canonical origin/path topology transactionally | `--root`, `--owner`, `--repository`, `--canonical-base-url`, `--path-prefix` |
|
|
111
|
-
| `gala entitlement` | Retrieve and commit the current paid attribution artifact | `--root` |
|
|
112
|
-
| `gala workflow` | Write the reusable GitHub Actions workflow | `--root`, `--site-id`, `--timezone`, `--action-ref`, `--default-branch`, `--mode` |
|
|
113
|
-
| `gala record-deployment` | Record state after a successful deployment | `--root`, `--today`, `--commit-sha` |
|
|
114
|
-
|
|
115
|
-
### Scaffold and configure options
|
|
116
|
-
|
|
117
|
-
The same author-owned options are accepted by `scaffold` and `configure`:
|
|
118
|
-
|
|
119
|
-
```text
|
|
120
|
-
--site-name
|
|
121
|
-
--author
|
|
122
|
-
--language
|
|
123
|
-
--timezone
|
|
124
|
-
--theme
|
|
125
|
-
--layout
|
|
126
|
-
--palette
|
|
127
|
-
--typography
|
|
128
|
-
--spacing
|
|
129
|
-
--radius
|
|
130
|
-
--density
|
|
131
|
-
--motion
|
|
132
|
-
--componentStyle
|
|
133
|
-
--share-target repeatable
|
|
134
|
-
--social-profile repeatable
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Use only identities supported by the installed theme package. Validation rejects unavailable layout, palette, and theme identities instead of silently substituting another design.
|
|
138
|
-
|
|
139
|
-
### Scaffold an existing empty repository
|
|
140
|
-
|
|
141
|
-
Use this only when the exact GitHub repository already exists and has no branches or content:
|
|
47
|
+
This creates the Markdown file and tells you the address the post will appear at. Write below the
|
|
48
|
+
second `---` line.
|
|
142
49
|
|
|
143
50
|
```console
|
|
144
|
-
npx --yes @rathnasgala/cli@latest
|
|
145
|
-
--repository YOUR_REPOSITORY_NAME \
|
|
146
|
-
--empty-existing-repository
|
|
51
|
+
npx --yes @rathnasgala/cli@latest preview
|
|
147
52
|
```
|
|
148
53
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
54
|
+
Builds the publication and serves it locally, using the exact framework version the repository is
|
|
55
|
+
pinned to — so what you see is what gets published. The first run installs that tooling, which takes
|
|
56
|
+
a moment. Stop it with Ctrl-C.
|
|
152
57
|
|
|
153
58
|
```console
|
|
154
|
-
npx --yes @rathnasgala/cli@latest
|
|
155
|
-
--repository YOUR_REPOSITORY_NAME \
|
|
156
|
-
--target ./YOUR_REPOSITORY_NAME \
|
|
157
|
-
--resume
|
|
59
|
+
npx --yes @rathnasgala/cli@latest publish
|
|
158
60
|
```
|
|
159
61
|
|
|
160
|
-
|
|
62
|
+
Checks your content, records it, and sends it to GitHub. GitHub builds and deploys from there; the
|
|
63
|
+
site updates a minute or two later.
|
|
161
64
|
|
|
162
|
-
##
|
|
65
|
+
## Prism configurations
|
|
163
66
|
|
|
164
|
-
|
|
67
|
+
Prism keeps one canonical work while letting you explicitly approve alternate reading depths and
|
|
68
|
+
intents. The CLI uses Gala's public lifecycle API; it never writes approval artifacts itself.
|
|
165
69
|
|
|
166
70
|
```console
|
|
167
|
-
npx --yes @rathnasgala/cli@latest
|
|
71
|
+
npx --yes @rathnasgala/cli@latest prism status
|
|
72
|
+
npx --yes @rathnasgala/cli@latest prism create my-post --language en --depth brief --intent orientation
|
|
73
|
+
npx --yes @rathnasgala/cli@latest prism list my-post --language en
|
|
168
74
|
```
|
|
169
75
|
|
|
170
|
-
|
|
76
|
+
Use `prism edit`, `submit`, `approve`, `reject`, and `revoke` to advance a configuration. Approval,
|
|
77
|
+
rejection, revocation, and reducing the publication mode require terminal confirmation or `--yes`.
|
|
78
|
+
Configuration links default to `nofollow`; change the publication or one work with
|
|
79
|
+
`prism link-policy` when ordinary followed links are intentional. Commands that change repository
|
|
80
|
+
artifacts stay attached through Gala's materialization and GitHub Pages publication states, then
|
|
81
|
+
print the live publication address or a concrete terminal failure. Proposal generation likewise
|
|
82
|
+
waits until its revision is ready for review or generation fails.
|
|
171
83
|
|
|
172
|
-
|
|
173
|
-
npx --yes @rathnasgala/cli@latest validate
|
|
174
|
-
```
|
|
84
|
+
## Custom domain
|
|
175
85
|
|
|
176
|
-
|
|
86
|
+
Reserve a domain after setup, then advance the verified GitHub Pages flow as DNS propagates:
|
|
177
87
|
|
|
178
88
|
```console
|
|
179
|
-
npx --yes @rathnasgala/cli@latest
|
|
89
|
+
npx --yes @rathnasgala/cli@latest domain set blog.example.com
|
|
90
|
+
npx --yes @rathnasgala/cli@latest domain check
|
|
180
91
|
```
|
|
181
92
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
```console
|
|
185
|
-
npx --yes @rathnasgala/cli@latest publish
|
|
186
|
-
```
|
|
93
|
+
`domain status` resumes an interrupted change, `domain cancel` abandons it, and `domain remove`
|
|
94
|
+
returns the publication to its GitHub Pages address. After removal, delete the old DNS records.
|
|
187
95
|
|
|
188
|
-
|
|
96
|
+
## When something is wrong
|
|
189
97
|
|
|
190
98
|
```console
|
|
191
99
|
npx --yes @rathnasgala/cli@latest doctor
|
|
192
100
|
```
|
|
193
101
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
- Your repository remains the canonical source for publication content and configuration.
|
|
197
|
-
- Gala credentials and GitHub App credentials are stored outside the repository.
|
|
198
|
-
- Credential directories are created with private permissions; credential files use mode `0600` on operating systems that support POSIX modes.
|
|
199
|
-
- The site signing secret is returned once by the API and sealed directly into GitHub Actions secrets.
|
|
200
|
-
- Do not copy credential files into the repository, dotfiles, cloud-sync folders, or `/tmp`.
|
|
201
|
-
- The generated workflow pins the public Gala Action contract; managed framework files are integrity-checked before repair or upgrade.
|
|
202
|
-
|
|
203
|
-
## Troubleshooting
|
|
204
|
-
|
|
205
|
-
### `GitHub authentication is missing`
|
|
206
|
-
|
|
207
|
-
Run:
|
|
102
|
+
Inspect a verified managed-theme release without changing anything unless you confirm it:
|
|
208
103
|
|
|
209
104
|
```console
|
|
210
|
-
npx --yes @rathnasgala/cli@latest
|
|
105
|
+
npx --yes @rathnasgala/cli@latest upgrade
|
|
211
106
|
```
|
|
212
107
|
|
|
213
|
-
|
|
108
|
+
Reports on your sign-ins, the publication folder, the publishing workflow, and anything you have
|
|
109
|
+
written but not sent. Each check either passes, names what is wrong and how to fix it, or says it
|
|
110
|
+
could not be determined — never one of those disguised as another.
|
|
214
111
|
|
|
215
|
-
|
|
112
|
+
## Commands
|
|
216
113
|
|
|
217
|
-
|
|
218
|
-
npx --yes @rathnasgala/cli@latest auth
|
|
219
|
-
```
|
|
114
|
+
Run any command with `--help`.
|
|
220
115
|
|
|
221
|
-
|
|
116
|
+
| Command | What it does | Options |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `init` | Create a publication in the current or named empty directory | `--name`, `--domain` |
|
|
119
|
+
| `domain` | Inspect or change the custom domain | `--root` |
|
|
120
|
+
| `new` | Start a post | `--language`, `--root`, `--today` |
|
|
121
|
+
| `preview` | Build and serve the publication locally | `--root`, `--today` |
|
|
122
|
+
| `publish` | Check, record and send your work to GitHub | `--root`, `--today`, `--skip-checks` |
|
|
123
|
+
| `prism` | Manage author-approved reading configurations | `--root`, `--language`, `--depth`, `--intent`, `--modality`, `--file`, `--reason`, `--yes` |
|
|
124
|
+
| `upgrade` | Inspect and apply a verified managed-theme update | `--root`, `--channel`, `--yes` |
|
|
125
|
+
| `doctor` | Check a publication and say what is wrong | `--root` |
|
|
126
|
+
| `auth` | Sign in to Gala and GitHub | — |
|
|
222
127
|
|
|
223
|
-
`
|
|
224
|
-
|
|
225
|
-
at [the installation page](https://github.com/apps/gala67-app/installations/new) and run `scaffold`
|
|
226
|
-
again, or pass `--installation-id` explicitly.
|
|
128
|
+
`auth` is never a prerequisite you have to remember: any command that needs a credential obtains
|
|
129
|
+
one. It exists for when you want to do it deliberately — a new machine, or a different account.
|
|
227
130
|
|
|
228
|
-
|
|
131
|
+
Every command prompts for what it needs when run in a terminal, and every prompt has an option that
|
|
132
|
+
supplies it instead. With no terminal attached — in CI — nothing is ever prompted for: a value that
|
|
133
|
+
cannot be worked out is an error naming the option, so an automated run fails immediately rather
|
|
134
|
+
than waiting for an answer that will not come.
|
|
229
135
|
|
|
230
|
-
|
|
136
|
+
## Access
|
|
231
137
|
|
|
232
|
-
|
|
138
|
+
The CLI signs in as the **Gala GitHub App**. It reaches only the repositories you have given that
|
|
139
|
+
App, and never asks for the broad `repo` scope, which would have meant read and write access to
|
|
140
|
+
every repository you can see.
|
|
233
141
|
|
|
234
|
-
|
|
142
|
+
Credentials are stored outside the publication, in your operating system's application-config
|
|
143
|
+
directory, with private file permissions. Your GitHub sign-in expires after eight hours and the CLI
|
|
144
|
+
asks you to sign in again rather than quietly using a credential the server will refuse.
|
|
235
145
|
|
|
236
|
-
|
|
146
|
+
Git operations use that same sign-in, not whatever credential your machine happens to have
|
|
147
|
+
configured — so publishing works on a machine where those differ, or where none is configured.
|
|
237
148
|
|
|
238
|
-
|
|
149
|
+
## Troubleshooting
|
|
150
|
+
|
|
151
|
+
### `GitHub authentication expired`
|
|
152
|
+
|
|
153
|
+
Sign in again:
|
|
239
154
|
|
|
240
155
|
```console
|
|
241
|
-
npx --yes @rathnasgala/cli@latest
|
|
156
|
+
npx --yes @rathnasgala/cli@latest auth
|
|
242
157
|
```
|
|
243
158
|
|
|
244
|
-
|
|
159
|
+
### Gala cannot reach the repository
|
|
245
160
|
|
|
246
|
-
|
|
161
|
+
The Gala GitHub App is installed but has not been given this repository. The command prints a link
|
|
162
|
+
to the installation that needs it; add the one repository and continue. Nothing else needs granting.
|
|
247
163
|
|
|
248
|
-
|
|
164
|
+
### A post is not appearing
|
|
249
165
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
```
|
|
166
|
+
Run `doctor`. The most common cause is work written but never sent, which it reports along with the
|
|
167
|
+
command to fix it.
|
|
253
168
|
|
|
254
|
-
|
|
169
|
+
### Something failed and the message was not enough
|
|
255
170
|
|
|
256
171
|
```console
|
|
257
|
-
npx --yes @rathnasgala/cli@latest
|
|
172
|
+
GALA_DEBUG=1 npx --yes @rathnasgala/cli@latest publish
|
|
258
173
|
```
|
|
259
174
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
- npm: [`@rathnasgala/cli`](https://www.npmjs.com/package/@rathnasgala/cli)
|
|
263
|
-
- source: [`rathnasgala/cli`](https://github.com/rathnasgala/cli)
|
|
175
|
+
Prints the full stack. Without it, failures are one line you can act on.
|
|
264
176
|
|
|
265
|
-
##
|
|
177
|
+
## Package and source
|
|
266
178
|
|
|
267
|
-
|
|
179
|
+
Published as [`@rathnasgala/cli`](https://www.npmjs.com/package/@rathnasgala/cli). Source at
|
|
180
|
+
[rathnasgala/cli](https://github.com/rathnasgala/cli).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rathnasgala/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"gala": "src/index.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "node --test",
|
|
16
|
+
"test": "node --test test/*.test.js",
|
|
17
17
|
"lint": "node scripts/lint.js",
|
|
18
18
|
"preversion": "npm test && npm run lint",
|
|
19
19
|
"push": "node scripts/push.js"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
package/src/api/gala.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { request, requestJson } from './http.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Gala API.
|
|
5
|
+
*
|
|
6
|
+
* Only the calls the six commands make. v0 carried a generated client covering the whole surface —
|
|
7
|
+
* comments, reactions, admin, moderation — none of which a CLI ever touches.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_API_BASE_URL = 'https://api.gala67.com';
|
|
10
|
+
|
|
11
|
+
export function galaApi({ baseUrl = DEFAULT_API_BASE_URL, token } = {}) {
|
|
12
|
+
const root = String(baseUrl).replace(/\/$/, '');
|
|
13
|
+
const authorized = (action, extra = {}) => ({
|
|
14
|
+
action,
|
|
15
|
+
...extra,
|
|
16
|
+
headers: {
|
|
17
|
+
accept: 'application/json',
|
|
18
|
+
...(token == null ? {} : { authorization: `Bearer ${token}` }),
|
|
19
|
+
...extra.headers
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
baseUrl: root,
|
|
25
|
+
|
|
26
|
+
/** Cheap authenticated call, used to find out whether a stored credential is still accepted. */
|
|
27
|
+
async accepted() {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${root}/v1/me/sites`, {
|
|
30
|
+
headers: { accept: 'application/json', authorization: `Bearer ${token}` }
|
|
31
|
+
});
|
|
32
|
+
// 403 means "not an author yet", which is a stage of the product, not a dead credential.
|
|
33
|
+
return response.status !== 401;
|
|
34
|
+
} catch {
|
|
35
|
+
// Offline is not an answer. Forcing a sign-in the writer does not need is worse than
|
|
36
|
+
// letting the real call fail with its own error.
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/** Exchanges the GitHub token for the short-lived capability the GitHub-scoped routes require. */
|
|
42
|
+
async githubCapability(githubToken) {
|
|
43
|
+
const body = await requestJson(`${root}/v1/auth/github/device-authorizations`,
|
|
44
|
+
authorized('GitHub authorization', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'content-type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ accessToken: githubToken })
|
|
48
|
+
}));
|
|
49
|
+
if (typeof body?.authorization !== 'string') {
|
|
50
|
+
throw new TypeError('GitHub authorization returned no capability');
|
|
51
|
+
}
|
|
52
|
+
return body.authorization;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates the publication repository, the same call the browser editor makes.
|
|
57
|
+
*
|
|
58
|
+
* v0 called GitHub's template endpoint itself: a second implementation with no fallback and no
|
|
59
|
+
* wait for the App installation to reach the result, which is why repositories the CLI created
|
|
60
|
+
* never appeared in the web UI.
|
|
61
|
+
*/
|
|
62
|
+
createPublication({ capability, name, installationId }) {
|
|
63
|
+
return requestJson(`${root}/v1/auth/github/publications`,
|
|
64
|
+
authorized('Publication creation', {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'content-type': 'application/json', 'GitHub-Authorization': capability },
|
|
67
|
+
body: JSON.stringify({ name, installationId })
|
|
68
|
+
}));
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
githubInstallationAccounts({ capability }) {
|
|
72
|
+
return requestJson(`${root}/v1/auth/github/accounts`,
|
|
73
|
+
authorized('GitHub installation accounts', {
|
|
74
|
+
headers: { 'GitHub-Authorization': capability }
|
|
75
|
+
}));
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
registerSite({ capability, idempotencyKey, repositoryOwner, repositoryName, topology, canonicalBaseUrl }) {
|
|
79
|
+
return requestJson(`${root}/v1/sites`,
|
|
80
|
+
authorized('Site registration', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'content-type': 'application/json',
|
|
84
|
+
'GitHub-Authorization': capability,
|
|
85
|
+
'idempotency-key': idempotencyKey
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ repositoryOwner, repositoryName, topology, canonicalBaseUrl })
|
|
88
|
+
}));
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
listPublications() {
|
|
92
|
+
return requestJson(`${root}/v1/me/sites`, authorized('Publication list'));
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
prepareTopologyChange(siteId, body) {
|
|
96
|
+
return requestJson(`${root}/v1/sites/${encodeURIComponent(siteId)}/topology-changes/prepare`,
|
|
97
|
+
authorized('Custom domain reservation', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'content-type': 'application/json' },
|
|
100
|
+
body: JSON.stringify(body)
|
|
101
|
+
}));
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
pendingTopologyChange(siteId) {
|
|
105
|
+
return requestJson(`${root}/v1/sites/${encodeURIComponent(siteId)}/topology-changes/pending`,
|
|
106
|
+
authorized('Custom domain status'));
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
configureTopologyChange(siteId, changeId) {
|
|
110
|
+
return requestJson(`${root}/v1/sites/${encodeURIComponent(siteId)}/topology-changes/${encodeURIComponent(changeId)}/configure`,
|
|
111
|
+
authorized('GitHub Pages domain verification', { method: 'POST' }));
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
commitTopologyChange(siteId, changeId) {
|
|
115
|
+
return requestJson(`${root}/v1/sites/${encodeURIComponent(siteId)}/topology-changes/${encodeURIComponent(changeId)}/commit`,
|
|
116
|
+
authorized('Custom domain activation', { method: 'POST' }));
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
discardTopologyChange(siteId, changeId) {
|
|
120
|
+
return request(`${root}/v1/sites/${encodeURIComponent(siteId)}/topology-changes/${encodeURIComponent(changeId)}`,
|
|
121
|
+
authorized('Custom domain cancellation', { method: 'DELETE' }));
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/** Not in the OpenAPI document, though the endpoint exists and is public. */
|
|
125
|
+
async signInConfiguration() {
|
|
126
|
+
return requestJson(`${root}/v1/auth/configuration`, { action: 'Sign-in configuration' });
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* Form-encoded, not JSON. These are RFC 8628 device-flow endpoints and the spec declares them
|
|
131
|
+
* as `application/x-www-form-urlencoded`; sending JSON gets a bare "Authentication is required",
|
|
132
|
+
* which reads as a credential problem and is nothing of the kind.
|
|
133
|
+
*/
|
|
134
|
+
async startDeviceAuthorization() {
|
|
135
|
+
return requestJson(`${root}/v1/auth/device/code`, {
|
|
136
|
+
action: 'Gala sign-in',
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded' },
|
|
139
|
+
body: new URLSearchParams({ client_id: 'gala-cli' }).toString()
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/** Returns the token, or null while the writer has not finished authorizing. */
|
|
144
|
+
async pollDeviceAuthorization(deviceCode) {
|
|
145
|
+
const response = await fetch(`${root}/v1/auth/device/token`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded' },
|
|
148
|
+
body: new URLSearchParams({
|
|
149
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
150
|
+
device_code: deviceCode,
|
|
151
|
+
client_id: 'gala-cli'
|
|
152
|
+
}).toString()
|
|
153
|
+
});
|
|
154
|
+
const body = await response.json().catch(() => null);
|
|
155
|
+
if (response.ok) return body;
|
|
156
|
+
if (body?.error === 'authorization_pending' || body?.error === 'slow_down') return null;
|
|
157
|
+
throw new Error(`Gala sign-in failed: ${body?.error_description ?? body?.error ?? response.status}`);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
request: (path, options) => request(`${root}${path}`, authorized(options?.action ?? path, options)),
|
|
161
|
+
json: (path, options) => requestJson(`${root}${path}`, authorized(options?.action ?? path, options))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { requestJson } from './http.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GitHub, as the Gala App.
|
|
5
|
+
*
|
|
6
|
+
* The CLI holds a GitHub App user token, not an OAuth App token. That single difference is what
|
|
7
|
+
* separates this from v0: an App token can list installations, is not blocked by an organisation's
|
|
8
|
+
* OAuth App restrictions, and reaches only repositories the App has been given — rather than every
|
|
9
|
+
* repository the writer can see, which is what `repo` scope meant.
|
|
10
|
+
*/
|
|
11
|
+
const API = 'https://api.github.com';
|
|
12
|
+
const API_VERSION = '2026-03-10';
|
|
13
|
+
|
|
14
|
+
export function githubApi(token) {
|
|
15
|
+
const headers = {
|
|
16
|
+
accept: 'application/vnd.github+json',
|
|
17
|
+
authorization: `Bearer ${token}`,
|
|
18
|
+
'x-github-api-version': API_VERSION
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
/** The account behind the token. Works for every token type and needs no permission. */
|
|
23
|
+
async viewer() {
|
|
24
|
+
const body = await requestJson(`${API}/user`, { action: 'GitHub account lookup', headers });
|
|
25
|
+
const login = body?.login;
|
|
26
|
+
if (typeof login !== 'string' || login === '') {
|
|
27
|
+
throw new TypeError('GitHub returned an unusable account login');
|
|
28
|
+
}
|
|
29
|
+
return login;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether a repository has content yet.
|
|
34
|
+
*
|
|
35
|
+
* Creating a repository is asynchronous: GitHub answers with a clone URL before the template
|
|
36
|
+
* lands. Cloning into that window produces an empty checkout and a missing site.config.yml —
|
|
37
|
+
* a confusing error about a file the template certainly contains. `size` is not usable as the
|
|
38
|
+
* signal; GitHub still reported 0 for a repository that already had commits.
|
|
39
|
+
*/
|
|
40
|
+
async hasContent(owner, repository) {
|
|
41
|
+
const branches = await requestJson(
|
|
42
|
+
`${API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/branches?per_page=1`,
|
|
43
|
+
{ action: 'GitHub branch lookup', headers }
|
|
44
|
+
);
|
|
45
|
+
return Array.isArray(branches) && branches.length > 0;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
setVariable(owner, repository, name, value) {
|
|
49
|
+
const base = `${API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/actions/variables`;
|
|
50
|
+
return requestJson(`${base}/${encodeURIComponent(name)}`, {
|
|
51
|
+
action: 'GitHub repository variable',
|
|
52
|
+
method: 'PATCH',
|
|
53
|
+
headers: { ...headers, 'content-type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ name, value })
|
|
55
|
+
}).catch(() => requestJson(base, {
|
|
56
|
+
action: 'GitHub repository variable',
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { ...headers, 'content-type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ name, value })
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|