@ethlete/cli 2.0.1-next.0 → 2.1.0-next.10

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 (159) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +173 -6
  3. package/design/define-call.ts +92 -0
  4. package/design/web/env.d.ts +11 -0
  5. package/design/web/frame.html +17 -0
  6. package/design/web/frame.ts +38 -0
  7. package/design/web/host.css +494 -0
  8. package/design/web/host.ts +445 -0
  9. package/design/web/index.html +11 -0
  10. package/package.json +12 -1
  11. package/src/index.d.ts +1 -1
  12. package/src/index.js +96 -10
  13. package/src/index.js.map +1 -1
  14. package/src/lib/api/api-command.d.ts +5 -0
  15. package/src/lib/api/api-command.js +27 -0
  16. package/src/lib/api/api-command.js.map +1 -0
  17. package/src/lib/api/auth-hint.d.ts +16 -0
  18. package/src/lib/api/auth-hint.js +59 -0
  19. package/src/lib/api/auth-hint.js.map +1 -0
  20. package/src/lib/api/clear.d.ts +36 -0
  21. package/src/lib/api/clear.js +121 -0
  22. package/src/lib/api/clear.js.map +1 -0
  23. package/src/lib/api/clone.d.ts +12 -0
  24. package/src/lib/api/clone.js +24 -0
  25. package/src/lib/api/clone.js.map +1 -0
  26. package/src/lib/api/compose.d.ts +46 -0
  27. package/src/lib/api/compose.js +101 -0
  28. package/src/lib/api/compose.js.map +1 -0
  29. package/src/lib/api/definition.d.ts +37 -0
  30. package/src/lib/api/definition.js +33 -0
  31. package/src/lib/api/definition.js.map +1 -0
  32. package/src/lib/api/git.d.ts +25 -0
  33. package/src/lib/api/git.js +71 -0
  34. package/src/lib/api/git.js.map +1 -0
  35. package/src/lib/api/help.d.ts +11 -0
  36. package/src/lib/api/help.js +72 -0
  37. package/src/lib/api/help.js.map +1 -0
  38. package/src/lib/api/index.d.ts +15 -0
  39. package/src/lib/api/index.js +19 -0
  40. package/src/lib/api/index.js.map +1 -0
  41. package/src/lib/api/load-definitions.d.ts +16 -0
  42. package/src/lib/api/load-definitions.js +22 -0
  43. package/src/lib/api/load-definitions.js.map +1 -0
  44. package/src/lib/api/ports.d.ts +13 -0
  45. package/src/lib/api/ports.js +98 -0
  46. package/src/lib/api/ports.js.map +1 -0
  47. package/src/lib/api/resolve-checkout.d.ts +45 -0
  48. package/src/lib/api/resolve-checkout.js +91 -0
  49. package/src/lib/api/resolve-checkout.js.map +1 -0
  50. package/src/lib/api/run.d.ts +10 -0
  51. package/src/lib/api/run.js +332 -0
  52. package/src/lib/api/run.js.map +1 -0
  53. package/src/lib/api/setup.d.ts +15 -0
  54. package/src/lib/api/setup.js +41 -0
  55. package/src/lib/api/setup.js.map +1 -0
  56. package/src/lib/api/state.d.ts +20 -0
  57. package/src/lib/api/state.js +33 -0
  58. package/src/lib/api/state.js.map +1 -0
  59. package/src/lib/api/suggest.d.ts +7 -0
  60. package/src/lib/api/suggest.js +35 -0
  61. package/src/lib/api/suggest.js.map +1 -0
  62. package/src/lib/auth/auth-command.d.ts +11 -0
  63. package/src/lib/auth/auth-command.js +165 -0
  64. package/src/lib/auth/auth-command.js.map +1 -0
  65. package/src/lib/auth/composer-auth.d.ts +30 -0
  66. package/src/lib/auth/composer-auth.js +70 -0
  67. package/src/lib/auth/composer-auth.js.map +1 -0
  68. package/src/lib/auth/composer-repositories.d.ts +5 -0
  69. package/src/lib/auth/composer-repositories.js +33 -0
  70. package/src/lib/auth/composer-repositories.js.map +1 -0
  71. package/src/lib/auth/gitlab-token.d.ts +43 -0
  72. package/src/lib/auth/gitlab-token.js +72 -0
  73. package/src/lib/auth/gitlab-token.js.map +1 -0
  74. package/src/lib/auth/index.d.ts +4 -0
  75. package/src/lib/auth/index.js +8 -0
  76. package/src/lib/auth/index.js.map +1 -0
  77. package/src/lib/config/diagnose.d.ts +7 -0
  78. package/src/lib/config/diagnose.js +112 -0
  79. package/src/lib/config/diagnose.js.map +1 -0
  80. package/src/lib/config/index.d.ts +2 -0
  81. package/src/lib/config/index.js +6 -0
  82. package/src/lib/config/index.js.map +1 -0
  83. package/src/lib/config/local-config.d.ts +46 -0
  84. package/src/lib/config/local-config.js +57 -0
  85. package/src/lib/config/local-config.js.map +1 -0
  86. package/src/lib/design/calls-tsconfig.d.ts +6 -0
  87. package/src/lib/design/calls-tsconfig.js +37 -0
  88. package/src/lib/design/calls-tsconfig.js.map +1 -0
  89. package/src/lib/design/check.d.ts +11 -0
  90. package/src/lib/design/check.js +192 -0
  91. package/src/lib/design/check.js.map +1 -0
  92. package/src/lib/design/design-command.d.ts +10 -0
  93. package/src/lib/design/design-command.js +32 -0
  94. package/src/lib/design/design-command.js.map +1 -0
  95. package/src/lib/design/index.d.ts +5 -0
  96. package/src/lib/design/index.js +9 -0
  97. package/src/lib/design/index.js.map +1 -0
  98. package/src/lib/design/paths.d.ts +24 -0
  99. package/src/lib/design/paths.js +31 -0
  100. package/src/lib/design/paths.js.map +1 -0
  101. package/src/lib/design/serve.d.ts +7 -0
  102. package/src/lib/design/serve.js +144 -0
  103. package/src/lib/design/serve.js.map +1 -0
  104. package/src/lib/doctor/doctor-command.d.ts +7 -0
  105. package/src/lib/doctor/doctor-command.js +62 -0
  106. package/src/lib/doctor/doctor-command.js.map +1 -0
  107. package/src/lib/doctor/index.d.ts +1 -0
  108. package/src/lib/doctor/index.js +5 -0
  109. package/src/lib/doctor/index.js.map +1 -0
  110. package/src/lib/index.d.ts +7 -0
  111. package/src/lib/index.js +7 -0
  112. package/src/lib/index.js.map +1 -1
  113. package/src/lib/invocation.d.ts +9 -0
  114. package/src/lib/invocation.js +47 -0
  115. package/src/lib/invocation.js.map +1 -0
  116. package/src/lib/release.js +21 -3
  117. package/src/lib/release.js.map +1 -1
  118. package/src/lib/update/ai.d.ts +30 -0
  119. package/src/lib/update/ai.js +44 -0
  120. package/src/lib/update/ai.js.map +1 -0
  121. package/src/lib/update/args.d.ts +21 -0
  122. package/src/lib/update/args.js +89 -0
  123. package/src/lib/update/args.js.map +1 -0
  124. package/src/lib/update/index.d.ts +12 -0
  125. package/src/lib/update/index.js +16 -0
  126. package/src/lib/update/index.js.map +1 -0
  127. package/src/lib/update/migration-manifest.d.ts +40 -0
  128. package/src/lib/update/migration-manifest.js +137 -0
  129. package/src/lib/update/migration-manifest.js.map +1 -0
  130. package/src/lib/update/package-manager.d.ts +23 -0
  131. package/src/lib/update/package-manager.js +47 -0
  132. package/src/lib/update/package-manager.js.map +1 -0
  133. package/src/lib/update/packages.d.ts +65 -0
  134. package/src/lib/update/packages.js +147 -0
  135. package/src/lib/update/packages.js.map +1 -0
  136. package/src/lib/update/pending.d.ts +21 -0
  137. package/src/lib/update/pending.js +41 -0
  138. package/src/lib/update/pending.js.map +1 -0
  139. package/src/lib/update/plan.d.ts +51 -0
  140. package/src/lib/update/plan.js +101 -0
  141. package/src/lib/update/plan.js.map +1 -0
  142. package/src/lib/update/registry.d.ts +31 -0
  143. package/src/lib/update/registry.js +54 -0
  144. package/src/lib/update/registry.js.map +1 -0
  145. package/src/lib/update/run-migrations.d.ts +44 -0
  146. package/src/lib/update/run-migrations.js +73 -0
  147. package/src/lib/update/run-migrations.js.map +1 -0
  148. package/src/lib/update/semver.d.ts +19 -0
  149. package/src/lib/update/semver.js +79 -0
  150. package/src/lib/update/semver.js.map +1 -0
  151. package/src/lib/update/tasks.d.ts +58 -0
  152. package/src/lib/update/tasks.js +174 -0
  153. package/src/lib/update/tasks.js.map +1 -0
  154. package/src/lib/update/update-command.d.ts +12 -0
  155. package/src/lib/update/update-command.js +304 -0
  156. package/src/lib/update/update-command.js.map +1 -0
  157. package/src/lib/utils.d.ts +14 -0
  158. package/src/lib/utils.js +14 -1
  159. package/src/lib/utils.js.map +1 -1
