@octanejs/cli 0.0.9 → 0.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.22.2"
@@ -11,7 +11,7 @@ import { SYMBOLS } from '../kernel/ui.js';
11
11
  * @property {string} file relative to the project root
12
12
  * @property {number} line
13
13
  * @property {number} column 1-based, matching editor gutters
14
- * @property {'error' | 'warning'} severity
14
+ * @property {'error' | 'warning' | 'hint'} severity
15
15
  * @property {string} code
16
16
  * @property {string} message
17
17
  * @property {string[]} suggestions
@@ -175,7 +175,12 @@ export default defineCommand({
175
175
  file,
176
176
  line: diagnostic.start?.line ?? 1,
177
177
  column: (diagnostic.start?.column ?? 0) + 1,
178
- severity: diagnostic.severity === 'error' ? 'error' : 'warning',
178
+ severity:
179
+ diagnostic.severity === 'error'
180
+ ? 'error'
181
+ : diagnostic.severity === 'hint'
182
+ ? 'hint'
183
+ : 'warning',
179
184
  code: diagnostic.code,
180
185
  // The compiler prefixes its own code; the report already has a
181
186
  // column for it.
@@ -198,7 +203,8 @@ export default defineCommand({
198
203
  render(ctx, selected, targets.length);
199
204
 
200
205
  const errors = selected.filter((finding) => finding.severity === 'error').length;
201
- const warnings = selected.length - errors;
206
+ const warnings = selected.filter((finding) => finding.severity === 'warning').length;
207
+ const hints = selected.filter((finding) => finding.severity === 'hint').length;
202
208
  const failed = errors > 0 || (input.flags.strict && warnings > 0);
203
209
 
204
210
  return {
@@ -206,7 +212,7 @@ export default defineCommand({
206
212
  json: {
207
213
  ok: !failed,
208
214
  analyzed: targets.length,
209
- summary: { errors, warnings },
215
+ summary: { errors, warnings, hints },
210
216
  findings: selected,
211
217
  },
212
218
  };
@@ -249,7 +255,11 @@ function render(ctx, findings, analyzed) {
249
255
 
250
256
  for (const finding of findings.filter((entry) => entry.file === file)) {
251
257
  const mark =
252
- finding.severity === 'error' ? colors.red(SYMBOLS.fail) : colors.yellow(SYMBOLS.warn);
258
+ finding.severity === 'error'
259
+ ? colors.red(SYMBOLS.fail)
260
+ : finding.severity === 'hint'
261
+ ? colors.dim('hint')
262
+ : colors.yellow(SYMBOLS.warn);
253
263
  const where = colors.dim(`${finding.line}:${finding.column}`);
254
264
  ctx.ui.log(` ${mark} ${where} ${finding.message}`);
255
265
  ctx.ui.log(` ${colors.dim(finding.code)}`);
@@ -260,11 +270,13 @@ function render(ctx, findings, analyzed) {
260
270
  }
261
271
 
262
272
  const errors = findings.filter((finding) => finding.severity === 'error').length;
263
- const warnings = findings.length - errors;
273
+ const warnings = findings.filter((finding) => finding.severity === 'warning').length;
274
+ const hints = findings.filter((finding) => finding.severity === 'hint').length;
264
275
  /** @type {string[]} */
265
276
  const parts = [];
266
277
  if (errors > 0) parts.push(colors.red(`${errors} error${errors === 1 ? '' : 's'}`));
267
278
  if (warnings > 0) parts.push(colors.yellow(`${warnings} warning${warnings === 1 ? '' : 's'}`));
279
+ if (hints > 0) parts.push(colors.dim(`${hints} hint${hints === 1 ? '' : 's'}`));
268
280
 
269
281
  ctx.ui.log('');
270
282
  ctx.ui.log(`${parts.join(colors.dim(' · '))} ${colors.dim(`across ${analyzed} file(s)`)}`);
@@ -126,48 +126,64 @@ const LOGO = `<svg class="mark" viewBox="0 0 84 108" fill="none" aria-hidden="tr
126
126
  * The documentation cards, matching the card treatment on octanejs.dev: the
127
127
  * same radius, the same subtle surface, and the same accent-tinted hover rather
128
128
  * than a plain border swap.
129
+ *
130
+ * An assigned `<style>` is a theme rather than a block scoped to one template:
131
+ * it keeps every selector and evaluates to a class map. The page applies it by
132
+ * putting `apply={linkTheme}` on its own `<style>` block, which stamps the
133
+ * theme's class on the items beside that block and everything below them —
134
+ * never on the element that contains it, which is why the block and the
135
+ * page's root are fragment siblings — so these rules reach them the same way
136
+ * the block's own rules do. One block rather than a self-closed
137
+ * `<style apply />` beside it: the type-checker's virtual TSX accepts only one
138
+ * `<style>` per element. The theme sits at module scope, above the component
139
+ * that applies it, which is the order the compiler requires.
129
140
  */
130
- const LINK_STYLES = ` .links {
131
- display: grid;
132
- grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
133
- gap: 0.75rem;
134
- width: 100%;
135
- max-width: 42rem;
136
- margin: 0;
137
- padding: 0;
138
- list-style: none;
139
- }
140
- .link {
141
- display: block;
142
- padding: 1rem;
143
- border: 1px solid var(--border);
144
- border-radius: 0.9rem;
145
- background: var(--surface-subtle);
146
- color: var(--text);
147
- text-align: left;
148
- text-decoration: none;
149
- transition:
150
- background 0.15s,
151
- border-color 0.15s,
152
- transform 0.15s;
153
- }
154
- .link:hover {
155
- border-color: var(--card-hover-border);
156
- background: var(--card-hover-bg);
157
- transform: translateY(-1px);
158
- }
159
- .link-title {
160
- display: block;
161
- font-size: 0.95rem;
162
- font-weight: 600;
163
- }
164
- .link-body {
165
- display: block;
166
- margin-top: 0.15rem;
167
- color: var(--text-secondary);
168
- font-size: 0.82rem;
169
- line-height: 1.4;
170
- }`;
141
+ const LINK_THEME = `// The documentation cards. An assigned <style> is a theme: the page's own
142
+ // <style apply={linkTheme}> block below applies it, so its rules reach the
143
+ // items beside that block and everything below them.
144
+ const linkTheme = <style>
145
+ .links {
146
+ display: grid;
147
+ grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
148
+ gap: 0.75rem;
149
+ width: 100%;
150
+ max-width: 42rem;
151
+ margin: 0;
152
+ padding: 0;
153
+ list-style: none;
154
+ }
155
+ .link {
156
+ display: block;
157
+ padding: 1rem;
158
+ border: 1px solid var(--border);
159
+ border-radius: 0.9rem;
160
+ background: var(--surface-subtle);
161
+ color: var(--text);
162
+ text-align: left;
163
+ text-decoration: none;
164
+ transition:
165
+ background 0.15s,
166
+ border-color 0.15s,
167
+ transform 0.15s;
168
+ }
169
+ .link:hover {
170
+ border-color: var(--card-hover-border);
171
+ background: var(--card-hover-bg);
172
+ transform: translateY(-1px);
173
+ }
174
+ .link-title {
175
+ display: block;
176
+ font-size: 0.95rem;
177
+ font-weight: 600;
178
+ }
179
+ .link-body {
180
+ display: block;
181
+ margin-top: 0.15rem;
182
+ color: var(--text-secondary);
183
+ font-size: 0.82rem;
184
+ line-height: 1.4;
185
+ }
186
+ </style>;`;
171
187
 
172
188
  /** Where the landing page sends people next. */
173
189
  const DOC_LINKS = `const LINKS = [
@@ -208,6 +224,8 @@ export const appComponent = (mode) =>
208
224
 
209
225
  ${DOC_LINKS}
210
226
 
227
+ ${LINK_THEME}
228
+
211
229
  export function App() @{
212
230
  <Layout>
213
231
  <section class="hero">
@@ -229,7 +247,7 @@ export function App() @{
229
247
  </ul>
230
248
  </section>
231
249
 
232
- <style>
250
+ <style apply={linkTheme}>
233
251
  .hero {
234
252
  display: flex;
235
253
  flex-direction: column;
@@ -253,33 +271,17 @@ export function App() @{
253
271
  color: var(--text-secondary);
254
272
  font-size: 1.25rem;
255
273
  }
256
- ${LINK_STYLES}
257
274
  </style>
258
275
  </Layout>
259
276
  }
260
277
  `
261
278
  : `${DOC_LINKS}
262
279
 
263
- export function App() @{
264
- <main class="page">
265
- ${LOGO}
266
- <h1 class="title">octane</h1>
267
- <p class="lede">
268
- React’s programming model, compiled. Edit src/App.tsrx and save — the page
269
- updates without a reload.
270
- </p>
271
- <ul class="links">
272
- @for (const link of LINKS; key link.href) {
273
- <li>
274
- <a class="link" href={link.href} target="_blank" rel="noreferrer">
275
- <span class="link-title">{link.title as string}</span>
276
- <span class="link-body">{link.body as string}</span>
277
- </a>
278
- </li>
279
- }
280
- </ul>
280
+ ${LINK_THEME}
281
281
 
282
- <style>
282
+ export function App() @{
283
+ <>
284
+ <style apply={linkTheme}>
283
285
  .page {
284
286
  display: flex;
285
287
  min-height: 100dvh;
@@ -306,9 +308,26 @@ export function App() @{
306
308
  color: var(--text-secondary);
307
309
  font-size: 1.25rem;
308
310
  }
309
- ${LINK_STYLES}
310
311
  </style>
311
- </main>
312
+ <main class="page">
313
+ ${LOGO}
314
+ <h1 class="title">octane</h1>
315
+ <p class="lede">
316
+ React’s programming model, compiled. Edit src/App.tsrx and save — the page
317
+ updates without a reload.
318
+ </p>
319
+ <ul class="links">
320
+ @for (const link of LINKS; key link.href) {
321
+ <li>
322
+ <a class="link" href={link.href} target="_blank" rel="noreferrer">
323
+ <span class="link-title">{link.title as string}</span>
324
+ <span class="link-body">{link.body as string}</span>
325
+ </a>
326
+ </li>
327
+ }
328
+ </ul>
329
+ </main>
330
+ </>
312
331
  }
313
332
  `;
314
333
 
@@ -323,14 +342,7 @@ interface LayoutProps {
323
342
  }
324
343
 
325
344
  export function Layout(props: LayoutProps) @{
326
- <div class="shell">
327
- <nav class="nav">
328
- <a class="nav-link" href="/">Home</a>
329
- <a class="nav-link" href="/counter">Counter</a>
330
- <a class={["nav-link", "nav-endpoint"]} href="/api/health">/api/health</a>
331
- </nav>
332
- <main class="main">{props.children}</main>
333
-
345
+ <>
334
346
  <style>
335
347
  .shell {
336
348
  display: flex;
@@ -373,7 +385,15 @@ export function Layout(props: LayoutProps) @{
373
385
  padding: 3rem 1.5rem;
374
386
  }
375
387
  </style>
376
- </div>
388
+ <div class="shell">
389
+ <nav class="nav">
390
+ <a class="nav-link" href="/">Home</a>
391
+ <a class="nav-link" href="/counter">Counter</a>
392
+ <a class={["nav-link", "nav-endpoint"]} href="/api/health">/api/health</a>
393
+ </nav>
394
+ <main class="main">{props.children}</main>
395
+ </div>
396
+ </>
377
397
  }
378
398
  `;
379
399
 
@@ -391,6 +411,42 @@ export function Counter() @{
391
411
  const [count, setCount] = useState(0);
392
412
 
393
413
  <Layout>
414
+ <style>
415
+ .counter {
416
+ display: flex;
417
+ flex-direction: column;
418
+ align-items: center;
419
+ gap: 1.25rem;
420
+ text-align: center;
421
+ }
422
+ .title {
423
+ margin: 0;
424
+ font-size: 2rem;
425
+ line-height: 1.1;
426
+ letter-spacing: -0.02em;
427
+ }
428
+ .lede {
429
+ max-width: 34rem;
430
+ margin: 0;
431
+ color: var(--text-secondary);
432
+ font-size: 1.25rem;
433
+ }
434
+ /* The site's primary action: an accent pill. */
435
+ .button {
436
+ padding: 0.7rem 1.4rem;
437
+ border: none;
438
+ border-radius: 9999px;
439
+ background: var(--accent);
440
+ color: var(--on-accent);
441
+ font: inherit;
442
+ font-weight: 600;
443
+ cursor: pointer;
444
+ transition: background 0.15s;
445
+ }
446
+ .button:hover {
447
+ background: var(--accent-hover);
448
+ }
449
+ </style>
394
450
  <section class="counter">
395
451
  <h1 class="title">Server-rendered, then interactive</h1>
396
452
  <p class="lede">
@@ -400,43 +456,6 @@ export function Counter() @{
400
456
  <button class="button" onClick={() => setCount(count + 1)}>
401
457
  {"Pressed " + count + " times"}
402
458
  </button>
403
-
404
- <style>
405
- .counter {
406
- display: flex;
407
- flex-direction: column;
408
- align-items: center;
409
- gap: 1.25rem;
410
- text-align: center;
411
- }
412
- .title {
413
- margin: 0;
414
- font-size: 2rem;
415
- line-height: 1.1;
416
- letter-spacing: -0.02em;
417
- }
418
- .lede {
419
- max-width: 34rem;
420
- margin: 0;
421
- color: var(--text-secondary);
422
- font-size: 1.25rem;
423
- }
424
- /* The site's primary action: an accent pill. */
425
- .button {
426
- padding: 0.7rem 1.4rem;
427
- border: none;
428
- border-radius: 9999px;
429
- background: var(--accent);
430
- color: var(--on-accent);
431
- font: inherit;
432
- font-weight: 600;
433
- cursor: pointer;
434
- transition: background 0.15s;
435
- }
436
- .button:hover {
437
- background: var(--accent-hover);
438
- }
439
- </style>
440
459
  </section>
441
460
  </Layout>
442
461
  }
@@ -462,12 +481,14 @@ export function health(): Response {
462
481
  * The reset and the theme tokens every component reads, taken from
463
482
  * octanejs.dev so a scaffolded app and the documentation look like one thing.
464
483
  *
465
- * A stylesheet rather than a `<style>` in the shell. A component's own `<style>`
466
- * is scoped — the compiler appends a scope hash to its selectors — so it can
467
- * style that component's markup but never `body`, and these custom properties
468
- * have to resolve for every component at once. Keeping them in a file the shell
469
- * links, rather than inline in the shell, means `init` still has something to
470
- * point at when the project brought its own `index.html` and kept it.
484
+ * A stylesheet rather than a `<style>` in the shell. A `<style>` in a component
485
+ * styles only the items beside it and everything below them — the compiler
486
+ * rewrites its selectors with a scope hash — and a theme such as `linkTheme`
487
+ * reaches only the scopes that apply it, so neither can style `body`, and these custom
488
+ * properties have to resolve for every component at once. Keeping them in a
489
+ * file the shell links, rather than inline in the shell, means `init` still has
490
+ * something to point at when the project brought its own `index.html` and kept
491
+ * it.
471
492
  *
472
493
  * Linked from the shell rather than imported by a component on purpose: a CSS
473
494
  * import is injected by JavaScript in dev, so the tokens would arrive after the