@devdogsuga/devtools 0.1.26 → 0.1.28

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 (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
@@ -1,234 +0,0 @@
1
- import { loadOpenGraph } from "../repo/source.js";
2
- import { UsageError } from "../ui.js";
3
- const BRAND = "apps/platform/public/brand";
4
- /** The two renditions anything may need, because anything may reach the GDG platform. */
5
- const GDG = ["gdgc-wide", "gdgc-square"];
6
- const EVENT_FORMATS = ["og", ...GDG, "involvement-network"];
7
- const EMAIL = ["email-1x", "email-2x", "email-3x"];
8
- /* ------------------------------------------------------------------- brand */
9
- /**
10
- * The club lockup, on each of the two grounds it is ever set on.
11
- *
12
- * One subject, two grounds, and the rendition decides which template draws it:
13
- * the email densities want the signature's tight horizontal strip, everything
14
- * else wants the banner. That is why "the email signature" is not a graphic of
15
- * its own — it is this lockup at `email-2x`, and its GDG export is the same
16
- * lockup at `gdgc-square` rather than a second drawing that has to be kept in
17
- * step with the first.
18
- */
19
- function brandGraphics(og) {
20
- return ["dark", "light"].map((ground) => ({
21
- name: ground === "dark" ? "brand/club" : "brand/club-light",
22
- group: "brand",
23
- stem: ground === "dark" ? "club" : "club-light",
24
- formats: [
25
- "og",
26
- ...GDG,
27
- ...(ground === "dark" ? ["savvycal"] : []),
28
- ...EMAIL,
29
- ],
30
- why: `The DevDogs lockup with the GDG chapter cobrand, on a ${ground} ground.`,
31
- render: (format) => format.family === "email"
32
- ? og.EmailSignature({ width: format.width, ground })
33
- : og.Banner({ width: format.width, height: format.height, ground }),
34
- destination: (format) => ({
35
- dir: format.family === "email" ? `${BRAND}/email` : `${BRAND}/banners`,
36
- file: format.family === "email"
37
- ? `signature-${ground}${format.scale > 1 ? `@${format.scale}x` : ""}.png`
38
- : `club-${ground === "dark" ? "" : "light-"}${format.name}.png`,
39
- }),
40
- }));
41
- }
42
- /* -------------------------------------------------------------------- pages */
43
- /**
44
- * One graphic per public page, keyed by the route it belongs to.
45
- *
46
- * The platform renders these live from `opengraph-image.tsx`, so the files
47
- * written here are for looking at — an Open Graph card is otherwise only
48
- * visible by deploying and pasting a link somewhere, which is a slow way to
49
- * find out that a title wrapped badly.
50
- */
51
- function pageGraphics(og) {
52
- return Object.entries(og.PAGE_CARDS).map(([route, copy]) => {
53
- const stem = route === "/" ? "home" : route.slice(1).replace(/\//g, "-");
54
- return {
55
- name: `page/${stem}`,
56
- group: "page",
57
- stem,
58
- formats: ["og", ...GDG],
59
- why: `The card ${route} unfurls as.`,
60
- render: (format) => og.PageCard({
61
- width: format.width,
62
- height: format.height,
63
- cobrand: true,
64
- ...copy,
65
- }),
66
- // The format is in the filename, not just the directory: `og` and
67
- // `gdgc-square` are the same page at two sizes, and without it the second
68
- // silently overwrites the first.
69
- destination: (format) => ({
70
- dir: `${BRAND}/og`,
71
- file: `${stem}-${format.name}.png`,
72
- }),
73
- };
74
- });
75
- }
76
- /* --------------------------------------------------------------------- apps */
77
- /** Which app's icons go where, and which of them Next serves by convention. */
78
- const APP_HOME = {
79
- platform: { icons: `${BRAND}/icons`, app: "apps/platform/src/app" },
80
- dogdays: {
81
- icons: "apps/schedule-builder/public/icons",
82
- app: "apps/schedule-builder/src/app",
83
- },
84
- // Flutter, and nothing deployed yet, so its icons wait in the club's own
85
- // brand folder rather than in an app directory that does not exist.
86
- dogpack: { icons: `${BRAND}/icons` },
87
- };
88
- function appGraphics(og) {
89
- return Object.keys(og.APPS).map((app) => {
90
- // Non-null by construction: every real AppKey (`Object.keys(og.APPS)`)
91
- // has an APP_HOME entry. `AppKey` itself is widened to `string` here
92
- // (see `open-graph-types.ts`'s header on why), so TS can no longer see
93
- // that closed correspondence the way it could against the real literal
94
- // union.
95
- const home = APP_HOME[app];
96
- const sizes = [16, 32, 48, 64, 96, 128, 180, 192, 256, 384, 512, 1024];
97
- // Non-null by construction, same reasoning as `home` above: every real
98
- // AppKey this loop iterates came from `Object.keys(og.APPS)` itself.
99
- const brand = og.APPS[app];
100
- return {
101
- name: `app/${app}`,
102
- group: "app",
103
- stem: app,
104
- formats: [
105
- ...sizes.map((size) => `icon-${size}`),
106
- ...(home.app ? ["icon-favicon", "icon-apple"] : []),
107
- "og",
108
- ...GDG,
109
- ],
110
- why: `${brand.name} — its mark as an icon, and its card as a link.`,
111
- render: (format) => format.family === "icon"
112
- ? og.AppIcon({
113
- app,
114
- size: format.width,
115
- background: og.OPAQUE_FORMATS.has(format.name)
116
- ? og.THEME.background
117
- : undefined,
118
- })
119
- : og.PageCard({
120
- width: format.width,
121
- height: format.height,
122
- title: brand.name,
123
- description: brand.blurb,
124
- eyebrow: brand.tagline,
125
- accent: brand.ground,
126
- footer: brand.host,
127
- cobrand: true,
128
- }),
129
- destination: (format) => {
130
- // The two Next reads by name, which have to land in `src/app` under
131
- // exactly those filenames or the framework does not see them.
132
- if (home.app && format.name === "icon-favicon") {
133
- return { dir: home.app, file: "icon.png" };
134
- }
135
- if (home.app && format.name === "icon-apple") {
136
- return { dir: home.app, file: "apple-icon.png" };
137
- }
138
- // The platform serves its own link cards per request, so only the apps
139
- // WITHOUT a live renderer get a static `opengraph-image.png`.
140
- if (home.app && app !== "platform" && format.name === "og") {
141
- return { dir: home.app, file: "opengraph-image.png" };
142
- }
143
- return format.family === "icon"
144
- ? {
145
- dir: home.icons,
146
- file: `${app}-${format.name.replace("icon-", "")}.png`,
147
- }
148
- : { dir: `${BRAND}/og`, file: `app-${app}-${format.name}.png` };
149
- },
150
- };
151
- });
152
- }
153
- /**
154
- * One graphic per meeting.
155
- *
156
- * These are the reason the two axes were split at all: "this meeting" x "the
157
- * GDG platform's banner" is a pairing nobody could have enumerated in advance.
158
- *
159
- * Their default directory is the one that is NOT in the repo. Every other
160
- * graphic is a committed asset the club re-renders; an event poster is an
161
- * export headed somewhere else, made once for one night. Writing those into
162
- * `public/brand` would fill a tracked directory with a growing pile of
163
- * one-offs, so they go to {@link EVENT_SCRATCH_DIR}, which `.gitignore` covers.
164
- */
165
- export const EVENT_SCRATCH_DIR = ".images/events";
166
- export function eventGraphics(og, events) {
167
- return events.flatMap((event) => {
168
- const meeting = {
169
- name: `event/${event.slug}/meeting`,
170
- group: "event",
171
- stem: event.slug,
172
- formats: EVENT_FORMATS,
173
- why: `The ${event.slug} meeting — ${event.hint}.`,
174
- render: (format) => og.EventCard({
175
- width: format.width,
176
- height: format.height,
177
- cobrand: true,
178
- ...event.detail,
179
- }),
180
- destination: (format) => ({
181
- dir: `${EVENT_SCRATCH_DIR}/${event.slug}`,
182
- file: `meeting-${format.name}.png`,
183
- }),
184
- };
185
- if ((event.items?.length ?? 0) < 2)
186
- return [meeting];
187
- const occurrences = new Map();
188
- const items = event.items.map((item) => {
189
- const occurrence = (occurrences.get(item.stem) ?? 0) + 1;
190
- occurrences.set(item.stem, occurrence);
191
- const path = occurrence === 1 ? item.stem : `${item.stem}-${occurrence}`;
192
- return {
193
- name: `event/${event.slug}/${path}`,
194
- group: "event",
195
- // `--out` is deliberately flat, so its private stem still carries
196
- // the meeting even though default output can use nested folders.
197
- stem: `${event.slug}-${path}`,
198
- formats: EVENT_FORMATS,
199
- why: `${item.detail.badge?.label ?? "Agenda item"}: ${item.detail.title} — ${event.hint}.`,
200
- render: (format) => og.EventCard({
201
- width: format.width,
202
- height: format.height,
203
- cobrand: true,
204
- ...item.detail,
205
- }),
206
- destination: (format) => ({
207
- dir: `${EVENT_SCRATCH_DIR}/${event.slug}`,
208
- file: `${path}-${format.name}.png`,
209
- }),
210
- };
211
- });
212
- return [meeting, ...items];
213
- });
214
- }
215
- /* --------------------------------------------------------------------- all */
216
- /** Everything that does not need a database. */
217
- export async function staticGraphics() {
218
- const og = await loadOpenGraph();
219
- return [...brandGraphics(og), ...pageGraphics(og), ...appGraphics(og)];
220
- }
221
- /**
222
- * Every leaf is unique, so a flat `--out` directory cannot silently overwrite
223
- * one graphic's file with another's.
224
- */
225
- export function assertUniqueStems(graphics) {
226
- const seen = new Map();
227
- for (const graphic of graphics) {
228
- const clash = seen.get(graphic.stem);
229
- if (clash) {
230
- throw new UsageError(`${graphic.name} and ${clash} share the leaf "${graphic.stem}", so a flat --out would overwrite one with the other.`);
231
- }
232
- seen.set(graphic.stem, graphic.name);
233
- }
234
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Type-only surface for `@devdogsuga/open-graph` — a hand-maintained mirror
3
- * of what `images/*.ts` actually uses, not a full re-export.
4
- *
5
- * `@devdogsuga/open-graph` is `"private": true` and never published — it
6
- * stays in DevDogsUGA (see the ledger) and devtools resolves it FROM the
7
- * repo at runtime (`repo/source.ts`'s `loadOpenGraph()`), never as a
8
- * Backstage dependency. Backstage has no copy of the package to pull real
9
- * types from, so these are duplicated for editor/typecheck convenience only
10
- * and MUST be kept in sync by hand if the real shape changes. Runtime
11
- * values always come from `loadOpenGraph()`, never from here.
12
- *
13
- * `EventDetail` and the `THEME` shape are NOT duplicated here: both are
14
- * re-exported by open-graph from `@devdogsuga/brand`, which — unlike
15
- * open-graph — IS a real Backstage package (published; see the ledger), so
16
- * those two types are imported for real below.
17
- */
18
- import type { ReactElement } from "react";
19
- import type { EventDetail } from "@devdogsuga/brand/event";
20
- import type { LoadedFont } from "@devdogsuga/brand";
21
- export type { EventDetail, LoadedFont };
22
- export type FormatFamily = "card" | "email" | "icon";
23
- export interface Format {
24
- name: string;
25
- width: number;
26
- height: number;
27
- scale: number;
28
- family: FormatFamily;
29
- why: string;
30
- }
31
- /**
32
- * `@devdogsuga/open-graph`'s `AppKey` is a string-literal union
33
- * (`"platform" | "dogdays" | "dogpack"` as of this writing). Widened to
34
- * `string` here rather than duplicating the literal list, which would
35
- * silently drift the moment a fourth app gets an icon — `APPS`'s real keys
36
- * are what devtools actually iterates over at runtime.
37
- */
38
- export type AppKey = string;
39
- export interface AppBrand {
40
- name: string;
41
- tagline: string;
42
- blurb: string;
43
- host: string;
44
- ground: string;
45
- mark: string;
46
- }
47
- export interface PageCardCopy {
48
- title: string;
49
- description: string;
50
- eyebrow?: string;
51
- accent?: string;
52
- footer?: string;
53
- }
54
- /**
55
- * The five JSX template components. Typed loosely (`(...args) =>
56
- * ReactElement`, props as `any`) rather than duplicating each one's exact
57
- * props interface — those are the deepest, most likely to drift part of
58
- * open-graph's surface, and devtools only ever calls them with object
59
- * literals built right next to the call site, where a real typo would show
60
- * up as a runtime prop-shape mismatch the same way it would with `any`.
61
- */
62
- export type TemplateComponent = (props: any) => ReactElement;
63
- export interface OpenGraphModule {
64
- FORMATS: Record<string, Format>;
65
- OPAQUE_FORMATS: ReadonlySet<string>;
66
- loadFonts: () => LoadedFont[];
67
- APPS: Record<AppKey, AppBrand>;
68
- AppIcon: TemplateComponent;
69
- Banner: TemplateComponent;
70
- EmailSignature: TemplateComponent;
71
- EventCard: TemplateComponent;
72
- PageCard: TemplateComponent;
73
- PAGE_CARDS: Record<string, PageCardCopy>;
74
- THEME: Record<string, string>;
75
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,34 +0,0 @@
1
- import type { Graphic } from "./graphics.js";
2
- /**
3
- * What the command asks for when the command line did not say.
4
- *
5
- * Three questions, and each one is skipped entirely when a flag answered it, so
6
- * a scripted invocation never blocks. That is the whole contract: this command
7
- * has to be usable both as "make me the September 8 posters" typed by an
8
- * officer who does not remember the format names, and as a line in a build.
9
- *
10
- * {@link requireInteractive} is what keeps the second half true. Without it a
11
- * missing flag in CI would hang on a prompt nobody can answer until the job
12
- * times out, which is the least debuggable failure available.
13
- */
14
- /** Refuses, naming the flag, rather than prompting where nobody can answer. */
15
- export declare function requireInteractive(what: string, flag: string): void;
16
- /**
17
- * Which graphics.
18
- *
19
- * Autocomplete rather than a plain list: there are dozens once a semester of
20
- * meetings is loaded, and the useful interaction is typing "sep" or "dogdays"
21
- * rather than scrolling. Grouped by prefix in the label so the list reads as
22
- * the `group/name` vocabulary the flags use.
23
- */
24
- export declare function pickGraphics(graphics: readonly Graphic[]): Promise<Graphic[]>;
25
- /**
26
- * Which formats.
27
- *
28
- * Offered from the union of what the chosen graphics support, so the list never
29
- * contains a size that would be skipped — asking somebody to pick `savvycal`
30
- * for an event and then quietly not rendering it is worse than not offering it.
31
- */
32
- export declare function pickFormats(graphics: readonly Graphic[]): Promise<string[]>;
33
- /** `undefined` means "each graphic's own default directory". */
34
- export declare function pickOutput(graphics: readonly Graphic[]): Promise<string | undefined>;
@@ -1,100 +0,0 @@
1
- import { autocompleteMultiselect, isTTY, multiselect, path, select, } from "@clack/prompts";
2
- import { loadOpenGraph } from "../repo/source.js";
3
- import { unwrap, UsageError } from "../ui.js";
4
- import { formatsFor } from "./select.js";
5
- /**
6
- * What the command asks for when the command line did not say.
7
- *
8
- * Three questions, and each one is skipped entirely when a flag answered it, so
9
- * a scripted invocation never blocks. That is the whole contract: this command
10
- * has to be usable both as "make me the September 8 posters" typed by an
11
- * officer who does not remember the format names, and as a line in a build.
12
- *
13
- * {@link requireInteractive} is what keeps the second half true. Without it a
14
- * missing flag in CI would hang on a prompt nobody can answer until the job
15
- * times out, which is the least debuggable failure available.
16
- */
17
- /** Refuses, naming the flag, rather than prompting where nobody can answer. */
18
- export function requireInteractive(what, flag) {
19
- if (isTTY(process.stdout))
20
- return;
21
- throw new UsageError(`No terminal to ask which ${what} to use. Pass ${flag} — this is not an interactive shell.`);
22
- }
23
- /**
24
- * Which graphics.
25
- *
26
- * Autocomplete rather than a plain list: there are dozens once a semester of
27
- * meetings is loaded, and the useful interaction is typing "sep" or "dogdays"
28
- * rather than scrolling. Grouped by prefix in the label so the list reads as
29
- * the `group/name` vocabulary the flags use.
30
- */
31
- export async function pickGraphics(graphics) {
32
- requireInteractive("graphics", "a graphic name, or `*` for all of them");
33
- const chosen = unwrap(await autocompleteMultiselect({
34
- message: "Which images?",
35
- options: graphics.map((graphic) => ({
36
- value: graphic.name,
37
- label: graphic.name,
38
- hint: graphic.why,
39
- })),
40
- }));
41
- return graphics.filter((graphic) => chosen.includes(graphic.name));
42
- }
43
- /**
44
- * Which formats.
45
- *
46
- * Offered from the union of what the chosen graphics support, so the list never
47
- * contains a size that would be skipped — asking somebody to pick `savvycal`
48
- * for an event and then quietly not rendering it is worse than not offering it.
49
- */
50
- export async function pickFormats(graphics) {
51
- requireInteractive("formats", "--format or --all-formats");
52
- const available = await formatsFor(graphics);
53
- return unwrap(await multiselect({
54
- message: "Which formats?",
55
- options: available.map((format) => ({
56
- value: format.name,
57
- label: format.name,
58
- hint: format.why,
59
- })),
60
- // The link card is what most of these are for, and it is supported by
61
- // every graphic that is not an icon.
62
- initialValues: available.some((format) => format.name === "og")
63
- ? ["og"]
64
- : [],
65
- }));
66
- }
67
- /** `undefined` means "each graphic's own default directory". */
68
- export async function pickOutput(graphics) {
69
- requireInteractive("output directory", "--out or --default-out");
70
- const { FORMATS } = await loadOpenGraph();
71
- // Shown so the choice is concrete. One directory names itself; several are
72
- // summarised, because listing eight paths in a prompt hint is unreadable.
73
- const defaults = [
74
- ...new Set(graphics.flatMap((graphic) => graphic.formats
75
- .map((name) => FORMATS[name])
76
- .filter((format) => format !== undefined)
77
- .map((format) => graphic.destination(format).dir))),
78
- ];
79
- const choice = unwrap(await select({
80
- message: "Where should these go?",
81
- options: [
82
- {
83
- value: "default",
84
- label: defaults.length === 1 ? defaults[0] : "Their default directories",
85
- hint: defaults.length === 1
86
- ? "where this one belongs"
87
- : `${defaults.length} directories, one per graphic`,
88
- },
89
- { value: "other", label: "Other…", hint: "a directory of your own" },
90
- ],
91
- }));
92
- if (choice === "default")
93
- return undefined;
94
- return unwrap(await path({
95
- message: "Which directory?",
96
- // Completes against the filesystem, and only offers directories — the
97
- // answer is a folder, and offering files would invite naming one.
98
- directory: true,
99
- }));
100
- }
@@ -1,21 +0,0 @@
1
- import type { ReactElement } from "react";
2
- export interface RenderOptions {
3
- /** The layout's own width. Every size in the template is against this. */
4
- width: number;
5
- height: number;
6
- /**
7
- * Output pixels per layout pixel. The layout is computed once at `width` and
8
- * the vector result is rasterised larger, so 3x is genuinely three times the
9
- * detail rather than an upscaled 1x — and the type stays on the same metrics
10
- * at every scale, which is what makes @1x/@2x/@3x a set rather than three
11
- * slightly different pictures.
12
- */
13
- scale?: number;
14
- }
15
- export interface Rendered {
16
- png: Buffer;
17
- svg: string;
18
- width: number;
19
- height: number;
20
- }
21
- export declare function render(element: ReactElement, { width, height, scale }: RenderOptions): Promise<Rendered>;
@@ -1,61 +0,0 @@
1
- /**
2
- * Turning a template into a PNG on disk.
3
- *
4
- * Two libraries, and the choice of each is load-bearing:
5
- *
6
- * * **Satori** lays the template out and emits SVG. It is the same engine
7
- * `next/og` runs inside the platform, so a card exported here and a card
8
- * unfurled from a URL are the same picture rather than two implementations
9
- * of one design.
10
- * * **resvg** rasterises it, for the same reason. The obvious alternative was
11
- * sharp, which this package already depends on — but sharp rasterises
12
- * through librsvg, and librsvg does not read `rgba()` in a gradient stop,
13
- * which is exactly the notation Satori emits. The background washes came
14
- * out as flat black, silently and only in the CLI's copy of an image the
15
- * site rendered correctly. resvg is what `@vercel/og` itself uses.
16
- *
17
- * `@vercel/og` is deliberately NOT used here even though this mirrors it. Its
18
- * Node build is bundled for a CommonJS loader: imported as ESM it wants a
19
- * global `require`, then a global `__dirname`, and then an `hb.wasm` the
20
- * package does not ship. `next/og` cannot be imported outside Next's bundler at
21
- * all. Driving Satori directly is the same rendering, three hacks fewer.
22
- */
23
- import { Resvg } from "@resvg/resvg-js";
24
- import satori, {} from "satori";
25
- import { loadOpenGraph } from "../repo/source.js";
26
- let fonts;
27
- /** The embedded faces, in Satori's shape. Decoded once per process. */
28
- async function satoriFonts() {
29
- if (fonts)
30
- return fonts;
31
- const { loadFonts } = await loadOpenGraph();
32
- return (fonts = loadFonts().map((font) => ({
33
- name: font.name,
34
- data: font.data,
35
- weight: font.weight,
36
- style: font.style,
37
- })));
38
- }
39
- export async function render(element, { width, height, scale = 1 }) {
40
- const svg = await satori(element, {
41
- width,
42
- height,
43
- fonts: await satoriFonts(),
44
- });
45
- const png = new Resvg(svg, {
46
- fitTo: { mode: "width", value: Math.round(width * scale) },
47
- // Satori has already turned every glyph into a path, so resvg never needs
48
- // to resolve a family. Saying so keeps it from probing the host's fonts,
49
- // which is the one thing here that could differ between a contributor's
50
- // machine and CI.
51
- font: { loadSystemFonts: false },
52
- })
53
- .render()
54
- .asPng();
55
- return {
56
- png,
57
- svg,
58
- width: Math.round(width * scale),
59
- height: Math.round(height * scale),
60
- };
61
- }
@@ -1,64 +0,0 @@
1
- import type { Format } from "./open-graph-types.js";
2
- import type { Graphic } from "./graphics.js";
3
- /**
4
- * Turning what somebody typed into a list of (graphic, format) pairs.
5
- *
6
- * Kept apart from `commands.ts` because all of it is pure: given a registry and
7
- * some patterns, these answer the same way every time and can be tested
8
- * without a terminal, a database or a renderer.
9
- */
10
- /** One image to render: a subject, and the size to draw it at. */
11
- export interface Selection {
12
- graphic: Graphic;
13
- format: Format;
14
- }
15
- export interface GraphicMatch {
16
- matched: Graphic[];
17
- /** Patterns that named nothing, so the caller can say which. */
18
- unmatched: string[];
19
- }
20
- /**
21
- * Resolves graphic patterns.
22
- *
23
- * Every graphic is `group/name`, and three shapes of pattern read against
24
- * that: a lone star (or a star over a star) is everything, `group/` plus a star
25
- * is a whole group, and anything else is an exact name. Deliberately not a
26
- * general glob — `event/2026-` plus a star looks like it should work and would
27
- * be one more thing to specify, and the picker covers the case where somebody
28
- * does not know the exact name.
29
- *
30
- * A bare group is accepted as shorthand for `group/*`, because `images icons`
31
- * is what people type and refusing it teaches nothing.
32
- */
33
- export declare function matchGraphics(patterns: readonly string[], graphics: readonly Graphic[]): GraphicMatch;
34
- /**
35
- * Pairs each graphic with each requested format it actually supports.
36
- *
37
- * The matrix is sparse, and silently dropping a pair is the wrong answer in one
38
- * direction only: asking for `*` at every format should quietly skip the
39
- * combinations that do not exist, while asking for one graphic at one format it
40
- * cannot do should say so. The caller decides which case it is; this reports
41
- * both halves.
42
- */
43
- export declare function pair(graphics: readonly Graphic[], formats: readonly string[]): Promise<{
44
- selections: Selection[];
45
- unsupported: {
46
- graphic: string;
47
- format: string;
48
- }[];
49
- }>;
50
- /** Every format any of these graphics can be drawn at, in registry order. */
51
- export declare function formatsFor(graphics: readonly Graphic[]): Promise<Format[]>;
52
- /**
53
- * Splits `--flag=value` into two tokens.
54
- *
55
- * The rest of this CLI reads flags as `--flag value`, and `positionals()`
56
- * depends on that to tell a flag's value from a subcommand. `--format=a,b`
57
- * arrives as one token, so it would be read as a positional — that is, as a
58
- * graphic name — and `images --format=og` would try to render a graphic called
59
- * `--format=og` and report nothing matched. Normalising here lets both spellings
60
- * work without teaching the shared parser about either.
61
- */
62
- export declare function normalizeArgv(argv: readonly string[]): string[];
63
- /** A comma-separated flag value, trimmed and emptied of blanks. */
64
- export declare function commaList(value: string | undefined): string[];