@@ -0,0 +1,445 @@
1
+ import type { Call, CallVariant, CallRound } from '@design-explore';
2
+ import { calls, defaultCall } from 'virtual:design-explore';
3
+ import './host.css';
4
+
5
+ /** The contact sheet draws about five 1100px frames per screen at this scale. */
6
+ const SHEET_SCALE = 0.32;
7
+
8
+ const params = new URLSearchParams(location.search);
9
+ const slugs = Object.keys(calls);
10
+ const asked = params.get('call') ?? '';
11
+ const landing = defaultCall && slugs.includes(defaultCall) ? defaultCall : (slugs.at(-1) ?? '');
12
+ const slug = slugs.includes(asked) ? asked : landing;
13
+ const view = params.get('view') === 'sheet' ? 'sheet' : 'rounds';
14
+ const opened = new Set((params.get('open') ?? '').split(',').filter(Boolean));
15
+ const picked = (params.get('pick') ?? '').split(',').filter(Boolean);
16
+
17
+ const esc = (text: string) => text.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' })[c] ?? c);
18
+
19
+ const link = (next: Record<string, string | null>) => {
20
+ const url = new URLSearchParams(params);
21
+ for (const [name, value] of Object.entries(next)) {
22
+ if (value === null) url.delete(name);
23
+ else url.set(name, value);
24
+ }
25
+ return `?${url}`;
26
+ };
27
+
28
+ const listLink = (name: string, list: string[], key: string) => {
29
+ const next = list.includes(key) ? list.filter((entry) => entry !== key) : [...list, key];
30
+ return link({ [name]: next.length > 0 ? next.join(',') : null });
31
+ };
32
+
33
+ const toggleOpen = (key: string) => listLink('open', [...opened], key);
34
+ const togglePick = (key: string) => listLink('pick', picked, key);
35
+
36
+ type Band = { key: string | null; number: number; round: CallRound | null; variants: CallVariant[] };
37
+
38
+ /**
39
+ * The variants are the call. A band exists because a variant names it, never because `rounds`
40
+ * declares it, so a variant added without its round entry still gets a band, a heading and a
41
+ * menu row - all three say the bare key until somebody writes the prose.
42
+ */
43
+ const bandsOf = (call: Call): Band[] => {
44
+ const declared = new Map((call.rounds ?? []).map((round) => [round.key, round]));
45
+ const bands = new Map<string, Band>();
46
+
47
+ for (const variant of call.variants) {
48
+ const key = variant.round ?? '';
49
+ const band = bands.get(key) ?? { key: key || null, number: 0, round: declared.get(key) ?? null, variants: [] };
50
+ band.variants.push(variant);
51
+ bands.set(key, band);
52
+ }
53
+
54
+ let number = 0;
55
+ for (const band of bands.values()) if (band.key) band.number = ++number;
56
+
57
+ return [...bands.values()];
58
+ };
59
+
60
+ /** Prose that no variant claims is stale prose, and it is the one thing the variants cannot show. */
61
+ const problemsOf = (call: Call, bands: Band[]) => {
62
+ const keys = (call.rounds ?? []).map((round) => round.key);
63
+ const drawn = new Set(bands.map((band) => band.key));
64
+
65
+ return [
66
+ ...[...new Set(keys.filter((key, index) => keys.indexOf(key) !== index))].map(
67
+ (key) => `two rounds share the key "${key}"`,
68
+ ),
69
+ ...keys.filter((key) => !drawn.has(key)).map((key) => `round "${key}" has no variants`),
70
+ ];
71
+ };
72
+
73
+ /**
74
+ * A call is resolved once every round has ruled and at least one round has a winner. Its rounds
75
+ * then draw nothing, because each winner is the one before it plus a change, so the last of them
76
+ * already holds all the others.
77
+ */
78
+ const chainOf = (bands: Band[]) => {
79
+ const named = bands.filter((band) => band.key);
80
+ const ruled = named.length > 1 && named.every((band) => band.variants.every((variant) => variant.verdict));
81
+ const chain = named
82
+ .map((band) => band.variants.find((variant) => variant.verdict === 'chosen'))
83
+ .filter((variant): variant is CallVariant => !!variant);
84
+
85
+ return ruled && chain.length > 0 ? chain : [];
86
+ };
87
+
88
+ /** A call's folder path is its place in the tree, so the sidebar nests on the path segments. */
89
+ const tree = (bands: Band[]) => {
90
+ const groups = new Map<string, string[]>();
91
+ for (const s of slugs) {
92
+ const parts = s.split('/');
93
+ const group = parts.slice(0, -1).join(' / ');
94
+ groups.set(group, [...(groups.get(group) ?? []), s]);
95
+ }
96
+
97
+ const named = bands.filter((band) => band.key);
98
+
99
+ const rounds = (s: string) =>
100
+ s !== slug || view === 'sheet' || named.length < 2
101
+ ? ''
102
+ : `<div class="nav-rounds">${named
103
+ .map(
104
+ (band) =>
105
+ `<a href="#round-${esc(band.key ?? '')}">${band.number} · ${esc(band.round?.title ?? band.key ?? '')}</a>`,
106
+ )
107
+ .join('')}</div>`;
108
+
109
+ return [...groups]
110
+ .map(
111
+ ([group, members]) => `
112
+ <section>
113
+ ${group ? `<h3>${esc(group)}</h3>` : ''}
114
+ ${members
115
+ .map(
116
+ (s) =>
117
+ `<a href="${link({ call: s, open: null, pick: null })}" ${s === slug ? 'aria-current="page"' : ''}>${esc(
118
+ s.split('/').at(-1) ?? s,
119
+ )}</a>${rounds(s)}`,
120
+ )
121
+ .join('')}
122
+ </section>`,
123
+ )
124
+ .join('');
125
+ };
126
+
127
+ const load = slug ? calls[slug] : undefined;
128
+
129
+ if (!load) {
130
+ document.body.innerHTML = `<p class="empty">No call under .ethlete/design/calls.</p>`;
131
+ } else {
132
+ const call: Call = (await load()).default;
133
+ const bands = bandsOf(call);
134
+ const chain = chainOf(bands);
135
+ const result = chain.at(-1);
136
+ const isView = call.variants.length === 1 && !call.variants[0]?.claim;
137
+ const geometry = `--frame-width:${call.frameWidth}px`;
138
+
139
+ const frame = (variant: CallVariant) => `
140
+ <iframe
141
+ src="frame.html?call=${encodeURIComponent(slug)}&variant=${encodeURIComponent(variant.key)}"
142
+ data-variant="${esc(variant.key)}"
143
+ title="${esc(variant.name)}"
144
+ style="width:${call.frameWidth}px"
145
+ ></iframe>`;
146
+
147
+ const column = (variant: CallVariant) => `
148
+ <div class="column" data-verdict="${variant.verdict ?? 'open'}" ${picked.includes(variant.key) ? 'data-picked' : ''}>
149
+ ${
150
+ isView
151
+ ? ''
152
+ : `<h3>
153
+ <a href="${togglePick(variant.key)}">${esc(variant.name)}</a>
154
+ <span class="tag">${variant.verdict ?? 'open'}</span>
155
+ </h3>`
156
+ }
157
+ <div class="stage">${frame(variant)}</div>
158
+ ${variant.claim ? `<p class="claim">${esc(variant.claim)}</p>` : ''}
159
+ ${variant.cost ? `<p class="cost">${esc(variant.cost)}</p>` : ''}
160
+ </div>`;
161
+
162
+ const grid = (variants: CallVariant[]) =>
163
+ variants.length === 0
164
+ ? ''
165
+ : `<div class="grid" style="${geometry}" ${isView ? 'data-view' : ''}>${variants.map(column).join('')}</div>`;
166
+
167
+ const rows = (variants: CallVariant[]) =>
168
+ variants.length === 0
169
+ ? ''
170
+ : `<ul class="folded">${variants
171
+ .map(
172
+ (variant) =>
173
+ `<li data-verdict="${variant.verdict ?? 'open'}"><a href="${togglePick(variant.key)}">${esc(
174
+ variant.name,
175
+ )}</a><span class="tag">${variant.verdict ?? 'open'}</span></li>`,
176
+ )
177
+ .join('')}</ul>`;
178
+
179
+ const band = ({ key, number, round, variants }: Band) => {
180
+ const settled = variants.every((variant) => variant.verdict);
181
+ const open = !key || !settled || opened.has(key);
182
+ const drawn = open ? variants : chain.length > 0 ? [] : variants.filter((variant) => variant.verdict === 'chosen');
183
+ const folded = variants.filter((variant) => !drawn.includes(variant));
184
+
185
+ const head = !key
186
+ ? ''
187
+ : `<div class="round-head">
188
+ <span class="eyebrow">Round ${number}${settled ? ' · settled' : ''}</span>
189
+ <h2>${esc(round?.title ?? key)}</h2>
190
+ ${round?.note ? `<p>${esc(round.note)}</p>` : ''}
191
+ ${
192
+ settled
193
+ ? `<a class="fold" href="${toggleOpen(key)}">${
194
+ open ? `Fold ${variants.length} variants away` : `Show all ${variants.length} variants`
195
+ }</a>`
196
+ : ''
197
+ }
198
+ </div>`;
199
+
200
+ return `
201
+ <section class="round" ${key ? `id="round-${esc(key)}"` : ''}>
202
+ ${head}
203
+ ${grid(drawn)}
204
+ ${rows(folded)}
205
+ </section>`;
206
+ };
207
+
208
+ /** Each step is the one before it plus a change, so the arrow is the order they were drawn in. */
209
+ const trail = () =>
210
+ chain
211
+ .map((variant) => {
212
+ const band = bands.find((entry) => entry.variants.includes(variant));
213
+ const target = band?.key ? `${toggleOpen(band.key)}#round-${band.key}` : togglePick(variant.key);
214
+ return `<a href="${esc(target)}" ${variant === result ? 'aria-current="step"' : ''}>${esc(
215
+ variant.key.toUpperCase(),
216
+ )}</a>`;
217
+ })
218
+ .join('<span class="arrow">→</span>');
219
+
220
+ const resultBand = () =>
221
+ !result
222
+ ? ''
223
+ : `<section class="round result">
224
+ <div class="round-head">
225
+ <span class="eyebrow">Result</span>
226
+ <h2>${esc(result.name)}</h2>
227
+ ${result.claim ? `<p>${esc(result.claim)}</p>` : ''}
228
+ <div class="trail">${trail()}</div>
229
+ </div>
230
+ <div class="grid" style="${geometry}">
231
+ <div class="column" data-verdict="chosen">
232
+ <div class="stage">${frame(result)}</div>
233
+ ${result.cost ? `<p class="cost">${esc(result.cost)}</p>` : ''}
234
+ </div>
235
+ </div>
236
+ </section>`;
237
+
238
+ const tray = () => {
239
+ const variants = picked
240
+ .map((key) => call.variants.find((variant) => variant.key === key))
241
+ .filter((variant): variant is CallVariant => !!variant);
242
+ if (variants.length === 0) return '';
243
+
244
+ const two = variants.length === 2;
245
+
246
+ return `
247
+ <section class="round tray">
248
+ <div class="round-head">
249
+ <span class="eyebrow">Compare · ${variants.length}</span>
250
+ <h2>${variants.map((variant) => esc(variant.name)).join(' · ')}</h2>
251
+ <div class="switch">
252
+ ${variants
253
+ .map((variant, index) => `<button data-step="${index}">${esc(variant.key.toUpperCase())}</button>`)
254
+ .join('')}
255
+ <span class="hint">${
256
+ two ? 'click or space to blink · arrows to wipe' : 'click or space for the next one'
257
+ }</span>
258
+ </div>
259
+ <a class="fold" href="${link({ pick: null })}">Clear the comparison</a>
260
+ </div>
261
+ <div class="overlay" data-two="${two}" style="width:${call.frameWidth}px">
262
+ ${variants.map(frame).join('')}
263
+ <div class="seam"></div>
264
+ </div>
265
+ </section>`;
266
+ };
267
+
268
+ const sheet = () => `
269
+ <div class="grid sheet" style="${geometry};--de-scale:${SHEET_SCALE}">
270
+ ${call.variants
271
+ .map(
272
+ (variant) => `
273
+ <a class="thumb" href="${togglePick(variant.key)}" data-verdict="${variant.verdict ?? 'open'}" ${
274
+ picked.includes(variant.key) ? 'data-picked' : ''
275
+ }>
276
+ <div class="thumb-frame">${frame(variant)}</div>
277
+ <span class="thumb-name">${esc(variant.name)}</span>
278
+ </a>`,
279
+ )
280
+ .join('')}
281
+ </div>`;
282
+
283
+ const views = () =>
284
+ isView
285
+ ? ''
286
+ : `<div class="views">
287
+ <a href="${link({ view: null })}" ${view === 'rounds' ? 'aria-current="page"' : ''}>rounds</a>
288
+ <a href="${link({ view: 'sheet' })}" ${view === 'sheet' ? 'aria-current="page"' : ''}>all ${
289
+ call.variants.length
290
+ }</a>
291
+ </div>`;
292
+
293
+ document.body.innerHTML = `
294
+ <nav>${tree(bands)}</nav>
295
+ <main>
296
+ <header>
297
+ <span class="eyebrow">${esc(call.eyebrow)}</span>
298
+ <h1>${esc(call.headline)}</h1>
299
+ <p>${esc(call.intro)}</p>
300
+ ${views()}
301
+ </header>
302
+ ${problemsOf(call, bands)
303
+ .map((problem) => `<p class="problem">design-explore: ${esc(problem)}</p>`)
304
+ .join('')}
305
+ ${tray()}
306
+ ${view === 'sheet' ? sheet() : `${resultBand()}${bands.map(band).join('')}`}
307
+ </main>`;
308
+ }
309
+
310
+ /**
311
+ * A frame reports its height late, so the page grows under the reader for a second or two after
312
+ * load. The scroll is put back on every one of those reports, until the reader scrolls or the
313
+ * frames go quiet, because a single restore lands on a page that is still the wrong height.
314
+ */
315
+ const scrollKey = `design-explore:scroll:${slug}:${view}`;
316
+
317
+ history.scrollRestoration = 'manual';
318
+
319
+ let restoring = true;
320
+ const target = Number(sessionStorage.getItem(scrollKey) ?? 0);
321
+
322
+ const stopRestore = () => {
323
+ restoring = false;
324
+ };
325
+
326
+ if (target > 0 && !location.hash) {
327
+ for (const event of ['wheel', 'touchstart', 'keydown', 'pointerdown'] as const) {
328
+ addEventListener(event, stopRestore, { once: true, passive: true });
329
+ }
330
+ setTimeout(stopRestore, 3000);
331
+ scrollTo(0, target);
332
+ } else {
333
+ restoring = false;
334
+ }
335
+
336
+ addEventListener(
337
+ 'scroll',
338
+ () => {
339
+ if (!restoring) sessionStorage.setItem(scrollKey, String(Math.round(scrollY)));
340
+ },
341
+ { passive: true },
342
+ );
343
+
344
+ const heights = new Map<string, number>();
345
+
346
+ /** The overlay is as tall as its tallest pick, so no frame is cut and none of them move. */
347
+ const size = () => {
348
+ for (const frame of document.querySelectorAll<HTMLIFrameElement>('iframe[data-variant]')) {
349
+ const height = heights.get(frame.dataset.variant ?? '');
350
+ if (!height) continue;
351
+
352
+ frame.style.height = `${height}px`;
353
+
354
+ const box = frame.parentElement;
355
+ if (box?.classList.contains('thumb-frame')) box.style.height = `${Math.ceil(height * SHEET_SCALE)}px`;
356
+ }
357
+
358
+ const overlay = document.querySelector<HTMLElement>('.overlay');
359
+ if (!overlay) return;
360
+
361
+ const tallest = [...overlay.querySelectorAll<HTMLIFrameElement>('iframe')]
362
+ .map((frame) => heights.get(frame.dataset.variant ?? '') ?? 0)
363
+ .reduce((a, b) => Math.max(a, b), 0);
364
+
365
+ if (tallest > 0) overlay.style.height = `${tallest}px`;
366
+ };
367
+
368
+ addEventListener('message', (event) => {
369
+ const data = event.data as { type?: string; variant?: string; height?: number };
370
+ if (data?.type !== 'design-explore:height' || !data.height || !data.variant) return;
371
+
372
+ heights.set(data.variant, data.height);
373
+ size();
374
+
375
+ if (restoring) scrollTo(0, target);
376
+ });
377
+
378
+ /**
379
+ * Two drawings that differ by a few pixels can only be told apart in one place, so the picks are
380
+ * stacked at full size and the reader switches between them. Two picks also get a wipe: the
381
+ * second one is clipped from the left, so dragging the seam turns one into the other.
382
+ */
383
+ const overlay = document.querySelector<HTMLElement>('.overlay');
384
+
385
+ if (overlay) {
386
+ const frames = [...overlay.querySelectorAll<HTMLIFrameElement>('iframe')];
387
+ const steps = [...document.querySelectorAll<HTMLButtonElement>('.switch button')];
388
+ const seam = overlay.querySelector<HTMLElement>('.seam');
389
+ const two = overlay.dataset.two === 'true';
390
+
391
+ let wipe = 50;
392
+ let active = 0;
393
+
394
+ const paint = () => {
395
+ frames.forEach((frame, index) => {
396
+ if (two && index === 1) {
397
+ frame.style.clipPath = `inset(0 0 0 ${wipe}%)`;
398
+ frame.style.opacity = '1';
399
+ } else if (two) {
400
+ frame.style.opacity = '1';
401
+ } else {
402
+ frame.style.opacity = index === active ? '1' : '0';
403
+ }
404
+ });
405
+
406
+ if (seam) {
407
+ seam.style.left = `${wipe}%`;
408
+ seam.hidden = !two || wipe === 0 || wipe === 100;
409
+ }
410
+
411
+ const shown = two ? (wipe > 50 ? 0 : 1) : active;
412
+ steps.forEach((step, index) => step.toggleAttribute('aria-current', index === shown));
413
+ };
414
+
415
+ const next = () => {
416
+ if (two) wipe = wipe > 50 ? 0 : 100;
417
+ else active = (active + 1) % frames.length;
418
+ paint();
419
+ };
420
+
421
+ const go = (index: number) => {
422
+ if (two) wipe = index === 0 ? 100 : 0;
423
+ else active = index;
424
+ paint();
425
+ };
426
+
427
+ steps.forEach((step, index) => step.addEventListener('click', () => go(index)));
428
+ overlay.addEventListener('click', next);
429
+
430
+ addEventListener('keydown', (event) => {
431
+ if (event.target !== document.body) return;
432
+
433
+ if (event.key === ' ') next();
434
+ else if (two && event.key === 'ArrowLeft') wipe = Math.max(0, wipe - 5);
435
+ else if (two && event.key === 'ArrowRight') wipe = Math.min(100, wipe + 5);
436
+ else if (!two && event.key === 'ArrowLeft') active = (active - 1 + frames.length) % frames.length;
437
+ else if (!two && event.key === 'ArrowRight') active = (active + 1) % frames.length;
438
+ else return;
439
+
440
+ event.preventDefault();
441
+ paint();
442
+ });
443
+
444
+ paint();
445
+ }
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Design explore</title>
7
+ </head>
8
+ <body>
9
+ <script type="module" src="./host.ts"></script>
10
+ </body>
11
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethlete/cli",
3
- "version": "2.0.1-next.0",
3
+ "version": "2.1.0-next.10",
4
4
  "license": "MIT",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -8,8 +8,19 @@
