@thomasfarineau/anvil 0.0.1 → 0.0.3

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 (45) hide show
  1. package/README.md +20 -194
  2. package/dist/cli.cjs +554 -0
  3. package/package.json +50 -37
  4. package/src/client/config.schema.json +24 -1
  5. package/src/client/index.d.ts +36 -0
  6. package/src/rust/src/lib.rs +320 -17
  7. package/src/template/_gitignore +1 -0
  8. package/src/template/capabilities/default.json +7 -1
  9. package/src/template/config.json +2 -2
  10. package/src/template/frontends/react-js/src/App.jsx +183 -0
  11. package/src/template/frontends/react-js/src/index.html +12 -0
  12. package/src/template/frontends/react-js/src/main.jsx +5 -0
  13. package/src/template/frontends/react-js/vite.config.js +10 -0
  14. package/src/template/frontends/react-ts/src/App.tsx +190 -0
  15. package/src/template/frontends/react-ts/src/index.html +12 -0
  16. package/src/template/frontends/react-ts/src/main.tsx +5 -0
  17. package/src/template/frontends/react-ts/tsconfig.json +14 -0
  18. package/src/template/frontends/react-ts/vite.config.ts +10 -0
  19. package/src/template/frontends/solid-js/src/App.jsx +190 -0
  20. package/src/template/frontends/solid-js/src/index.html +12 -0
  21. package/src/template/frontends/solid-js/src/main.jsx +5 -0
  22. package/src/template/frontends/solid-js/vite.config.js +10 -0
  23. package/src/template/frontends/solid-ts/src/App.tsx +193 -0
  24. package/src/template/frontends/solid-ts/src/index.html +12 -0
  25. package/src/template/frontends/solid-ts/src/main.tsx +5 -0
  26. package/src/template/frontends/solid-ts/tsconfig.json +15 -0
  27. package/src/template/frontends/solid-ts/vite.config.ts +10 -0
  28. package/src/template/{src → frontends/vanilla-js/src}/index.html +110 -178
  29. package/src/template/frontends/vanilla-ts/src/index.html +51 -0
  30. package/src/template/frontends/vanilla-ts/src/main.ts +193 -0
  31. package/src/template/frontends/vanilla-ts/tsconfig.json +13 -0
  32. package/src/template/frontends/vanilla-ts/vite.config.ts +8 -0
  33. package/src/template/frontends/vue-js/src/App.vue +155 -0
  34. package/src/template/frontends/vue-js/src/index.html +12 -0
  35. package/src/template/frontends/vue-js/src/main.js +5 -0
  36. package/src/template/frontends/vue-js/vite.config.js +10 -0
  37. package/src/template/frontends/vue-ts/src/App.vue +158 -0
  38. package/src/template/frontends/vue-ts/src/index.html +12 -0
  39. package/src/template/frontends/vue-ts/src/main.ts +5 -0
  40. package/src/template/frontends/vue-ts/tsconfig.json +13 -0
  41. package/src/template/frontends/vue-ts/vite.config.ts +10 -0
  42. package/src/template/{src → shared}/api.js +27 -1
  43. package/src/template/shared/logo.svg +6 -0
  44. package/src/template/shared/style.css +226 -0
  45. package/src/cli.cjs +0 -352
