@valancex/cli 0.12.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/LICENSE +21 -0
- package/README.md +69 -11
- package/dist/add.d.ts +9 -1
- package/dist/add.js +68 -5
- package/dist/archive.d.ts +17 -0
- package/dist/archive.js +111 -0
- package/dist/args.js +2 -2
- package/dist/budgets.json +175 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +120 -30
- package/dist/check.js +2 -2
- package/dist/cli.js +131 -17
- package/dist/compile.d.ts +11 -3
- package/dist/compile.js +123 -25
- package/dist/config.d.ts +13 -0
- package/dist/config.js +34 -1
- package/dist/contract.d.ts +18 -1
- package/dist/contract.js +85 -3
- package/dist/dev-desktop.d.ts +8 -0
- package/dist/dev-desktop.js +114 -0
- package/dist/dev.d.ts +8 -0
- package/dist/dev.js +91 -29
- package/dist/emit.d.ts +9 -4
- package/dist/emit.js +129 -28
- package/dist/init.js +14 -4
- package/dist/package.d.ts +18 -0
- package/dist/package.js +142 -0
- package/dist/project.js +1 -1
- package/dist/recording-guard.d.ts +5 -0
- package/dist/recording-guard.js +28 -0
- package/dist/replay.d.ts +15 -0
- package/dist/replay.js +59 -0
- package/dist/rules.d.ts +23 -0
- package/dist/rules.js +123 -0
- package/dist/runtime-guard.d.ts +11 -0
- package/dist/runtime-guard.js +62 -0
- package/dist/security.d.ts +2 -0
- package/dist/security.js +8 -1
- package/dist/size.d.ts +54 -0
- package/dist/size.js +153 -0
- package/dist/targets.d.ts +6 -1
- package/dist/targets.js +26 -4
- package/dist/theme.d.ts +35 -0
- package/dist/theme.js +44 -0
- package/dist/tree.d.ts +14 -2
- package/dist/tree.js +16 -1
- package/dist/ui/init.js +4 -4
- package/dist/ui/report.js +1 -1
- package/dist/ui/run.d.ts +4 -1
- package/dist/ui/run.js +18 -3
- package/dist/vite.d.ts +3 -2
- package/dist/vite.js +3 -3
- package/package.json +15 -7
- package/schemas/valance.desktop.schema.json +17 -0
- package/schemas/valance.schema.json +71 -0
- package/templates/desktop/README.md +14 -0
- package/templates/desktop/content/404.md +7 -0
- package/templates/desktop/content/guide/getting-started.md +15 -0
- package/templates/desktop/content/index.md +14 -0
- package/templates/desktop/gitignore +7 -0
- package/templates/desktop/src/layout.mprx +23 -0
- package/templates/desktop/src/main.ts +15 -0
- package/templates/desktop/tsconfig.json +18 -0
- package/templates/multi/README.md +13 -0
- package/templates/multi/content/404.md +7 -0
- package/templates/multi/content/guide/getting-started.md +15 -0
- package/templates/multi/content/index.md +14 -0
- package/templates/multi/src/desktop/layout.mprx +21 -0
- package/templates/multi/src/main.ts +23 -0
- package/templates/multi/src/web/layout.mprx +27 -0
- package/templates/multi/src/web/styles.css +28 -0
- package/templates/multi/src/web/theme-toggle.mprx +1 -0
- package/templates/multi/src/web/theme-toggle.ts +10 -0
- package/templates/web/content/guide/getting-started.md +1 -1
- package/templates/web/gitignore +3 -0
- package/templates/web/src/layout.mprx +2 -2
- package/views/block.mprx +1 -1
- package/views/layout.mprx +2 -2
- package/views/list-items.mprx +1 -1
- package/views-desktop/block.mprx +3 -0
- package/views-desktop/blocks.mprx +3 -0
- package/views-desktop/inline.mprx +3 -0
- package/views-desktop/layout.mprx +20 -0
- package/views-desktop/list-block.mprx +3 -0
- package/views-desktop/list-items.mprx +3 -0
- package/views-desktop/runs.mprx +3 -0
- /package/views/{heading.mprx → heading-block.mprx} +0 -0
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Diagnostic } from "./diagnostics.js";
|
|
2
|
+
export declare const THEME_DIR = "theme";
|
|
3
|
+
/** The theme file the project uses: its own `theme/theme.json`, else the default theme's. */
|
|
4
|
+
export declare const themeFileOf: (root: string) => {
|
|
5
|
+
readonly file: string;
|
|
6
|
+
readonly custom: boolean;
|
|
7
|
+
};
|
|
8
|
+
export interface ThemeResult {
|
|
9
|
+
readonly custom: boolean;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
12
|
+
readonly contrast: ReadonlyArray<{
|
|
13
|
+
readonly mode: string;
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly on: string;
|
|
16
|
+
readonly min: number;
|
|
17
|
+
readonly ratio: number;
|
|
18
|
+
readonly passes: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
/** The stylesheet, when the theme has no problem. */
|
|
21
|
+
readonly css?: string;
|
|
22
|
+
/** The theme as the Desktop is told it (resolved values), when the theme has no problem. */
|
|
23
|
+
readonly desktop?: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Loads and checks the project's theme. */
|
|
26
|
+
export declare const themeOf: (root: string) => ThemeResult;
|
|
27
|
+
/** What `vlx theme check` says: the theme's problems, the contrast of every pair it declares, and what each target applies of it. */
|
|
28
|
+
export declare const themeReport: (root: string) => {
|
|
29
|
+
custom: boolean;
|
|
30
|
+
name: string;
|
|
31
|
+
modes: string[];
|
|
32
|
+
diagnostics: Diagnostic[];
|
|
33
|
+
contrast: readonly import("@valancex/style").ContrastResult[];
|
|
34
|
+
coverage: import("@valancex/style").Coverage[];
|
|
35
|
+
};
|
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,
|
|
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
|
|
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/init.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Box, Text, useApp } from "ink";
|
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
5
|
import { relative } from "node:path";
|
|
6
6
|
import { directoryFor, directoryProblem, execute as defaultExecute, nameProblem, PACKAGE_MANAGERS, runCommand, titleOf } from "../init.js";
|
|
7
|
-
import { TARGETS } from "../targets.js";
|
|
7
|
+
import { DEFAULT_TARGETS, TARGETS } from "../targets.js";
|
|
8
8
|
import { Spinner } from "./Spinner.js";
|
|
9
9
|
import { Answered, Confirm, MultiSelect, Select, TextPrompt } from "./prompts.js";
|
|
10
10
|
const ORDER = ["name", "targets", "base", "url", "pm", "install", "git"];
|
|
@@ -75,7 +75,7 @@ export const Wizard = ({ args, cwd, detected, version, execute = defaultExecute,
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
finished.current = true;
|
|
78
|
-
const targets = draft.targets.length === 0 ?
|
|
78
|
+
const targets = draft.targets.length === 0 ? DEFAULT_TARGETS : draft.targets;
|
|
79
79
|
const name = draft.name ?? "my-app";
|
|
80
80
|
const answers = {
|
|
81
81
|
name,
|
|
@@ -107,10 +107,10 @@ export const Wizard = ({ args, cwd, detected, version, execute = defaultExecute,
|
|
|
107
107
|
}
|
|
108
108
|
}, [outcome, failure]);
|
|
109
109
|
const question = running ? undefined : ORDER[at];
|
|
110
|
-
const picked = draft.targets.length === 0 ?
|
|
110
|
+
const picked = draft.targets.length === 0 ? DEFAULT_TARGETS : draft.targets;
|
|
111
111
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: "cyan", children: "vlx init" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 VALANCE ", version] })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [answered.map(([label, value]) => (_jsx(Answered, { label: label, value: value }, label))), question === "name" ? (_jsx(TextPrompt, { label: "What is the application called?", hint: "a name, or a path: it is made in a directory of that name", initial: "my-app", validate: (value) => nameProblem(value) ?? directoryProblem(directoryFor(value, cwd)), onSubmit: (value) => {
|
|
112
112
|
answer({ name: value.trim() }, "Application", `${titleOf(value)} → ${relative(cwd, directoryFor(value, cwd)) || "."}`);
|
|
113
|
-
} })) : null, question === "targets" ? (_jsx(MultiSelect, { label: "Which targets is it for?", choices: TARGETS.map((each) => ({ value: each.id, label: each.label, hint: each.hint })), initial:
|
|
113
|
+
} })) : null, question === "targets" ? (_jsx(MultiSelect, { label: "Which targets is it for?", hint: "one application, built for each", choices: TARGETS.map((each) => ({ value: each.id, label: each.label, hint: each.hint })), initial: DEFAULT_TARGETS, onSubmit: (values) => {
|
|
114
114
|
answer({ targets: values }, "Targets", values.map((id) => TARGETS.find((each) => each.id === id)?.label ?? id).join(", "));
|
|
115
115
|
} })) : null, question === "base" ? (_jsx(TextPrompt, { label: "Web: where is the site served?", hint: "a base path: / or /docs/", initial: "/", validate: (value) => (BASE.test(value) ? undefined : "a path that starts and ends with /, like / or /docs/"), onSubmit: (value) => {
|
|
116
116
|
answer({ base: value }, "Base path", value);
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TargetId } from "../targets.js";
|
|
1
2
|
import type { InitArgs } from "../args.js";
|
|
2
3
|
import type { Checked } from "../check.js";
|
|
3
4
|
import type { Diagnostic } from "../diagnostics.js";
|
|
@@ -6,10 +7,12 @@ import type { Result } from "./init.js";
|
|
|
6
7
|
import type { Built } from "../build.js";
|
|
7
8
|
export declare const drawInit: (args: InitArgs, cwd: string, detected: PackageManager, version: string) => Promise<Result>;
|
|
8
9
|
export declare const drawCheck: (root: string) => Promise<Checked>;
|
|
9
|
-
export declare const drawBuild: (root: string) => Promise<Built>;
|
|
10
|
+
export declare const drawBuild: (root: string, targets?: ReadonlyArray<TargetId>) => Promise<Built>;
|
|
10
11
|
/** Mistakes that stopped a command, drawn the way `check` draws them. */
|
|
11
12
|
export declare const drawDiagnostics: (diagnostics: ReadonlyArray<Diagnostic>) => Promise<void>;
|
|
12
13
|
export declare const drawDev: (root: string, options: {
|
|
14
|
+
readonly target?: TargetId;
|
|
13
15
|
readonly port?: number;
|
|
14
16
|
readonly host?: string;
|
|
17
|
+
readonly record?: string;
|
|
15
18
|
}) => Promise<void>;
|
package/dist/ui/run.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// Putting the views on the terminal. Ink and React are loaded only here, and only when a command is going to draw: a command that prints plain text never pays for them.
|
|
3
2
|
import { render } from "ink";
|
|
4
3
|
import { build as runBuild } from "../build.js";
|
|
5
4
|
import { check as runCheck } from "../check.js";
|
|
@@ -33,14 +32,30 @@ export const drawCheck = async (root) => {
|
|
|
33
32
|
await shown.waitUntilExit();
|
|
34
33
|
}
|
|
35
34
|
};
|
|
36
|
-
export const drawBuild = async (root) => {
|
|
35
|
+
export const drawBuild = async (root, targets = []) => {
|
|
37
36
|
const started = Date.now();
|
|
38
37
|
const shown = show(_jsx(BuildProgress, { current: "configuration" }));
|
|
38
|
+
let measured = [];
|
|
39
39
|
try {
|
|
40
|
-
const built = await runBuild(root, {
|
|
40
|
+
const built = await runBuild(root, {
|
|
41
|
+
...(targets.length === 0 ? {} : { targets }),
|
|
42
|
+
onReport: (report) => {
|
|
43
|
+
measured = report;
|
|
44
|
+
},
|
|
45
|
+
onStage: ((stage) => shown.rerender(_jsx(BuildProgress, { current: stage }))),
|
|
46
|
+
});
|
|
41
47
|
shown.rerender(_jsx(BuildResult, { built: built, seconds: (Date.now() - started) / 1000 }));
|
|
42
48
|
return built;
|
|
43
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
|
+
}
|
|
44
59
|
finally {
|
|
45
60
|
shown.unmount();
|
|
46
61
|
await shown.waitUntilExit();
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { InlineConfig, ViteDevServer } from "vite";
|
|
1
|
+
import type { InlineConfig, Plugin, ViteDevServer } from "vite";
|
|
2
2
|
import type { Config } from "./config.js";
|
|
3
3
|
export declare const CLIENT_ENTRY = ".valance/client.ts";
|
|
4
4
|
export declare const SERVER_ENTRY = "/.valance/server.ts";
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const templates: (root: string) => Plugin;
|
|
6
|
+
export declare const baseConfig: (root: string, config: Config, out?: string) => InlineConfig;
|
|
6
7
|
/** A Vite server that serves nothing: it loads the author's TypeScript and the generated server entry in Node. Used by build and check. */
|
|
7
8
|
export declare const createLoader: (root: string, config: Config) => Promise<ViteDevServer>;
|
package/dist/vite.js
CHANGED
|
@@ -20,7 +20,7 @@ logger.warnOnce = (message, options) => {
|
|
|
20
20
|
};
|
|
21
21
|
// `import layout from "./layout.mprx"` in `src/main.ts` is the author naming a template. The import gives the file's address and nothing else: the compiler reads the file itself, and the
|
|
22
22
|
// browser bundle must not carry template text it has already been compiled from.
|
|
23
|
-
const templates = (root) => ({
|
|
23
|
+
export const templates = (root) => ({
|
|
24
24
|
name: "vlx-templates",
|
|
25
25
|
enforce: "pre",
|
|
26
26
|
load(id) {
|
|
@@ -30,7 +30,7 @@ const templates = (root) => ({
|
|
|
30
30
|
// The module is only the file's address, so a change to the template changes nothing the browser holds; the rebuild (dev.ts) recompiles it and reloads once, with the overlay if it fails.
|
|
31
31
|
handleHotUpdate: ({ file }) => (file.endsWith(".mprx") ? [] : undefined),
|
|
32
32
|
});
|
|
33
|
-
export const baseConfig = (root, config) => ({
|
|
33
|
+
export const baseConfig = (root, config, out = OUTPUT_DIR) => ({
|
|
34
34
|
root,
|
|
35
35
|
configFile: false,
|
|
36
36
|
base: config.web.base,
|
|
@@ -41,7 +41,7 @@ export const baseConfig = (root, config) => ({
|
|
|
41
41
|
cacheDir: join(root, GENERATED_DIR, "cache"),
|
|
42
42
|
customLogger: logger,
|
|
43
43
|
clearScreen: false,
|
|
44
|
-
build: { outDir: join(root,
|
|
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.
|
|
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",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"dist",
|
|
41
41
|
"bin",
|
|
42
42
|
"views",
|
|
43
|
+
"views-desktop",
|
|
43
44
|
"schemas",
|
|
44
45
|
"templates"
|
|
45
46
|
],
|
|
@@ -48,25 +49,32 @@
|
|
|
48
49
|
"provenance": true
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@valancex/mesh-compiler": "^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",
|
|
52
54
|
"effect": "^3.10.0",
|
|
53
55
|
"ink": "7.1.1",
|
|
56
|
+
"postject": "1.0.0-alpha.6",
|
|
54
57
|
"react": "19.2.8",
|
|
55
58
|
"vite": "7.3.6",
|
|
56
|
-
"@valancex/
|
|
57
|
-
"@valancex/valance": "^0.
|
|
59
|
+
"@valancex/style": "^0.14.0",
|
|
60
|
+
"@valancex/valance": "^0.14.0",
|
|
61
|
+
"@valancex/common": "^0.14.0"
|
|
58
62
|
},
|
|
59
63
|
"devDependencies": {
|
|
64
|
+
"@types/jsdom": "^21.1.7",
|
|
60
65
|
"@types/node": "^22.0.0",
|
|
61
66
|
"@types/react": "19.2.18",
|
|
62
|
-
"@valancex/mesh-runtime": "^0.
|
|
67
|
+
"@valancex/mesh-runtime": "^0.17.0",
|
|
68
|
+
"dom-accessibility-api": "^0.7.0",
|
|
63
69
|
"ink-testing-library": "4.0.0",
|
|
70
|
+
"jsdom": "^26.0.0",
|
|
64
71
|
"playwright": "1.56.1",
|
|
65
72
|
"typescript": "^5.6.0",
|
|
66
|
-
"vitest": "^3.2.0"
|
|
73
|
+
"vitest": "^3.2.0",
|
|
74
|
+
"vnu-jar": "^26.10.12"
|
|
67
75
|
},
|
|
68
76
|
"scripts": {
|
|
69
|
-
"build": "tsc -p tsconfig.json",
|
|
77
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-data.mjs",
|
|
70
78
|
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
71
79
|
"test": "vitest run"
|
|
72
80
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://valancex.dev/schemas/valance.desktop.schema.json",
|
|
4
|
+
"title": "valance.desktop.json",
|
|
5
|
+
"description": "Marks the project as a desktop one: it is built to an engine that a native window runs. It has no settings yet.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"version": {
|
|
13
|
+
"const": 1,
|
|
14
|
+
"description": "The version of this file's format."
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
A VALANCE application for the desktop, made with `vlx init --desktop`.
|
|
4
|
+
|
|
5
|
+
```console
|
|
6
|
+
$ {{run}} dev # open the window, and update it as you edit
|
|
7
|
+
$ {{run}} build # write the engine to dist/desktop/
|
|
8
|
+
$ {{run}} check # find mistakes in your files without building
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `content/` is Markdown: a page's path is its address.
|
|
12
|
+
- `src/layout.mprx` is the window around a page; `src/main.ts` declares it, the state and the commands.
|
|
13
|
+
- `valance.desktop.json` marks the project as a desktop one; `valance.json` names the application.
|
|
14
|
+
- The window is the Desktop PORT's (`valancex-port-desktop`); `vlx build` writes the engine it runs, `dist/desktop/engine.mjs`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Create the project, run it, change a page.
|
|
4
|
+
order: 1
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Getting started
|
|
8
|
+
|
|
9
|
+
Edit `content/index.md` and save: the window shows it.
|
|
10
|
+
|
|
11
|
+
> A link is an address. In a window, following one is the application's own navigation, the same command the web uses.
|
|
12
|
+
|
|
13
|
+
1. Add a page: a Markdown file under `content/`.
|
|
14
|
+
2. Change the window: `src/layout.mprx`, with the components `column`, `row`, `heading`, `text`, `button`, `checkbox` and `textfield`.
|
|
15
|
+
3. Add state or a command: `src/main.ts`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: {{name}}
|
|
3
|
+
description: A desktop application, written the way a VALANCE application is written.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{name}}
|
|
7
|
+
|
|
8
|
+
This is a **VALANCE application** in a window. Everything you are reading is a Markdown file in `content/`; the window around it is `src/layout.mprx`.
|
|
9
|
+
|
|
10
|
+
- Pages are Markdown files. Their path is their address.
|
|
11
|
+
- The layout is a template, drawn with the Desktop PORT's components.
|
|
12
|
+
- `vlx dev` opens the window and updates it as you edit.
|
|
13
|
+
|
|
14
|
+
Start with [Getting started](/guide/getting-started).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<column>
|
|
2
|
+
<row>
|
|
3
|
+
<button disabled={!canGoBack} on.click={back()}>Back</button>
|
|
4
|
+
<button disabled={!canGoForward} on.click={forward()}>Forward</button>
|
|
5
|
+
<link href={home}>{site}</link>
|
|
6
|
+
<button on.click={toggleNavigation()}>{#if navigation}Hide pages{:else}Show pages{/if}</button>
|
|
7
|
+
</row>
|
|
8
|
+
{#if navigation}
|
|
9
|
+
{#each sections as s (s.id)}
|
|
10
|
+
<column>
|
|
11
|
+
{#if s.title != ""}<text>{s.title}</text>{/if}
|
|
12
|
+
<list ordered={false}>
|
|
13
|
+
{#each s.items as n (n.id)}<listitem><link href={n.href}>{n.label}</link></listitem>{/each}
|
|
14
|
+
</list>
|
|
15
|
+
</column>
|
|
16
|
+
{/each}
|
|
17
|
+
{/if}
|
|
18
|
+
<blocks items={blocks} />
|
|
19
|
+
<row>
|
|
20
|
+
{#if prev.href != ""}<link href={prev.href}>{prev.label}</link>{/if}
|
|
21
|
+
{#if next.href != ""}<link href={next.href}>{next.label}</link>{/if}
|
|
22
|
+
</row>
|
|
23
|
+
</column>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { component, defineApp, field } from "@valancex/common";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import layout from "./layout.mprx";
|
|
4
|
+
|
|
5
|
+
// The application, from the top. `layout` is the window's content around a page; the Markdown in content/ is what goes in `<blocks>`. The state is the application's own, beyond "which page is
|
|
6
|
+
// shown": each field is its type and its default, together. A field is a name the layout can read (`navigation`), and the commands below are names it can run (`toggleNavigation()`).
|
|
7
|
+
export default defineApp({
|
|
8
|
+
layout: component(layout),
|
|
9
|
+
state: {
|
|
10
|
+
navigation: field(Schema.Boolean, true),
|
|
11
|
+
},
|
|
12
|
+
commands: (command) => ({
|
|
13
|
+
toggleNavigation: command((state) => ({ ...state, navigation: !state.navigation })),
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"types": ["node"],
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"lib": ["ES2022"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"exactOptionalPropertyTypes": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"allowArbitraryExtensions": true,
|
|
15
|
+
"rootDirs": ["src", ".valance/types"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
A VALANCE application for the web and the desktop, made with `vlx init --web --desktop`.
|
|
4
|
+
|
|
5
|
+
```console
|
|
6
|
+
$ {{run}} dev -- --web # serve the site (--desktop opens the window instead)
|
|
7
|
+
$ {{run}} build # build every target: dist/web/ and dist/desktop/
|
|
8
|
+
$ {{run}} check # find mistakes in your files, for every target
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `content/` is Markdown, and the state and commands in `src/main.ts` are the application's: the same on every target.
|
|
12
|
+
- Each target has its own templates, because each draws with its own vocabulary: `src/web/` and `src/desktop/`. `src/main.ts` says which is whose (`targets`).
|
|
13
|
+
- `valance.web.json` and `valance.desktop.json` say which targets the project has; `valance.json` names the application.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Create the project, run it, change a page.
|
|
4
|
+
order: 1
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Getting started
|
|
8
|
+
|
|
9
|
+
Edit `content/index.md` and save: the window shows it.
|
|
10
|
+
|
|
11
|
+
> A link is an address. In a window, following one is the application's own navigation, the same command the web uses.
|
|
12
|
+
|
|
13
|
+
1. Add a page: a Markdown file under `content/`.
|
|
14
|
+
2. Change the window: `src/layout.mprx`, with the components `column`, `row`, `heading`, `text`, `button`, `checkbox` and `textfield`.
|
|
15
|
+
3. Add state or a command: `src/main.ts`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: {{name}}
|
|
3
|
+
description: A desktop application, written the way a VALANCE application is written.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{name}}
|
|
7
|
+
|
|
8
|
+
This is a **VALANCE application** in a window. Everything you are reading is a Markdown file in `content/`; the window around it is `src/layout.mprx`.
|
|
9
|
+
|
|
10
|
+
- Pages are Markdown files. Their path is their address.
|
|
11
|
+
- The layout is a template, drawn with the Desktop PORT's components.
|
|
12
|
+
- `vlx dev --desktop` opens the window and updates it as you edit.
|
|
13
|
+
|
|
14
|
+
Start with [Getting started](/guide/getting-started).
|