8
8
  "bin": {
9
9
  "et": "./src/index.js"
10
10
  },
11
+ "dependencies": {
12
+ "@tailwindcss/postcss": "4.3.3",
13
+ "typescript": "6.0.3",
14
+ "vite": "8.1.5"
15
+ },
11
16
  "peerDependencies": {
17
+ "playwright": "1.61.1",
12
18
  "tslib": "2.8.1"
13
19
  },
20
+ "peerDependenciesMeta": {
21
+ "playwright": {
22
+ "optional": true
23
+ }
24
+ },
14
25
  "types": "./src/index.d.ts"
15
26
  }
package/src/index.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ export * from './lib';
package/src/index.js CHANGED
@@ -2,19 +2,105 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const lib_1 = require("./lib");
5
+ const USAGE_ROWS = [
6
+ { subcommand: 'release', args: '', lines: ['Turn pending changesets into a tagged, pushed release commit'] },
7
+ {
8
+ subcommand: 'api',
9
+ args: ' <cmd> <api>',
10
+ lines: [
11
+ "Run an API from this repo's ethlete.apis.js locally",
12
+ "(up, down, logs, shell, setup, plus that API's own exec entries)",
13
+ ],
14
+ },
15
+ {
16
+ subcommand: 'auth',
17
+ args: ' [host] <token>',
18
+ lines: [
19
+ "Write a GitLab token into composer's auth.json, which the",
20
+ 'API containers mount, after checking it can download code',
21
+ ],
22
+ },
23
+ {
24
+ subcommand: 'doctor',
25
+ args: '',
26
+ lines: ["Check this machine's ethlete.config.local.json, container engine", 'and every API checkout'],
27
+ },
28
+ {
29
+ subcommand: 'design',
30
+ args: ' [checkout]',
31
+ lines: [
32
+ "Serve the design page of a checkout's .ethlete/design folder,",
33
+ 'or run `design check` to find out why a call does not render',
34
+ ],
35
+ },
36
+ {
37
+ subcommand: 'update',
38
+ args: ' [packages...]',
39
+ lines: ['Move the @ethlete/* packages to a newer version and run the', 'migrations those versions ship'],
40
+ },
41
+ ];
42
+ const usage = (root) => {
43
+ const rows = USAGE_ROWS.map((row) => (Object.assign(Object.assign({}, row), { command: `${(0, lib_1.repoInvocation)({ root, subcommand: row.subcommand })}${row.args}` })));
44
+ const width = Math.max(...rows.map((row) => row.command.length));
45
+ return [
46
+ 'et — Ethlete repo tooling',
47
+ '',
48
+ ...rows.flatMap((row) => row.lines.map((line, index) => ` ${(index === 0 ? row.command : '').padEnd(width)} ${line}`)),
49
+ '',
50
+ ].join('\n');
51
+ };
5
52
  const cli = (args) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
