@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/build.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import type { TargetId } from "./targets.js";
|
|
2
|
+
import type { BudgetResult, SizeReport } from "./size.js";
|
|
1
3
|
export interface Built {
|
|
2
4
|
readonly dir: string;
|
|
3
5
|
readonly files: ReadonlyArray<string>;
|
|
4
6
|
readonly pages: ReadonlyArray<string>;
|
|
7
|
+
/** The web build's start-up and after-start sets, measured, and each budget it was held to (a production build only). */
|
|
8
|
+
readonly size?: SizeReport;
|
|
9
|
+
readonly budgets?: ReadonlyArray<BudgetResult>;
|
|
10
|
+
/** The size report as the lines a terminal prints. */
|
|
11
|
+
readonly report?: ReadonlyArray<string>;
|
|
5
12
|
}
|
|
6
13
|
/** The file a pathname is written to in the site: `/` → `index.html`, `/guide/x` → `guide/x/index.html`, relative to the base. */
|
|
7
14
|
export declare const fileOf: (pathname: string, base: string) => string;
|
|
@@ -10,5 +17,9 @@ export type BuildStage = "configuration" | "content" | "templates" | "bundle" |
|
|
|
10
17
|
export interface BuildOptions {
|
|
11
18
|
/** Called as each stage begins. */
|
|
12
19
|
readonly onStage?: (stage: BuildStage) => void;
|
|
20
|
+
/** Which of the project's targets to build; all of them by default. */
|
|
21
|
+
readonly targets?: ReadonlyArray<TargetId>;
|
|
22
|
+
/** Called with the size report as soon as the bundle is measured, before a budget error stops the build (a terminal prints it). */
|
|
23
|
+
readonly onReport?: (lines: ReadonlyArray<string>) => void;
|
|
13
24
|
}
|
|
14
25
|
export declare const build: (root: string, options?: BuildOptions) => Promise<Built>;
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// `vlx build`: the project → a static site in `dist/`. The stages, in order: configuration, content, templates, the browser bundle, then one document per page (and a 404 page, a sitemap
|
|
2
|
-
// when the site has a published address). Everything is generated under `.valance/` first; `dist/` holds only
|
|
1
|
+
// `vlx build`: the project → a static site in `dist/web/`. The stages, in order: configuration, content, templates, the browser bundle, then one document per page (and a 404 page, a sitemap
|
|
2
|
+
// when the site has a published address). Everything is generated under `.valance/` first; `dist/` holds only what is built.
|
|
3
3
|
import { build as bundle } from "vite";
|
|
4
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { loadAuthor } from "./author.js";
|
|
7
8
|
import { loadConfig } from "./config.js";
|
|
@@ -9,44 +10,89 @@ import { headersFile } from "./security.js";
|
|
|
9
10
|
import { AppError } from "./diagnostics.js";
|
|
10
11
|
import { generateContent, generateEntries, generateProgram } from "./emit.js";
|
|
11
12
|
import { OUTPUT_DIR } from "./project.js";
|
|
12
|
-
import {
|
|
13
|
+
import { check, DEFAULT_BUDGETS, failure, lines, measure } from "./size.js";
|
|
14
|
+
import { assertOneRuntime } from "./runtime-guard.js";
|
|
15
|
+
import { baseConfig, createLoader, SERVER_ENTRY, templates } from "./vite.js";
|
|
16
|
+
import { GENERATED_DIR } from "./project.js";
|
|
13
17
|
/** The file a pathname is written to in the site: `/` → `index.html`, `/guide/x` → `guide/x/index.html`, relative to the base. */
|
|
14
18
|
export const fileOf = (pathname, base) => {
|
|
15
19
|
const inside = pathname.slice(base.length).replace(/\/+$/, "");
|
|
16
20
|
return inside === "" ? "index.html" : `${inside}/index.html`;
|
|
17
21
|
};
|
|
18
22
|
const sitemap = (content) => `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${content.pages.map((page) => ` <url><loc>${content.site.url}${content.site.base}${page.path.replace(/^\//, "")}</loc></url>`).join("\n")}\n</urlset>\n`;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
/** The MESH runtime's module, found the way the project's own dependency would be: through `@valancex/common`, which depends on it. */
|
|
24
|
+
const wasmOf = (root) => {
|
|
25
|
+
const fromRoot = createRequire(join(root, "package.json"));
|
|
26
|
+
const fromCommon = createRequire(fromRoot.resolve("@valancex/common/package.json"));
|
|
27
|
+
return fromCommon.resolve("@valancex/mesh-runtime/mesh-runtime.wasm");
|
|
28
|
+
};
|
|
29
|
+
/** The desktop build: the engine, one ES module with everything it needs bundled in, and the MESH runtime's module beside it, in `dist/desktop/`. The window is the Desktop PORT's. */
|
|
30
|
+
const buildDesktop = async (root) => {
|
|
31
|
+
const out = join(root, OUTPUT_DIR, "desktop");
|
|
32
|
+
rmSync(out, { recursive: true, force: true });
|
|
33
|
+
await bundle({
|
|
34
|
+
root,
|
|
35
|
+
configFile: false,
|
|
36
|
+
publicDir: false, // the site's public files are the web's
|
|
37
|
+
logLevel: "warn",
|
|
38
|
+
plugins: [templates(root)],
|
|
39
|
+
cacheDir: join(root, GENERATED_DIR, "cache"),
|
|
40
|
+
ssr: { noExternal: true, target: "node" },
|
|
41
|
+
esbuild: { keepNames: true },
|
|
42
|
+
build: {
|
|
43
|
+
ssr: join(root, GENERATED_DIR, "desktop.ts"),
|
|
44
|
+
outDir: out,
|
|
45
|
+
emptyOutDir: true,
|
|
46
|
+
target: "node22",
|
|
47
|
+
// W10: the engine is minified (half the bytes; start-up measured no worse, 2026-10-24). Names are kept: errors and their `_tag` are read by name.
|
|
48
|
+
minify: "esbuild",
|
|
49
|
+
rollupOptions: {
|
|
50
|
+
output: {
|
|
51
|
+
format: "es",
|
|
52
|
+
entryFileNames: "engine.mjs",
|
|
53
|
+
banner: 'import { createRequire as __vlxRequire } from "node:module"; const require = __vlxRequire(import.meta.url);',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
copyFileSync(wasmOf(root), join(out, "mesh-runtime.wasm"));
|
|
59
|
+
// The files images are drawn from, beside the engine (the site's `public/`, which the desktop's content refers to the same way).
|
|
60
|
+
const publicDir = join(root, "public");
|
|
61
|
+
if (existsSync(publicDir)) {
|
|
62
|
+
cpSync(publicDir, join(out, "public"), { recursive: true });
|
|
40
63
|
}
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
return { dir: out, files: ["desktop/engine.mjs", "desktop/mesh-runtime.wasm"], pages: [] };
|
|
65
|
+
};
|
|
66
|
+
/** The web build: the browser bundle into `out`, then one document per page. The loader is the one the templates were compiled with. */
|
|
67
|
+
const buildWeb = async (root, config, loader, content, out, prefix, stage, onReport) => {
|
|
68
|
+
// The browser bundle first: it empties its directory, and the documents are written into it after.
|
|
69
|
+
stage("bundle");
|
|
70
|
+
await bundle({ ...baseConfig(root, config, out), mode: "production" });
|
|
71
|
+
const rendered = (await loader.ssrLoadModule(SERVER_ENTRY));
|
|
72
|
+
{
|
|
73
|
+
const dist = join(root, out);
|
|
43
74
|
const manifest = JSON.parse(readFileSync(join(dist, ".vite", "manifest.json"), "utf8"));
|
|
44
75
|
const entry = Object.values(manifest).find((chunk) => chunk.isEntry === true);
|
|
45
76
|
if (entry === undefined) {
|
|
46
|
-
throw new AppError([{ file:
|
|
77
|
+
throw new AppError([{ file: out, line: 0, column: 0, code: "build-entry", message: "the bundle has no entry chunk" }]);
|
|
47
78
|
}
|
|
48
79
|
rmSync(join(dist, ".vite"), { recursive: true, force: true }); // the bundler's bookkeeping is not part of the site
|
|
49
|
-
|
|
80
|
+
// The WebAssembly runtime is in the start-up set (H7): the head starts compiling it while the page's script downloads (W11).
|
|
81
|
+
const wasm = Object.values(manifest).find((chunk) => chunk.file.endsWith(".wasm"))?.file;
|
|
82
|
+
const built = {
|
|
83
|
+
script: `${config.web.base}${entry.file}`,
|
|
84
|
+
styles: (entry.css ?? []).map((file) => `${config.web.base}${file}`),
|
|
85
|
+
...(wasm === undefined ? {} : { wasm: `${config.web.base}${wasm}` }),
|
|
86
|
+
};
|
|
87
|
+
// The size report: the start-up set and the after-start set, held to the project's budgets or the framework's (W9, W14). Production only.
|
|
88
|
+
const size = measure(dist, manifest, wasm);
|
|
89
|
+
const budgets = check(size, config.budgets ?? DEFAULT_BUDGETS);
|
|
90
|
+
const report = lines(size, budgets);
|
|
91
|
+
onReport(report);
|
|
92
|
+
const over = failure(budgets);
|
|
93
|
+
if (over !== undefined) {
|
|
94
|
+
throw over;
|
|
95
|
+
}
|
|
50
96
|
const files = [];
|
|
51
97
|
const write = (relative, text) => {
|
|
52
98
|
mkdirSync(dirname(join(dist, relative)), { recursive: true });
|
|
@@ -67,9 +113,53 @@ export const build = async (root, options = {}) => {
|
|
|
67
113
|
if (config.web.headers) {
|
|
68
114
|
write("_headers", headersFile(config));
|
|
69
115
|
}
|
|
70
|
-
return { dir: dist, files, pages };
|
|
116
|
+
return { dir: dist, files: files.map((file) => `${prefix}${file}`), pages, size, budgets, report };
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
export const build = async (root, options = {}) => {
|
|
120
|
+
const stage = options.onStage ?? (() => undefined);
|
|
121
|
+
stage("configuration");
|
|
122
|
+
const config = loadConfig(root);
|
|
123
|
+
const selected = options.targets ?? config.targets;
|
|
124
|
+
const unknown = selected.filter((id) => !config.targets.includes(id));
|
|
125
|
+
if (unknown.length > 0) {
|
|
126
|
+
throw new AppError([
|
|
127
|
+
{
|
|
128
|
+
file: "valance.json",
|
|
129
|
+
line: 0,
|
|
130
|
+
column: 0,
|
|
131
|
+
code: "build-target",
|
|
132
|
+
message: `this project is not built for ${unknown.join(", ")} (its targets: ${config.targets.join(", ")}): add valance.${unknown[0]}.json`,
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
// Production only (`vlx dev` does not build through here): one copy of the MESH runtime, or the site ships its WebAssembly module more than once (W20).
|
|
137
|
+
assertOneRuntime(root);
|
|
138
|
+
generateEntries(root, config);
|
|
139
|
+
stage("content");
|
|
140
|
+
const content = generateContent(root, config);
|
|
141
|
+
const loader = await createLoader(root, config);
|
|
142
|
+
const built = [];
|
|
143
|
+
try {
|
|
144
|
+
stage("templates");
|
|
145
|
+
await generateProgram(root, await loadAuthor(loader, root), config, selected);
|
|
146
|
+
// Each target writes to a directory of its own, whatever else the project has: dist/web/ and dist/desktop/.
|
|
147
|
+
if (selected.includes("web")) {
|
|
148
|
+
built.push(await buildWeb(root, config, loader, content, `${OUTPUT_DIR}/web`, "web/", stage, options.onReport ?? (() => undefined)));
|
|
149
|
+
}
|
|
150
|
+
if (selected.includes("desktop")) {
|
|
151
|
+
stage("bundle");
|
|
152
|
+
built.push(await buildDesktop(root));
|
|
153
|
+
}
|
|
71
154
|
}
|
|
72
155
|
finally {
|
|
73
156
|
await loader.close();
|
|
74
157
|
}
|
|
158
|
+
const web = built.find((each) => each.size !== undefined);
|
|
159
|
+
return {
|
|
160
|
+
dir: join(root, OUTPUT_DIR),
|
|
161
|
+
files: built.flatMap((each) => each.files),
|
|
162
|
+
pages: built.flatMap((each) => each.pages),
|
|
163
|
+
...(web?.size === undefined ? {} : { size: web.size, budgets: web.budgets, report: web.report }),
|
|
164
|
+
};
|
|
75
165
|
};
|
package/dist/check.js
CHANGED
|
@@ -22,13 +22,13 @@ export const check = async (root) => {
|
|
|
22
22
|
if (config === undefined) {
|
|
23
23
|
return { diagnostics: problems, pages: 0, components: [] };
|
|
24
24
|
}
|
|
25
|
-
generateEntries(root);
|
|
25
|
+
generateEntries(root, config);
|
|
26
26
|
const content = await collect(problems, () => generateContent(root, config));
|
|
27
27
|
const server = await createLoader(root, config);
|
|
28
28
|
try {
|
|
29
29
|
const author = await collect(problems, () => loadAuthor(server, root));
|
|
30
30
|
const failedAuthor = problems.some((each) => each.file === "src/main.ts");
|
|
31
|
-
const components = failedAuthor ? undefined : await collect(problems, () => generateProgram(root, author));
|
|
31
|
+
const components = failedAuthor ? undefined : await collect(problems, () => generateProgram(root, author, config));
|
|
32
32
|
return { diagnostics: problems, pages: content?.pages.length ?? 0, components: components ?? [] };
|
|
33
33
|
}
|
|
34
34
|
finally {
|
package/dist/cli.js
CHANGED
|
@@ -2,46 +2,58 @@
|
|
|
2
2
|
//
|
|
3
3
|
// vlx init [name] [--web ...] make a new application (asks what the flags do not say)
|
|
4
4
|
// vlx dev [--port N] [--host H] serve the project and rebuild it as it changes
|
|
5
|
-
// vlx build write the site to dist/
|
|
5
|
+
// vlx build write the site to dist/web/
|
|
6
6
|
// vlx check [--json] find mistakes in authored files without building
|
|
7
|
+
// vlx theme check [--json] the theme: its tokens, recipes, contrast and what each target applies
|
|
7
8
|
// vlx generate page|component <name> add a page, or a component registered in src/main.ts (alias: g)
|
|
8
9
|
// vlx sync bring .valance/ (generated types, manifest, entries) up to date; runs by itself after install and generate
|
|
9
10
|
// vlx help [command] what a command does
|
|
10
11
|
//
|
|
11
12
|
// In a terminal, `init`, `dev`, `build` and `check` draw an interface (Ink, `ui/`). Anywhere else (a pipe, CI, `--json`, `--plain`) they print the plain text they always have.
|
|
13
|
+
import { resolve } from "node:path";
|
|
12
14
|
import { readFileSync } from "node:fs";
|
|
13
15
|
import { pathToFileURL } from "node:url";
|
|
14
16
|
import { INIT_HELP, parseInit } from "./args.js";
|
|
15
17
|
import { build } from "./build.js";
|
|
16
|
-
import {
|
|
18
|
+
import { packageDesktop } from "./package.js";
|
|
19
|
+
import { generateComponent, generatePage, generateTarget, GenerateError, KINDS } from "./add.js";
|
|
17
20
|
import { check } from "./check.js";
|
|
21
|
+
import { themeReport } from "./theme.js";
|
|
18
22
|
import { findRoot } from "./config.js";
|
|
19
23
|
import { localCli } from "./delegate.js";
|
|
20
24
|
import { dev } from "./dev.js";
|
|
25
|
+
import { defaultRecording, replayFile } from "./replay.js";
|
|
21
26
|
import { AppError, format } from "./diagnostics.js";
|
|
22
27
|
import { detectPackageManager, directoryFor, directoryProblem, execute, nameProblem, runCommand, titleOf } from "./init.js";
|
|
23
|
-
import { TARGETS } from "./targets.js";
|
|
28
|
+
import { DEFAULT_TARGETS, TARGETS } from "./targets.js";
|
|
24
29
|
import { canAsk, canDraw } from "./tty.js";
|
|
25
30
|
const HELP = `vlx <command>
|
|
26
31
|
|
|
27
32
|
init [name] [--web] make a new application
|
|
28
|
-
dev [--port N] [--host H] serve it and rebuild as it changes
|
|
29
|
-
|
|
33
|
+
dev [--port N] [--host H] serve it and rebuild as it changes (--record [file]: write a session log)
|
|
34
|
+
replay <log> [--route R] [--allow-user-data] play a session log against this project and find where it differs
|
|
35
|
+
build write the site to dist/web/
|
|
30
36
|
check [--json] find mistakes in your files without building
|
|
37
|
+
theme check [--json] check the theme: tokens, recipes, contrast, and what each target applies
|
|
31
38
|
generate page|component <name> add a page, or a component (alias: g)
|
|
39
|
+
generate target web|desktop add a target to the project
|
|
40
|
+
package --desktop the desktop target as one executable beside its window (unsigned)
|
|
32
41
|
sync refresh the generated files (this runs by itself; see vlx help sync)
|
|
33
42
|
help [command] say more about one command
|
|
34
43
|
|
|
35
44
|
Run it in a project (a directory with a valance.json), or any directory inside one.
|
|
36
45
|
In a terminal the commands draw an interface; --plain (or a pipe, or CI) prints plain text.
|
|
37
46
|
`;
|
|
38
|
-
const GENERATE_HELP = `vlx generate <page|component> <name> (alias: vlx g)
|
|
47
|
+
const GENERATE_HELP = `vlx generate <page|component|target> <name> (alias: vlx g)
|
|
39
48
|
|
|
40
49
|
vlx generate page guide/getting-started content/guide/getting-started.md, with its front matter
|
|
41
50
|
(--title "Getting started" to say the title)
|
|
42
51
|
vlx generate component post-card src/post-card.mprx and src/post-card.ts, imported in
|
|
43
52
|
src/main.ts and listed in its \`components\`. Write
|
|
44
53
|
<post-card></post-card> in a template to use it.
|
|
54
|
+
vlx generate target desktop valance.desktop.json and a layout of its own in src/desktop/,
|
|
55
|
+
registered in \`targets\` of src/main.ts; the content, the state and
|
|
56
|
+
the commands are shared. Nothing the project has is moved.
|
|
45
57
|
|
|
46
58
|
Nothing is overwritten: a name that is taken is refused.
|
|
47
59
|
`;
|
|
@@ -49,9 +61,12 @@ const COMMAND_HELP = {
|
|
|
49
61
|
init: INIT_HELP,
|
|
50
62
|
generate: GENERATE_HELP,
|
|
51
63
|
g: GENERATE_HELP,
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
replay: "vlx replay <log> [--route changes,update,render] [--json] [--allow-user-data]\n\nPlay a session log (written by vlx dev --record, or one of MESH's conformance logs) against this project's templates by the three routes of the equivalence law, and find the first line where one differs from the log: a tree, an event's outcome, a program or manifest that is not the one recorded. It runs no command and reads no clock. Exits 0 when every route gave what the log recorded, 1 when one did not, 2 when the log cannot be read. A log that holds user data (every recording does: its header says containsUserData) is refused unless you pass --allow-user-data: a divergence prints what was typed and shown.\n",
|
|
65
|
+
dev: "vlx dev [--web|--desktop] [--port N] [--host H] [--record [file]]\n\nServe the project (the web is served, a desktop project opens its window; a project of more than one target does both, or the one named) and rebuild it as files change. A mistake leaves the last good build serving and shows in the terminal and in the page. --record writes the web page's session as a log (default .valance/sessions/<time>.jsonl) for vlx replay: development only, never uploaded, and it holds what the person typed.\n",
|
|
66
|
+
build: "vlx build [--web] [--desktop]\n\nBuild every target the project has (or the ones named). The web: the site, in dist/web/; any host that serves files can serve it. The desktop: the engine, in dist/desktop/.\n",
|
|
54
67
|
sync: "vlx sync\n\nBring .valance/ up to date: the generated types for your templates (so your editor and tsc agree with them), the manifest and the entries. You rarely run it: a new project does it after install (the `prepare` script), `vlx generate` does it after it writes, and dev, build and check do it as they run. It never fails: mistakes in your files are reported by vlx check.\n",
|
|
68
|
+
package: "vlx package --desktop [--platform linux-x64|win32-x64] [--window <path>] [--node <path>] [--archive]\n\nBuild the desktop target and package it: the engine as one executable (Node's single executable application, with the MESH runtime inside) beside the Desktop PORT's window program, in dist/package/<platform>/. --window is the window program for that platform (default: VLX_WINDOW, else vlx-window on the PATH; build it from the PORT repository). For another platform, --node is a node binary of this version for it. --archive also writes the package as one file, a .tar.gz (Linux) or a .zip (Windows), with its SHA-256 beside it. The result is unsigned.\n",
|
|
69
|
+
theme: "vlx theme check [--json]\n\nCheck the theme: the project's `theme/theme.json` (its tokens by mode, in the Design Tokens format, and its recipes), or the default theme when it has none. Reports a token that does not resolve, a recipe that names a part, state, variant, property or token that does not exist, a removed focus ring, the contrast of every pair the theme declares (WCAG 2.2), and what the web and the desktop apply of it. --json prints the diagnostics, the contrast and the coverage.\n",
|
|
55
70
|
check: "vlx check [--json]\n\nFind mistakes in your files without building. --json prints them as { file, line, column, code, message } for editors and CI.\n",
|
|
56
71
|
};
|
|
57
72
|
/** Refreshes `.valance/` and says so in a line. Best effort: a mistake in the project is `check`'s to report, and a project with no `valance.json` (the install ran before init finished) is left alone. */
|
|
@@ -66,10 +81,22 @@ const sync = async (from) => {
|
|
|
66
81
|
return `skipped (${error instanceof Error ? error.message.split("\n")[0] : String(error)})`;
|
|
67
82
|
}
|
|
68
83
|
};
|
|
84
|
+
/** The targets named by flags (`--web`, `--desktop`), in the order given. */
|
|
85
|
+
const targetFlags = (args) => TARGETS.map((each) => each.id).filter((id) => args.includes(`--${id}`));
|
|
69
86
|
const option = (args, name) => {
|
|
70
87
|
const at = args.indexOf(name);
|
|
71
88
|
return at < 0 ? undefined : args[at + 1];
|
|
72
89
|
};
|
|
90
|
+
/** `--record` alone, `--record file` or `--record=file`: the file, `""` for the default, nothing when it is not there. A word after it that is another flag is not its file. */
|
|
91
|
+
const recordOption = (args) => {
|
|
92
|
+
const joined = args.find((each) => each.startsWith("--record="));
|
|
93
|
+
if (joined !== undefined) {
|
|
94
|
+
return joined.slice("--record=".length);
|
|
95
|
+
}
|
|
96
|
+
const at = args.indexOf("--record");
|
|
97
|
+
const next = at < 0 ? undefined : args[at + 1];
|
|
98
|
+
return at < 0 ? undefined : next === undefined || next.startsWith("-") ? "" : next;
|
|
99
|
+
};
|
|
73
100
|
const version = () => JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
74
101
|
const runInit = async (args, cwd) => {
|
|
75
102
|
if (args.name !== undefined) {
|
|
@@ -90,13 +117,13 @@ const runInit = async (args, cwd) => {
|
|
|
90
117
|
return 2;
|
|
91
118
|
}
|
|
92
119
|
if (args.targets.length === 0 && !args.yes) {
|
|
93
|
-
console.error(`vlx init: choose a target (${TARGETS.map((each) => `--${each.id}`).join(", ")}), or --yes for
|
|
120
|
+
console.error(`vlx init: choose a target (${TARGETS.map((each) => `--${each.id}`).join(", ")}), or --yes for the web`);
|
|
94
121
|
return 2;
|
|
95
122
|
}
|
|
96
123
|
const answers = {
|
|
97
124
|
name: args.name,
|
|
98
125
|
dir: directoryFor(args.name, cwd),
|
|
99
|
-
targets: args.targets.length === 0 ?
|
|
126
|
+
targets: args.targets.length === 0 ? DEFAULT_TARGETS : args.targets,
|
|
100
127
|
web: { base: args.base ?? "", url: args.url ?? "" },
|
|
101
128
|
packageManager: args.packageManager ?? detectPackageManager(),
|
|
102
129
|
install: args.install ?? true,
|
|
@@ -128,7 +155,15 @@ export const main = async (args, cwd = process.cwd()) => {
|
|
|
128
155
|
return 0;
|
|
129
156
|
}
|
|
130
157
|
// A global `vlx` runs the project's own (see `delegate.ts`).
|
|
131
|
-
if (command === "dev" ||
|
|
158
|
+
if (command === "dev" ||
|
|
159
|
+
command === "replay" ||
|
|
160
|
+
command === "build" ||
|
|
161
|
+
command === "package" ||
|
|
162
|
+
command === "check" ||
|
|
163
|
+
command === "theme" ||
|
|
164
|
+
command === "sync" ||
|
|
165
|
+
command === "generate" ||
|
|
166
|
+
command === "g") {
|
|
132
167
|
const local = localCli(cwd);
|
|
133
168
|
if (local !== undefined) {
|
|
134
169
|
const theirs = (await import(pathToFileURL(local).href));
|
|
@@ -153,32 +188,85 @@ export const main = async (args, cwd = process.cwd()) => {
|
|
|
153
188
|
case "dev": {
|
|
154
189
|
const port = option(rest, "--port");
|
|
155
190
|
const host = option(rest, "--host");
|
|
191
|
+
const picked = targetFlags(rest);
|
|
192
|
+
if (picked.length > 1) {
|
|
193
|
+
console.error("vlx dev: name one target, or none for all of them: vlx dev --web or vlx dev --desktop");
|
|
194
|
+
return 2;
|
|
195
|
+
}
|
|
196
|
+
const target = picked[0];
|
|
197
|
+
const asked = recordOption(rest);
|
|
198
|
+
const record = asked === undefined ? undefined : asked === "" ? defaultRecording(findRoot(cwd)) : resolve(cwd, asked);
|
|
156
199
|
if (draws) {
|
|
157
200
|
const { drawDev } = await import("./ui/run.js");
|
|
158
|
-
await drawDev(findRoot(cwd), {
|
|
201
|
+
await drawDev(findRoot(cwd), {
|
|
202
|
+
...(record === undefined ? {} : { record }),
|
|
203
|
+
...(target === undefined ? {} : { target }),
|
|
204
|
+
...(port === undefined ? {} : { port: Number(port) }),
|
|
205
|
+
...(host === undefined ? {} : { host }),
|
|
206
|
+
});
|
|
159
207
|
return 0;
|
|
160
208
|
}
|
|
161
|
-
const running = await dev(findRoot(cwd), {
|
|
209
|
+
const running = await dev(findRoot(cwd), {
|
|
210
|
+
...(record === undefined ? {} : { record }),
|
|
211
|
+
...(target === undefined ? {} : { target }),
|
|
212
|
+
...(port === undefined ? {} : { port: Number(port) }),
|
|
213
|
+
...(host === undefined ? {} : { host }),
|
|
214
|
+
});
|
|
162
215
|
console.log(`vlx dev: ${running.url}`);
|
|
163
|
-
// Until interrupted.
|
|
216
|
+
// Until interrupted (or, for a desktop project, until the window is closed).
|
|
164
217
|
await new Promise((resolve) => {
|
|
165
218
|
process.once("SIGINT", resolve);
|
|
166
219
|
process.once("SIGTERM", resolve);
|
|
220
|
+
void running.closed?.then(resolve);
|
|
167
221
|
});
|
|
168
222
|
await running.close();
|
|
169
223
|
return 0;
|
|
170
224
|
}
|
|
225
|
+
case "replay": {
|
|
226
|
+
const file = rest.find((each, index) => !each.startsWith("-") && rest[index - 1] !== "--route");
|
|
227
|
+
if (file === undefined) {
|
|
228
|
+
console.error("vlx replay: name a log: vlx replay <log> [--route changes,update,render] [--json]");
|
|
229
|
+
return 2;
|
|
230
|
+
}
|
|
231
|
+
const routes = option(rest, "--route");
|
|
232
|
+
const replayed = await replayFile(findRoot(cwd), resolve(cwd, file), {
|
|
233
|
+
...(routes === undefined ? {} : { routes: routes.split(",") }),
|
|
234
|
+
json: rest.includes("--json"),
|
|
235
|
+
allowUserData: rest.includes("--allow-user-data"),
|
|
236
|
+
});
|
|
237
|
+
(replayed.code === 0 ? console.log : console.error)(replayed.output);
|
|
238
|
+
return replayed.code;
|
|
239
|
+
}
|
|
171
240
|
case "build": {
|
|
172
241
|
if (draws) {
|
|
173
242
|
const { drawBuild } = await import("./ui/run.js");
|
|
174
|
-
await drawBuild(findRoot(cwd));
|
|
243
|
+
await drawBuild(findRoot(cwd), targetFlags(rest));
|
|
175
244
|
return 0;
|
|
176
245
|
}
|
|
177
246
|
const started = Date.now();
|
|
178
|
-
const
|
|
247
|
+
const flagged = targetFlags(rest);
|
|
248
|
+
const built = await build(findRoot(cwd), { ...(flagged.length === 0 ? {} : { targets: flagged }), onReport: (report) => report.forEach((line) => console.log(line)) });
|
|
179
249
|
console.log(`vlx build: ${built.pages.length} pages, ${built.files.length} files → ${built.dir} (${((Date.now() - started) / 1000).toFixed(1)}s)`);
|
|
180
250
|
return 0;
|
|
181
251
|
}
|
|
252
|
+
case "package": {
|
|
253
|
+
if (!rest.includes("--desktop")) {
|
|
254
|
+
console.error("vlx package: say what: vlx package --desktop [--platform linux-x64|win32-x64] [--window <path>] [--node <path>] [--archive]");
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
const platform = option(rest, "--platform");
|
|
258
|
+
const window = option(rest, "--window");
|
|
259
|
+
const node = option(rest, "--node");
|
|
260
|
+
const packaged = await packageDesktop(findRoot(cwd), {
|
|
261
|
+
...(platform === undefined ? {} : { platform }),
|
|
262
|
+
...(window === undefined ? {} : { window }),
|
|
263
|
+
...(node === undefined ? {} : { node }),
|
|
264
|
+
...(rest.includes("--archive") ? { archive: true } : {}),
|
|
265
|
+
});
|
|
266
|
+
console.log(`vlx package: ${packaged.files.join(", ")} → ${packaged.dir}`);
|
|
267
|
+
packaged.notes.forEach((note) => console.log(`vlx package: ${note}`));
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
182
270
|
case "check": {
|
|
183
271
|
if (draws && !rest.includes("--json")) {
|
|
184
272
|
const { drawCheck } = await import("./ui/run.js");
|
|
@@ -199,6 +287,32 @@ export const main = async (args, cwd = process.cwd()) => {
|
|
|
199
287
|
}
|
|
200
288
|
return result.diagnostics.length === 0 ? 0 : 1;
|
|
201
289
|
}
|
|
290
|
+
case "theme": {
|
|
291
|
+
if (rest[0] !== "check") {
|
|
292
|
+
console.error("vlx theme: say what: vlx theme check [--json]");
|
|
293
|
+
return 2;
|
|
294
|
+
}
|
|
295
|
+
const report = themeReport(findRoot(cwd));
|
|
296
|
+
if (rest.includes("--json")) {
|
|
297
|
+
console.log(JSON.stringify({ theme: report.name, custom: report.custom, diagnostics: report.diagnostics, contrast: report.contrast, coverage: report.coverage }, null, 2));
|
|
298
|
+
return report.diagnostics.length === 0 ? 0 : 1;
|
|
299
|
+
}
|
|
300
|
+
console.log(`vlx theme check: ${report.custom ? "the project's theme" : "the default theme"} "${report.name}" (modes: ${report.modes.join(", ")})`);
|
|
301
|
+
for (const each of report.contrast) {
|
|
302
|
+
console.log(` contrast ${each.mode.padEnd(9)} ${each.text} on ${each.on} ${each.ratio.toFixed(2)}:1 (min ${each.min}) ${each.passes ? "ok" : "BELOW"}`);
|
|
303
|
+
}
|
|
304
|
+
for (const each of report.coverage) {
|
|
305
|
+
console.log(` ${each.target}: ${each.styled.length} of ${each.styled.length + each.unstyled.length} primitives of the portable core styled${each.unstyled.length === 0 ? "" : ` (not styled: ${each.unstyled.join(", ")})`}`);
|
|
306
|
+
for (const note of each.notApplied) {
|
|
307
|
+
console.log(` not applied: ${note.primitive} ${note.kind} ${note.name} (${note.support})`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
report.diagnostics.forEach((each) => {
|
|
311
|
+
console.error(format(each));
|
|
312
|
+
});
|
|
313
|
+
console.log(report.diagnostics.length === 0 ? "vlx theme check: no problems" : `vlx theme check: ${report.diagnostics.length} problem${report.diagnostics.length === 1 ? "" : "s"}`);
|
|
314
|
+
return report.diagnostics.length === 0 ? 0 : 1;
|
|
315
|
+
}
|
|
202
316
|
case "sync": {
|
|
203
317
|
const synced = await sync(cwd);
|
|
204
318
|
console.log(synced);
|
|
@@ -219,7 +333,7 @@ export const main = async (args, cwd = process.cwd()) => {
|
|
|
219
333
|
}
|
|
220
334
|
const root = findRoot(cwd);
|
|
221
335
|
try {
|
|
222
|
-
const made = kind === "page" ? generatePage(root, positional[1], title) : generateComponent(root, positional[1]);
|
|
336
|
+
const made = kind === "page" ? generatePage(root, positional[1], title) : kind === "target" ? generateTarget(root, positional[1]) : generateComponent(root, positional[1]);
|
|
223
337
|
made.created.forEach((file) => console.log(`vlx generate: created ${file}`));
|
|
224
338
|
made.changed.forEach((file) => console.log(`vlx generate: changed ${file}`));
|
|
225
339
|
console.log(`vlx generate: ${await sync(root)}`);
|
package/dist/compile.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare const SOURCE_DIR = "src";
|
|
|
6
6
|
/** The component the page is drawn by. */
|
|
7
7
|
export declare const ROOT = "layout";
|
|
8
8
|
export declare const BUILT_IN: string;
|
|
9
|
+
/** The desktop target's built-in views: the same components as the web's, written in the desktop vocabulary. */
|
|
10
|
+
export declare const BUILT_IN_DESKTOP: string;
|
|
9
11
|
/** The package directory: where its README, which documents the framework's own names, is. */
|
|
10
12
|
export declare const PACKAGE_DIR: string;
|
|
11
13
|
interface Template {
|
|
@@ -24,8 +26,14 @@ export interface Templates {
|
|
|
24
26
|
* Built-in templates, then the author's. The author's are exactly the ones `src/main.ts` declares: `layout`, `components` and `override`. A file under `src/` that is not declared is an error, not
|
|
25
27
|
* a guess; a registered component that no template writes is allowed (it is not part of the program until one does). Which components a template uses is read from the template. A name that is a built-in's replaces it only through `override`.
|
|
26
28
|
*/
|
|
27
|
-
export declare const templatesOf: (root: string, author: DefinedApp | undefined, vocabulary?: Vocabulary) => Templates;
|
|
28
|
-
export declare const manifestOf: (author: DefinedApp | undefined, vocabulary?: Vocabulary) =>
|
|
29
|
+
export declare const templatesOf: (root: string, author: DefinedApp | undefined, vocabulary?: Vocabulary, views?: string) => Templates;
|
|
30
|
+
export declare const manifestOf: (author: DefinedApp | undefined, vocabulary?: Vocabulary) => {
|
|
31
|
+
components: {
|
|
32
|
+
[x: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
version: 1;
|
|
35
|
+
types: Readonly<Record<string, never>>;
|
|
36
|
+
};
|
|
29
37
|
export interface Compiled {
|
|
30
38
|
readonly program: Valance.Program;
|
|
31
39
|
/**
|
|
@@ -48,5 +56,5 @@ export interface Compiled {
|
|
|
48
56
|
}>;
|
|
49
57
|
}
|
|
50
58
|
/** Compiles the application's templates against a target's vocabulary (the web's by default). Fails with every diagnostic, each pointing into the file it is about. */
|
|
51
|
-
export declare const compileViews: (root: string, author: DefinedApp | undefined, vocabulary?: Vocabulary) => Promise<Compiled>;
|
|
59
|
+
export declare const compileViews: (root: string, author: DefinedApp | undefined, vocabulary?: Vocabulary, views?: string) => Promise<Compiled>;
|
|
52
60
|
export {};
|