@vantail/create 0.1.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.
Files changed (53) hide show
  1. package/dist/index.d.ts +9 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +189 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/naming.d.ts +6 -0
  6. package/dist/naming.d.ts.map +1 -0
  7. package/dist/naming.js +18 -0
  8. package/dist/naming.js.map +1 -0
  9. package/package.json +27 -0
  10. package/templates/react-ts/README.md +34 -0
  11. package/templates/react-ts/_gitignore +5 -0
  12. package/templates/react-ts/icon.png +0 -0
  13. package/templates/react-ts/index.html +12 -0
  14. package/templates/react-ts/package.json +25 -0
  15. package/templates/react-ts/src/App.tsx +39 -0
  16. package/templates/react-ts/src/main.tsx +14 -0
  17. package/templates/react-ts/src/style.css +60 -0
  18. package/templates/react-ts/tsconfig.json +14 -0
  19. package/templates/react-ts/vantail.config.ts +60 -0
  20. package/templates/react-ts/vite.config.ts +8 -0
  21. package/templates/svelte-ts/README.md +34 -0
  22. package/templates/svelte-ts/_gitignore +5 -0
  23. package/templates/svelte-ts/icon.png +0 -0
  24. package/templates/svelte-ts/index.html +12 -0
  25. package/templates/svelte-ts/package.json +22 -0
  26. package/templates/svelte-ts/src/App.svelte +35 -0
  27. package/templates/svelte-ts/src/main.ts +9 -0
  28. package/templates/svelte-ts/src/style.css +60 -0
  29. package/templates/svelte-ts/src/vite-env.d.ts +3 -0
  30. package/templates/svelte-ts/svelte.config.js +6 -0
  31. package/templates/svelte-ts/tsconfig.json +14 -0
  32. package/templates/svelte-ts/vantail.config.ts +60 -0
  33. package/templates/svelte-ts/vite.config.ts +8 -0
  34. package/templates/vanilla-ts/README.md +34 -0
  35. package/templates/vanilla-ts/_gitignore +5 -0
  36. package/templates/vanilla-ts/icon.png +0 -0
  37. package/templates/vanilla-ts/index.html +21 -0
  38. package/templates/vanilla-ts/package.json +20 -0
  39. package/templates/vanilla-ts/src/main.ts +36 -0
  40. package/templates/vanilla-ts/src/style.css +60 -0
  41. package/templates/vanilla-ts/tsconfig.json +13 -0
  42. package/templates/vanilla-ts/vantail.config.ts +60 -0
  43. package/templates/vue-ts/README.md +34 -0
  44. package/templates/vue-ts/_gitignore +5 -0
  45. package/templates/vue-ts/icon.png +0 -0
  46. package/templates/vue-ts/index.html +12 -0
  47. package/templates/vue-ts/package.json +22 -0
  48. package/templates/vue-ts/src/App.vue +40 -0
  49. package/templates/vue-ts/src/main.ts +9 -0
  50. package/templates/vue-ts/src/style.css +60 -0
  51. package/templates/vue-ts/tsconfig.json +15 -0
  52. package/templates/vue-ts/vantail.config.ts +60 -0
  53. package/templates/vue-ts/vite.config.ts +8 -0
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npm create @vantail my-app`
4
+ *
5
+ * Copies a template, fills in the two things that are actually specific to a
6
+ * project - its name and its bundle identifier - and gets out of the way.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/index.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npm create @vantail my-app`
4
+ *
5
+ * Copies a template, fills in the two things that are actually specific to a
6
+ * project - its name and its bundle identifier - and gets out of the way.
7
+ */
8
+ import { cp, readFile, readdir, rename, writeFile } from "node:fs/promises";
9
+ import { existsSync } from "node:fs";
10
+ import { createInterface } from "node:readline/promises";
11
+ import { basename, dirname, join, resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { suggestIdentifier, toPackageName } from "./naming.js";
14
+ const TEMPLATES = [
15
+ { id: "react-ts", label: "React + TypeScript" },
16
+ { id: "svelte-ts", label: "Svelte + TypeScript" },
17
+ { id: "vue-ts", label: "Vue + TypeScript" },
18
+ { id: "vanilla-ts", label: "Vanilla TypeScript" },
19
+ ];
20
+ const templatesRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates");
21
+ async function main() {
22
+ const argv = process.argv.slice(2);
23
+ const flags = new Map();
24
+ const positional = [];
25
+ for (let index = 0; index < argv.length; index += 1) {
26
+ const arg = argv[index];
27
+ if (arg.startsWith("--")) {
28
+ const [name, inline] = splitFlag(arg.slice(2));
29
+ const next = argv[index + 1];
30
+ if (inline !== undefined) {
31
+ flags.set(name, inline);
32
+ }
33
+ else if (next !== undefined && !next.startsWith("-")) {
34
+ flags.set(name, next);
35
+ index += 1;
36
+ }
37
+ else {
38
+ flags.set(name, "true");
39
+ }
40
+ continue;
41
+ }
42
+ positional.push(arg);
43
+ }
44
+ const interactive = process.stdin.isTTY === true && flags.get("yes") !== "true";
45
+ const rl = interactive
46
+ ? createInterface({ input: process.stdin, output: process.stdout })
47
+ : null;
48
+ try {
49
+ // Only ask when the directory was not given on the command line.
50
+ const asked = positional[0] ??
51
+ (rl ? (await rl.question("Project directory: ")).trim() : "");
52
+ const directory = asked === "" ? "vantail-app" : asked;
53
+ const target = resolve(process.cwd(), directory);
54
+ if (existsSync(target) && (await readdir(target)).length > 0) {
55
+ console.error(`${directory} already exists and is not empty.`);
56
+ return 1;
57
+ }
58
+ const template = await chooseTemplate(flags.get("template"), rl);
59
+ const name = flags.get("name") ?? basename(target);
60
+ const identifier = flags.get("identifier") ?? suggestIdentifier(name);
61
+ await scaffold({ template, target, name, identifier });
62
+ // Nothing is published to npm yet, so a project scaffolded from a checkout
63
+ // has to point back at it or `npm install` cannot resolve anything.
64
+ const root = flags.get("no-link") === "true" ? undefined : checkout();
65
+ const linked = root ? await linkToCheckout(target, root) : [];
66
+ console.log(`
67
+ Created ${name} in ${directory}
68
+
69
+ cd ${directory}
70
+ npm install
71
+ npm run dev
72
+ `);
73
+ if (linked.length > 0) {
74
+ console.log(`${linked.join(" and ")} point at ${root}.`);
75
+ console.log(`Build them there first: pnpm install && pnpm build && pnpm build:runtime\n`);
76
+ }
77
+ return 0;
78
+ }
79
+ finally {
80
+ rl?.close();
81
+ }
82
+ }
83
+ async function scaffold(input) {
84
+ await cp(join(templatesRoot, input.template), input.target, {
85
+ recursive: true,
86
+ });
87
+ // npm refuses to publish a file called `.gitignore`, so templates ship it
88
+ // under a placeholder name.
89
+ const placeholder = join(input.target, "_gitignore");
90
+ if (existsSync(placeholder)) {
91
+ await rename(placeholder, join(input.target, ".gitignore"));
92
+ }
93
+ await substitute(join(input.target, "package.json"), input);
94
+ await substitute(join(input.target, "vantail.config.ts"), input);
95
+ await substitute(join(input.target, "index.html"), input);
96
+ await substitute(join(input.target, "README.md"), input);
97
+ }
98
+ /**
99
+ * The Vantail checkout this scaffolder is running out of, if it is running out
100
+ * of one rather than an installed package.
101
+ */
102
+ function checkout() {
103
+ let dir = templatesRoot;
104
+ for (;;) {
105
+ if (existsSync(join(dir, "runtime", "Cargo.toml")) &&
106
+ existsSync(join(dir, "packages"))) {
107
+ return dir;
108
+ }
109
+ const parent = dirname(dir);
110
+ if (parent === dir)
111
+ return undefined;
112
+ dir = parent;
113
+ }
114
+ }
115
+ /**
116
+ * Point the `@vantail/*` dependencies at a local checkout.
117
+ *
118
+ * npm links a `file:` dependency rather than copying it, which is what makes
119
+ * this work end to end: the runtime resolver walks up from the linked package
120
+ * and finds the `cargo build` in the checkout's `target/`, so a scaffolded app
121
+ * runs against a locally built runtime with nothing else configured.
122
+ *
123
+ * Returns the packages it rewrote.
124
+ */
125
+ async function linkToCheckout(target, root) {
126
+ const path = join(target, "package.json");
127
+ const manifest = JSON.parse(await readFile(path, "utf8"));
128
+ const linked = [];
129
+ for (const group of ["dependencies", "devDependencies"]) {
130
+ const deps = manifest[group];
131
+ if (!deps)
132
+ continue;
133
+ for (const name of Object.keys(deps)) {
134
+ if (!name.startsWith("@vantail/"))
135
+ continue;
136
+ const local = join(root, "packages", name.slice("@vantail/".length));
137
+ if (!existsSync(local))
138
+ continue;
139
+ deps[name] = `file:${local}`;
140
+ linked.push(name);
141
+ }
142
+ }
143
+ await writeFile(path, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
144
+ return linked;
145
+ }
146
+ /** Replace the two tokens templates are allowed to contain. */
147
+ async function substitute(path, input) {
148
+ if (!existsSync(path))
149
+ return;
150
+ const source = await readFile(path, "utf8");
151
+ const replaced = source
152
+ .replaceAll("__APP_NAME__", input.name)
153
+ .replaceAll("__PACKAGE_NAME__", toPackageName(input.name))
154
+ .replaceAll("__APP_IDENTIFIER__", input.identifier);
155
+ await writeFile(path, replaced, "utf8");
156
+ }
157
+ async function chooseTemplate(requested, rl) {
158
+ if (requested) {
159
+ const match = TEMPLATES.find((template) => template.id === requested);
160
+ if (!match) {
161
+ throw new Error(`Unknown template "${requested}". Available: ${TEMPLATES.map((t) => t.id).join(", ")}`);
162
+ }
163
+ return match.id;
164
+ }
165
+ if (!rl)
166
+ return "react-ts";
167
+ console.log("\nTemplate:");
168
+ TEMPLATES.forEach((template, index) => {
169
+ console.log(` ${index + 1}. ${template.label}`);
170
+ });
171
+ const answer = (await rl.question("Choose [1]: ")).trim();
172
+ const index = answer === "" ? 0 : Number(answer) - 1;
173
+ return TEMPLATES[index]?.id ?? "react-ts";
174
+ }
175
+ function splitFlag(body) {
176
+ const equals = body.indexOf("=");
177
+ return equals === -1
178
+ ? [body, undefined]
179
+ : [body.slice(0, equals), body.slice(equals + 1)];
180
+ }
181
+ main()
182
+ .then((code) => {
183
+ process.exitCode = code;
184
+ })
185
+ .catch((error) => {
186
+ console.error(error instanceof Error ? error.message : String(error));
187
+ process.exitCode = 1;
188
+ });
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Turning a project name into the identifiers a template needs. */
2
+ /** `My Notes` becomes `com.example.mynotes` - a starting point, not a decree. */
3
+ export declare function suggestIdentifier(name: string): string;
4
+ /** A name npm will accept in `package.json`. */
5
+ export declare function toPackageName(name: string): string;
6
+ //# sourceMappingURL=naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD"}
package/dist/naming.js ADDED
@@ -0,0 +1,18 @@
1
+ /** Turning a project name into the identifiers a template needs. */
2
+ /** `My Notes` becomes `com.example.mynotes` - a starting point, not a decree. */
3
+ export function suggestIdentifier(name) {
4
+ const slug = name
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]/g, "")
7
+ .slice(0, 32);
8
+ return `com.example.${slug || "app"}`;
9
+ }
10
+ /** A name npm will accept in `package.json`. */
11
+ export function toPackageName(name) {
12
+ const slug = name
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9-]+/g, "-")
15
+ .replace(/^-+|-+$/g, "");
16
+ return slug || "vantail-app";
17
+ }
18
+ //# sourceMappingURL=naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.js","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,OAAO,eAAe,IAAI,IAAI,KAAK,EAAE,CAAC;AACxC,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,IAAI,aAAa,CAAC;AAC/B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@vantail/create",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Vantail application.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Vantail/vantail.git",
9
+ "directory": "packages/create"
10
+ },
11
+ "homepage": "https://github.com/Vantail/vantail",
12
+ "type": "module",
13
+ "files": [
14
+ "dist",
15
+ "templates"
16
+ ],
17
+ "bin": {
18
+ "vantail-create": "./dist/index.js"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit"
23
+ },
24
+ "devDependencies": {
25
+ "@vantail/shared": "^0.1.0"
26
+ }
27
+ }
@@ -0,0 +1,34 @@
1
+ # __APP_NAME__
2
+
3
+ Built with [Vantail](https://github.com/jeroenvanwissen/vantail).
4
+
5
+ ```bash
6
+ npm install
7
+ npm run dev # native window against the Vite dev server
8
+ npm run build # build the web assets
9
+ npm run package # produce a distributable application
10
+ npm run doctor # check that everything needed is installed
11
+ ```
12
+
13
+ There is no Node.js in this app. `node:fs` and friends do not exist - use
14
+ `@vantail/api` instead:
15
+
16
+ ```ts
17
+ import { dialog, filesystem } from "@vantail/api";
18
+
19
+ const path = await dialog.openFile();
20
+ if (path) console.log(await filesystem.readText(path));
21
+ ```
22
+
23
+ Native access is denied by default and granted in `vantail.config.ts`. The same
24
+ file also describes the application menu, and can add a tray icon and a
25
+ self-updater.
26
+
27
+ More windows are labels, opened at runtime:
28
+
29
+ ```ts
30
+ import { app, createWindow } from "@vantail/api";
31
+
32
+ const settings = await createWindow("settings", { url: "settings.html" });
33
+ await app.emit("hello", { from: "main" }, { to: "settings" });
34
+ ```
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .vantail/
4
+ build/
5
+ .DS_Store
Binary file
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__APP_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vantail dev",
8
+ "build": "vantail build",
9
+ "package": "vantail package",
10
+ "doctor": "vantail doctor"
11
+ },
12
+ "dependencies": {
13
+ "@vantail/api": "^0.1.0",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0"
16
+ },
17
+ "devDependencies": {
18
+ "@vantail/cli": "^0.1.0",
19
+ "@types/react": "^19.2.0",
20
+ "@types/react-dom": "^19.2.0",
21
+ "@vitejs/plugin-react": "^6.0.0",
22
+ "typescript": "^5.9.0",
23
+ "vite": "^8.0.0"
24
+ }
25
+ }
@@ -0,0 +1,39 @@
1
+ import { useState } from "react";
2
+
3
+ import { app, dialog, filesystem, VantailError } from "@vantail/api";
4
+
5
+ export function App() {
6
+ const [contents, setContents] = useState<string | null>(null);
7
+ const [path, setPath] = useState<string | null>(null);
8
+ const [error, setError] = useState<string | null>(null);
9
+
10
+ const info = app.infoSync();
11
+
12
+ async function open() {
13
+ setError(null);
14
+ try {
15
+ const picked = await dialog.openFile({ title: "Open a text file" });
16
+ if (!picked) return;
17
+
18
+ // Picking the file in the dialog is what grants access to it - the
19
+ // config above never mentions this path.
20
+ setPath(picked);
21
+ setContents(await filesystem.readText(picked));
22
+ } catch (cause) {
23
+ setError(VantailError.is(cause) ? `${cause.code}: ${cause.message}` : String(cause));
24
+ }
25
+ }
26
+
27
+ return (
28
+ <main>
29
+ <h1>{info?.name ?? "Vantail"}</h1>
30
+ <p className="meta">v{info?.version}</p>
31
+
32
+ <button onClick={() => void open()}>Select file</button>
33
+
34
+ {error ? <p className="error">{error}</p> : null}
35
+ {path ? <p className="meta">{path}</p> : null}
36
+ {contents !== null ? <pre>{contents}</pre> : <p className="meta">No file open.</p>}
37
+ </main>
38
+ );
39
+ }
@@ -0,0 +1,14 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ import { App } from "./App.js";
5
+ import "./style.css";
6
+
7
+ const container = document.getElementById("root");
8
+ if (!container) throw new Error("index.html is missing #root");
9
+
10
+ createRoot(container).render(
11
+ <StrictMode>
12
+ <App />
13
+ </StrictMode>,
14
+ );
@@ -0,0 +1,60 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ font-family: ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
4
+ font-size: 14px;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ }
10
+
11
+ main {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 12px;
15
+ padding: 24px;
16
+ min-height: 100vh;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ h1 {
21
+ margin: 0;
22
+ font-size: 18px;
23
+ }
24
+
25
+ .meta {
26
+ margin: 0;
27
+ opacity: 0.6;
28
+ font-size: 12px;
29
+ overflow-wrap: anywhere;
30
+ }
31
+
32
+ .error {
33
+ margin: 0;
34
+ color: #d43b3b;
35
+ font-size: 13px;
36
+ }
37
+
38
+ button {
39
+ align-self: flex-start;
40
+ padding: 6px 12px;
41
+ border-radius: 7px;
42
+ border: 1px solid currentColor;
43
+ background: transparent;
44
+ color: inherit;
45
+ font: inherit;
46
+ cursor: pointer;
47
+ }
48
+
49
+ pre {
50
+ flex: 1;
51
+ margin: 0;
52
+ padding: 12px;
53
+ overflow: auto;
54
+ border-radius: 8px;
55
+ background: color-mix(in srgb, currentColor 8%, transparent);
56
+ font-family: ui-monospace, Menlo, monospace;
57
+ font-size: 12px;
58
+ white-space: pre-wrap;
59
+ overflow-wrap: anywhere;
60
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "types": ["vite/client"]
12
+ },
13
+ "include": ["src", "vantail.config.ts", "vite.config.ts"]
14
+ }
@@ -0,0 +1,60 @@
1
+ import { defineConfig } from "@vantail/cli";
2
+
3
+ export default defineConfig({
4
+ app: {
5
+ name: "__APP_NAME__",
6
+ identifier: "__APP_IDENTIFIER__",
7
+ version: "0.1.0",
8
+ // A square PNG. Every size each platform asks for is scaled down from it.
9
+ icon: "icon.png",
10
+ },
11
+
12
+ window: {
13
+ title: "__APP_NAME__",
14
+ width: 900,
15
+ height: 640,
16
+ minWidth: 480,
17
+ minHeight: 360,
18
+ },
19
+
20
+ // On macOS this is not decoration: without these predefined items in the
21
+ // menu, Cmd-C, Cmd-V and Cmd-Z do not work anywhere in the application.
22
+ // Add your own submenus alongside them.
23
+ menu: [
24
+ {
25
+ type: "submenu",
26
+ label: "__APP_NAME__",
27
+ items: [
28
+ { type: "predefined", item: "about" },
29
+ { type: "separator" },
30
+ { type: "predefined", item: "hide" },
31
+ { type: "predefined", item: "quit" },
32
+ ],
33
+ },
34
+ {
35
+ type: "submenu",
36
+ label: "Edit",
37
+ items: [
38
+ { type: "predefined", item: "undo" },
39
+ { type: "predefined", item: "redo" },
40
+ { type: "separator" },
41
+ { type: "predefined", item: "cut" },
42
+ { type: "predefined", item: "copy" },
43
+ { type: "predefined", item: "paste" },
44
+ { type: "predefined", item: "selectAll" },
45
+ ],
46
+ },
47
+ ],
48
+
49
+ // Everything is denied until you ask for it. Start small: `dialog` plus the
50
+ // default `grantFromDialog` is enough to open any file the user picks,
51
+ // without granting standing access to their disk.
52
+ permissions: {
53
+ dialog: true,
54
+ clipboard: true,
55
+ menu: true,
56
+ filesystem: {
57
+ write: ["$APPDATA/**"],
58
+ },
59
+ },
60
+ });
@@ -0,0 +1,8 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+
4
+ // `vantail dev` adds the Vantail plugin itself - this file is only for what
5
+ // your interface needs.
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ });
@@ -0,0 +1,34 @@
1
+ # __APP_NAME__
2
+
3
+ Built with [Vantail](https://github.com/jeroenvanwissen/vantail).
4
+
5
+ ```bash
6
+ npm install
7
+ npm run dev # native window against the Vite dev server
8
+ npm run build # build the web assets
9
+ npm run package # produce a distributable application
10
+ npm run doctor # check that everything needed is installed
11
+ ```
12
+
13
+ There is no Node.js in this app. `node:fs` and friends do not exist - use
14
+ `@vantail/api` instead:
15
+
16
+ ```ts
17
+ import { dialog, filesystem } from "@vantail/api";
18
+
19
+ const path = await dialog.openFile();
20
+ if (path) console.log(await filesystem.readText(path));
21
+ ```
22
+
23
+ Native access is denied by default and granted in `vantail.config.ts`. The same
24
+ file also describes the application menu, and can add a tray icon and a
25
+ self-updater.
26
+
27
+ More windows are labels, opened at runtime:
28
+
29
+ ```ts
30
+ import { app, createWindow } from "@vantail/api";
31
+
32
+ const settings = await createWindow("settings", { url: "settings.html" });
33
+ await app.emit("hello", { from: "main" }, { to: "settings" });
34
+ ```
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .vantail/
4
+ build/
5
+ .DS_Store
Binary file
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__APP_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vantail dev",
8
+ "build": "vantail build",
9
+ "package": "vantail package",
10
+ "doctor": "vantail doctor"
11
+ },
12
+ "dependencies": {
13
+ "@vantail/api": "^0.1.0"
14
+ },
15
+ "devDependencies": {
16
+ "@vantail/cli": "^0.1.0",
17
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
18
+ "svelte": "^5.56.10",
19
+ "typescript": "^5.9.0",
20
+ "vite": "^8.0.0"
21
+ }
22
+ }