@devdogsuga/devtools 0.1.26 → 0.1.27

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
package/dist/help.js DELETED
@@ -1,169 +0,0 @@
1
- /**
2
- * `--help`, one level at a time.
3
- *
4
- * ## Why this is small
5
- *
6
- * The help this replaced was ~200 lines and printed the whole tree: every
7
- * subcommand of every group, the Bitwarden target table, the four places an
8
- * access token is looked for, the grants `migration_planner` holds, and which
9
- * deploy steps must avoid the `with-env` wrapper. A contributor running
10
- * `pnpm devtools --help` to find out how to start a database read all of it.
11
- *
12
- * Two rules now:
13
- *
14
- * 1. **A level prints its own children and stops.** `--help` lists the
15
- * top-level commands; `env --help` lists env's subcommands; `env pull
16
- * --help` lists that command's options. Depth is reached by asking for
17
- * it.
18
- * 2. **No more than the caller needs to choose.** Operator internals are
19
- * `docs/`'s job: which project a target maps to, how a credential is
20
- * resolved, what a deploy job's environment holds. Help says what a
21
- * command does and what it takes.
22
- *
23
- * Both fall out of rendering `commands.ts` rather than hand-writing prose, so
24
- * neither can rot back into a wall of text one paragraph at a time.
25
- */
26
- import { findCommand, GROUPS, SCOPES, } from "./commands.js";
27
- const INDENT = " ";
28
- /** `--target <t>`, or `--prune` for a boolean. */
29
- function optionLabel(option) {
30
- return option.value ? `${option.flag} ${option.value}` : option.flag;
31
- }
32
- /**
33
- * Two columns, with the gutter sized to the widest label in THIS block.
34
- *
35
- * Per-block rather than one width for the whole file: `deploy`'s labels are
36
- * long and `setup`'s are not, and a shared width would indent the short list
37
- * halfway across the terminal to accommodate a group the reader is not
38
- * looking at.
39
- */
40
- function columns(rows) {
41
- const width = Math.max(...rows.map(([label]) => label.length));
42
- return rows.map(([label, text]) => `${INDENT}${label.padEnd(width)} ${text}`);
43
- }
44
- /**
45
- * A list of commands, split into scope blocks when any of them declares one.
46
- *
47
- * Used at two levels: a GROUP's own commands (no group has scoped commands
48
- * today — `db` folded the old "Supabase" group's scopes into itself), and a
49
- * command's own SUBCOMMANDS (`db`'s: `start`/`connect`/`stop`/`restart` on
50
- * this machine, `migration` in the repo, `status`/`migrate`/`reset`/… on an
51
- * endpoint, `planner`/`signing-key` naming their own connection). Either way,
52
- * a flat list leaves the reader to work out which of `restart` and `reset` is
53
- * which, the distinction that costs people an afternoon. A list with no
54
- * scopes renders as it always did: one block, one indent, no headings.
55
- *
56
- * The blocks follow declaration order rather than `SCOPES` order, so the tree
57
- * stays the one place that decides how the list reads. The gutter is sized
58
- * across the whole list, not per block, so every block lines up with the
59
- * others rather than each finding its own column.
60
- */
61
- function scopedBody(commands) {
62
- if (!commands.some((command) => command.scope)) {
63
- return columns(childRows(commands));
64
- }
65
- const width = Math.max(...commands.map((command) => command.name.length));
66
- const lines = [];
67
- let open;
68
- for (const command of commands) {
69
- if (command.scope && command.scope !== open) {
70
- open = command.scope;
71
- lines.push(`${INDENT}${SCOPES[open].help}:`);
72
- }
73
- lines.push(`${INDENT.repeat(2)}${command.name.padEnd(width)} ${command.summary}`);
74
- }
75
- return lines;
76
- }
77
- function optionRows(options) {
78
- return options.map((option) => [optionLabel(option), option.summary]);
79
- }
80
- function childRows(children) {
81
- return children.map((child) => [child.name, child.summary]);
82
- }
83
- // ── The three levels ─────────────────────────────────────────────────────────
84
- /** `pnpm devtools --help`: the groups, and nothing below them. */
85
- function renderRoot() {
86
- const lines = [
87
- "pnpm devtools [command] [options]",
88
- "",
89
- "Run with no command to choose from a menu.",
90
- "",
91
- "Common tasks:",
92
- ...columns([
93
- ["setup", "Prepare a new checkout"],
94
- ["run dev", "Start development servers"],
95
- ["db start", "Start Supabase on this machine"],
96
- ["db reset", "Rebuild the local database"],
97
- ["cron run", "Choose and run a scheduled job"],
98
- ]),
99
- ];
100
- for (const group of GROUPS) {
101
- lines.push("", `${group.title}:`, ...scopedBody(group.commands));
102
- }
103
- lines.push("", "Options:", ...columns([
104
- ["--help, -h", "Show this message"],
105
- [
106
- "--tier <t>",
107
- "Deploy tier for this whole invocation (development, staging, production)",
108
- ],
109
- ]), "", "`pnpm devtools <command> --help` shows what that command takes.");
110
- return lines.join("\n");
111
- }
112
- /** `pnpm devtools <path…> --help`: one command's own children and options. */
113
- function renderCommand(path, node) {
114
- const children = node.subcommands ?? [];
115
- const options = node.options ?? [];
116
- const trail = path.join(" ");
117
- const usage = [
118
- "pnpm devtools",
119
- trail,
120
- children.length > 0 ? "<subcommand>" : "",
121
- options.length > 0 ? "[options]" : "",
122
- ]
123
- .filter(Boolean)
124
- .join(" ");
125
- const lines = [usage, "", node.summary];
126
- if (children.length > 0) {
127
- lines.push("", "Subcommands:", ...scopedBody(children));
128
- }
129
- if (options.length > 0) {
130
- lines.push("", "Options:", ...columns(optionRows(options)));
131
- }
132
- if (children.length > 0) {
133
- lines.push("", `\`pnpm devtools ${trail} <subcommand> --help\` shows what that one takes.`);
134
- }
135
- return lines.join("\n");
136
- }
137
- /**
138
- * Renders help for a path, falling back to the root.
139
- *
140
- * An unknown path renders the root rather than an error: this is only reached
141
- * when `--help` was asked for, and answering a mistyped command with the list
142
- * it was mistyped from is more use than a refusal.
143
- */
144
- export function renderHelp(path = []) {
145
- if (path.length === 0)
146
- return renderRoot();
147
- const node = findCommand(path);
148
- if (!node)
149
- return renderRoot();
150
- return renderCommand(path, node);
151
- }
152
- /**
153
- * The command names in `argv` that precede any flag.
154
- *
155
- * `env pull --help` asks about `["env", "pull"]`; `--help env` asks about
156
- * nothing, because a flag's value is not a command path. Kept separate from
157
- * `positionals()`: that one strips a known set of value-taking flags to find a
158
- * subcommand, and here anything after the first flag is not part of the path at
159
- * all.
160
- */
161
- export function helpPath(argv) {
162
- const path = [];
163
- for (const arg of argv) {
164
- if (arg.startsWith("-"))
165
- break;
166
- path.push(arg);
167
- }
168
- return path;
169
- }
@@ -1,32 +0,0 @@
1
- import type { Format } from "./open-graph-types.js";
2
- import { type EventReader } from "./events.js";
3
- import { type Selection } from "./select.js";
4
- export interface ImagesOptions {
5
- /** Graphic patterns. Empty means ask. */
6
- patterns: string[];
7
- /** Format names. Empty with `allFormats` false means ask. */
8
- formats: string[];
9
- allFormats: boolean;
10
- /** An explicit directory; everything lands in it, flat. */
11
- out?: string;
12
- /** Use each graphic's own default directory. */
13
- defaultOut: boolean;
14
- /** List what would be written, and write nothing. */
15
- noOutput: boolean;
16
- }
17
- export declare function parseImagesArgs(argv: readonly string[]): ImagesOptions;
18
- /** `~/images` when the shell did not expand it, which quoting prevents. */
19
- export declare function expandHome(path: string): string;
20
- /** Whether anything asked for could be an event, so whether to reach for a database. */
21
- export declare function wantsEvents(patterns: readonly string[]): boolean;
22
- /** Whether a pattern names events specifically, rather than sweeping them up. */
23
- export declare function namesEvents(patterns: readonly string[]): boolean;
24
- export interface ImagesDeps {
25
- /** Overridden in tests so nothing reads the peer package or the filesystem. */
26
- reader?: () => EventReader;
27
- }
28
- /** Where one selection is written. */
29
- export declare function destinationOf({ graphic, format }: Selection, out: string | undefined): string;
30
- export declare function runImages(argv: string[], deps?: ImagesDeps): Promise<void>;
31
- /** Re-exported so `render.ts`'s options type is the format's own shape. */
32
- export type { Format };
@@ -1,231 +0,0 @@
1
- /**
2
- * `pnpm devtools images [graphic…] [--format …] [--out …]`
3
- *
4
- * Renders the club's pictures from the templates in `@devdogsuga/open-graph`. Two axes,
5
- * asked for separately: WHICH picture (`event/2026-09-08`, `page/events`,
6
- * `app/dogdays`) and at WHAT SIZE (`gdgc-square`, `og`, `icon-512`). That split
7
- * is the point of the command — an event poster for the GDG on Campus platform
8
- * is exactly the pairing "this meeting" x "that platform's banner", and it
9
- * could not be asked for while the two were one hard-coded list.
10
- *
11
- * Anything the command line leaves out, it asks for; anything it names, it does
12
- * not. So an officer can type `pnpm devtools images` and be walked through it,
13
- * and a script can pass every flag and never see a prompt.
14
- *
15
- * Event graphics used to be the one group backed by a database rather than by
16
- * files in this repo — and the one group that could therefore be unavailable
17
- * without one. They no longer are: `configEvents()` (`events.ts`) reads
18
- * `@devdogsuga/events`'s committed config directly, the same source a
19
- * reconcile job used to copy into the database first, so this command needs
20
- * no running stack at all any more. See {@link loadEvents} for what remains
21
- * of the "unavailable" branch — a repo `@devdogsuga/events` cannot resolve
22
- * in, rather than a database that is not up.
23
- */
24
- import { mkdir, writeFile } from "node:fs/promises";
25
- import { homedir } from "node:os";
26
- import { dirname, isAbsolute, relative, resolve } from "node:path";
27
- import { log, note } from "@clack/prompts";
28
- import { loadOpenGraph } from "../repo/source.js";
29
- import { findRepoRoot } from "../repo/root.js";
30
- import { positionals } from "../args.js";
31
- import { errorMessage, explain, explainError, UsageError } from "../ui.js";
32
- import { configEvents } from "./events.js";
33
- import { assertUniqueStems, eventGraphics, staticGraphics, } from "./graphics.js";
34
- import { pickFormats, pickGraphics, pickOutput } from "./prompts.js";
35
- import { render } from "./render.js";
36
- import { commaList, formatsFor, matchGraphics, normalizeArgv, pair, } from "./select.js";
37
- /**
38
- * The workspace root. Every default directory is written relative to it, and
39
- * resolving those against `process.cwd()` would put the club's icons wherever
40
- * the contributor happened to be standing.
41
- */
42
- function flagValue(argv, flag) {
43
- const index = argv.indexOf(flag);
44
- if (index === -1)
45
- return undefined;
46
- const value = argv[index + 1];
47
- return value && !value.startsWith("--") ? value : undefined;
48
- }
49
- export function parseImagesArgs(argv) {
50
- const normalized = normalizeArgv(argv);
51
- return {
52
- patterns: positionals(normalized),
53
- formats: commaList(flagValue(normalized, "--format")),
54
- allFormats: normalized.includes("--all-formats"),
55
- out: flagValue(normalized, "--out"),
56
- defaultOut: normalized.includes("--default-out"),
57
- noOutput: normalized.includes("--dry-run") || normalized.includes("--no-output"),
58
- };
59
- }
60
- /** `~/images` when the shell did not expand it, which quoting prevents. */
61
- export function expandHome(path) {
62
- return path === "~" || path.startsWith("~/")
63
- ? resolve(homedir(), path.slice(2))
64
- : path;
65
- }
66
- /** Whether anything asked for could be an event, so whether to reach for a database. */
67
- export function wantsEvents(patterns) {
68
- // No pattern means the picker, and the picker should list meetings.
69
- if (patterns.length === 0)
70
- return true;
71
- return patterns.some((pattern) => pattern === "*" ||
72
- pattern === "*/*" ||
73
- pattern === "event" ||
74
- pattern.startsWith("event/"));
75
- }
76
- /** Whether a pattern names events specifically, rather than sweeping them up. */
77
- export function namesEvents(patterns) {
78
- return patterns.some((pattern) => pattern === "event" || pattern.startsWith("event/"));
79
- }
80
- /**
81
- * Meetings, or a clear account of why there are none.
82
- *
83
- * `@devdogsuga/events` is an optional peer (see `repo/peers.ts`'s header), so
84
- * loading it can still fail — outside a DevDogsUGA checkout, or one where
85
- * nothing depends on it. That is the one way this command still needs an
86
- * account of what went wrong, and two failures are treated differently,
87
- * matching what each one means:
88
- *
89
- * - **Unavailable, under a wildcard.** `images *` means "everything", and
90
- * everything else is renderable. It renders, and reports the events it
91
- * could not.
92
- * - **Unavailable, named.** `images 'event/*'` asked for exactly the thing
93
- * that is missing. Nothing to degrade to, so it fails.
94
- */
95
- async function loadEvents(patterns, deps) {
96
- const required = namesEvents(patterns);
97
- const reader = (deps.reader ?? configEvents)();
98
- let meetings;
99
- try {
100
- meetings = await reader.meetings();
101
- }
102
- catch (err) {
103
- if (required)
104
- throw err;
105
- return { graphics: [], skipped: errorMessage(err) };
106
- }
107
- return {
108
- graphics: eventGraphics(await loadOpenGraph(), meetings),
109
- skipped: null,
110
- };
111
- }
112
- /** Where one selection is written. */
113
- export function destinationOf({ graphic, format }, out) {
114
- if (out) {
115
- // Flat, and always `<stem>-<format>.png`: the pinned filenames a graphic
116
- // uses in its own directory (`icon.png`, `opengraph-image.png`) are only
117
- // unambiguous BECAUSE the directories differ. Side by side they collide.
118
- return resolve(expandHome(out), `${graphic.stem}-${format.name}.png`);
119
- }
120
- const { dir, file } = graphic.destination(format);
121
- return resolve(findRepoRoot(), dir, file);
122
- }
123
- /** Repo-relative where possible, so the log reads as paths in this project. */
124
- function display(file) {
125
- const rel = relative(findRepoRoot(), file);
126
- return rel.startsWith("..") || isAbsolute(rel) ? file : rel;
127
- }
128
- export async function runImages(argv, deps = {}) {
129
- const options = parseImagesArgs(argv);
130
- if (options.out && options.defaultOut) {
131
- explain("Two answers to one question.", "--out names a directory and --default-out says to use each graphic's own.", [
132
- "pnpm devtools images 'event/*' --out ~/images",
133
- "pnpm devtools images '*' --default-out",
134
- ]);
135
- process.exitCode = 1;
136
- return;
137
- }
138
- try {
139
- await images(options, deps);
140
- }
141
- catch (err) {
142
- explainError("Could not render that.", err, [
143
- "pnpm devtools images # pick from a list",
144
- "pnpm devtools images 'event/*' --all-formats --out ~/images",
145
- "pnpm devtools images '*' --default-out",
146
- ]);
147
- process.exitCode = 1;
148
- }
149
- }
150
- async function images(options, deps) {
151
- // ── Which pictures ────────────────────────────────────────────────────────
152
- const registry = await staticGraphics();
153
- let skipped = null;
154
- if (wantsEvents(options.patterns)) {
155
- const events = await loadEvents(options.patterns, deps);
156
- registry.push(...events.graphics);
157
- skipped = events.skipped;
158
- }
159
- assertUniqueStems(registry);
160
- const graphics = options.patterns.length === 0
161
- ? await pickGraphics(registry)
162
- : resolvePatterns(options.patterns, registry);
163
- if (graphics.length === 0) {
164
- throw new UsageError("Nothing to render.");
165
- }
166
- // ── At what sizes ─────────────────────────────────────────────────────────
167
- const { FORMATS } = await loadOpenGraph();
168
- const requested = options.allFormats
169
- ? (await formatsFor(graphics)).map((format) => format.name)
170
- : options.formats.length > 0
171
- ? options.formats
172
- : await pickFormats(graphics);
173
- const unknown = requested.filter((name) => !FORMATS[name]);
174
- if (unknown.length > 0) {
175
- throw new UsageError(`No format called ${unknown.join(", ")}. Try one of ${Object.keys(FORMATS).join(", ")}.`);
176
- }
177
- const { selections, unsupported } = await pair(graphics, requested);
178
- // Naming one graphic and one format it cannot do is a mistake worth saying
179
- // out loud; sweeping up combinations that do not exist under a wildcard is
180
- // not, and reporting every one of those would bury the output.
181
- if (selections.length === 0 && unsupported.length > 0) {
182
- throw new UsageError(unsupported
183
- .map((miss) => `${miss.graphic} has no ${miss.format} rendition`)
184
- .join("; "));
185
- }
186
- if (selections.length === 0)
187
- throw new UsageError("Nothing to render.");
188
- // ── Where ─────────────────────────────────────────────────────────────────
189
- // An empty `--out` counts as absent, exactly as the old truthiness test did.
190
- const explicitOut = options.out === "" ? undefined : options.out;
191
- const out = options.defaultOut
192
- ? undefined
193
- : (explicitOut ??
194
- (options.noOutput ? undefined : await pickOutput(graphics)));
195
- if (skipped)
196
- log.warn(`event graphics skipped — ${skipped}`);
197
- if (options.noOutput) {
198
- note(selections
199
- .map((selection) => `${selection.graphic.name} ${selection.format.name}\n ${display(destinationOf(selection, out))}\n ${selection.format.why}`)
200
- .join("\n"), `${selections.length} image${selections.length === 1 ? "" : "s"}`);
201
- return;
202
- }
203
- // ── Render ────────────────────────────────────────────────────────────────
204
- let bytes = 0;
205
- for (const selection of selections) {
206
- const file = destinationOf(selection, out);
207
- const { png, width, height } = await render(selection.graphic.render(selection.format), selection.format);
208
- await mkdir(dirname(file), { recursive: true });
209
- await writeFile(file, png);
210
- bytes += png.length;
211
- log.success(`${display(file)} ${width}x${height} (${Math.round(png.length / 1024)} KB)`);
212
- }
213
- log.info(`${selections.length} image${selections.length === 1 ? "" : "s"}, ${Math.round(bytes / 1024)} KB total.`);
214
- }
215
- function resolvePatterns(patterns, registry) {
216
- const { matched, unmatched } = matchGraphics(patterns, registry);
217
- if (unmatched.length === 0)
218
- return matched;
219
- // An event pattern that matched nothing is almost never a typo — it is a
220
- // database with no meetings in it. Saying "try brand/*, page/*" sends the
221
- // reader hunting for a spelling mistake that is not there.
222
- const askedForEvents = unmatched.some((pattern) => pattern === "event" || pattern.startsWith("event/"));
223
- const haveEvents = registry.some((graphic) => graphic.group === "event");
224
- if (askedForEvents && !haveEvents) {
225
- throw new UsageError("This database has no meetings, so there are no event images to render. " +
226
- "Reconcile it from `@devdogsuga/events`, or point at a database that has been.");
227
- }
228
- const groups = [...new Set(registry.map((graphic) => `${graphic.group}/*`))];
229
- throw new UsageError(`Nothing called ${unmatched.join(", ")}. Names are group/name — try ` +
230
- `${groups.join(", ")}, or * for all of them.`);
231
- }
@@ -1,47 +0,0 @@
1
- import type { Meeting } from "@devdogsuga/events";
2
- import type { EventGraphicSource } from "./graphics.js";
3
- /**
4
- * Meetings, for the one group of graphics that cannot be drawn from files
5
- * alone.
6
- *
7
- * Every other graphic is a function of committed files. An event poster used
8
- * to be a function of meetings RECONCILED into the database from
9
- * `@devdogsuga/events` — which meant `devtools images event/*` needed a
10
- * running local stack just to draw a picture that comes entirely out of this
11
- * repo. It no longer does: `@devdogsuga/events`'s `getClubConfig()` is the
12
- * authored source those database rows were always reconciled FROM (see
13
- * `apps/platform/src/server/config/reconcile.ts` in the target repo), so
14
- * reading it directly here skips the round trip through Postgres rather than
15
- * duplicating it. {@link EventReader} is the seam the command talks to, so
16
- * tests never touch the filesystem or the network.
17
- */
18
- /** The seam the command talks to, so tests never open a socket. */
19
- export interface EventReader {
20
- meetings(): Promise<EventGraphicSource[]>;
21
- }
22
- /**
23
- * Builds the reader from `@devdogsuga/events`'s committed data file.
24
- *
25
- * Replaces `supabaseEvents`, which read the `meetings`/`workshops` tables a
26
- * reconcile job had already copied this same config into. The config's shape
27
- * (`schema.ts`) is deliberately a near-mirror of those tables' columns for
28
- * exactly this reason — see that file's header — so the mapping below is
29
- * closer to a rename than a translation: `Meeting.id` is the row's `slug`,
30
- * `Meeting.title` is what the DB called `nameOverride`, and a workshop's
31
- * `project` (free text in the config) stands in for the DB's
32
- * `projects.displayName` join.
33
- */
34
- export declare function configEvents(): EventReader;
35
- /**
36
- * The meeting's name, in descending order of how much somebody meant it.
37
- *
38
- * Mirrors `apps/platform/src/lib/meetingTitle.ts`: an authored name, then the
39
- * kind, then the workshops it teaches, then the date. Two named workshops read
40
- * as a heading; three do not, and the date carries it while the agenda below
41
- * shows the detail.
42
- *
43
- * Duplicated rather than imported because it lives in the Next app, which this
44
- * package cannot depend on. `events.test.ts` pins the chain so a change to one
45
- * shows up as a failure rather than as a differently-titled poster.
46
- */
47
- export declare function meetingTitle(nameOverride: string | null, kind: Meeting["kind"], startsAt: Date, agenda: readonly string[]): string;
@@ -1,116 +0,0 @@
1
- import { loadBrandEvent, loadEvents } from "../repo/peers.js";
2
- /**
3
- * Builds the reader from `@devdogsuga/events`'s committed data file.
4
- *
5
- * Replaces `supabaseEvents`, which read the `meetings`/`workshops` tables a
6
- * reconcile job had already copied this same config into. The config's shape
7
- * (`schema.ts`) is deliberately a near-mirror of those tables' columns for
8
- * exactly this reason — see that file's header — so the mapping below is
9
- * closer to a rename than a translation: `Meeting.id` is the row's `slug`,
10
- * `Meeting.title` is what the DB called `nameOverride`, and a workshop's
11
- * `project` (free text in the config) stands in for the DB's
12
- * `projects.displayName` join.
13
- */
14
- export function configEvents() {
15
- return {
16
- async meetings() {
17
- const { getClubConfig } = await loadEvents();
18
- const { EVENT_SEGMENT_VISUALS, meetingCardDetail, meetingLocation } = await loadBrandEvent();
19
- // Newest first, matching the DB reader's `order("startsAt", {
20
- // ascending: false })` — the picker should offer the most recent
21
- // meeting first, not whatever order the config file happens to list
22
- // them in.
23
- const meetings = [...getClubConfig().meetings].sort((a, b) => Date.parse(b.startsAt) - Date.parse(a.startsAt));
24
- return meetings.map((row) => {
25
- const meeting = {
26
- slug: row.id,
27
- startsAt: new Date(row.startsAt),
28
- endsAt: new Date(row.endsAt),
29
- building: row.building,
30
- location: row.location,
31
- kind: row.kind,
32
- cancelledAt: row.cancelledAt ? new Date(row.cancelledAt) : null,
33
- cancellationReason: row.cancellationReason,
34
- };
35
- // No competitions here either, for the same reason `supabaseEvents`
36
- // dropped them: a competition is a mirrored GitHub issue now, not
37
- // config a meeting kicks off. Every agenda item is plain "workshop".
38
- const items = row.agenda.map((workshop) => ({
39
- label: workshopLabel(workshop),
40
- segment: "workshop",
41
- }));
42
- const detail = meetingCardDetail({
43
- meeting,
44
- title: meetingTitle(row.title, row.kind, meeting.startsAt, items.map((item) => item.label)),
45
- agenda: items.map((item) => `${EVENT_SEGMENT_VISUALS[item.segment].label}: ${item.label}`),
46
- location: meetingLocation(row.building, row.location),
47
- });
48
- return {
49
- slug: row.id,
50
- hint: describeMeeting(meeting),
51
- detail,
52
- items: items.map((item) => {
53
- const visual = EVENT_SEGMENT_VISUALS[item.segment];
54
- return {
55
- stem: slugPart(item.label),
56
- detail: {
57
- ...detail,
58
- title: item.label,
59
- agenda: undefined,
60
- badge: { label: visual.label, accent: visual.accent },
61
- },
62
- };
63
- }),
64
- };
65
- });
66
- },
67
- };
68
- }
69
- /** A readable path component for a workshop card. */
70
- function slugPart(label) {
71
- return (label
72
- .toLowerCase()
73
- .normalize("NFKD")
74
- .replace(/[^a-z0-9]+/g, "-")
75
- .replace(/^-|-$/g, "") || "agenda-item");
76
- }
77
- /** Unlike the old DB `WorkshopRow`, `Workshop.title` is non-nullable in the
78
- * config schema (`workshopSchema`), so there is no `projects.displayName`
79
- * fallback left to reach for. */
80
- function workshopLabel(row) {
81
- return row.title;
82
- }
83
- /**
84
- * The meeting's name, in descending order of how much somebody meant it.
85
- *
86
- * Mirrors `apps/platform/src/lib/meetingTitle.ts`: an authored name, then the
87
- * kind, then the workshops it teaches, then the date. Two named workshops read
88
- * as a heading; three do not, and the date carries it while the agenda below
89
- * shows the detail.
90
- *
91
- * Duplicated rather than imported because it lives in the Next app, which this
92
- * package cannot depend on. `events.test.ts` pins the chain so a change to one
93
- * shows up as a failure rather than as a differently-titled poster.
94
- */
95
- export function meetingTitle(nameOverride, kind, startsAt, agenda) {
96
- if (nameOverride !== null)
97
- return nameOverride;
98
- if (kind !== null)
99
- return kind;
100
- if (agenda.length === 1)
101
- return `Workshop: ${agenda[0]}`;
102
- if (agenda.length === 2)
103
- return `Workshop: ${agenda[0]} & ${agenda[1]}`;
104
- return startsAt.toLocaleDateString("en-US", {
105
- timeZone: "America/New_York",
106
- dateStyle: "long",
107
- });
108
- }
109
- /** One line for the picker: when it is, and whether it is still on. */
110
- function describeMeeting(meeting) {
111
- const when = meeting.startsAt.toLocaleDateString("en-US", {
112
- timeZone: "America/New_York",
113
- dateStyle: "medium",
114
- });
115
- return meeting.cancelledAt === null ? when : `${when}, cancelled`;
116
- }
@@ -1,70 +0,0 @@
1
- import type { EventDetail, Format, OpenGraphModule } from "./open-graph-types.js";
2
- import type { ReactElement } from "react";
3
- /**
4
- * What the club draws pictures OF, as opposed to what size it draws them at.
5
- *
6
- * Those are two axes and this file owns one of them; `@devdogsuga/open-graph`'s
7
- * `formats.ts` owns the other. Keeping them apart is the point: an event poster
8
- * for the GDG on Campus platform is the pairing "this meeting" x "that
9
- * platform's banner", and while they were one flat list of hard-coded
10
- * (content, size) targets, no such pairing could be asked for — only the ones
11
- * somebody had already written down existed.
12
- *
13
- * The matrix is SPARSE, and each graphic declares its own column of it. Every
14
- * graphic supports the two GDG renditions, because anything the club makes may
15
- * end up on that platform. Beyond that: `savvycal` is one cover on one
16
- * scheduling page and belongs to the club lockup alone, the email densities
17
- * belong to the lockup too, and the icon sizes belong to the apps.
18
- */
19
- /** Where a rendered file goes, relative to the repo root. */
20
- export interface Destination {
21
- dir: string;
22
- file: string;
23
- }
24
- export interface Graphic {
25
- /** `group/name`, which is what the CLI matches and globs against. */
26
- name: string;
27
- group: string;
28
- /** The leaf. Unique across every graphic — `assertUniqueStems` enforces it. */
29
- stem: string;
30
- /** Format names this can be rendered at. */
31
- formats: string[];
32
- /** One line: what this picture is. */
33
- why: string;
34
- render: (format: Format) => ReactElement;
35
- /** Where `--default-out` puts it. */
36
- destination: (format: Format) => Destination;
37
- }
38
- /** A meeting, already loaded and formatted, ready to be drawn. */
39
- export interface EventGraphicSource {
40
- slug: string;
41
- detail: EventDetail;
42
- /** For the picker's hint: when it is, and whether it is on. */
43
- hint: string;
44
- /** Separate cards generated only when the night contains multiple items. */
45
- items?: Array<{
46
- detail: EventDetail;
47
- stem: string;
48
- }>;
49
- }
50
- /**
51
- * One graphic per meeting.
52
- *
53
- * These are the reason the two axes were split at all: "this meeting" x "the
54
- * GDG platform's banner" is a pairing nobody could have enumerated in advance.
55
- *
56
- * Their default directory is the one that is NOT in the repo. Every other
57
- * graphic is a committed asset the club re-renders; an event poster is an
58
- * export headed somewhere else, made once for one night. Writing those into
59
- * `public/brand` would fill a tracked directory with a growing pile of
60
- * one-offs, so they go to {@link EVENT_SCRATCH_DIR}, which `.gitignore` covers.
61
- */
62
- export declare const EVENT_SCRATCH_DIR = ".images/events";
63
- export declare function eventGraphics(og: OpenGraphModule, events: EventGraphicSource[]): Graphic[];
64
- /** Everything that does not need a database. */
65
- export declare function staticGraphics(): Promise<Graphic[]>;
66
- /**
67
- * Every leaf is unique, so a flat `--out` directory cannot silently overwrite
68
- * one graphic's file with another's.
69
- */
70
- export declare function assertUniqueStems(graphics: Graphic[]): void;