alpha-gate 0.1.0

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.
Files changed (140) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +138 -0
  4. package/bin/alpha-gate.mjs +75 -0
  5. package/deploy/backup.sh +45 -0
  6. package/deploy/deploy.sh +21 -0
  7. package/deploy/dev.sh +56 -0
  8. package/deploy/lib/statedir.sh +11 -0
  9. package/deploy/teardown.sh +17 -0
  10. package/docs/ONBOARDING.md +16 -0
  11. package/docs/PRINCIPLES.md +195 -0
  12. package/docs/README.md +54 -0
  13. package/docs/UPLOADING.md +14 -0
  14. package/docs/integrate/activation.md +56 -0
  15. package/docs/integrate/sparkle-go.md +106 -0
  16. package/docs/integrate/sparkle-swift.md +63 -0
  17. package/docs/maintain/backup.md +52 -0
  18. package/docs/maintain/migrate-account.md +95 -0
  19. package/docs/maintain/teardown.md +46 -0
  20. package/docs/maintain/troubleshooting.md +102 -0
  21. package/docs/maintain/updating.md +92 -0
  22. package/docs/operate/add-users.md +55 -0
  23. package/docs/operate/channels.md +54 -0
  24. package/docs/operate/email.md +51 -0
  25. package/docs/operate/monitoring.md +56 -0
  26. package/docs/operate/publish.md +90 -0
  27. package/docs/operate/remove-users.md +47 -0
  28. package/docs/setup/cloudflare-account.md +41 -0
  29. package/docs/setup/deploy.md +84 -0
  30. package/docs/setup/install.md +65 -0
  31. package/migrations/0001_clients.sql +12 -0
  32. package/migrations/0002_builds_streams.sql +30 -0
  33. package/migrations/0003_access_log.sql +14 -0
  34. package/migrations/0004_meta.sql +5 -0
  35. package/migrations/0005_admin_audit.sql +14 -0
  36. package/migrations/0006_build_dmg.sql +6 -0
  37. package/migrations/0007_access_requests.sql +12 -0
  38. package/migrations/0008_build_rollback_target.sql +7 -0
  39. package/migrations/0009_hidden.sql +6 -0
  40. package/migrations/0010_purged_at.sql +5 -0
  41. package/package.json +65 -0
  42. package/publish.sh +302 -0
  43. package/release.json +7 -0
  44. package/src/auth/access-jwt.ts +210 -0
  45. package/src/auth/token-gate.ts +27 -0
  46. package/src/core/appcast.ts +107 -0
  47. package/src/core/audit-chain.ts +145 -0
  48. package/src/core/invite-template.ts +127 -0
  49. package/src/core/no-build.ts +160 -0
  50. package/src/core/resolver.ts +60 -0
  51. package/src/core/tokens.ts +51 -0
  52. package/src/core/types.ts +76 -0
  53. package/src/core/validation.ts +60 -0
  54. package/src/core/verdict.ts +195 -0
  55. package/src/core/version.ts +113 -0
  56. package/src/cron.ts +37 -0
  57. package/src/db/access-log.ts +168 -0
  58. package/src/db/access-requests.ts +90 -0
  59. package/src/db/admin-audit.ts +101 -0
  60. package/src/db/builds.ts +169 -0
  61. package/src/db/client.ts +80 -0
  62. package/src/db/clients.ts +103 -0
  63. package/src/db/meta.ts +30 -0
  64. package/src/db/streams.ts +85 -0
  65. package/src/deploy/cli.ts +181 -0
  66. package/src/deploy/commands/deploy.ts +392 -0
  67. package/src/deploy/commands/dev.ts +190 -0
  68. package/src/deploy/commands/teardown.ts +159 -0
  69. package/src/deploy/core/args.ts +205 -0
  70. package/src/deploy/core/colors.ts +49 -0
  71. package/src/deploy/core/config.ts +65 -0
  72. package/src/deploy/core/parse.ts +51 -0
  73. package/src/deploy/core/paths.ts +27 -0
  74. package/src/deploy/core/plan.ts +138 -0
  75. package/src/deploy/core/result.ts +13 -0
  76. package/src/deploy/core/state.ts +64 -0
  77. package/src/deploy/core/table.ts +49 -0
  78. package/src/deploy/core/types.ts +39 -0
  79. package/src/deploy/core/ui.ts +107 -0
  80. package/src/deploy/seams/clock.ts +10 -0
  81. package/src/deploy/seams/files.ts +52 -0
  82. package/src/deploy/seams/io.ts +56 -0
  83. package/src/deploy/seams/wrangler.ts +100 -0
  84. package/src/deploy/ui-preview.ts +112 -0
  85. package/src/deps.ts +41 -0
  86. package/src/dev/admin-entry.ts +104 -0
  87. package/src/env.ts +47 -0
  88. package/src/lib/clock.ts +17 -0
  89. package/src/lib/hosts.ts +27 -0
  90. package/src/lib/text.ts +6 -0
  91. package/src/r2/builds-bucket.ts +50 -0
  92. package/src/r2/keys.ts +27 -0
  93. package/src/routes/admin/admin-context.ts +9 -0
  94. package/src/routes/admin/audit-fields.ts +22 -0
  95. package/src/routes/admin/branding.tsx +161 -0
  96. package/src/routes/admin/builds.tsx +388 -0
  97. package/src/routes/admin/clients.tsx +396 -0
  98. package/src/routes/admin/confirm.tsx +80 -0
  99. package/src/routes/admin/flash.ts +72 -0
  100. package/src/routes/admin/form.ts +43 -0
  101. package/src/routes/admin/index.ts +146 -0
  102. package/src/routes/admin/invite.ts +57 -0
  103. package/src/routes/admin/middleware.ts +52 -0
  104. package/src/routes/admin/negotiate.ts +13 -0
  105. package/src/routes/admin/pending.tsx +73 -0
  106. package/src/routes/admin/read-model.ts +518 -0
  107. package/src/routes/admin/streams.tsx +182 -0
  108. package/src/routes/admin/theme.ts +34 -0
  109. package/src/routes/admin/upload.tsx +320 -0
  110. package/src/routes/admin/views.tsx +293 -0
  111. package/src/routes/app/access.tsx +38 -0
  112. package/src/routes/app/app-context.ts +8 -0
  113. package/src/routes/app/appcast.ts +68 -0
  114. package/src/routes/app/assets.ts +25 -0
  115. package/src/routes/app/download.ts +45 -0
  116. package/src/routes/app/get.tsx +35 -0
  117. package/src/routes/app/index.ts +31 -0
  118. package/src/routes/app/resolve.ts +16 -0
  119. package/src/services/anchor.ts +54 -0
  120. package/src/services/audit.ts +19 -0
  121. package/src/services/branding.ts +51 -0
  122. package/src/services/email-cloudflare.ts +80 -0
  123. package/src/services/email.ts +68 -0
  124. package/src/services/self-update.ts +62 -0
  125. package/src/views/access-page.tsx +39 -0
  126. package/src/views/admin/ci-page.tsx +76 -0
  127. package/src/views/admin/combobox.tsx +191 -0
  128. package/src/views/admin/forms.tsx +37 -0
  129. package/src/views/admin/layout.tsx +496 -0
  130. package/src/views/admin/manage-pages.tsx +223 -0
  131. package/src/views/admin/manage.tsx +1120 -0
  132. package/src/views/admin/plist-extract.ts +233 -0
  133. package/src/views/admin/read-pages.tsx +1098 -0
  134. package/src/views/admin/setup-page.tsx +108 -0
  135. package/src/views/admin/table-enhance.ts +176 -0
  136. package/src/views/admin/ui.tsx +159 -0
  137. package/src/views/get-page.tsx +39 -0
  138. package/src/views/layout.tsx +57 -0
  139. package/src/worker.ts +23 -0
  140. package/tsconfig.json +35 -0