6
- const cliFunctionArg = args[0];
7
- switch (cliFunctionArg) {
53
+ switch (args[0]) {
8
54
  case 'release':
9
- yield (0, lib_1.release)(args);
10
- break;
55
+ // `release` reads its flags from a list that was split on "=" before this switch existed.
56
+ // Keep that shape here so its own parsing is untouched.
57
+ yield (0, lib_1.release)(args.join('=').split('='));
58
+ return 0;
59
+ case 'api':
60
+ return (0, lib_1.apiCommand)({
61
+ root: process.cwd(),
62
+ argv: args.slice(1),
63
+ invocation: (0, lib_1.repoInvocation)({ root: process.cwd(), subcommand: 'api' }),
64
+ });
65
+ case 'auth':
66
+ return (0, lib_1.authCommand)({
67
+ root: process.cwd(),
68
+ argv: args.slice(1),
69
+ invocation: (0, lib_1.repoInvocation)({ root: process.cwd(), subcommand: 'auth' }),
70
+ });
71
+ case 'design':
72
+ return (0, lib_1.designCommand)({
73
+ root: process.cwd(),
74
+ argv: args.slice(1),
75
+ invocation: (0, lib_1.repoInvocation)({ root: process.cwd(), subcommand: 'design' }),
76
+ });
77
+ case 'update':
78
+ return (0, lib_1.updateCommand)({
79
+ root: process.cwd(),
80
+ argv: args.slice(1),
81
+ invocation: (0, lib_1.repoInvocation)({ root: process.cwd(), subcommand: 'update' }),
82
+ });
83
+ case 'doctor':
84
+ // Every fix it names is an `et api` command, so it needs that script rather than its own.
85
+ return (0, lib_1.doctorCommand)({
86
+ root: process.cwd(),
87
+ apiInvocation: (0, lib_1.repoInvocation)({ root: process.cwd(), subcommand: 'api' }),
88
+ });
11
89
  default:
12
- console.log(`No command found named ${cliFunctionArg}. Available commands are: release`);
13
- break;
90
+ console.log(usage(process.cwd()));
91
+ return args[0] === undefined || args[0] === '--help' || args[0] === '-h' ? 0 : 1;
14
92
  }
15
93
  });
