@valancex/cli 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/theme.js ADDED
@@ -0,0 +1,44 @@
1
+ // The project's theme. A project that writes none has the default theme (Tier 1: nothing to do, and every primitive of the portable core is styled); a project that has a `theme/` directory with a
2
+ // `theme.json` (Tier 2) uses it instead, whole: its token documents by mode and its recipes. A recipe's `escape` block (Tier 3) is the marked way to a CSS property outside the contract, for the web.
3
+ // The stylesheet is compiled here, at build time; nothing is computed in the browser.
4
+ import { existsSync } from "node:fs";
5
+ import { join, relative } from "node:path";
6
+ import { checkTheme, compileWeb, coverageOf, defaultInputs, loadTheme, resolveForDesktop, root as styleRoot } from "@valancex/style";
7
+ import { diagnostic } from "./diagnostics.js";
8
+ export const THEME_DIR = "theme";
9
+ /** The theme file the project uses: its own `theme/theme.json`, else the default theme's. */
10
+ export const themeFileOf = (root) => {
11
+ const own = join(root, THEME_DIR, "theme.json");
12
+ return existsSync(own) ? { file: own, custom: true } : { file: join(styleRoot, "themes", "default", "theme.json"), custom: false };
13
+ };
14
+ const asDiagnostic = (each) => diagnostic(each.file, 0, each.code, `${each.path === undefined || each.path === "" ? "" : `${each.path}: `}${each.message}`);
15
+ /** Loads and checks the project's theme. */
16
+ export const themeOf = (root) => {
17
+ const { file, custom } = themeFileOf(root);
18
+ const loaded = loadTheme(file, (path) => relative(root, path).split("\\").join("/"));
19
+ const inputs = defaultInputs();
20
+ const checked = checkTheme(loaded, inputs);
21
+ const diagnostics = checked.problems.map(asDiagnostic);
22
+ return {
23
+ custom,
24
+ name: loaded.theme.name,
25
+ diagnostics,
26
+ contrast: checked.contrast,
27
+ ...(diagnostics.length === 0 ? { css: compileWeb(loaded, inputs), desktop: resolveForDesktop(loaded, inputs) } : {}),
28
+ };
29
+ };
30
+ /** What `vlx theme check` says: the theme's problems, the contrast of every pair it declares, and what each target applies of it. */
31
+ export const themeReport = (root) => {
32
+ const { file } = themeFileOf(root);
33
+ const loaded = loadTheme(file, (path) => relative(root, path).split("\\").join("/"));
34
+ const inputs = defaultInputs();
35
+ const checked = checkTheme(loaded, inputs);
36
+ return {
37
+ custom: themeFileOf(root).custom,
38
+ name: loaded.theme.name,
39
+ modes: Object.keys(loaded.modes),
40
+ diagnostics: checked.problems.map(asDiagnostic),
41
+ contrast: checked.contrast,
42
+ coverage: ["web", "desktop"].map((target) => coverageOf(loaded, inputs, target)),
43
+ };
44
+ };
package/dist/tree.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Schema } from "effect";
2
- import type { Component } from "@valancex/common";
2
+ import type { Component, PropDeclaration } from "@valancex/common";
3
3
  export interface Place {
4
4
  readonly file: string;
5
5
  readonly line: number;
@@ -12,8 +12,12 @@ export interface Node {
12
12
  readonly file: string;
13
13
  /** The interface the component declares: present when it declares one (an omitted side is none), absent when its contract is inferred from the template and where it is used. */
14
14
  readonly interface?: {
15
- readonly props: Readonly<Record<string, Schema.Schema<any, any, never>>>;
15
+ readonly props: Readonly<Record<string, PropDeclaration>>;
16
16
  readonly events: Readonly<Record<string, Schema.Schema<any, any, never>>>;
17
+ /** The prop that is the component's accessible name, if it declares one. */
18
+ readonly name?: string;
19
+ /** The state and commands of one occurrence of the component, if it has any. */
20
+ readonly unit?: Component["unit"];
17
21
  };
18
22
  /** The declared components its template writes. */
19
23
  readonly uses: ReadonlyArray<string>;
@@ -22,6 +26,14 @@ export interface Node {
22
26
  }
23
27
  export declare const APPLICATION = "(application)";
24
28
  export declare const nameOf: (file: string) => string;
29
+ /** A component as the definition lists it, before two with one name are told apart: how it was listed, and so whether it may replace a built-in. */
30
+ export interface Declared {
31
+ readonly name: string;
32
+ readonly file: string;
33
+ readonly role: "layout" | "override" | "component";
34
+ }
35
+ /** Every component the definition lists, in order and with repeats, each with the role it was listed in. `treeOf` is these, once each by name. */
36
+ export declare const declaredOf: (layout: Component | undefined, overrides: ReadonlyArray<Component>, components?: ReadonlyArray<Component>) => ReadonlyArray<Declared>;
25
37
  /** Every component the definition declares, once each by name: `layout`, each `override`, then each of `components`. Who uses whom is read from the templates (`linked`). */
26
38
  export declare const treeOf: (layout: Component | undefined, overrides: ReadonlyArray<Component>, components?: ReadonlyArray<Component>) => ReadonlyArray<Node>;
27
39
  /**
package/dist/tree.js CHANGED
@@ -5,13 +5,28 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { ENTRY_FILE } from "./project.js";
6
6
  export const APPLICATION = "(application)";
7
7
  export const nameOf = (file) => basename(file, ".mprx");
8
+ /** Every component the definition lists, in order and with repeats, each with the role it was listed in. `treeOf` is these, once each by name. */
9
+ export const declaredOf = (layout, overrides, components = []) => [
10
+ ...(layout === undefined ? [] : [{ component: layout, role: "layout" }]),
11
+ ...overrides.map((component) => ({ component, role: "override" })),
12
+ ...components.map((component) => ({ component, role: "component" })),
13
+ ].map(({ component, role }) => ({ name: nameOf(component.template.mprx), file: component.template.mprx, role }));
8
14
  /** Every component the definition declares, once each by name: `layout`, each `override`, then each of `components`. Who uses whom is read from the templates (`linked`). */
9
15
  export const treeOf = (layout, overrides, components = []) => {
10
16
  const found = new Map();
11
17
  for (const declared of [...(layout === undefined ? [] : [layout]), ...overrides, ...components]) {
12
18
  const name = nameOf(declared.template.mprx);
13
19
  if (!found.has(name)) {
14
- const contract = declared.props === undefined && declared.events === undefined ? {} : { interface: { props: declared.props ?? {}, events: declared.events ?? {} } };
20
+ const contract = declared.props === undefined && declared.events === undefined && declared.unit === undefined
21
+ ? {}
22
+ : {
23
+ interface: {
24
+ props: declared.props ?? {},
25
+ events: declared.events ?? {},
26
+ ...(declared.name === undefined ? {} : { name: declared.name }),
27
+ ...(declared.unit === undefined ? {} : { unit: declared.unit }),
28
+ },
29
+ };
15
30
  found.set(name, { name, file: declared.template.mprx, ...contract, uses: [], parents: [] });
16
31
  }
17
32
  }
package/dist/ui/report.js CHANGED
@@ -26,5 +26,5 @@ export const BuildProgress = ({ current }) => {
26
26
  const at = current === undefined ? STAGES.length : STAGES.findIndex((each) => each.stage === current);
27
27
  return (_jsx(Box, { flexDirection: "column", children: STAGES.map(({ stage, label }, index) => index > at ? (_jsxs(Text, { dimColor: true, children: ["\u25CB ", label] }, stage)) : index === at ? (_jsxs(Text, { children: [_jsx(Spinner, {}), " ", label] }, stage)) : (_jsxs(Text, { children: [_jsx(Text, { color: "green", children: "\u2714" }), " ", label] }, stage))) }));
28
28
  };
29
- export const BuildResult = ({ built, seconds }) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(BuildProgress, { current: undefined }), _jsxs(Text, { children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2714 Built ", plural(built.pages.length, "page")] }), _jsxs(Text, { dimColor: true, children: [" ", "\u00B7 ", plural(built.files.length, "file"), " \u2192 ", built.dir, " \u00B7 ", seconds.toFixed(1), "s"] })] })] }));
29
+ export const BuildResult = ({ built, seconds }) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(BuildProgress, { current: undefined }), _jsxs(Text, { children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2714 Built ", plural(built.pages.length, "page")] }), _jsxs(Text, { dimColor: true, children: [" ", "\u00B7 ", plural(built.files.length, "file"), " \u2192 ", built.dir, " \u00B7 ", seconds.toFixed(1), "s"] })] }), (built.report ?? []).map((line, index) => (_jsx(Text, { dimColor: !/WARNING|ERROR/.test(line), ...(/WARNING/.test(line) ? { color: "yellow" } : /ERROR/.test(line) ? { color: "red" } : {}), children: line }, index)))] }));
30
30
  export const DevScreen = ({ state }) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: "cyan", children: "vlx dev" }), state.starting ? (_jsxs(Text, { children: [" ", _jsx(Spinner, {}), " starting"] })) : (_jsxs(Text, { children: [" ", _jsx(Text, { color: "green", children: "\u25CF" }), " ", state.url] }))] }), state.open.length === 0 ? (state.starting ? null : (_jsx(Text, { color: "green", children: "\u2714 No problems" }))) : (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Diagnostics, { diagnostics: state.open }), _jsxs(Text, { color: "red", children: ["\u2716 ", plural(state.open.length, "problem"), ": fix and save; the last good build keeps serving"] })] })), state.lines.length === 0 ? null : (_jsx(Box, { flexDirection: "column", marginTop: 1, children: state.lines.map((line, index) => (_jsx(Text, { dimColor: true, children: line }, `${index}:${line}`))) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Ctrl+C to stop" }) })] }));
package/dist/ui/run.d.ts CHANGED
@@ -14,4 +14,5 @@ export declare const drawDev: (root: string, options: {
14
14
  readonly target?: TargetId;
15
15
  readonly port?: number;
16
16
  readonly host?: string;
17
+ readonly record?: string;
17
18
  }) => Promise<void>;
package/dist/ui/run.js CHANGED
@@ -35,14 +35,27 @@ export const drawCheck = async (root) => {
35
35
  export const drawBuild = async (root, targets = []) => {
36
36
  const started = Date.now();
37
37
  const shown = show(_jsx(BuildProgress, { current: "configuration" }));
38
+ let measured = [];
38
39
  try {
39
40
  const built = await runBuild(root, {
40
41
  ...(targets.length === 0 ? {} : { targets }),
42
+ onReport: (report) => {
43
+ measured = report;
44
+ },
41
45
  onStage: ((stage) => shown.rerender(_jsx(BuildProgress, { current: stage }))),
42
46
  });
43
47
  shown.rerender(_jsx(BuildResult, { built: built, seconds: (Date.now() - started) / 1000 }));
44
48
  return built;
45
49
  }
50
+ catch (error) {
51
+ // A budget error stops the build after the bundle is measured: the report says by how much.
52
+ if (measured.length > 0) {
53
+ shown.unmount();
54
+ await shown.waitUntilExit();
55
+ measured.forEach((line) => console.error(line));
56
+ }
57
+ throw error;
58
+ }
46
59
  finally {
47
60
  shown.unmount();
48
61
  await shown.waitUntilExit();
package/dist/vite.js CHANGED
@@ -41,7 +41,7 @@ export const baseConfig = (root, config, out = OUTPUT_DIR) => ({
41
41
  cacheDir: join(root, GENERATED_DIR, "cache"),
42
42
  customLogger: logger,
43
43
  clearScreen: false,
44
- build: { outDir: join(root, out), emptyOutDir: true, target: "esnext", manifest: true, chunkSizeWarningLimit: 1500, rollupOptions: { input: join(root, CLIENT_ENTRY) } },
44
+ build: { outDir: join(root, out), emptyOutDir: true, target: "esnext", manifest: true, rollupOptions: { input: join(root, CLIENT_ENTRY) } },
45
45
  // The MESH runtime refers to node:fs/promises on a path the browser never takes.
46
46
  optimizeDeps: { exclude: ["@valancex/mesh-runtime", "@valancex/cli", "@valancex/common"] },
47
47
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valancex/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "vlx, the VALANCE command: make an application (vlx init), serve it as you edit (vlx dev), build it to a static site (vlx build), check it (vlx check). Content, layout, components and styles in, a website out.",
5
5
  "keywords": [
6
6
  "valance",
@@ -49,27 +49,32 @@
49
49
  "provenance": true
50
50
  },
51
51
  "dependencies": {
52
- "@valancex/mesh-compiler": "^0.11.0",
53
- "@valancex/port-desktop-vocabulary": "^0.6.0",
52
+ "@valancex/mesh-compiler": "^0.15.0 || ^0.16.0 || ^0.17.0",
53
+ "@valancex/port-desktop-vocabulary": "^0.12.0 || ^0.13.0 || ^0.14.0",
54
54
  "effect": "^3.10.0",
55
55
  "ink": "7.1.1",
56
56
  "postject": "1.0.0-alpha.6",
57
57
  "react": "19.2.8",
58
58
  "vite": "7.3.6",
59
- "@valancex/valance": "^0.13.0",
60
- "@valancex/common": "^0.13.0"
59
+ "@valancex/style": "^0.14.0",
60
+ "@valancex/valance": "^0.14.0",
61
+ "@valancex/common": "^0.14.0"
61
62
  },
62
63
  "devDependencies": {
64
+ "@types/jsdom": "^21.1.7",
63
65
  "@types/node": "^22.0.0",
64
66
  "@types/react": "19.2.18",
65
- "@valancex/mesh-runtime": "^0.11.0",
67
+ "@valancex/mesh-runtime": "^0.17.0",
68
+ "dom-accessibility-api": "^0.7.0",
66
69
  "ink-testing-library": "4.0.0",
70
+ "jsdom": "^26.0.0",
67
71
  "playwright": "1.56.1",
68
72
  "typescript": "^5.6.0",
69
- "vitest": "^3.2.0"
73
+ "vitest": "^3.2.0",
74
+ "vnu-jar": "^26.10.12"
70
75
  },
71
76
  "scripts": {
72
- "build": "tsc -p tsconfig.json",
77
+ "build": "tsc -p tsconfig.json && node scripts/copy-data.mjs",
73
78
  "typecheck": "tsc -p tsconfig.typecheck.json",
74
79
  "test": "vitest run"
75
80
  }
@@ -27,6 +27,77 @@
27
27
  "pattern": "^[^{}]*(\\{page\\}|\\{site\\})[^{}]*(\\{page\\}|\\{site\\}|[^{}])*$",
28
28
  "maxLength": 120,
29
29
  "description": "How a document's title is written: \"{page}\" is the page's title and \"{site}\" the name above. Must contain \"{page}\". A page whose title is the site's name is that name alone."
30
+ },
31
+ "record": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": [
35
+ "redact"
36
+ ],
37
+ "description": "What `vlx dev --record` leaves out of a session log. A log holds what was typed and seen; a value marked here is never written (its place holds a digest) and the log still replays.",
38
+ "properties": {
39
+ "redact": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "properties": {
43
+ "scope": {
44
+ "type": "array",
45
+ "description": "Paths into the page's scope whose values are replaced by a digest; \"*\" is any list index or field name.",
46
+ "items": {
47
+ "type": "array",
48
+ "minItems": 1,
49
+ "items": {
50
+ "type": "string",
51
+ "minLength": 1
52
+ }
53
+ }
54
+ },
55
+ "commands": {
56
+ "type": "array",
57
+ "description": "Commands (\"component/name\") whose parameters are not recorded.",
58
+ "items": {
59
+ "type": "string",
60
+ "pattern": "^[\\w-]+/[\\w-]+$"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ },
67
+ "budgets": {
68
+ "type": "array",
69
+ "description": "The size budgets `vlx build` holds the production build to (never `vlx dev`). A type, a warning and an error threshold in bytes: 500000, \"500kB\" or \"1MB\" (1 kB is 1,000 bytes). With none, the framework's start-up budgets apply.",
70
+ "items": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "required": [
74
+ "type"
75
+ ],
76
+ "properties": {
77
+ "type": {
78
+ "enum": [
79
+ "initial",
80
+ "runtime",
81
+ "startup",
82
+ "afterStart",
83
+ "anyChunk"
84
+ ],
85
+ "description": "initial: the start-up JavaScript and CSS; runtime: the WebAssembly runtime; startup: the whole start-up set; afterStart: what loads once the application runs; anyChunk: the largest JavaScript chunk. Brotli bytes."
86
+ },
87
+ "warning": {
88
+ "type": [
89
+ "string",
90
+ "number"
91
+ ]
92
+ },
93
+ "error": {
94
+ "type": [
95
+ "string",
96
+ "number"
97
+ ]
98
+ }
99
+ }
100
+ }
30
101
  }
31
102
  }
32
103
  }
@@ -1,4 +1,7 @@
1
1
  node_modules
2
2
  dist
3
3
  .valance
4
+ # session recordings (vlx dev --record) hold what was typed: never commit them
5
+ .valance/sessions/
6
+ *.session.jsonl
4
7
  .work*
@@ -7,10 +7,10 @@
7
7
  <nav class="sidebar" label="Pages">
8
8
  {#each sections as s (s.id)}
9
9
  <section>
10
- {#if s.title != ""}<para class="nav-title">{s.title}</para>{/if}
10
+ {#if s.title != ""}<paragraph class="nav-title">{s.title}</paragraph>{/if}
11
11
  <list class="nav-list">
12
12
  {#each s.items as n (n.id)}
13
- <item><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></item>
13
+ <listitem><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></listitem>
14
14
  {/each}
15
15
  </list>
16
16
  </section>
@@ -1,4 +1,7 @@
1
1
  node_modules
2
2
  dist
3
3
  .valance
4
+ # session recordings (vlx dev --record) hold what was typed: never commit them
5
+ .valance/sessions/
6
+ *.session.jsonl
4
7
  .work*
@@ -7,10 +7,10 @@
7
7
  <nav class="sidebar" label="Pages">
8
8
  {#each sections as s (s.id)}
9
9
  <section>
10
- {#if s.title != ""}<para class="nav-title">{s.title}</para>{/if}
10
+ {#if s.title != ""}<paragraph class="nav-title">{s.title}</paragraph>{/if}
11
11
  <list class="nav-list">
12
12
  {#each s.items as n (n.id)}
13
- <item><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></item>
13
+ <listitem><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></listitem>
14
14
  {/each}
15
15
  </list>
16
16
  </section>
package/views/block.mprx CHANGED
@@ -1,3 +1,3 @@
1
1
  <mesh-fragment>
2
- {#if b.kind == "heading"}<heading-block level={b.level} anchor={b.anchor} spans={b.spans} />{:else if b.kind == "list"}<list-block ordered={b.ordered} items={b.items} />{:else if b.kind == "code"}<pre class="code" tabindex="0"><code>{b.text}</code></pre>{:else if b.kind == "quote"}<quote class="quote"><runs spans={b.spans} /></quote>{:else if b.kind == "image"}<image class="picture" src={b.src} alt={b.alt} loading="lazy" />{:else}<para class="prose"><runs spans={b.spans} /></para>{/if}
2
+ {#if b.kind == "heading"}<heading-block level={b.level} anchor={b.anchor} spans={b.spans} />{:else if b.kind == "list"}<list-block ordered={b.ordered} items={b.items} />{:else if b.kind == "code"}<pre class="code" tabindex="0"><code>{b.text}</code></pre>{:else if b.kind == "quote"}<blockquote class="quote"><runs spans={b.spans} /></blockquote>{:else if b.kind == "image"}<image class="picture" src={b.src} alt={b.alt} loading="lazy" />{:else}<paragraph class="prose"><runs spans={b.spans} /></paragraph>{/if}
3
3
  </mesh-fragment>
package/views/layout.mprx CHANGED
@@ -6,10 +6,10 @@
6
6
  <nav class="sidebar" label="Pages">
7
7
  {#each sections as s (s.id)}
8
8
  <section>
9
- {#if s.title != ""}<para class="nav-title">{s.title}</para>{/if}
9
+ {#if s.title != ""}<paragraph class="nav-title">{s.title}</paragraph>{/if}
10
10
  <list class="nav-list">
11
11
  {#each s.items as n (n.id)}
12
- <item><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></item>
12
+ <listitem><link class="nav-link" href={n.href} current={n.current}>{n.label}</link></listitem>
13
13
  {/each}
14
14
  </list>
15
15
  </section>
@@ -1,3 +1,3 @@
1
1
  <mesh-fragment>
2
- {#each items as i (i.id)}<item><runs spans={i.spans} /></item>{/each}
2
+ {#each items as i (i.id)}<listitem><runs spans={i.spans} /></listitem>{/each}
3
3
  </mesh-fragment>