@thomasfarineau/anvil 0.0.2 → 0.0.4

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 +21 -191
  2. package/dist/cli.cjs +557 -0
  3. package/package.json +26 -10
  4. package/src/client/config.schema.json +58 -6
  5. package/src/client/index.d.ts +60 -1
  6. package/src/rust/src/lib.rs +633 -21
  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 +38 -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
@@ -0,0 +1,226 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+ body {
7
+ font-family:
8
+ system-ui,
9
+ -apple-system,
10
+ sans-serif;
11
+ background: #0d0d0d;
12
+ color: #e8e8e8;
13
+ height: 100vh;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ user-select: none;
18
+ }
19
+ .card {
20
+ background: #111;
21
+ border: 1px solid #222;
22
+ border-radius: 12px;
23
+ padding: 28px 32px;
24
+ width: 380px;
25
+ }
26
+ .brand {
27
+ display: flex;
28
+ align-items: center;
29
+ gap: 12px;
30
+ margin-bottom: 4px;
31
+ }
32
+ .brand img {
33
+ width: 40px;
34
+ height: 40px;
35
+ border-radius: 8px;
36
+ }
37
+ h1 {
38
+ font-size: 1.4rem;
39
+ color: #4caf50;
40
+ }
41
+ p.sub {
42
+ font-size: 0.8rem;
43
+ color: #555;
44
+ margin-bottom: 20px;
45
+ }
46
+ label {
47
+ display: block;
48
+ font-size: 0.75rem;
49
+ color: #666;
50
+ margin-bottom: 6px;
51
+ text-transform: uppercase;
52
+ letter-spacing: 0.05em;
53
+ }
54
+ input[type='text'] {
55
+ width: 100%;
56
+ padding: 8px 12px;
57
+ border-radius: 6px;
58
+ background: #0a0a0a;
59
+ border: 1px solid #333;
60
+ color: #e8e8e8;
61
+ font-size: 0.9rem;
62
+ outline: none;
63
+ margin-bottom: 16px;
64
+ }
65
+ input[type='text']:focus {
66
+ border-color: #4caf50;
67
+ }
68
+ .instance-btn {
69
+ width: 100%;
70
+ padding: 10px;
71
+ border-radius: 8px;
72
+ border: none;
73
+ background: #1a2e1a;
74
+ color: #4caf50;
75
+ font-size: 0.9rem;
76
+ font-weight: 600;
77
+ cursor: pointer;
78
+ margin-bottom: 8px;
79
+ transition: background 0.15s;
80
+ }
81
+ .instance-btn:hover:not(:disabled) {
82
+ background: #4caf50;
83
+ color: #fff;
84
+ }
85
+ .instance-btn:disabled {
86
+ opacity: 0.4;
87
+ cursor: not-allowed;
88
+ }
89
+ .instance-row {
90
+ display: flex;
91
+ gap: 8px;
92
+ align-items: flex-start;
93
+ }
94
+ .instance-row .instance-btn {
95
+ flex: 1;
96
+ }
97
+ .icon-btn {
98
+ padding: 10px 12px;
99
+ border-radius: 8px;
100
+ border: 1px solid #333;
101
+ background: #111;
102
+ color: #888;
103
+ font-size: 0.85rem;
104
+ cursor: pointer;
105
+ margin-bottom: 8px;
106
+ }
107
+ .icon-btn:hover {
108
+ border-color: #4caf50;
109
+ color: #4caf50;
110
+ }
111
+ .status {
112
+ font-size: 0.75rem;
113
+ color: #555;
114
+ margin-top: 12px;
115
+ min-height: 1rem;
116
+ }
117
+
118
+ /* ── Mods panel ─────────────────────────────────────────── */
119
+ .mods-panel {
120
+ border: 1px solid #222;
121
+ border-radius: 8px;
122
+ padding: 10px 12px;
123
+ margin-bottom: 12px;
124
+ max-height: 180px;
125
+ overflow-y: auto;
126
+ }
127
+ .mods-panel .mod-row {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 8px;
131
+ font-size: 0.8rem;
132
+ padding: 4px 0;
133
+ color: #aaa;
134
+ }
135
+ .mods-panel .mod-row .mod-name {
136
+ flex: 1;
137
+ overflow: hidden;
138
+ text-overflow: ellipsis;
139
+ white-space: nowrap;
140
+ }
141
+ .mods-panel .mod-row.disabled .mod-name {
142
+ color: #555;
143
+ text-decoration: line-through;
144
+ }
145
+ .mods-empty {
146
+ font-size: 0.75rem;
147
+ color: #555;
148
+ }
149
+
150
+ /* ── Setup overlay ─────────────────────────────────────── */
151
+ #setup-overlay {
152
+ position: fixed;
153
+ inset: 0;
154
+ background: #080808;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ }
159
+ #setup-overlay .card {
160
+ width: 400px;
161
+ }
162
+ #setup-overlay h1 {
163
+ margin-bottom: 20px;
164
+ }
165
+ .dir-row {
166
+ display: flex;
167
+ gap: 8px;
168
+ margin-bottom: 16px;
169
+ }
170
+ .dir-row input {
171
+ flex: 1;
172
+ margin: 0;
173
+ }
174
+ .dir-row button {
175
+ padding: 8px 12px;
176
+ border-radius: 6px;
177
+ border: 1px solid #333;
178
+ background: #111;
179
+ color: #888;
180
+ font-size: 0.8rem;
181
+ cursor: pointer;
182
+ }
183
+ .step {
184
+ margin-bottom: 10px;
185
+ }
186
+ .step-name {
187
+ font-size: 0.85rem;
188
+ margin-bottom: 4px;
189
+ }
190
+ .bar-track {
191
+ height: 4px;
192
+ background: #222;
193
+ border-radius: 2px;
194
+ overflow: hidden;
195
+ }
196
+ .bar-fill {
197
+ height: 4px;
198
+ background: #4caf50;
199
+ transition: width 0.2s;
200
+ }
201
+ .step-label {
202
+ font-size: 0.7rem;
203
+ color: #555;
204
+ margin-top: 2px;
205
+ }
206
+ .install-btn {
207
+ width: 100%;
208
+ padding: 10px;
209
+ border-radius: 8px;
210
+ border: none;
211
+ background: #1a2e1a;
212
+ color: #4caf50;
213
+ font-weight: 700;
214
+ font-size: 0.9rem;
215
+ cursor: pointer;
216
+ margin-top: 16px;
217
+ transition: background 0.15s;
218
+ }
219
+ .install-btn:hover:not(:disabled) {
220
+ background: #4caf50;
221
+ color: #fff;
222
+ }
223
+ .install-btn:disabled {
224
+ opacity: 0.4;
225
+ cursor: not-allowed;
226
+ }
package/src/cli.cjs DELETED
@@ -1,352 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { spawnSync } = require('child_process');
7
-
8
- // __dirname = src/ → rust/, template/, client/ are siblings
9
- const PKG_DIR = __dirname;
10
- const { version: VERSION, name: PKG_NAME } = require('../package.json');
11
-
12
- // ── Helpers ───────────────────────────────────────────────────────────────────
13
-
14
- function copyDir(src, dest) {
15
- fs.mkdirSync(dest, { recursive: true });
16
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
17
- const destName = entry.name === '_gitignore' ? '.gitignore' : entry.name;
18
- const s = path.join(src, entry.name);
19
- const d = path.join(dest, destName);
20
- if (entry.isDirectory()) copyDir(s, d);
21
- else fs.copyFileSync(s, d);
22
- }
23
- }
24
-
25
- function renderTemplate(filePath, name, identifier) {
26
- const safeId = name.replace(/-/g, '_');
27
- const id = identifier || `com.launcher.${safeId}`;
28
- return fs
29
- .readFileSync(filePath, 'utf8')
30
- .replace(/\{\{name\}\}/g, name)
31
- .replace(/\{\{safe_id\}\}/g, safeId)
32
- .replace(/\{\{identifier\}\}/g, id);
33
- }
34
-
35
- function deriveName(dir) {
36
- return (
37
- path
38
- .basename(dir)
39
- .toLowerCase()
40
- .replace(/[^a-z0-9_-]/g, '-')
41
- .replace(/^-+|-+$/g, '') || 'my-launcher'
42
- );
43
- }
44
-
45
- // ── Scaffold src-anvil/ into a project directory ──────────────────────────────
46
-
47
- function readConfig(projectDir) {
48
- const configPath = path.join(projectDir, 'config.json');
49
- if (!fs.existsSync(configPath)) return {};
50
- try {
51
- return JSON.parse(fs.readFileSync(configPath, 'utf8'));
52
- } catch {
53
- return {};
54
- }
55
- }
56
-
57
- function readIdentifier(projectDir) {
58
- return readConfig(projectDir).identifier || '';
59
- }
60
-
61
- function readConfigField(projectDir, field) {
62
- return readConfig(projectDir)[field] || '';
63
- }
64
-
65
- function scaffoldTauri(projectDir, name) {
66
- const srcTauri = path.join(projectDir, 'src-anvil');
67
- const identifier = readIdentifier(projectDir);
68
-
69
- copyDir(path.join(PKG_DIR, 'rust'), srcTauri);
70
- fs.writeFileSync(
71
- path.join(srcTauri, 'Cargo.toml'),
72
- renderTemplate(path.join(PKG_DIR, 'rust', 'Cargo.toml'), name, identifier),
73
- );
74
- fs.writeFileSync(
75
- path.join(srcTauri, 'tauri.conf.json'),
76
- renderTemplate(
77
- path.join(PKG_DIR, 'template', 'tauri.conf.json'),
78
- name,
79
- identifier,
80
- ),
81
- );
82
- copyDir(
83
- path.join(PKG_DIR, 'template', 'capabilities'),
84
- path.join(srcTauri, 'capabilities'),
85
- );
86
- copyDir(
87
- path.join(PKG_DIR, 'template', 'icons'),
88
- path.join(srcTauri, 'icons'),
89
- );
90
-
91
- fs.writeFileSync(path.join(srcTauri, '.lc-version'), VERSION);
92
- }
93
-
94
- function generateIcons(projectDir) {
95
- const configPath = path.join(projectDir, 'config.json');
96
- if (!fs.existsSync(configPath)) return;
97
-
98
- let config;
99
- try {
100
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
101
- } catch {
102
- return;
103
- }
104
-
105
- if (!config.logo) return;
106
-
107
- const logoPath = path.join(projectDir, 'src', config.logo);
108
- if (!fs.existsSync(logoPath)) {
109
- console.warn(
110
- ` Warning: logo not found at src/${config.logo}, skipping icon generation.`,
111
- );
112
- return;
113
- }
114
-
115
- const isSvg = config.logo.toLowerCase().endsWith('.svg');
116
- const isPng = config.logo.toLowerCase().endsWith('.png');
117
-
118
- if (!isSvg && !isPng) {
119
- console.warn(
120
- ` Warning: icon generation requires a .png or .svg logo (got ${config.logo}), skipping.`,
121
- );
122
- return;
123
- }
124
-
125
- const bin = findBin('tauri', projectDir);
126
- if (!bin) return;
127
-
128
- let iconSrc = logoPath;
129
- let tmpPng = null;
130
-
131
- if (isSvg) {
132
- tmpPng = logoPath.replace(/\.svg$/i, '.tmp.png');
133
- console.log(` Converting src/${config.logo} to PNG via sharp...`);
134
- const sharpBin = require.resolve('sharp');
135
- const script = [
136
- `const sharp = require(${JSON.stringify(sharpBin)});`,
137
- `sharp(${JSON.stringify(logoPath)}, { density: 300 })`,
138
- ` .resize(1024, 1024).png()`,
139
- ` .toFile(${JSON.stringify(tmpPng)})`,
140
- ` .then(() => process.exit(0))`,
141
- ` .catch(e => { process.stderr.write(String(e) + '\\n'); process.exit(1); });`,
142
- ].join('\n');
143
- const r = spawnSync(process.execPath, ['-e', script], { stdio: 'inherit' });
144
- if (r.status !== 0) {
145
- console.warn(
146
- ' Warning: SVG→PNG conversion failed, skipping icon generation.',
147
- );
148
- return;
149
- }
150
- iconSrc = tmpPng;
151
- }
152
-
153
- console.log(` Generating app icons...`);
154
- spawnSync(bin, ['icon', path.resolve(iconSrc)], {
155
- stdio: 'inherit',
156
- shell: process.platform === 'win32',
157
- cwd: path.join(projectDir, 'src-anvil'),
158
- });
159
-
160
- if (tmpPng) {
161
- try {
162
- fs.unlinkSync(tmpPng);
163
- } catch {}
164
- }
165
- }
166
-
167
- // ── create ────────────────────────────────────────────────────────────────────
168
-
169
- function create(target) {
170
- const projectDir = path.resolve(target);
171
- const name = deriveName(projectDir);
172
-
173
- if (fs.existsSync(projectDir)) {
174
- const entries = fs.readdirSync(projectDir).filter((e) => e !== '.git');
175
- if (entries.length > 0) {
176
- process.stderr.write(
177
- `\nError: '${target}' already exists and is not empty.\n\n`,
178
- );
179
- process.exit(1);
180
- }
181
- }
182
-
183
- console.log(`\nCreating ${PKG_NAME} project: ${name}\n`);
184
- scaffoldTauri(projectDir, name);
185
- copyUserFiles(projectDir, false);
186
- writePackageJson(projectDir, name);
187
- generateIcons(projectDir);
188
-
189
- const cdLine = target !== '.' ? ` cd ${target}\n` : '';
190
- console.log(
191
- `Done!\n\n${cdLine} npm install\n # Edit config.json and src/index.html\n npm run dev\n`,
192
- );
193
- }
194
-
195
- // ── init ──────────────────────────────────────────────────────────────────────
196
-
197
- function init() {
198
- const projectDir = process.cwd();
199
- const name = deriveName(projectDir);
200
-
201
- console.log(`\nInitializing ${PKG_NAME} in: ${projectDir}\n`);
202
- scaffoldTauri(projectDir, name);
203
- copyUserFiles(projectDir, false);
204
- generateIcons(projectDir);
205
-
206
- const pkgPath = path.join(projectDir, 'package.json');
207
- if (fs.existsSync(pkgPath)) {
208
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
209
- pkg.scripts = { dev: 'anvil dev', build: 'anvil build', ...pkg.scripts };
210
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
211
- console.log('Updated package.json');
212
- } else {
213
- writePackageJson(projectDir, name);
214
- }
215
-
216
- console.log(`\nDone!\n\n npm install\n npm run dev\n`);
217
- }
218
-
219
- // ── update ────────────────────────────────────────────────────────────────────
220
-
221
- function update() {
222
- const projectDir = process.cwd();
223
- const srcTauri = path.join(projectDir, 'src-anvil');
224
-
225
- if (!fs.existsSync(srcTauri)) {
226
- process.stderr.write(
227
- `\nNo src-anvil/ found. Run this from the root of a ${PKG_NAME} project.\n\n`,
228
- );
229
- process.exit(1);
230
- }
231
-
232
- fs.copyFileSync(
233
- path.join(PKG_DIR, 'rust', 'src', 'lib.rs'),
234
- path.join(srcTauri, 'src', 'lib.rs'),
235
- );
236
-
237
- const apiDest = path.join(projectDir, 'src', 'api.js');
238
- if (fs.existsSync(path.dirname(apiDest))) {
239
- fs.copyFileSync(path.join(PKG_DIR, 'template', 'src', 'api.js'), apiDest);
240
- }
241
-
242
- fs.writeFileSync(path.join(srcTauri, '.lc-version'), VERSION);
243
- console.log(`\nUpdated to ${PKG_NAME}@${VERSION}\n`);
244
- }
245
-
246
- // ── Shared helpers ────────────────────────────────────────────────────────────
247
-
248
- function copyUserFiles(projectDir, overwrite) {
249
- for (const [src, dest] of [
250
- [
251
- path.join(PKG_DIR, 'template', 'config.json'),
252
- path.join(projectDir, 'config.json'),
253
- ],
254
- [
255
- path.join(PKG_DIR, 'template', '_gitignore'),
256
- path.join(projectDir, '.gitignore'),
257
- ],
258
- ]) {
259
- if (overwrite || !fs.existsSync(dest)) fs.copyFileSync(src, dest);
260
- }
261
- const destDir = path.join(projectDir, 'src');
262
- if (overwrite || !fs.existsSync(destDir)) {
263
- copyDir(path.join(PKG_DIR, 'template', 'src'), destDir);
264
- }
265
- }
266
-
267
- function writePackageJson(projectDir, name) {
268
- const pkgPath = path.join(projectDir, 'package.json');
269
- if (fs.existsSync(pkgPath)) return;
270
- fs.writeFileSync(
271
- pkgPath,
272
- JSON.stringify(
273
- {
274
- name,
275
- version: '1.0.0',
276
- private: true,
277
- scripts: { dev: 'anvil dev', build: 'anvil build' },
278
- devDependencies: { '@tauri-apps/cli': '^2' },
279
- },
280
- null,
281
- 2,
282
- ) + '\n',
283
- );
284
- }
285
-
286
- // ── Helpers ───────────────────────────────────────────────────────────────────
287
-
288
- function findBin(name, projectDir) {
289
- const candidates = [
290
- path.join(projectDir, 'node_modules', '.bin', name),
291
- path.join(PKG_DIR, '..', 'node_modules', '.bin', name),
292
- ];
293
- for (const bin of candidates) {
294
- if (fs.existsSync(bin) || fs.existsSync(bin + '.exe')) return bin;
295
- }
296
- return null;
297
- }
298
-
299
- // ── Tauri proxy ───────────────────────────────────────────────────────────────
300
-
301
- function runTauri(tauriCmd) {
302
- const projectDir = process.cwd();
303
- const srcTauri = path.join(projectDir, 'src-anvil');
304
-
305
- if (!fs.existsSync(srcTauri)) {
306
- console.log(`\nNo src-anvil/ found — running anvil init first...\n`);
307
- init();
308
- }
309
-
310
- const bin = findBin('tauri', projectDir);
311
- if (!bin) {
312
- process.stderr.write(
313
- `\n@tauri-apps/cli not found. Try reinstalling ${PKG_NAME}.\n\n`,
314
- );
315
- process.exit(1);
316
- }
317
-
318
- const env = { ...process.env };
319
- const targetDir = readConfigField(projectDir, 'target');
320
- if (targetDir) env.CARGO_TARGET_DIR = path.resolve(projectDir, targetDir);
321
-
322
- const result = spawnSync(bin, [tauriCmd], {
323
- stdio: 'inherit',
324
- shell: process.platform === 'win32',
325
- cwd: path.join(projectDir, 'src-anvil'),
326
- env,
327
- });
328
-
329
- process.exit(result.status ?? 1);
330
- }
331
-
332
- // ── CLI dispatch ──────────────────────────────────────────────────────────────
333
-
334
- const [, , cmd, arg] = process.argv;
335
-
336
- if (cmd === 'init') {
337
- init();
338
- } else if (cmd === 'dev') {
339
- runTauri('dev');
340
- } else if (cmd === 'build') {
341
- runTauri('build');
342
- } else if (cmd === 'update') {
343
- update();
344
- } else if (cmd === '--version' || cmd === '-v') {
345
- console.log(`${PKG_NAME}@${VERSION}`);
346
- } else if (!cmd || cmd === 'create' || !cmd.startsWith('-')) {
347
- create(cmd === 'create' ? arg || '.' : cmd || '.');
348
- } else {
349
- console.log(
350
- `\nUsage:\n npx ${PKG_NAME} <name>\n npx ${PKG_NAME} init\n npx ${PKG_NAME} dev\n npx ${PKG_NAME} build\n npx ${PKG_NAME} update\n`,
351
- );
352
- }