@@ -0,0 +1,223 @@
1
+ import type { Child, FC } from "hono/jsx";
2
+ import { COPY_SCRIPT } from "./forms";
3
+ import { AdminLayout } from "./layout";
4
+ import { Tag } from "./ui";
5
+
6
+ // Result/confirmation pages for admin mutations. InvitePage surfaces the copy-paste link + message
7
+ // (§13 add user); ConfirmPage is the §11 "this would strand these users — proceed?" gate;
8
+ // ConfirmActionPage confirms destructive-but-not-stranding actions; ResultPage is the generic
9
+ // success/error landing for a browser form post (the HTML half of content negotiation).
10
+
11
+ /** Generic outcome page for a browser form submit: a status line, a message, and a way back. */
12
+ export const ResultPage: FC<{
13
+ title: string;
14
+ intent?: "success" | "error";
15
+ back?: { href: string; label: string };
16
+ children?: Child;
17
+ }> = ({ title, intent = "success", back, children }) => (
18
+ <AdminLayout title={title}>
19
+ {intent === "error" ? <Tag kind="warn" label="nothing was changed" /> : null}
20
+ {children}
21
+ {back ? (
22
+ <p class="actions">
23
+ <a class="btn" href={back.href}>
24
+ {back.label}
25
+ </a>
26
+ </p>
27
+ ) : null}
28
+ </AdminLayout>
29
+ );
30
+
31
+ export const InvitePage: FC<{
32
+ email: string;
33
+ getUrl: string;
34
+ delivery?: { sent: boolean; error?: string } | undefined;
35
+ /** The rendered invite (template + link) — shown in copy-paste mode so the operator can send it. */
36
+ message?: { subject: string; body: string } | undefined;
37
+ /** True when this invite also reactivated a previously revoked user. */
38
+ restored?: boolean;
39
+ }> = ({ email, getUrl, delivery, message, restored }) => (
40
+ <AdminLayout title={`Invite ready — ${email}`}>
41
+ {restored ? (
42
+ <p class="callout ok">
43
+ {email} was revoked — their access is now restored with the fresh link below (the old link
44
+ stays dead).
45
+ </p>
46
+ ) : null}
47
+ {delivery?.sent ? (
48
+ <p class="callout ok">Emailed to {email} — the link below is the same one they received.</p>
49
+ ) : delivery && !delivery.sent ? (
50
+ <p class="callout warn">
51
+ The user was created, but the invite email to <strong>{email}</strong> didn't send:{" "}
52
+ {delivery.error || "delivery failed"}. Send the link below manually, or fix email under{" "}
53
+ <a href="/admin/settings">Settings</a>.
54
+ </p>
55
+ ) : null}
56
+
57
+ <section aria-label="Link">
58
+ <div class="slab">
59
+ <h2>Private link</h2>
60
+ <span class="hint">durable while the token is active — they can revisit it</span>
61
+ </div>
62
+ <code class="token" id="invite-link">
63
+ {getUrl}
64
+ </code>
65
+ <p class="actions">
66
+ <button type="button" class="btn btn-primary" data-copy="#invite-link">
67
+ Copy link
68
+ </button>
69
+ </p>
70
+ </section>
71
+
72
+ {message && !delivery?.sent ? (
73
+ <section aria-label="Message">
74
+ <div class="slab">
75
+ <h2>Message to send</h2>
76
+ <span class="hint">your invite template, filled in — paste it anywhere</span>
77
+ </div>
78
+ <code class="token" id="invite-message">
79
+ {message.body}
80
+ </code>
81
+ <p class="actions">
82
+ <button type="button" class="btn" data-copy="#invite-message">
83
+ Copy message
84
+ </button>
85
+ </p>
86
+ </section>
87
+ ) : null}
88
+
89
+ <p class="actions">
90
+ <a class="btn" href="/admin/users">
91
+ Back to users
92
+ </a>
93
+ </p>
94
+ {/* Progressive enhancement: the link is selectable without JS; this just adds one-click copy. */}
95
+ <script dangerouslySetInnerHTML={{ __html: COPY_SCRIPT }} />
96
+ </AdminLayout>
97
+ );
98
+
99
+ /**
100
+ * The §11 stranding confirm: names the exact action in operator language, lists who would be left
101
+ * with no available build (their apps report "up to date" and receive nothing), and returns to the
102
+ * page the operator came from on Cancel. `hidden` must carry everything the re-post needs.
103
+ */
104
+ export const ConfirmPage: FC<{
105
+ /** The action in operator words, e.g. "Withdraw build 1400 (1.3.1-beta)". */
106
+ subject: string;
107
+ affected: string[];
108
+ postTo: string;
109
+ hidden: Record<string, string>;
110
+ cancelTo: string;
111
+ }> = ({ subject, affected, postTo, hidden, cancelTo }) => (
112
+ <AdminLayout title="Are you sure?">
113
+ <p>
114
+ <strong>{subject}</strong> would leave {affected.length === 1 ? "this user" : "these users"}{" "}
115
+ with no available build:
116
+ </p>
117
+ <ul>
118
+ {affected.map((email) => (
119
+ <li>{email}</li>
120
+ ))}
121
+ </ul>
122
+ <p class="mut">
123
+ Their apps will report “up to date” and receive nothing until a higher build reaches them
124
+ (publish a newer build, reassign their channel, or adjust the pin).
125
+ </p>
126
+ <form method="post" action={postTo} class="actions">
127
+ {Object.entries(hidden).map(([name, value]) => (
128
+ <input type="hidden" name={name} value={value} />
129
+ ))}
130
+ <button type="submit" class="btn-danger">
131
+ {subject} anyway
132
+ </button>
133
+ <a class="btn" href={cancelTo}>
134
+ Cancel
135
+ </a>
136
+ </form>
137
+ </AdminLayout>
138
+ );
139
+
140
+ /** The §11 confirm for a bulk operation (§13 #3): re-posts the op and every selected id on confirm. */
141
+ export const BulkConfirmPage: FC<{
142
+ op: string;
143
+ ids: number[];
144
+ affected: string[];
145
+ postTo: string;
146
+ }> = ({ op, ids, affected, postTo }) => (
147
+ <AdminLayout title="Are you sure?">
148
+ <p>
149
+ <strong>
150
+ {op === "withdraw" ? "Withdraw" : op} {ids.length} {ids.length === 1 ? "build" : "builds"}
151
+ </strong>{" "}
152
+ would leave these users with no available build:
153
+ </p>
154
+ <ul>
155
+ {affected.map((email) => (
156
+ <li>{email}</li>
157
+ ))}
158
+ </ul>
159
+ <p class="mut">
160
+ Their apps will report “up to date” and receive nothing until a higher build reaches them.
161
+ </p>
162
+ <form method="post" action={postTo} class="actions">
163
+ <input type="hidden" name="op" value={op} />
164
+ <input type="hidden" name="confirm" value="true" />
165
+ {ids.map((id) => (
166
+ <input type="hidden" name="id" value={String(id)} />
167
+ ))}
168
+ <button type="submit" class="btn-danger">
169
+ Confirm anyway
170
+ </button>
171
+ <a class="btn" href="/admin/builds">
172
+ Cancel
173
+ </a>
174
+ </form>
175
+ </AdminLayout>
176
+ );
177
+
178
+ /**
179
+ * Confirmation for a destructive-but-not-stranding action (revoke, reissue, delete channel). States
180
+ * the action and its consequence in operator words; Cancel returns whence the operator came. The §11
181
+ * stranding list, when there is one, rides along via `affected`.
182
+ */
183
+ export const ConfirmActionPage: FC<{
184
+ /** The action in operator words, e.g. "Revoke alice@corner.studio". */
185
+ subject: string;
186
+ confirmLabel: string;
187
+ postTo: string;
188
+ hidden: Record<string, string>;
189
+ cancelTo: string;
190
+ affected?: string[];
191
+ children?: Child;
192
+ }> = ({ subject, confirmLabel, postTo, hidden, cancelTo, affected = [], children }) => (
193
+ <AdminLayout title="Are you sure?">
194
+ <p>
195
+ <strong>{subject}</strong>
196
+ </p>
197
+ {children}
198
+ {affected.length > 0 ? (
199
+ <>
200
+ <p>
201
+ This would also leave {affected.length === 1 ? "this user" : "these users"} with no
202
+ available build:
203
+ </p>
204
+ <ul>
205
+ {affected.map((email) => (
206
+ <li>{email}</li>
207
+ ))}
208
+ </ul>
209
+ </>
210
+ ) : null}
211
+ <form method="post" action={postTo} class="actions">
212
+ {Object.entries(hidden).map(([name, value]) => (
213
+ <input type="hidden" name={name} value={value} />
214
+ ))}
215
+ <button type="submit" class="btn-danger">
216
+ {confirmLabel}
217
+ </button>
218
+ <a class="btn" href={cancelTo}>
219
+ Cancel
220
+ </a>
221
+ </form>
222
+ </AdminLayout>
223
+ );