16
- // Get the command line arguments and normalize them
17
- const args = process.argv.slice(2).join('=').split('=');
18
- cli(args);
19
- // Burst cache
94
+ tslib_1.__exportStar(require("./lib"), exports);
95
+ // Guarded so the package can also be imported as a library without running the CLI.
96
+ if (require.main === module) {
97
+ cli(process.argv.slice(2))
98
+ .then((code) => {
99
+ process.exitCode = code;
100
+ })
101
+ .catch((error) => {
102
+ console.error(error instanceof Error ? error.message : error);
103
+ process.exitCode = 1;
104
+ });
105
+ }
20
106
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cli/src/index.ts"],"names":[],"mappings":";;;AAAA,+BAAgC;AAEhC,MAAM,GAAG,GAAG,CAAO,IAAc,EAAE,EAAE;IACnC,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAE/B,QAAQ,cAAc,EAAE,CAAC;QACvB,KAAK,SAAS;YACZ,MAAM,IAAA,aAAO,EAAC,IAAI,CAAC,CAAC;YACpB,MAAM;QAER;YACE,OAAO,CAAC,GAAG,CAAC,0BAA0B,cAAc,mCAAmC,CAAC,CAAC;YACzF,MAAM;IACV,CAAC;AACH,CAAC,CAAA,CAAC;AAEF,oDAAoD;AACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAExD,GAAG,CAAC,IAAI,CAAC,CAAC;AAEV,cAAc"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cli/src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const apiCommand: ({ root, argv, invocation }: {
2
+ root: string;
3
+ argv: string[];
4
+ invocation?: string;
5
+ }) => 1 | Promise<number>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiCommand = void 0;
4
+ const load_definitions_1 = require("./load-definitions");
5
+ const run_1 = require("./run");
6
+ const apiCommand = ({ root, argv, invocation }) => {
7
+ const { found, apis } = (0, load_definitions_1.loadApiDefinitions)(root);
8
+ if (!found) {
9
+ console.error(`No API definitions found. Create ${load_definitions_1.API_DEFINITIONS_FILE_NAMES[0]} in the repo root:\n\n` +
10
+ ` const { sshKeyPath } = require('@ethlete/cli');\n\n` +
11
+ ` module.exports = {\n` +
12
+ ` hub: {\n` +
13
+ ` composeDir: 'development',\n` +
14
+ ` services: ['app', 'database'],\n` +
15
+ ` execService: 'app',\n` +
16
+ ` port: 8040,\n` +
17
+ ` envFile: '.env',\n` +
18
+ ` setupCommand: 'make setup',\n` +
19
+ ` exec: { install: ['composer', 'install'] },\n` +
20
+ ` },\n` +
21
+ ` };`);
22
+ return 1;
23
+ }
24
+ return (0, run_1.runApiCommand)({ apis, argv, root, invocation });
25
+ };
26
+ exports.apiCommand = apiCommand;
27
+ //# sourceMappingURL=api-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-command.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/api-command.ts"],"names":[],"mappings":";;;AAAA,yDAAoF;AACpF,+BAAsC;AAE/B,MAAM,UAAU,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAyD,EAAE,EAAE;IAC9G,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAA,qCAAkB,EAAC,IAAI,CAAC,CAAC;IAEjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX,oCAAoC,6CAA0B,CAAC,CAAC,CAAC,wBAAwB;YACvF,uDAAuD;YACvD,wBAAwB;YACxB,cAAc;YACd,oCAAoC;YACpC,wCAAwC;YACxC,6BAA6B;YAC7B,qBAAqB;YACrB,0BAA0B;YAC1B,qCAAqC;YACrC,qDAAqD;YACrD,UAAU;YACV,MAAM,CACT,CAAC;QAEF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,IAAA,mBAAa,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;AACzD,CAAC,CAAC;AAxBW,QAAA,UAAU,cAwBrB"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A private dependency that will not download is the most common way these commands fail, and git
3
+ * and the package managers report it as a bare 403 or a public-key error. This says what to check.
4
+ */
5
+ export declare const printPrivateDependencyHint: (options: {
6
+ repoHost?: string;
7
+ }) => void;
8
+ /** The host of a git url, for naming it in the hint. Undefined when the url has no host. */
9
+ export declare const gitUrlHost: (repoUrl: string) => string | undefined;
10
+ /**
11
+ * The host a user names on the command line, which is often pasted as a url: `https://git.example.com/`
12
+ * holds the same host as `git.example.com`. Undefined when there is no host name in it at all.
13
+ */
14
+ export declare const gitHostFromInput: (value: string) => string | undefined;
15
+ /** The `group/project` part of a git url, for addressing the project in a host's own API. */
16
+ export declare const gitUrlProjectPath: (repoUrl: string) => string | undefined;