@valancex/cli 0.0.0-stage → 0.11.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/README.md +263 -2
- package/bin/vlx.js +4 -0
- package/dist/add.d.ts +20 -0
- package/dist/add.js +143 -0
- package/dist/args.d.ts +20 -0
- package/dist/args.js +78 -0
- package/dist/author.d.ts +4 -0
- package/dist/author.js +30 -0
- package/dist/build.d.ts +14 -0
- package/dist/build.js +70 -0
- package/dist/check.d.ts +8 -0
- package/dist/check.js +37 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +280 -0
- package/dist/compile.d.ts +51 -0
- package/dist/compile.js +217 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +99 -0
- package/dist/content.d.ts +12 -0
- package/dist/content.js +193 -0
- package/dist/contract.d.ts +31 -0
- package/dist/contract.js +45 -0
- package/dist/delegate.d.ts +2 -0
- package/dist/delegate.js +23 -0
- package/dist/dev.d.ts +21 -0
- package/dist/dev.js +250 -0
- package/dist/diagnostics.d.ts +16 -0
- package/dist/diagnostics.js +16 -0
- package/dist/emit.d.ts +13 -0
- package/dist/emit.js +107 -0
- package/dist/init.d.ts +69 -0
- package/dist/init.js +185 -0
- package/dist/markdown.d.ts +20 -0
- package/dist/markdown.js +185 -0
- package/dist/node.d.ts +10 -0
- package/dist/node.js +6 -0
- package/dist/outline.d.ts +17 -0
- package/dist/outline.js +88 -0
- package/dist/project.d.ts +8 -0
- package/dist/project.js +20 -0
- package/dist/targets.d.ts +9 -0
- package/dist/targets.js +2 -0
- package/dist/tree.d.ts +47 -0
- package/dist/tree.js +149 -0
- package/dist/tty.d.ts +13 -0
- package/dist/tty.js +4 -0
- package/dist/ui/Spinner.d.ts +3 -0
- package/dist/ui/Spinner.js +16 -0
- package/dist/ui/init.d.ts +22 -0
- package/dist/ui/init.js +137 -0
- package/dist/ui/prompts.d.ts +39 -0
- package/dist/ui/prompts.js +98 -0
- package/dist/ui/report.d.ts +31 -0
- package/dist/ui/report.js +30 -0
- package/dist/ui/run.d.ts +15 -0
- package/dist/ui/run.js +75 -0
- package/dist/vite.d.ts +7 -0
- package/dist/vite.js +49 -0
- package/dist/wiring.d.ts +108 -0
- package/dist/wiring.js +330 -0
- package/package.json +69 -3
- package/schemas/valance.schema.json +14 -0
- package/schemas/valance.web.schema.json +14 -0
- package/templates/web/README.md +14 -0
- package/templates/web/content/404.md +7 -0
- package/templates/web/content/guide/getting-started.md +33 -0
- package/templates/web/content/guide/writing-pages.md +21 -0
- package/templates/web/content/index.md +16 -0
- package/templates/web/gitignore +4 -0
- package/templates/web/public/logo.svg +1 -0
- package/templates/web/src/layout.mprx +27 -0
- package/templates/web/src/main.ts +20 -0
- package/templates/web/src/styles.css +28 -0
- package/templates/web/src/theme-toggle.mprx +1 -0
- package/templates/web/src/theme-toggle.ts +10 -0
- package/templates/web/tsconfig.json +18 -0
- package/views/block.mprx +3 -0
- package/views/blocks.mprx +3 -0
- package/views/heading.mprx +3 -0
- package/views/inline.mprx +3 -0
- package/views/layout.mprx +26 -0
- package/views/list-block.mprx +5 -0
- package/views/list-items.mprx +3 -0
- package/views/runs.mprx +3 -0
package/dist/tree.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// The components the author declares in `src/main.ts`: `layout`, the overrides and `components`. This module reads the declaration and finds where in the file each part is written (for a
|
|
2
|
+
// diagnostic, and for the editor); who uses whom is read from the templates (`linked`, with `outline.ts`).
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { ENTRY_FILE } from "./project.js";
|
|
6
|
+
export const APPLICATION = "(application)";
|
|
7
|
+
export const nameOf = (file) => basename(file, ".mprx");
|
|
8
|
+
/** 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
|
+
export const treeOf = (layout, overrides, components = []) => {
|
|
10
|
+
const found = new Map();
|
|
11
|
+
for (const declared of [...(layout === undefined ? [] : [layout]), ...overrides, ...components]) {
|
|
12
|
+
const name = nameOf(declared.template.mprx);
|
|
13
|
+
if (!found.has(name)) {
|
|
14
|
+
const contract = declared.props === undefined && declared.events === undefined ? {} : { interface: { props: declared.props ?? {}, events: declared.events ?? {} } };
|
|
15
|
+
found.set(name, { name, file: declared.template.mprx, ...contract, uses: [], parents: [] });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [...found.values()];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The tree with who uses whom filled in: `usesOf` gives the tags each component's template writes. A component uses the declared components its template writes (not itself); the layout and each
|
|
22
|
+
* override are also the application's.
|
|
23
|
+
*/
|
|
24
|
+
export const linked = (nodes, layout, overrides, usesOf) => {
|
|
25
|
+
const declared = new Set(nodes.map((each) => each.name));
|
|
26
|
+
const uses = new Map(nodes.map((each) => [each.name, usesOf(each.name).filter((tag) => declared.has(tag) && tag !== each.name)]));
|
|
27
|
+
const roots = new Set([...(layout === undefined ? [] : [nameOf(layout.template.mprx)]), ...overrides.map((each) => nameOf(each.template.mprx))]);
|
|
28
|
+
return nodes.map((each) => ({
|
|
29
|
+
...each,
|
|
30
|
+
uses: uses.get(each.name),
|
|
31
|
+
parents: [...(roots.has(each.name) ? [APPLICATION] : []), ...nodes.filter((other) => uses.get(other.name).includes(each.name)).map((other) => other.name)],
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
// ---- where it is written ----------------------------------------------------------------------------------------------------------------------------------------
|
|
35
|
+
export const placeIn = (text, offset, file) => {
|
|
36
|
+
const before = text.slice(0, offset).split("\n");
|
|
37
|
+
return { file, line: before.length, column: before[before.length - 1].length + 1 };
|
|
38
|
+
};
|
|
39
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40
|
+
/** The offset just past the `)` that closes the `(` at `open`, strings respected. */
|
|
41
|
+
const closing = (text, open) => {
|
|
42
|
+
let depth = 0;
|
|
43
|
+
for (let at = open; at < text.length; at += 1) {
|
|
44
|
+
const char = text[at];
|
|
45
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
46
|
+
const quote = char;
|
|
47
|
+
at += 1;
|
|
48
|
+
while (at < text.length && text[at] !== quote) {
|
|
49
|
+
at += text[at] === "\\" ? 2 : 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (char === "(") {
|
|
53
|
+
depth += 1;
|
|
54
|
+
}
|
|
55
|
+
else if (char === ")") {
|
|
56
|
+
depth -= 1;
|
|
57
|
+
if (depth === 0) {
|
|
58
|
+
return at + 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return text.length;
|
|
63
|
+
};
|
|
64
|
+
/** The offset just past the `}` that closes the `{` at `open`, strings respected. */
|
|
65
|
+
const braceEnd = (text, open) => {
|
|
66
|
+
let depth = 0;
|
|
67
|
+
for (let at = open; at < text.length; at += 1) {
|
|
68
|
+
const char = text[at];
|
|
69
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
70
|
+
const quote = char;
|
|
71
|
+
at += 1;
|
|
72
|
+
while (at < text.length && text[at] !== quote) {
|
|
73
|
+
at += text[at] === "\\" ? 2 : 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (char === "{") {
|
|
77
|
+
depth += 1;
|
|
78
|
+
}
|
|
79
|
+
else if (char === "}") {
|
|
80
|
+
depth -= 1;
|
|
81
|
+
if (depth === 0) {
|
|
82
|
+
return at + 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return text.length;
|
|
87
|
+
};
|
|
88
|
+
const IMPORT = /import\s+(\w+)\s+from\s+["']([^"']+\.mprx)["']/g;
|
|
89
|
+
/** The site of the template imported as `ident` in `text` (the file `file`): its import, the `component(ident, …)` call, and the keys of the contract written in it. */
|
|
90
|
+
const siteIn = (file, text, ident, imported) => {
|
|
91
|
+
const at = (offset) => placeIn(text, offset, file);
|
|
92
|
+
const call = new RegExp(`component\\(\\s*${ident}\\b`).exec(text);
|
|
93
|
+
const range = call === null ? undefined : { from: call.index, to: closing(text, call.index + "component".length) };
|
|
94
|
+
return {
|
|
95
|
+
file,
|
|
96
|
+
imported: at(imported),
|
|
97
|
+
...(call === null ? {} : { call: at(call.index) }),
|
|
98
|
+
member: (kind, name) => {
|
|
99
|
+
if (range === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const body = text.slice(range.from, range.to);
|
|
103
|
+
const start = new RegExp(`\\b${kind}\\s*:\\s*\\{`).exec(body);
|
|
104
|
+
if (start === null) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const open = start.index + start[0].length - 1;
|
|
108
|
+
const close = braceEnd(body, open);
|
|
109
|
+
const key = new RegExp(`(^|[\\s{,])(["']?)${escape(name)}\\2\\s*:`).exec(body.slice(open, close));
|
|
110
|
+
return key === null ? undefined : at(range.from + open + key.index + key[1].length);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const typescriptIn = (dir) => existsSync(dir)
|
|
115
|
+
? readdirSync(dir, { recursive: true, encoding: "utf8" })
|
|
116
|
+
.filter((file) => file.endsWith(".ts") && !file.endsWith(".d.ts"))
|
|
117
|
+
.sort()
|
|
118
|
+
: [];
|
|
119
|
+
/**
|
|
120
|
+
* Where each template's component is defined: the `component(template, …)` call in the project's TypeScript, wherever it is written (the entry file, or a file of its own beside the template), and the
|
|
121
|
+
* keys of its contract. A text search for pointing, not a parse: what it cannot find it leaves out, and callers fall back to the template.
|
|
122
|
+
*/
|
|
123
|
+
export const declarationsOf = (root) => {
|
|
124
|
+
const found = new Map();
|
|
125
|
+
const files = [
|
|
126
|
+
ENTRY_FILE,
|
|
127
|
+
...typescriptIn(join(root, "src"))
|
|
128
|
+
.map((file) => `src/${file.split(sep).join("/")}`)
|
|
129
|
+
.filter((file) => file !== ENTRY_FILE),
|
|
130
|
+
];
|
|
131
|
+
for (const file of files) {
|
|
132
|
+
const path = join(root, file);
|
|
133
|
+
if (!existsSync(path)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const text = readFileSync(path, "utf8");
|
|
137
|
+
for (const match of text.matchAll(IMPORT)) {
|
|
138
|
+
const template = relative(root, resolve(dirname(path), match[2]))
|
|
139
|
+
.split(sep)
|
|
140
|
+
.join("/");
|
|
141
|
+
const site = siteIn(file, text, match[1], match.index);
|
|
142
|
+
// A definition wins over an import that only passes the component on.
|
|
143
|
+
if (!found.has(template) || (found.get(template).call === undefined && site.call !== undefined)) {
|
|
144
|
+
found.set(template, site);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return found;
|
|
149
|
+
};
|
package/dist/tty.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Environment {
|
|
2
|
+
readonly stdout?: {
|
|
3
|
+
readonly isTTY?: boolean;
|
|
4
|
+
};
|
|
5
|
+
readonly stdin?: {
|
|
6
|
+
readonly isTTY?: boolean;
|
|
7
|
+
};
|
|
8
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
/** A terminal to draw on. */
|
|
11
|
+
export declare const canDraw: ({ stdout, env }?: Environment) => boolean;
|
|
12
|
+
/** A terminal to draw on and to read keys from: what a question needs. */
|
|
13
|
+
export declare const canAsk: (environment?: Environment) => boolean;
|
package/dist/tty.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** A terminal to draw on. */
|
|
2
|
+
export const canDraw = ({ stdout = process.stdout, env = process.env } = {}) => stdout.isTTY === true && (env["CI"] === undefined || env["CI"] === "" || env["CI"] === "false") && env["TERM"] !== "dumb";
|
|
3
|
+
/** A terminal to draw on and to read keys from: what a question needs. */
|
|
4
|
+
export const canAsk = (environment = {}) => canDraw(environment) && (environment.stdin ?? process.stdin).isTTY === true;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
|
+
export const Spinner = ({ color = "cyan" }) => {
|
|
6
|
+
const [frame, setFrame] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const timer = setInterval(() => {
|
|
9
|
+
setFrame((current) => (current + 1) % FRAMES.length);
|
|
10
|
+
}, 80);
|
|
11
|
+
return () => {
|
|
12
|
+
clearInterval(timer);
|
|
13
|
+
};
|
|
14
|
+
}, []);
|
|
15
|
+
return _jsx(Text, { color: color, children: FRAMES[frame] });
|
|
16
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InitArgs } from "../args.js";
|
|
2
|
+
import type { Answers, Outcome, PackageManager } from "../init.js";
|
|
3
|
+
import { execute as defaultExecute } from "../init.js";
|
|
4
|
+
export type Result = {
|
|
5
|
+
readonly status: "done";
|
|
6
|
+
readonly answers: Answers;
|
|
7
|
+
readonly outcome: Outcome;
|
|
8
|
+
} | {
|
|
9
|
+
readonly status: "failed";
|
|
10
|
+
readonly message: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly status: "cancelled";
|
|
13
|
+
};
|
|
14
|
+
export interface WizardProps {
|
|
15
|
+
readonly args: InitArgs;
|
|
16
|
+
readonly cwd: string;
|
|
17
|
+
readonly detected: PackageManager;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
readonly execute?: typeof defaultExecute;
|
|
20
|
+
readonly onFinish: (result: Result) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare const Wizard: ({ args, cwd, detected, version, execute, onFinish }: WizardProps) => import("react").JSX.Element;
|
package/dist/ui/init.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// The `vlx init` wizard: the questions flags did not answer, then the work with its progress, then what to do next. The work and the rules are `../init.ts`; this file only asks and shows.
|
|
3
|
+
import { Box, Text, useApp } from "ink";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { relative } from "node:path";
|
|
6
|
+
import { directoryFor, directoryProblem, execute as defaultExecute, nameProblem, PACKAGE_MANAGERS, runCommand, titleOf } from "../init.js";
|
|
7
|
+
import { TARGETS } from "../targets.js";
|
|
8
|
+
import { Spinner } from "./Spinner.js";
|
|
9
|
+
import { Answered, Confirm, MultiSelect, Select, TextPrompt } from "./prompts.js";
|
|
10
|
+
const ORDER = ["name", "targets", "base", "url", "pm", "install", "git"];
|
|
11
|
+
const BASE = /^\/([\w.~-]+\/)*$/;
|
|
12
|
+
const URL_FORM = /^https?:\/\/.*[^/]$/;
|
|
13
|
+
/** Whether a question has to be asked: nothing answered it, and the person has not said to take the defaults. */
|
|
14
|
+
const asked = (question, draft, args) => {
|
|
15
|
+
if (args.yes) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
switch (question) {
|
|
19
|
+
case "name":
|
|
20
|
+
return draft.name === undefined;
|
|
21
|
+
case "targets":
|
|
22
|
+
return draft.targets.length === 0;
|
|
23
|
+
case "base":
|
|
24
|
+
return draft.targets.includes("web") && draft.base === undefined;
|
|
25
|
+
case "url":
|
|
26
|
+
return draft.targets.includes("web") && draft.url === undefined;
|
|
27
|
+
case "pm":
|
|
28
|
+
return args.packageManager === undefined;
|
|
29
|
+
case "install":
|
|
30
|
+
return draft.install === undefined;
|
|
31
|
+
case "git":
|
|
32
|
+
return draft.git === undefined;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const next = (from, draft, args) => {
|
|
36
|
+
for (let at = from; at < ORDER.length; at += 1) {
|
|
37
|
+
if (asked(ORDER[at], draft, args)) {
|
|
38
|
+
return at;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return ORDER.length;
|
|
42
|
+
};
|
|
43
|
+
const STEPS = [
|
|
44
|
+
{ step: "files", label: "Writing the files" },
|
|
45
|
+
{ step: "install", label: "Installing the dependencies" },
|
|
46
|
+
{ step: "git", label: "Making a git repository" },
|
|
47
|
+
];
|
|
48
|
+
export const Wizard = ({ args, cwd, detected, version, execute = defaultExecute, onFinish }) => {
|
|
49
|
+
const { exit } = useApp();
|
|
50
|
+
const initial = {
|
|
51
|
+
...(args.name === undefined ? {} : { name: args.name }),
|
|
52
|
+
targets: args.targets,
|
|
53
|
+
...(args.base === undefined ? {} : { base: args.base }),
|
|
54
|
+
...(args.url === undefined ? {} : { url: args.url }),
|
|
55
|
+
pm: args.packageManager ?? detected,
|
|
56
|
+
...(args.install === undefined ? {} : { install: args.install }),
|
|
57
|
+
...(args.git === undefined ? {} : { git: args.git }),
|
|
58
|
+
};
|
|
59
|
+
const [draft, setDraft] = useState(initial);
|
|
60
|
+
const [at, setAt] = useState(() => next(0, initial, args));
|
|
61
|
+
const [answered, setAnswered] = useState([]);
|
|
62
|
+
const [progress, setProgress] = useState({});
|
|
63
|
+
const [outcome, setOutcome] = useState(undefined);
|
|
64
|
+
const [failure, setFailure] = useState(undefined);
|
|
65
|
+
const finished = useRef(false);
|
|
66
|
+
const answer = (patch, label, shown) => {
|
|
67
|
+
const merged = { ...draft, ...patch };
|
|
68
|
+
setDraft(merged);
|
|
69
|
+
setAnswered((current) => [...current, [label, shown]]);
|
|
70
|
+
setAt(next(at + 1, merged, args));
|
|
71
|
+
};
|
|
72
|
+
const running = at >= ORDER.length;
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!running || finished.current) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
finished.current = true;
|
|
78
|
+
const targets = draft.targets.length === 0 ? TARGETS.map((each) => each.id) : draft.targets;
|
|
79
|
+
const name = draft.name ?? "my-app";
|
|
80
|
+
const answers = {
|
|
81
|
+
name,
|
|
82
|
+
dir: directoryFor(name, cwd),
|
|
83
|
+
targets,
|
|
84
|
+
web: { base: draft.base ?? "", url: draft.url ?? "" },
|
|
85
|
+
packageManager: draft.pm,
|
|
86
|
+
install: draft.install ?? true,
|
|
87
|
+
git: draft.git ?? true,
|
|
88
|
+
...(args.cli === undefined ? {} : { cli: args.cli }),
|
|
89
|
+
};
|
|
90
|
+
execute(answers, (step, state, detail) => {
|
|
91
|
+
setProgress((current) => ({ ...current, [step]: { state, detail } }));
|
|
92
|
+
})
|
|
93
|
+
.then((done) => {
|
|
94
|
+
setOutcome({ answers, outcome: done });
|
|
95
|
+
onFinish({ status: "done", answers, outcome: done });
|
|
96
|
+
})
|
|
97
|
+
.catch((error) => {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
setFailure(message);
|
|
100
|
+
onFinish({ status: "failed", message });
|
|
101
|
+
});
|
|
102
|
+
}, [running]);
|
|
103
|
+
// Leave once the last frame (what was created, what is next) is on the screen: exiting in the same tick would unmount before it is drawn.
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (outcome !== undefined || failure !== undefined) {
|
|
106
|
+
exit();
|
|
107
|
+
}
|
|
108
|
+
}, [outcome, failure]);
|
|
109
|
+
const question = running ? undefined : ORDER[at];
|
|
110
|
+
const picked = draft.targets.length === 0 ? TARGETS.map((each) => each.id) : draft.targets;
|
|
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
|
+
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: TARGETS.map((each) => each.id), onSubmit: (values) => {
|
|
114
|
+
answer({ targets: values }, "Targets", values.map((id) => TARGETS.find((each) => each.id === id)?.label ?? id).join(", "));
|
|
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
|
+
answer({ base: value }, "Base path", value);
|
|
117
|
+
} })) : null, question === "url" ? (_jsx(TextPrompt, { label: "Web: where is it published?", hint: "optional, https://example.com: adds canonical links and a sitemap", validate: (value) => (value === "" || URL_FORM.test(value) ? undefined : "an address like https://example.com, without a trailing slash"), onSubmit: (value) => {
|
|
118
|
+
answer({ url: value }, "Published at", value === "" ? "not yet" : value);
|
|
119
|
+
} })) : null, question === "pm" ? (_jsx(Select, { label: "Which package manager?", choices: PACKAGE_MANAGERS.map((each) => ({ value: each, label: each, ...(each === detected ? { hint: "(running this)" } : {}) })), initial: draft.pm, onSubmit: (value) => {
|
|
120
|
+
answer({ pm: value }, "Package manager", value);
|
|
121
|
+
} })) : null, question === "install" ? (_jsx(Confirm, { label: "Install the dependencies now?", initial: true, onSubmit: (value) => {
|
|
122
|
+
answer({ install: value }, "Install", value ? "yes" : "no");
|
|
123
|
+
} })) : null, question === "git" ? (_jsx(Confirm, { label: "Make a git repository?", initial: true, onSubmit: (value) => {
|
|
124
|
+
answer({ git: value }, "Git", value ? "yes" : "no");
|
|
125
|
+
} })) : null] }), running ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: STEPS.map(({ step, label }) => {
|
|
126
|
+
const now = progress[step];
|
|
127
|
+
if (now === undefined || now.state === "skipped") {
|
|
128
|
+
return now === undefined ? null : (_jsxs(Text, { dimColor: true, children: ["\u2013 ", label, ": skipped"] }, step));
|
|
129
|
+
}
|
|
130
|
+
return (_jsxs(Text, { children: [now.state === "start" ? _jsx(Spinner, {}) : now.state === "done" ? _jsx(Text, { color: "green", children: "\u2714" }) : _jsx(Text, { color: "yellow", children: "!" }), " ", label, now.state === "failed" ? _jsx(Text, { color: "yellow", children: " (failed)" }) : null] }, step));
|
|
131
|
+
}) })) : null, failure === undefined ? null : (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2716 ", failure] }) })), outcome === undefined ? null : _jsx(Next, { answers: outcome.answers, outcome: outcome.outcome, cwd: cwd, picked: picked })] }));
|
|
132
|
+
};
|
|
133
|
+
const Next = ({ answers, outcome, cwd, picked }) => {
|
|
134
|
+
const where = relative(cwd, answers.dir) || ".";
|
|
135
|
+
const run = runCommand(answers.packageManager);
|
|
136
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2714 Created ", titleOf(answers.name)] }), _jsxs(Text, { dimColor: true, children: [" ", "in ", where, " \u00B7 ", outcome.files.length, " files \u00B7 ", picked.join(", ")] })] }), outcome.notes.map((note) => (_jsxs(Text, { color: "yellow", children: ["! ", note] }, note))), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Next" }), where === "." ? null : _jsxs(Text, { children: [" cd ", where] }), outcome.installed ? null : _jsxs(Text, { children: [" ", answers.packageManager, " install"] }), _jsxs(Text, { children: [" ", run, " dev"] }), _jsxs(Text, { dimColor: true, children: [" ", "Start reading at src/main.ts. ", run, " build writes the site; ", run, " check finds mistakes."] })] })] }));
|
|
137
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface Choice<T extends string> {
|
|
2
|
+
readonly value: T;
|
|
3
|
+
readonly label: string;
|
|
4
|
+
readonly hint?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const Problem: ({ children }: {
|
|
7
|
+
readonly children: string;
|
|
8
|
+
}) => import("react").JSX.Element;
|
|
9
|
+
export declare const TextPrompt: ({ label, hint, initial, validate, onSubmit, }: {
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly hint?: string;
|
|
12
|
+
readonly initial?: string;
|
|
13
|
+
readonly validate?: (value: string) => string | undefined;
|
|
14
|
+
readonly onSubmit: (value: string) => void;
|
|
15
|
+
}) => import("react").JSX.Element;
|
|
16
|
+
export declare const Select: <T extends string>({ label, hint, choices, initial, onSubmit, }: {
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly hint?: string;
|
|
19
|
+
readonly choices: ReadonlyArray<Choice<T>>;
|
|
20
|
+
readonly initial?: T;
|
|
21
|
+
readonly onSubmit: (value: T) => void;
|
|
22
|
+
}) => import("react").JSX.Element;
|
|
23
|
+
export declare const MultiSelect: <T extends string>({ label, hint, choices, initial, onSubmit, }: {
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly hint?: string;
|
|
26
|
+
readonly choices: ReadonlyArray<Choice<T>>;
|
|
27
|
+
readonly initial: ReadonlyArray<T>;
|
|
28
|
+
readonly onSubmit: (values: ReadonlyArray<T>) => void;
|
|
29
|
+
}) => import("react").JSX.Element;
|
|
30
|
+
export declare const Confirm: ({ label, initial, onSubmit }: {
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly initial: boolean;
|
|
33
|
+
readonly onSubmit: (value: boolean) => void;
|
|
34
|
+
}) => import("react").JSX.Element;
|
|
35
|
+
/** An answered question, left on screen. */
|
|
36
|
+
export declare const Answered: ({ label, value }: {
|
|
37
|
+
readonly label: string;
|
|
38
|
+
readonly value: string;
|
|
39
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// The four questions the wizard asks, as small controlled components: a line of text, one of several, any of several, yes or no. Each takes `onSubmit` and nothing else from its parent; the parent
|
|
3
|
+
// keeps what was answered. Keys: Enter answers, arrows (or j and k) move, Space toggles, and the text keys are what they say.
|
|
4
|
+
import { Box, Text, useInput } from "ink";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
const Question = ({ label, hint, children }) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "? " }), _jsx(Text, { bold: true, children: label })] }), hint === undefined ? null : _jsxs(Text, { dimColor: true, children: [" ", hint] }), children] }));
|
|
7
|
+
export const Problem = ({ children }) => _jsxs(Text, { color: "red", children: [" \u2716 ", children] });
|
|
8
|
+
export const TextPrompt = ({ label, hint, initial = "", validate, onSubmit, }) => {
|
|
9
|
+
// `initial` is the default, shown dimmed until something is typed; Enter on nothing takes it.
|
|
10
|
+
const [value, setValue] = useState("");
|
|
11
|
+
const [problem, setProblem] = useState(undefined);
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (key.return) {
|
|
14
|
+
const answer = value === "" ? initial : value;
|
|
15
|
+
const found = validate?.(answer);
|
|
16
|
+
if (found === undefined) {
|
|
17
|
+
onSubmit(answer);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
setProblem(found);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (key.backspace || key.delete) {
|
|
25
|
+
setValue((current) => current.slice(0, -1));
|
|
26
|
+
setProblem(undefined);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (key.ctrl || key.meta || key.escape || key.tab || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || input === "") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
setValue((current) => current + input.replace(/[\r\n]+/g, ""));
|
|
33
|
+
setProblem(undefined);
|
|
34
|
+
});
|
|
35
|
+
return (_jsxs(Question, { label: label, hint: hint, children: [_jsxs(Text, { children: [" ", value === "" ? _jsx(Text, { dimColor: true, children: initial }) : value, _jsx(Text, { color: "cyan", children: "\u258C" })] }), problem === undefined ? null : _jsx(Problem, { children: problem })] }));
|
|
36
|
+
};
|
|
37
|
+
export const Select = ({ label, hint, choices, initial, onSubmit, }) => {
|
|
38
|
+
const [at, setAt] = useState(Math.max(0, choices.findIndex((each) => each.value === initial)));
|
|
39
|
+
useInput((input, key) => {
|
|
40
|
+
if (key.upArrow || input === "k") {
|
|
41
|
+
setAt((current) => (current + choices.length - 1) % choices.length);
|
|
42
|
+
}
|
|
43
|
+
else if (key.downArrow || input === "j") {
|
|
44
|
+
setAt((current) => (current + 1) % choices.length);
|
|
45
|
+
}
|
|
46
|
+
else if (key.return) {
|
|
47
|
+
onSubmit(choices[at].value);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return (_jsx(Question, { label: label, hint: hint ?? "↑/↓ to move, Enter to choose", children: choices.map((choice, index) => (_jsxs(Text, { ...(index === at ? { color: "cyan" } : {}), children: [" ", index === at ? "❯" : " ", " ", choice.label, choice.hint === undefined ? null : _jsxs(Text, { dimColor: true, children: [" ", choice.hint] })] }, choice.value))) }));
|
|
51
|
+
};
|
|
52
|
+
export const MultiSelect = ({ label, hint, choices, initial, onSubmit, }) => {
|
|
53
|
+
const [at, setAt] = useState(0);
|
|
54
|
+
const [chosen, setChosen] = useState(initial);
|
|
55
|
+
const [problem, setProblem] = useState(undefined);
|
|
56
|
+
useInput((input, key) => {
|
|
57
|
+
if (key.upArrow || input === "k") {
|
|
58
|
+
setAt((current) => (current + choices.length - 1) % choices.length);
|
|
59
|
+
}
|
|
60
|
+
else if (key.downArrow || input === "j") {
|
|
61
|
+
setAt((current) => (current + 1) % choices.length);
|
|
62
|
+
}
|
|
63
|
+
else if (input === " ") {
|
|
64
|
+
const value = choices[at].value;
|
|
65
|
+
setChosen((current) => (current.includes(value) ? current.filter((each) => each !== value) : [...current, value]));
|
|
66
|
+
setProblem(undefined);
|
|
67
|
+
}
|
|
68
|
+
else if (input === "a") {
|
|
69
|
+
setChosen((current) => (current.length === choices.length ? [] : choices.map((each) => each.value)));
|
|
70
|
+
setProblem(undefined);
|
|
71
|
+
}
|
|
72
|
+
else if (key.return) {
|
|
73
|
+
if (chosen.length === 0) {
|
|
74
|
+
setProblem("choose at least one (Space)");
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
onSubmit(choices.filter((each) => chosen.includes(each.value)).map((each) => each.value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return (_jsxs(Question, { label: label, hint: hint ?? "↑/↓ to move, Space to toggle, a for all, Enter to confirm", children: [choices.map((choice, index) => (_jsxs(Text, { ...(index === at ? { color: "cyan" } : {}), children: [" ", index === at ? "❯" : " ", " ", chosen.includes(choice.value) ? "◉" : "○", " ", choice.label, choice.hint === undefined ? null : _jsxs(Text, { dimColor: true, children: [" ", choice.hint] })] }, choice.value))), problem === undefined ? null : _jsx(Problem, { children: problem })] }));
|
|
82
|
+
};
|
|
83
|
+
export const Confirm = ({ label, initial, onSubmit }) => {
|
|
84
|
+
useInput((input, key) => {
|
|
85
|
+
if (input === "y" || input === "Y") {
|
|
86
|
+
onSubmit(true);
|
|
87
|
+
}
|
|
88
|
+
else if (input === "n" || input === "N") {
|
|
89
|
+
onSubmit(false);
|
|
90
|
+
}
|
|
91
|
+
else if (key.return) {
|
|
92
|
+
onSubmit(initial);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return _jsx(Question, { label: label, hint: initial ? "(Y/n)" : "(y/N)" });
|
|
96
|
+
};
|
|
97
|
+
/** An answered question, left on screen. */
|
|
98
|
+
export const Answered = ({ label, value }) => (_jsxs(Text, { children: [_jsx(Text, { color: "green", children: "\u2714 " }), label, " ", _jsx(Text, { color: "cyan", children: value })] }));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BuildStage, Built } from "../build.js";
|
|
2
|
+
import type { Checked } from "../check.js";
|
|
3
|
+
import type { Diagnostic } from "../diagnostics.js";
|
|
4
|
+
export declare const Working: ({ label }: {
|
|
5
|
+
readonly label: string;
|
|
6
|
+
}) => import("react").JSX.Element;
|
|
7
|
+
/** Mistakes grouped by file, each `line:column code message`. A line of 0 is "no line" (a whole file, or the configuration). */
|
|
8
|
+
export declare const Diagnostics: ({ diagnostics }: {
|
|
9
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
11
|
+
export declare const CheckResult: ({ result }: {
|
|
12
|
+
readonly result: Checked;
|
|
13
|
+
}) => import("react").JSX.Element;
|
|
14
|
+
/** The stages up to `current` are done, `current` is spinning, the rest wait. With no `current`, all are done. */
|
|
15
|
+
export declare const BuildProgress: ({ current }: {
|
|
16
|
+
readonly current: BuildStage | undefined;
|
|
17
|
+
}) => import("react").JSX.Element;
|
|
18
|
+
export declare const BuildResult: ({ built, seconds }: {
|
|
19
|
+
readonly built: Built;
|
|
20
|
+
readonly seconds: number;
|
|
21
|
+
}) => import("react").JSX.Element;
|
|
22
|
+
export interface DevState {
|
|
23
|
+
readonly url?: string;
|
|
24
|
+
readonly open: ReadonlyArray<Diagnostic>;
|
|
25
|
+
/** The latest reports, oldest first. */
|
|
26
|
+
readonly lines: ReadonlyArray<string>;
|
|
27
|
+
readonly starting: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare const DevScreen: ({ state }: {
|
|
30
|
+
readonly state: DevState;
|
|
31
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// What `vlx check`, `vlx build` and `vlx dev` show in a terminal. They are views of results the commands already compute: the commands run the same with or without them, and a pipe, a CI log or
|
|
3
|
+
// `--json` never gets one (see `../tty.ts`).
|
|
4
|
+
import { Box, Text } from "ink";
|
|
5
|
+
import { Spinner } from "./Spinner.js";
|
|
6
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
7
|
+
export const Working = ({ label }) => (_jsxs(Text, { children: [_jsx(Spinner, {}), " ", label] }));
|
|
8
|
+
/** Mistakes grouped by file, each `line:column code message`. A line of 0 is "no line" (a whole file, or the configuration). */
|
|
9
|
+
export const Diagnostics = ({ diagnostics }) => {
|
|
10
|
+
const byFile = new Map();
|
|
11
|
+
for (const each of diagnostics) {
|
|
12
|
+
byFile.set(each.file, [...(byFile.get(each.file) ?? []), each]);
|
|
13
|
+
}
|
|
14
|
+
return (_jsx(Box, { flexDirection: "column", children: [...byFile].map(([file, list]) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, underline: true, children: file }), list.map((each, index) => (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: each.line > 0 ? `${each.line}:${each.column}`.padEnd(8) : "".padEnd(8) }), _jsx(Text, { color: "red", children: each.code }), _jsxs(Text, { children: [" ", each.message] })] }, `${each.line}:${each.column}:${each.code}:${index}`)))] }, file))) }));
|
|
15
|
+
};
|
|
16
|
+
export const CheckResult = ({ result }) => result.diagnostics.length === 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714 No problems" }), _jsxs(Text, { dimColor: true, children: [" ", "\u00B7 ", plural(result.pages, "page"), ", ", plural(result.components.length, "component")] })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Diagnostics, { diagnostics: result.diagnostics }), _jsxs(Text, { color: "red", bold: true, children: ["\u2716 ", plural(result.diagnostics.length, "problem")] })] }));
|
|
17
|
+
const STAGES = [
|
|
18
|
+
{ stage: "configuration", label: "Reading the configuration" },
|
|
19
|
+
{ stage: "content", label: "Reading the content" },
|
|
20
|
+
{ stage: "templates", label: "Compiling the templates" },
|
|
21
|
+
{ stage: "bundle", label: "Bundling the browser code" },
|
|
22
|
+
{ stage: "pages", label: "Writing the pages" },
|
|
23
|
+
];
|
|
24
|
+
/** The stages up to `current` are done, `current` is spinning, the rest wait. With no `current`, all are done. */
|
|
25
|
+
export const BuildProgress = ({ current }) => {
|
|
26
|
+
const at = current === undefined ? STAGES.length : STAGES.findIndex((each) => each.stage === current);
|
|
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
|
+
};
|
|
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"] })] })] }));
|
|
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
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InitArgs } from "../args.js";
|
|
2
|
+
import type { Checked } from "../check.js";
|
|
3
|
+
import type { Diagnostic } from "../diagnostics.js";
|
|
4
|
+
import type { PackageManager } from "../init.js";
|
|
5
|
+
import type { Result } from "./init.js";
|
|
6
|
+
import type { Built } from "../build.js";
|
|
7
|
+
export declare const drawInit: (args: InitArgs, cwd: string, detected: PackageManager, version: string) => Promise<Result>;
|
|
8
|
+
export declare const drawCheck: (root: string) => Promise<Checked>;
|
|
9
|
+
export declare const drawBuild: (root: string) => Promise<Built>;
|
|
10
|
+
/** Mistakes that stopped a command, drawn the way `check` draws them. */
|
|
11
|
+
export declare const drawDiagnostics: (diagnostics: ReadonlyArray<Diagnostic>) => Promise<void>;
|
|
12
|
+
export declare const drawDev: (root: string, options: {
|
|
13
|
+
readonly port?: number;
|
|
14
|
+
readonly host?: string;
|
|
15
|
+
}) => Promise<void>;
|
package/dist/ui/run.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import { render } from "ink";
|
|
4
|
+
import { build as runBuild } from "../build.js";
|
|
5
|
+
import { check as runCheck } from "../check.js";
|
|
6
|
+
import { dev as runDev } from "../dev.js";
|
|
7
|
+
import { Wizard } from "./init.js";
|
|
8
|
+
import { BuildProgress, BuildResult, CheckResult, DevScreen, Diagnostics, Working } from "./report.js";
|
|
9
|
+
const show = (element) => render(element, { exitOnCtrlC: true });
|
|
10
|
+
/** Draws `element` once and leaves it on the screen. */
|
|
11
|
+
const leave = async (element) => {
|
|
12
|
+
const shown = show(element);
|
|
13
|
+
shown.unmount();
|
|
14
|
+
await shown.waitUntilExit();
|
|
15
|
+
};
|
|
16
|
+
export const drawInit = async (args, cwd, detected, version) => {
|
|
17
|
+
let result = { status: "cancelled" };
|
|
18
|
+
const shown = render(_jsx(Wizard, { args: args, cwd: cwd, detected: detected, version: version, onFinish: (done) => {
|
|
19
|
+
result = done;
|
|
20
|
+
} }), { exitOnCtrlC: true });
|
|
21
|
+
await shown.waitUntilExit();
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
export const drawCheck = async (root) => {
|
|
25
|
+
const shown = show(_jsx(Working, { label: "Checking your project" }));
|
|
26
|
+
try {
|
|
27
|
+
const result = await runCheck(root);
|
|
28
|
+
shown.rerender(_jsx(CheckResult, { result: result }));
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
shown.unmount();
|
|
33
|
+
await shown.waitUntilExit();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const drawBuild = async (root) => {
|
|
37
|
+
const started = Date.now();
|
|
38
|
+
const shown = show(_jsx(BuildProgress, { current: "configuration" }));
|
|
39
|
+
try {
|
|
40
|
+
const built = await runBuild(root, { onStage: ((stage) => shown.rerender(_jsx(BuildProgress, { current: stage }))) });
|
|
41
|
+
shown.rerender(_jsx(BuildResult, { built: built, seconds: (Date.now() - started) / 1000 }));
|
|
42
|
+
return built;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
shown.unmount();
|
|
46
|
+
await shown.waitUntilExit();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/** Mistakes that stopped a command, drawn the way `check` draws them. */
|
|
50
|
+
export const drawDiagnostics = (diagnostics) => leave(_jsx(Diagnostics, { diagnostics: diagnostics }));
|
|
51
|
+
export const drawDev = async (root, options) => {
|
|
52
|
+
let state = { open: [], lines: [], starting: true };
|
|
53
|
+
const shown = show(_jsx(DevScreen, { state: state }));
|
|
54
|
+
const update = (patch) => {
|
|
55
|
+
state = { ...state, ...patch };
|
|
56
|
+
shown.rerender(_jsx(DevScreen, { state: state }));
|
|
57
|
+
};
|
|
58
|
+
const running = await runDev(root, {
|
|
59
|
+
...options,
|
|
60
|
+
log: (line) => {
|
|
61
|
+
update({ lines: [...state.lines, line].slice(-6) });
|
|
62
|
+
},
|
|
63
|
+
onStatus: (open) => {
|
|
64
|
+
update({ open });
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
update({ url: running.url, starting: false });
|
|
68
|
+
await new Promise((resolve) => {
|
|
69
|
+
process.once("SIGINT", resolve);
|
|
70
|
+
process.once("SIGTERM", resolve);
|
|
71
|
+
});
|
|
72
|
+
await running.close();
|
|
73
|
+
shown.unmount();
|
|
74
|
+
await shown.waitUntilExit();
|
|
75
|
+
};
|