package/dist/cli.cjs ADDED
@@ -0,0 +1,554 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+
34
+ // package.json
35
+ var require_package = __commonJS((exports2, module2) => {
36
+ module2.exports = {
37
+ name: "@thomasfarineau/anvil",
38
+ version: "0.0.3",
39
+ description: "Zero-config Minecraft launcher framework built on Tauri",
40
+ keywords: [
41
+ "launcher",
42
+ "minecraft",
43
+ "tauri"
44
+ ],
45
+ license: "MIT",
46
+ bin: {
47
+ anvil: "dist/cli.cjs",
48
+ "create-anvil": "dist/cli.cjs"
49
+ },
50
+ files: [
51
+ "dist/",
52
+ "src/template/",
53
+ "src/rust/",
54
+ "src/client/"
55
+ ],
56
+ type: "module",
57
+ scripts: {
58
+ build: "bun build ./src/cli/index.ts --target node --format cjs --packages external --outfile dist/cli.cjs",
59
+ prepack: "bun run build",
60
+ test: "bun test",
61
+ lint: "oxlint .",
62
+ format: "oxfmt .",
63
+ "format:check": "oxfmt --check .",
64
+ check: "bun run build && bun run lint && bun run format:check && bun test",
65
+ "docs:dev": "vitepress dev docs",
66
+ "docs:build": "vitepress build docs",
67
+ "docs:preview": "vitepress preview docs"
68
+ },
69
+ dependencies: {
70
+ "@clack/prompts": "^1.6.0",
71
+ "@tauri-apps/cli": "^2",
72
+ commander: "^15.0.0",
73
+ sharp: "^0.33.0"
74
+ },
75
+ devDependencies: {
76
+ "@types/node": "^26.1.0",
77
+ bun: "^1.3.14",
78
+ oxfmt: "^0.57.0",
79
+ oxlint: "^1.72.0",
80
+ vitepress: "^1.6.4"
81
+ },
82
+ engines: {
83
+ node: ">=18"
84
+ }
85
+ };
86
+ });
87
+
88
+ // src/cli/index.ts
89
+ var import_commander = require("commander");
90
+
91
+ // src/cli/templates.ts
92
+ var { version: VERSION, name: PKG_NAME } = require_package();
93
+ var TEMPLATES = {
94
+ "vanilla-js": {
95
+ label: "Vanilla (no build step)",
96
+ vite: false,
97
+ dependencies: {},
98
+ devDependencies: {}
99
+ },
100
+ "vanilla-ts": {
101
+ label: "Vanilla + TypeScript",
102
+ vite: true,
103
+ dependencies: {},
104
+ devDependencies: { typescript: "^6", vite: "^8" }
105
+ },
106
+ "react-js": {
107
+ label: "React",
108
+ vite: true,
109
+ dependencies: { react: "^19", "react-dom": "^19" },
110
+ devDependencies: { "@vitejs/plugin-react": "^6", vite: "^8" }
111
+ },
112
+ "react-ts": {
113
+ label: "React + TypeScript",
114
+ vite: true,
115
+ dependencies: { react: "^19", "react-dom": "^19" },
116
+ devDependencies: {
117
+ "@types/react": "^19",
118
+ "@types/react-dom": "^19",
119
+ "@vitejs/plugin-react": "^6",
120
+ typescript: "^6",
121
+ vite: "^8"
122
+ }
123
+ },
124
+ "vue-js": {
125
+ label: "Vue",
126
+ vite: true,
127
+ dependencies: { vue: "^3.5" },
128
+ devDependencies: { "@vitejs/plugin-vue": "^6", vite: "^8" }
129
+ },
130
+ "vue-ts": {
131
+ label: "Vue + TypeScript",
132
+ vite: true,
133
+ dependencies: { vue: "^3.5" },
134
+ devDependencies: {
135
+ "@vitejs/plugin-vue": "^6",
136
+ typescript: "^6",
137
+ vite: "^8"
138
+ }
139
+ },
140
+ "solid-js": {
141
+ label: "Solid",
142
+ vite: true,
143
+ dependencies: { "solid-js": "^1.9" },
144
+ devDependencies: { "vite-plugin-solid": "^2", vite: "^8" }
145
+ },
146
+ "solid-ts": {
147
+ label: "Solid + TypeScript",
148
+ vite: true,
149
+ dependencies: { "solid-js": "^1.9" },
150
+ devDependencies: {
151
+ "vite-plugin-solid": "^2",
152
+ typescript: "^6",
153
+ vite: "^8"
154
+ }
155
+ }
156
+ };
157
+ async function resolveTemplate(flags) {
158
+ if (flags.template) {
159
+ if (!TEMPLATES[flags.template]) {
160
+ process.stderr.write(`
161
+ Error: unknown template '${flags.template}'.
162
+ Available: ${Object.keys(TEMPLATES).join(", ")}
163
+
164
+ `);
165
+ process.exit(1);
166
+ }
167
+ return flags.template;
168
+ }
169
+ if (flags.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
170
+ return "vanilla-js";
171
+ }
172
+ const clack = require("@clack/prompts");
173
+ clack.intro(`${PKG_NAME} v${VERSION}`);
174
+ const lang = await clack.select({
175
+ message: "Which language?",
176
+ options: [
177
+ { value: "ts", label: "TypeScript" },
178
+ { value: "js", label: "JavaScript" }
179
+ ]
180
+ });
181
+ if (clack.isCancel(lang)) {
182
+ clack.cancel("Aborted.");
183
+ process.exit(1);
184
+ }
185
+ const fw = await clack.select({
186
+ message: "Which framework?",
187
+ options: [
188
+ { value: "vanilla", label: "Vanilla", hint: "no framework" },
189
+ { value: "react", label: "React" },
190
+ { value: "vue", label: "Vue" },
191
+ { value: "solid", label: "Solid" }
192
+ ]
193
+ });
194
+ if (clack.isCancel(fw)) {
195
+ clack.cancel("Aborted.");
196
+ process.exit(1);
197
+ }
198
+ const templateId = `${fw}-${lang}`;
199
+ clack.outro(`Template: ${templateId}`);
200
+ return templateId;
201
+ }
202
+
203
+ // src/cli/scaffold.ts
204
+ var import_fs = __toESM(require("fs"));
205
+ var import_path = __toESM(require("path"));
206
+ var __dirname = "/home/runner/work/anvil/anvil/src/cli";
207
+ function resolvePkgDir() {
208
+ const fromDist = import_path.default.join(__dirname, "..", "src");
209
+ if (import_fs.default.existsSync(import_path.default.join(fromDist, "template")))
210
+ return fromDist;
211
+ return import_path.default.join(__dirname, "..");
212
+ }
213
+ var PKG_DIR = resolvePkgDir();
214
+ var pkgJson = require_package();
215
+ var VERSION2 = pkgJson.version;
216
+ var PKG_NAME2 = pkgJson.name;
217
+ function copyDir(src, dest) {
218
+ import_fs.default.mkdirSync(dest, { recursive: true });
219
+ for (const entry of import_fs.default.readdirSync(src, { withFileTypes: true })) {
220
+ const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
221
+ const s = import_path.default.join(src, entry.name);
222
+ const d = import_path.default.join(dest, destName);
223
+ if (entry.isDirectory())
224
+ copyDir(s, d);
225
+ else
226
+ import_fs.default.copyFileSync(s, d);
227
+ }
228
+ }
229
+ function renderTemplate(filePath, name, identifier) {
230
+ const safeId = name.replace(/-/g, "_");
231
+ const id = identifier || `com.launcher.${safeId}`;
232
+ return import_fs.default.readFileSync(filePath, "utf8").replace(/\{\{name\}\}/g, name).replace(/\{\{safe_id\}\}/g, safeId).replace(/\{\{identifier\}\}/g, id);
233
+ }
234
+ function deriveName(dir) {
235
+ return import_path.default.basename(dir).toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "") || "my-launcher";
236
+ }
237
+ function readConfig(projectDir) {
238
+ const configPath = import_path.default.join(projectDir, "config.json");
239
+ if (!import_fs.default.existsSync(configPath))
240
+ return {};
241
+ try {
242
+ return JSON.parse(import_fs.default.readFileSync(configPath, "utf8"));
243
+ } catch {
244
+ return {};
245
+ }
246
+ }
247
+ function readIdentifier(projectDir) {
248
+ return readConfig(projectDir).identifier || "";
249
+ }
250
+ function readConfigField(projectDir, field) {
251
+ return readConfig(projectDir)[field] || "";
252
+ }
253
+ function usesVite(projectDir) {
254
+ return ["vite.config.js", "vite.config.ts", "vite.config.mjs"].some((f) => import_fs.default.existsSync(import_path.default.join(projectDir, f)));
255
+ }
256
+ function scaffoldTauri(projectDir, name) {
257
+ const srcTauri = import_path.default.join(projectDir, "src-anvil");
258
+ const identifier = readIdentifier(projectDir);
259
+ copyDir(import_path.default.join(PKG_DIR, "rust"), srcTauri);
260
+ import_fs.default.writeFileSync(import_path.default.join(srcTauri, "Cargo.toml"), renderTemplate(import_path.default.join(PKG_DIR, "rust", "Cargo.toml"), name, identifier));
261
+ const conf = JSON.parse(renderTemplate(import_path.default.join(PKG_DIR, "template", "tauri.conf.json"), name, identifier));
262
+ if (usesVite(projectDir)) {
263
+ conf.build = {
264
+ beforeDevCommand: "npm run dev:ui",
265
+ beforeBuildCommand: "npm run build:ui",
266
+ devUrl: "http://localhost:5173",
267
+ frontendDist: "../dist"
268
+ };
269
+ }
270
+ import_fs.default.writeFileSync(import_path.default.join(srcTauri, "tauri.conf.json"), JSON.stringify(conf, null, 2) + `
271
+ `);
272
+ copyDir(import_path.default.join(PKG_DIR, "template", "capabilities"), import_path.default.join(srcTauri, "capabilities"));
273
+ copyDir(import_path.default.join(PKG_DIR, "template", "icons"), import_path.default.join(srcTauri, "icons"));
274
+ import_fs.default.writeFileSync(import_path.default.join(srcTauri, ".lc-version"), VERSION2);
275
+ }
276
+ function copyUserFiles(projectDir, templateId = "vanilla-js") {
277
+ import_fs.default.mkdirSync(projectDir, { recursive: true });
278
+ for (const [src, dest] of [
279
+ [
280
+ import_path.default.join(PKG_DIR, "template", "config.json"),
281
+ import_path.default.join(projectDir, "config.json")
282
+ ],
283
+ [
284
+ import_path.default.join(PKG_DIR, "template", "_gitignore"),
285
+ import_path.default.join(projectDir, ".gitignore")
286
+ ]
287
+ ]) {
288
+ if (!import_fs.default.existsSync(dest))
289
+ import_fs.default.copyFileSync(src, dest);
290
+ }
291
+ const destDir = import_path.default.join(projectDir, "src");
292
+ if (import_fs.default.existsSync(destDir))
293
+ return;
294
+ copyDir(import_path.default.join(PKG_DIR, "template", "frontends", templateId), projectDir);
295
+ for (const f of ["api.js", "logo.svg", "style.css"]) {
296
+ import_fs.default.copyFileSync(import_path.default.join(PKG_DIR, "template", "shared", f), import_path.default.join(destDir, f));
297
+ }
298
+ if (templateId.endsWith("-ts")) {
299
+ import_fs.default.copyFileSync(import_path.default.join(PKG_DIR, "client", "index.d.ts"), import_path.default.join(destDir, "api.d.ts"));
300
+ }
301
+ }
302
+ function writePackageJson(projectDir, name, templateId = "vanilla-js") {
303
+ const pkgPath = import_path.default.join(projectDir, "package.json");
304
+ if (import_fs.default.existsSync(pkgPath))
305
+ return;
306
+ const tpl = TEMPLATES[templateId];
307
+ const pkg = {
308
+ name,
309
+ version: "1.0.0",
310
+ private: true,
311
+ type: "module",
312
+ scripts: { dev: "anvil dev", build: "anvil build" },
313
+ devDependencies: {
314
+ [PKG_NAME2]: `^${VERSION2}`,
315
+ ...tpl.devDependencies
316
+ }
317
+ };
318
+ if (tpl.vite) {
319
+ pkg.scripts["dev:ui"] = "vite";
320
+ pkg.scripts["build:ui"] = "vite build";
321
+ }
322
+ if (Object.keys(tpl.dependencies).length > 0) {
323
+ pkg.dependencies = tpl.dependencies;
324
+ }
325
+ import_fs.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
326
+ `);
327
+ }
328
+
329
+ // src/cli/commands.ts
330
+ var import_fs3 = __toESM(require("fs"));
331
+ var import_path3 = __toESM(require("path"));
332
+
333
+ // src/cli/icons.ts
334
+ var import_fs2 = __toESM(require("fs"));
335
+ var import_path2 = __toESM(require("path"));
336
+ var import_crypto = __toESM(require("crypto"));
337
+ var import_child_process = require("child_process");
338
+ function findBin(name, projectDir) {
339
+ const candidates = [
340
+ import_path2.default.join(projectDir, "node_modules", ".bin", name),
341
+ import_path2.default.join(PKG_DIR, "..", "node_modules", ".bin", name)
342
+ ];
343
+ for (const bin of candidates) {
344
+ if (import_fs2.default.existsSync(bin) || import_fs2.default.existsSync(bin + ".exe"))
345
+ return bin;
346
+ }
347
+ return null;
348
+ }
349
+ function generateIcons(projectDir, { force = false } = {}) {
350
+ if (process.env.ANVIL_SKIP_ICONS === "1" && !force)
351
+ return;
352
+ const config = readConfig(projectDir);
353
+ if (!config.logo) {
354
+ if (force) {
355
+ console.warn(' No "logo" field in config.json — nothing to generate.');
356
+ }
357
+ return;
358
+ }
359
+ const logo = config.logo;
360
+ const logoPath = import_path2.default.join(projectDir, "src", logo);
361
+ if (!import_fs2.default.existsSync(logoPath)) {
362
+ console.warn(` Warning: logo not found at src/${logo}, skipping icon generation.`);
363
+ return;
364
+ }
365
+ const isSvg = logo.toLowerCase().endsWith(".svg");
366
+ const isPng = logo.toLowerCase().endsWith(".png");
367
+ if (!isSvg && !isPng) {
368
+ console.warn(` Warning: icon generation requires a .png or .svg logo (got ${logo}), skipping.`);
369
+ return;
370
+ }
371
+ const hashFile = import_path2.default.join(projectDir, "src-anvil", ".logo-hash");
372
+ const hash = import_crypto.default.createHash("sha256").update(import_fs2.default.readFileSync(logoPath)).digest("hex");
373
+ if (!force && import_fs2.default.existsSync(hashFile)) {
374
+ if (import_fs2.default.readFileSync(hashFile, "utf8").trim() === hash)
375
+ return;
376
+ }
377
+ const bin = findBin("tauri", projectDir);
378
+ if (!bin) {
379
+ console.warn(" Warning: @tauri-apps/cli not found, skipping icon generation.");
380
+ return;
381
+ }
382
+ let iconSrc = logoPath;
383
+ let tmpPng = null;
384
+ if (isSvg) {
385
+ tmpPng = logoPath.replace(/\.svg$/i, ".tmp.png");
386
+ console.log(` Converting src/${logo} to PNG via sharp...`);
387
+ const sharpBin = require.resolve("sharp");
388
+ const script = [
389
+ `const sharp = require(${JSON.stringify(sharpBin)});`,
390
+ `sharp(${JSON.stringify(logoPath)}, { density: 300 })`,
391
+ ` .resize(1024, 1024).png()`,
392
+ ` .toFile(${JSON.stringify(tmpPng)})`,
393
+ ` .then(() => process.exit(0))`,
394
+ ` .catch(e => { process.stderr.write(String(e) + '\\n'); process.exit(1); });`
395
+ ].join(`
396
+ `);
397
+ const r2 = import_child_process.spawnSync(process.execPath, ["-e", script], { stdio: "inherit" });
398
+ if (r2.status !== 0) {
399
+ console.warn(" Warning: SVG→PNG conversion failed, skipping icon generation.");
400
+ return;
401
+ }
402
+ iconSrc = tmpPng;
403
+ }
404
+ console.log(` Generating app icons...`);
405
+ const r = import_child_process.spawnSync(bin, ["icon", "--output", "icons", import_path2.default.resolve(iconSrc)], {
406
+ stdio: "inherit",
407
+ shell: process.platform === "win32",
408
+ cwd: import_path2.default.join(projectDir, "src-anvil")
409
+ });
410
+ if (tmpPng) {
411
+ try {
412
+ import_fs2.default.unlinkSync(tmpPng);
413
+ } catch {}
414
+ }
415
+ if (r.status === 0) {
416
+ import_fs2.default.writeFileSync(hashFile, hash + `
417
+ `);
418
+ console.log(" App icons updated from " + logo);
419
+ } else {
420
+ console.warn(" Warning: icon generation failed.");
421
+ }
422
+ }
423
+
424
+ // src/cli/commands.ts
425
+ async function create(target, flags) {
426
+ const projectDir = import_path3.default.resolve(target);
427
+ const name = deriveName(projectDir);
428
+ if (import_fs3.default.existsSync(projectDir)) {
429
+ const entries = import_fs3.default.readdirSync(projectDir).filter((e) => e !== ".git");
430
+ if (entries.length > 0) {
431
+ process.stderr.write(`
432
+ Error: '${target}' already exists and is not empty.
433
+
434
+ `);
435
+ process.exit(1);
436
+ }
437
+ }
438
+ const templateId = await resolveTemplate(flags);
439
+ console.log(`
440
+ Creating ${PKG_NAME2} project: ${name} (${templateId})
441
+ `);
442
+ copyUserFiles(projectDir, templateId);
443
+ writePackageJson(projectDir, name, templateId);
444
+ scaffoldTauri(projectDir, name);
445
+ generateIcons(projectDir);
446
+ const cdLine = target !== "." ? ` cd ${target}
447
+ ` : "";
448
+ console.log(`Done!
449
+
450
+ ${cdLine} npm install
451
+ # Edit config.json and src/
452
+ npm run dev
453
+ `);
454
+ }
455
+ function init() {
456
+ const projectDir = process.cwd();
457
+ const name = deriveName(projectDir);
458
+ console.log(`
459
+ Initializing ${PKG_NAME2} in: ${projectDir}
460
+ `);
461
+ copyUserFiles(projectDir);
462
+ scaffoldTauri(projectDir, name);
463
+ generateIcons(projectDir);
464
+ const pkgPath = import_path3.default.join(projectDir, "package.json");
465
+ if (import_fs3.default.existsSync(pkgPath)) {
466
+ const pkg = JSON.parse(import_fs3.default.readFileSync(pkgPath, "utf8"));
467
+ pkg.scripts = { dev: "anvil dev", build: "anvil build", ...pkg.scripts };
468
+ import_fs3.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
469
+ `);
470
+ console.log("Updated package.json");
471
+ } else {
472
+ writePackageJson(projectDir, name);
473
+ }
474
+ console.log(`
475
+ Done!
476
+
477
+ npm install
478
+ npm run dev
479
+ `);
480
+ }
481
+ function update() {
482
+ const projectDir = process.cwd();
483
+ const srcTauri = import_path3.default.join(projectDir, "src-anvil");
484
+ if (!import_fs3.default.existsSync(srcTauri)) {
485
+ process.stderr.write(`
486
+ No src-anvil/ found. Run this from the root of a ${PKG_NAME2} project.
487
+
488
+ `);
489
+ process.exit(1);
490
+ }
491
+ import_fs3.default.copyFileSync(import_path3.default.join(PKG_DIR, "rust", "src", "lib.rs"), import_path3.default.join(srcTauri, "src", "lib.rs"));
492
+ const srcDir = import_path3.default.join(projectDir, "src");
493
+ if (import_fs3.default.existsSync(srcDir)) {
494
+ import_fs3.default.copyFileSync(import_path3.default.join(PKG_DIR, "template", "shared", "api.js"), import_path3.default.join(srcDir, "api.js"));
495
+ if (import_fs3.default.existsSync(import_path3.default.join(srcDir, "api.d.ts"))) {
496
+ import_fs3.default.copyFileSync(import_path3.default.join(PKG_DIR, "client", "index.d.ts"), import_path3.default.join(srcDir, "api.d.ts"));
497
+ }
498
+ }
499
+ import_fs3.default.writeFileSync(import_path3.default.join(srcTauri, ".lc-version"), VERSION2);
500
+ console.log(`
501
+ Updated to ${PKG_NAME2}@${VERSION2}
502
+ `);
503
+ }
504
+
505
+ // src/cli/tauri.ts
506
+ var import_fs4 = __toESM(require("fs"));
507
+ var import_path4 = __toESM(require("path"));
508
+ var import_child_process2 = require("child_process");
509
+ function runTauri(tauriCmd) {
510
+ const projectDir = process.cwd();
511
+ const srcTauri = import_path4.default.join(projectDir, "src-anvil");
512
+ if (!import_fs4.default.existsSync(srcTauri)) {
513
+ console.log(`
514
+ No src-anvil/ found — running anvil init first...
515
+ `);
516
+ init();
517
+ }
518
+ generateIcons(projectDir);
519
+ const bin = findBin("tauri", projectDir);
520
+ if (!bin) {
521
+ process.stderr.write(`
522
+ @tauri-apps/cli not found. Try reinstalling ${PKG_NAME2}.
523
+
524
+ `);
525
+ process.exit(1);
526
+ }
527
+ const env = { ...process.env };
528
+ const targetDir = readConfigField(projectDir, "target");
529
+ if (targetDir)
530
+ env.CARGO_TARGET_DIR = import_path4.default.resolve(projectDir, targetDir);
531
+ const result = import_child_process2.spawnSync(bin, [tauriCmd], {
532
+ stdio: "inherit",
533
+ shell: process.platform === "win32",
534
+ cwd: import_path4.default.join(projectDir, "src-anvil"),
535
+ env
536
+ });
537
+ process.exit(result.status ?? 1);
538
+ }
539
+
540
+ // src/cli/index.ts
541
+ var program = new import_commander.Command;
542
+ program.name("anvil").description("Zero-config Minecraft launcher framework built on Tauri").version(`${PKG_NAME2}@${VERSION2}`, "-v, --version");
543
+ program.command("create [name]", { isDefault: true }).description("Scaffold a new project (interactive)").option("-t, --template <id>", `skip prompts (${Object.keys(TEMPLATES).join(", ")})`).option("-y, --yes", "skip prompts and use the default (vanilla-js)").action((name, opts) => create(name || ".", opts));
544
+ program.command("init").description("Initialize anvil in the current folder").action(init);
545
+ program.command("dev").description("Start the launcher in development mode").action(() => runTauri("dev"));
546
+ program.command("build").description("Compile the launcher for distribution").action(() => runTauri("build"));
547
+ program.command("update").description("Update the Rust backend and api.js to the current version").action(update);
548
+ program.command("icons").description("Regenerate app icons from the logo set in config.json").action(() => generateIcons(process.cwd(), { force: true }));
549
+ program.parseAsync().catch((e) => {
550
+ process.stderr.write(`
551
+ Error: ${e?.message ?? e}
552
+ `);
553
+ process.exit(1);
554
+ });
package/package.json CHANGED
@@ -1,37 +1,50 @@
1
- {
2
- "name": "@thomasfarineau/anvil",
3
- "version": "0.0.1",
4
- "description": "Zero-config Minecraft launcher framework built on Tauri",
5
- "keywords": [
6
- "launcher",
7
- "minecraft",
8
- "tauri"
9
- ],
10
- "license": "MIT",
11
- "bin": {
12
- "anvil": "src/cli.cjs",
13
- "create-anvil": "src/cli.cjs"
14
- },
15
- "files": [
16
- "src/"
17
- ],
18
- "type": "module",
19
- "scripts": {
20
- "test": "bun test",
21
- "lint": "oxlint .",
22
- "format": "oxfmt .",
23
- "check": "bun run lint && bun run format"
24
- },
25
- "dependencies": {
26
- "@tauri-apps/cli": "^2",
27
- "sharp": "^0.33.0"
28
- },
29
- "devDependencies": {
30
- "@types/node": "^26.1.0",
31
- "oxfmt": "^0.57.0",
32
- "oxlint": "^1.72.0"
33
- },
34
- "engines": {
35
- "node": ">=18"
36
- }
37
- }
1
+ {
2
+ "name": "@thomasfarineau/anvil",
3
+ "version": "0.0.3",
4
+ "description": "Zero-config Minecraft launcher framework built on Tauri",
5
+ "keywords": [
6
+ "launcher",
7
+ "minecraft",
8
+ "tauri"
9
+ ],
10
+ "license": "MIT",
11
+ "bin": {
12
+ "anvil": "dist/cli.cjs",
13
+ "create-anvil": "dist/cli.cjs"
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "src/template/",
18
+ "src/rust/",
19
+ "src/client/"
20
+ ],
21
+ "type": "module",
22
+ "scripts": {
23
+ "build": "bun build ./src/cli/index.ts --target node --format cjs --packages external --outfile dist/cli.cjs",
24
+ "prepack": "bun run build",
25
+ "test": "bun test",
26
+ "lint": "oxlint .",
27
+ "format": "oxfmt .",
28
+ "format:check": "oxfmt --check .",
29
+ "check": "bun run build && bun run lint && bun run format:check && bun test",
30
+ "docs:dev": "vitepress dev docs",
31
+ "docs:build": "vitepress build docs",
32
+ "docs:preview": "vitepress preview docs"
33
+ },
34
+ "dependencies": {
35
+ "@clack/prompts": "^1.6.0",
36
+ "@tauri-apps/cli": "^2",
37
+ "commander": "^15.0.0",
38
+ "sharp": "^0.33.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^26.1.0",
42
+ "bun": "^1.3.14",
43
+ "oxfmt": "^0.57.0",
44
+ "oxlint": "^1.72.0",
45
+ "vitepress": "^1.6.4"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ }
50
+ }
@@ -10,7 +10,7 @@
10
10
  "$schema": {
11
11
  "type": "string",
12
12
  "description": "JSON schema URL for validation",
13
- "default": "./node_modules/anvil/src/client/config.schema.json"
13
+ "default": "./node_modules/@thomasfarineau/anvil/src/client/config.schema.json"
14
14
  },
15
15
  "identifier": {
16
16
  "type": "string",
@@ -105,6 +105,29 @@
105
105
  "maximum": 65535,
106
106
  "description": "Server port",
107
107
  "default": 25565
108
+ },
109
+ "mods": {
110
+ "type": "array",
111
+ "description": "Mods downloaded into this instance's mods/ folder during setup",
112
+ "items": {
113
+ "type": "object",
114
+ "required": ["url"],
115
+ "additionalProperties": false,
116
+ "properties": {
117
+ "name": {
118
+ "type": "string",
119
+ "description": "Display name of the mod"
120
+ },
121
+ "url": {
122
+ "type": "string",
123
+ "description": "Direct download URL of the mod .jar"
124
+ },
125
+ "file_name": {
126
+ "type": "string",
127
+ "description": "Target file name in mods/ (defaults to the URL basename)"
128
+ }
129
+ }
130
+ }
108
131
  }
109
132
  }
110
133
  }