@weaver-wow/cli 1.0.0 → 1.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.
package/package.json CHANGED
@@ -1,29 +1,28 @@
1
- {
2
- "name": "@weaver-wow/cli",
3
- "version": "1.0.0",
4
- "description": "Command line toolchain for building Weaver addons",
5
- "type": "module",
6
- "bin": {
7
- "wvr": "src/cli/index.ts"
8
- },
9
- "files": [
10
- "src",
11
- "README.md"
12
- ],
13
- "license": "MIT",
14
- "author": "Mike Eling <mike.eling97@gmail.com>",
15
- "dependencies": {
16
- "@clack/prompts": "^1.0.0",
17
- "commander": "^14.0.3",
18
- "picocolors": "^1.1.1",
19
- "zod": "^4.3.6",
20
- "@weaver-wow/core": "^1.0.0",
21
- "typescript-to-lua": "^1.33.2"
22
- },
23
- "scripts": {
24
- "build": "echo 'Building weaver CLI...'"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- }
29
- }
1
+ {
2
+ "name": "@weaver-wow/cli",
3
+ "version": "1.1.0",
4
+ "description": "Command line toolchain for building Weaver addons",
5
+ "bin": {
6
+ "wvr": "dist/wvr.mjs"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Mike Eling <mike.eling97@gmail.com>",
14
+ "devDependencies": {
15
+ "@clack/prompts": "^1.0.0",
16
+ "commander": "^14.0.3",
17
+ "picocolors": "^1.1.1",
18
+ "zod": "^4.3.6",
19
+ "@weaver-wow/core": "^1.1.0",
20
+ "typescript-to-lua": "^1.33.2"
21
+ },
22
+ "scripts": {
23
+ "build": "bun build src/cli/index.ts --outfile dist/wvr.mjs --target node --format esm"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }
@@ -1,280 +0,0 @@
1
- import { $ } from "bun";
2
- import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, unlinkSync } from "node:fs";
3
- import { dirname, join, relative } from "node:path";
4
- import { WeaverConfig } from "../schema";
5
- import picocolors from "picocolors";
6
- import { createRequire } from "node:module";
7
-
8
- const require = createRequire(import.meta.url);
9
-
10
- const GENERATED_TSTL_CONFIG = ".tstl.build.json";
11
-
12
- /**
13
- * Build the addon project.
14
- *
15
- * The builder:
16
- * 1. Reads the base tsconfig.tstl.json template
17
- * 2. Generates a temporary build config with the correct entry point and include
18
- * 3. Runs TSTL to compile TypeScript → Lua
19
- * 4. Post-processes the Lua output (namespace wrapping)
20
- * 5. Generates the .toc manifest
21
- * 6. Scans for assets
22
- */
23
- export async function buildProject(config: WeaverConfig | null, silent = false) {
24
- if (!silent) console.log("Building...");
25
-
26
- let pkg: any = {};
27
- if (existsSync("package.json")) {
28
- try {
29
- pkg = JSON.parse(readFileSync("package.json", "utf-8"));
30
- } catch {}
31
- }
32
-
33
- const addonName = config?.addonName || (pkg.name ? pkg.name.charAt(0).toUpperCase() + pkg.name.slice(1) : "MyAddon");
34
- const interfaceVersion = config?.interface || "110000";
35
- const author = pkg.author || "Unknown";
36
- const version = pkg.version || "1.0.0";
37
- const description = pkg.description || "Generated by Weaver";
38
- // Pre-flight checks
39
- if (!existsSync("tsconfig.json") && !existsSync("tsconfig.tstl.json")) {
40
- throw new Error(`Missing ${picocolors.bold("tsconfig.json")}. \n Run ${picocolors.cyan("wvr init")} to set up your project structure.`);
41
- }
42
-
43
- // Discovery of entry point if not specified
44
- let entryPoint = config?.entry;
45
- if (!entryPoint) {
46
- const potentialEntries = ["src/App.tsx", "src/index.tsx", "src/App.ts", "src/index.ts"];
47
- for (const pe of potentialEntries) {
48
- if (existsSync(pe)) {
49
- entryPoint = pe;
50
- break;
51
- }
52
- }
53
- }
54
-
55
- // Fallback to default if still not found
56
- const finalEntryPoint = entryPoint || "src/App.tsx";
57
-
58
- if (!existsSync(finalEntryPoint)) {
59
- const others = ["src/App.tsx", "src/index.tsx", "src/App.ts", "src/index.ts"].filter(pe => pe !== finalEntryPoint && existsSync(pe));
60
- let suggestion = "";
61
- if (others.length > 0) {
62
- suggestion = `\n ${picocolors.yellow("Suggestion:")} Found ${picocolors.cyan(others[0])}. Did you mean to use that?`;
63
- }
64
- throw new Error(`Entry point not found: ${picocolors.red(finalEntryPoint)}${suggestion}\n Check your ${picocolors.bold("weaver.config.ts")} or ensure the source file exists.`);
65
- }
66
-
67
- if (!existsSync("dist")) mkdirSync("dist");
68
-
69
- // 1. Generate build-time TSTL config
70
- const buildConfig = generateBuildConfig(finalEntryPoint);
71
-
72
- try {
73
- // 2. Run TSTL with the generated config
74
- if (silent) {
75
- await $`bun x tstl -p ${GENERATED_TSTL_CONFIG}`.quiet();
76
- } else {
77
- await $`bun x tstl -p ${GENERATED_TSTL_CONFIG}`;
78
- }
79
- } catch (e: any) {
80
- let rawError = (e.stdout?.toString() || "") + (e.stderr?.toString() || "") + (e.message || "");
81
-
82
- // Parse TSTL/TS errors for pretty printing
83
- const lines = rawError.split('\n');
84
- const formattedErrors: string[] = [];
85
-
86
- for (const line of lines) {
87
- // Match pattern: path/to/file.tsx(line,col): error TSXXXX: message
88
- const match = line.match(/^(.+)\((\d+),(\d+)\): error (TS\d+): (.+)$/);
89
- if (match) {
90
- const [_, file, lineNum, colNum, code, msg] = match;
91
- let processedMsg = msg;
92
-
93
- if (msg.includes("Cannot find module '@weaver-wow/core'")) {
94
- processedMsg += `\n ${picocolors.yellow("Tip:")} Run ${picocolors.bold("bun install")} to link the Weaver runtime.`;
95
- }
96
-
97
- formattedErrors.push(
98
- ` ${picocolors.dim(file)}:${picocolors.cyan(lineNum)}:${picocolors.cyan(colNum)}\n` +
99
- ` ${picocolors.red("✖")} ${picocolors.white(processedMsg)} ${picocolors.dim(`[${code}]`)}`
100
- );
101
- } else if (line.includes("error TSTL")) {
102
- let msg = line.trim();
103
- let tip = "";
104
- if (msg.includes("'@weaver-wow/core'")) {
105
- tip = `\n ${picocolors.yellow("Tip:")} Run ${picocolors.bold("bun install")} or ensure Weaver is in your node_modules.`;
106
- }
107
- formattedErrors.push(` ${picocolors.red("✖")} ${picocolors.white(msg)}${tip}`);
108
- }
109
- }
110
-
111
- if (formattedErrors.length > 0) {
112
- throw new Error(`Compilation failed:\n\n${formattedErrors.join('\n\n')}`);
113
- }
114
-
115
- throw new Error(`Compilation failed with unknown error:\n${rawError}`);
116
- } finally {
117
- // Always clean up the temporary config
118
- // cleanupBuildConfig();
119
- }
120
-
121
- // 3. Post-Process — wrap in addon namespace
122
- const bundlePath = "./dist/App.lua";
123
- let luaContent = "";
124
-
125
- if (existsSync(bundlePath)) {
126
- luaContent = readFileSync(bundlePath, "utf-8");
127
- } else {
128
- throw new Error(`Could not find generated Lua file (${bundlePath})`);
129
- }
130
-
131
- const wrappedLua = `
132
- local addonName, ns = ...
133
- -- [WEAVER RUNTIME STUB]
134
- ${luaContent}
135
- `.trim();
136
-
137
- writeFileSync("./dist/Core.lua", wrappedLua);
138
-
139
- // Remove intermediate bundle (Core.lua is the final artifact)
140
- try { unlinkSync(bundlePath); } catch {}
141
-
142
- // 4. Generate TOC
143
- const tocContent = `## Interface: ${interfaceVersion}
144
- ## Title: ${addonName}
145
- ## Notes: ${description}
146
- ## Author: ${author}
147
- ## Version: ${version}
148
-
149
- Core.lua
150
- `;
151
- writeFileSync(`./dist/${addonName}.toc`, tocContent);
152
-
153
- // 5. Assets
154
- let assetCount = 0;
155
- if (existsSync("assets")) {
156
- const files = readdirSync("assets");
157
- for (const file of files) {
158
- if (file.endsWith(".png")) {
159
- assetCount++;
160
- }
161
- }
162
- }
163
-
164
- return { addonName, assetCount };
165
- }
166
-
167
- /**
168
- * Generate a temporary TSTL config that extends the base template
169
- * with the correct entry point and include paths.
170
- */
171
- function generateBuildConfig(entryPoint: string): string {
172
- console.log("BUILDER UPDATED V2");
173
- // Read the base template
174
- const baseConfigPath = existsSync("tsconfig.tstl.json") ? "tsconfig.tstl.json" : "tsconfig.json";
175
- const baseConfig = JSON.parse(readFileSync(baseConfigPath, "utf-8"));
176
-
177
- // Determine the entry's directory for include patterns
178
- const entryDir = dirname(entryPoint);
179
-
180
- // Extend include with the entry point
181
- const include = [
182
- ...(baseConfig.include || []),
183
- entryPoint,
184
- ];
185
-
186
- // Build the final config
187
- // Heuristic:
188
- // 1. If CWD has package.json with "weaver" dependency, use node_modules (Consumer Project)
189
- // 2. Else if we are in the monorepo structure, use relative source paths (Monorepo specific)
190
-
191
- let isConsumerProject = false;
192
- console.log("CWD:", process.cwd());
193
- if (existsSync("package.json")) {
194
- try {
195
- const pkgC = readFileSync("package.json", "utf-8");
196
- const pkg = JSON.parse(pkgC);
197
- console.log("Found package.json with deps:", Object.keys(pkg.dependencies || {}));
198
- if ((pkg.dependencies && pkg.dependencies["@weaver-wow/core"]) || (pkg.devDependencies && pkg.devDependencies["@weaver-wow/core"])) {
199
- isConsumerProject = true;
200
- }
201
- } catch (e) { console.log("Error reading pkg", e); }
202
- }
203
- console.log("isConsumerProject:", isConsumerProject);
204
-
205
- let weaverPath: string;
206
- let jsxRuntimePath: string;
207
- let includePaths: string[] = [];
208
-
209
- // Check if we are running from source (monorepo) or installed package
210
- const absoluteRootDir = join(dirname(import.meta.url.replace("file:///", "")), "..", "..", "..", "..");
211
-
212
- if (isConsumerProject) {
213
- // We are in a consumer project, resolve from node_modules
214
- // The project's node_modules should have weaver installed
215
- weaverPath = "node_modules/@weaver-wow/core/src/index.ts";
216
- jsxRuntimePath = "node_modules/@weaver-wow/core/src/types/react-jsx-runtime.d.ts";
217
- includePaths = [
218
- "node_modules/@weaver-wow/core/src/**/*.ts",
219
- "node_modules/@weaver-wow/core/src/**/*.tsx"
220
- ];
221
- } else if (existsSync(join(absoluteRootDir, "packages", "weaver"))) {
222
- // We are in the monorepo (e.g. running sandbox tests)
223
- const rootDir = relative(process.cwd(), absoluteRootDir) || ".";
224
- weaverPath = join(rootDir, "packages/weaver/src/index.ts");
225
- jsxRuntimePath = join(rootDir, "packages/weaver/src/types/react-jsx-runtime.d.ts");
226
- includePaths = [
227
- join(rootDir, "packages/weaver/src/**/*.ts"),
228
- join(rootDir, "packages/weaver/src/**/*.tsx"),
229
- ];
230
- } else {
231
- // Fallback: linked but not detecting monorepo (should be covered by isConsumerProject if linked correctly)
232
- // or strange install location. Default to node_modules
233
- weaverPath = "node_modules/@weaver-wow/core/src/index.ts";
234
- jsxRuntimePath = "node_modules/@weaver-wow/core/src/types/react-jsx-runtime.d.ts";
235
- includePaths = [
236
- "node_modules/@weaver-wow/core/src/**/*.ts",
237
- "node_modules/@weaver-wow/core/src/**/*.tsx"
238
- ];
239
- }
240
-
241
- const buildConfig = {
242
- ...baseConfig,
243
- compilerOptions: {
244
- ...baseConfig.compilerOptions,
245
- preserveSymlinks: true,
246
- paths: {
247
- ...(baseConfig.compilerOptions?.paths || {}),
248
- "@weaver-wow/core": [weaverPath],
249
- "react/jsx-runtime": [jsxRuntimePath]
250
- }
251
- },
252
- tstl: {
253
- ...baseConfig.tstl,
254
- luaBundleEntry: entryPoint,
255
- },
256
- include: [
257
- ...include,
258
- ...includePaths
259
- ],
260
- };
261
-
262
- // Write the temporary config
263
- if (process.env.DEBUG) console.log("Generated config:", JSON.stringify(buildConfig, null, 2));
264
- writeFileSync(GENERATED_TSTL_CONFIG, JSON.stringify(buildConfig, null, 2));
265
-
266
- return GENERATED_TSTL_CONFIG;
267
- }
268
-
269
- /**
270
- * Clean up the temporary build config file.
271
- */
272
- function cleanupBuildConfig(): void {
273
- try {
274
- if (existsSync(GENERATED_TSTL_CONFIG)) {
275
- unlinkSync(GENERATED_TSTL_CONFIG);
276
- }
277
- } catch {
278
- // Non-critical — don't block build on cleanup failure
279
- }
280
- }
@@ -1,34 +0,0 @@
1
-
2
- import { intro, outro, spinner } from "@clack/prompts";
3
- import picocolors from "picocolors";
4
- import { loadConfig } from "../utils/config";
5
- import { buildProject } from "../builder";
6
-
7
- export async function buildCommand() {
8
- intro(picocolors.bgBlue(" 🧵 Weaver Build "));
9
-
10
- const s = spinner();
11
- s.start("Reading configuration...");
12
-
13
- const config = await loadConfig();
14
-
15
- s.message(`Building configured project...`);
16
-
17
- try {
18
- const result = await buildProject(config, true);
19
- s.stop(picocolors.green("Build complete!"));
20
- outro(`Built ${picocolors.cyan(result.addonName)} successfully to ${picocolors.bold("dist/")}`);
21
- } catch (e: any) {
22
- s.stop(picocolors.red("Build failed"));
23
-
24
- console.log("");
25
- const message = e.message || String(e);
26
- message.split('\n').forEach(line => {
27
- console.log(` ${line}`);
28
- });
29
- console.log("");
30
-
31
- outro(picocolors.red("Build failed. Fix the issues above and try again."));
32
- process.exit(1);
33
- }
34
- }
@@ -1,92 +0,0 @@
1
-
2
- import { watch } from "node:fs";
3
- import { intro, outro, spinner } from "@clack/prompts";
4
- import picocolors from "picocolors";
5
- import { loadConfig } from "../utils/config";
6
- import { buildProject } from "../builder";
7
- import { join } from "node:path";
8
-
9
- export async function devCommand() {
10
- intro(picocolors.bgBlue(" 🧵 Weaver Dev "));
11
-
12
- const s = spinner();
13
- s.start("Starting development server...");
14
-
15
- const config = await loadConfig();
16
-
17
- // Initial build
18
- try {
19
- await buildProject(config, true);
20
- s.stop(picocolors.green("Initial build complete!"));
21
- } catch (e) {
22
- s.stop(picocolors.red("Initial build failed"));
23
- console.error(e);
24
- // Continue watching even if build fails? Yes, to fix errors.
25
- }
26
-
27
- console.log(picocolors.cyan(`Watching for changes in ${picocolors.bold("src/")}...`));
28
-
29
- let isBuilding = false;
30
-
31
- // Recursive watch on src/
32
- // Debounce simply by ignoring triggers while building?
33
- watch("src", { recursive: true }, async (event, filename) => {
34
- if (filename && (filename.endsWith(".ts") || filename.endsWith(".tsx"))) {
35
- if (isBuilding) return;
36
- isBuilding = true;
37
-
38
- console.log(picocolors.dim(`File changed: ${filename}`));
39
- const rebuildSpinner = spinner();
40
- rebuildSpinner.start("Rebuilding...");
41
-
42
- try {
43
- // Determine if we need full build or partial?
44
- // For MVP, full build via buildProject.
45
- const start = performance.now();
46
- await buildProject(config, true);
47
- const end = performance.now();
48
- rebuildSpinner.stop(picocolors.green(`Rebuilt in ${(end - start).toFixed(0)}ms`));
49
- } catch (e) {
50
- rebuildSpinner.stop(picocolors.red("Rebuild failed"));
51
- if (e instanceof Error) console.error(e.message);
52
- } finally {
53
- isBuilding = false;
54
- }
55
- }
56
- });
57
-
58
- // Also watch sandbox if present?
59
- // Since we moved sandbox to root, we should watch it too if configured.
60
- // However, sandbox usually lives outside src in this setup.
61
- // Let's watch 'sandbox' too if it exists.
62
- // Or watch root/sandbox.
63
-
64
- // Using fs.watch implies we need a process keep-alive?
65
- // Node.js keeps process alive as long as watcher is active.
66
-
67
- // Watch sandbox
68
- try {
69
- watch("sandbox", { recursive: true }, async (event, filename) => {
70
- if (filename && (filename.endsWith(".ts") || filename.endsWith(".tsx"))) {
71
- if (isBuilding) return;
72
- isBuilding = true;
73
- console.log(picocolors.dim(`Sandbox file changed: ${filename}`));
74
- const rb = spinner();
75
- rb.start("Rebuilding...");
76
- try {
77
- const start = performance.now();
78
- await buildProject(config, true);
79
- const end = performance.now();
80
- rb.stop(picocolors.green(`Rebuilt (sandbox) in ${(end - start).toFixed(0)}ms`));
81
- } catch (e) {
82
- rb.stop(picocolors.red("Rebuild failed"));
83
- if (e instanceof Error) console.error(e.message);
84
- } finally {
85
- isBuilding = false;
86
- }
87
- }
88
- });
89
- } catch {
90
- // sandbox might not exist or verify failed
91
- }
92
- }
@@ -1,228 +0,0 @@
1
-
2
- import { intro, outro, text, select, confirm, spinner } from "@clack/prompts";
3
- import { findWoWPath } from "../utils/wow-path";
4
- import { writeFileSync, existsSync, mkdirSync } from "node:fs";
5
- import picocolors from "picocolors";
6
-
7
- export async function initCommand(options: any) {
8
- intro(picocolors.bgBlue(" 🧵 Weaver Init "));
9
-
10
- // 1. Metadata Discovery
11
- let defaultName = "MyAddon";
12
- let defaultAuthor = "Me";
13
-
14
- if (existsSync("package.json")) {
15
- try {
16
- const pkg = JSON.parse(await Bun.file("package.json").text());
17
- if (pkg.name) defaultName = pkg.name;
18
- if (pkg.author) defaultAuthor = pkg.author;
19
- } catch {}
20
- }
21
-
22
- let wowPath = "";
23
- let addonName = defaultName;
24
- let flavor = "_retail_";
25
-
26
- if (options.yes) {
27
- // NON-INTERACTIVE MODE
28
- wowPath = (await findWoWPath()) || "C:\\Program Files (x86)\\World of Warcraft";
29
- } else {
30
- // INTERACTIVE MODE
31
- // 2. WoW Path Discovery
32
- const path = await findWoWPath();
33
- wowPath = path || "";
34
-
35
- if (path) {
36
- const confirmPath = await confirm({
37
- message: `Found WoW at ${picocolors.cyan(path)}. Use this?`,
38
- initialValue: true
39
- });
40
-
41
- if (!confirmPath) {
42
- const manualPath = await text({
43
- message: "Enter the path to your World of Warcraft installation:",
44
- placeholder: "C:\\Program Files (x86)\\World of Warcraft",
45
- validate(value) {
46
- if (value.length === 0) return "Path is required";
47
- }
48
- });
49
- if (typeof manualPath === 'symbol') { process.exit(0); }
50
- wowPath = manualPath;
51
- }
52
- } else {
53
- const manualPath = await text({
54
- message: "Could not auto-detect WoW. Enter the path manually:",
55
- placeholder: "C:\\Program Files (x86)\\World of Warcraft",
56
- validate(value) {
57
- if (value.length === 0) return "Path is required";
58
- }
59
- });
60
- if (typeof manualPath === 'symbol') { process.exit(0); }
61
- wowPath = manualPath;
62
- }
63
-
64
- // 3. Project Configuration
65
- const addonNameInput = await text({
66
- message: "Addon Name (used for folder name and TOC):",
67
- placeholder: defaultName,
68
- validate(value) {
69
- if (value && !/^[a-zA-Z0-9]+$/.test(value)) return "Only alphanumeric characters allowed";
70
- }
71
- });
72
- if (typeof addonNameInput === 'symbol') { process.exit(0); }
73
- addonName = (addonNameInput || defaultName) as string;
74
-
75
- const flavorInput = await select({
76
- message: "Target Game Version:",
77
- options: [
78
- { value: "_retail_", label: "Retail (The War Within)" },
79
- { value: "_classic_", label: "Cataclysm Classic" },
80
- { value: "_classic_era_", label: "Classic Era / Hardcore" },
81
- ],
82
- }) as string;
83
- if (typeof flavorInput === 'symbol') { process.exit(0); }
84
- flavor = flavorInput;
85
- }
86
-
87
- // Map flavor to default interface version
88
- const interfaceMap: Record<string, string> = {
89
- "_retail_": "110007",
90
- "_classic_": "40400",
91
- "_classic_era_": "11503",
92
- };
93
- const interfaceVersion = interfaceMap[flavor] || "110000";
94
-
95
- const s = spinner();
96
- s.start("Scaffolding your Weaver project...");
97
-
98
- // 4. File Generation
99
-
100
- // weaver.config.ts
101
- const configContent = `
102
- import type { WeaverConfig } from "@weaver-wow/core";
103
-
104
- const config: WeaverConfig = {
105
- addonName: "${addonName}",
106
- flavor: "${flavor}",
107
- interface: "${interfaceVersion}",
108
- wowPath: String.raw\`${wowPath}\`,
109
- };
110
-
111
- export default config;
112
- `.trim();
113
- writeFileSync("weaver.config.ts", configContent);
114
-
115
- // tsconfig.json (Standard TSTL + Weaver setup)
116
- const tsconfigContent = `
117
- {
118
- "compilerOptions": {
119
- "target": "ESNext",
120
- "lib": ["ESNext"],
121
- "moduleResolution": "Node",
122
- "types": [],
123
- "jsx": "react",
124
- "jsxFactory": "createElement",
125
- "strict": false,
126
- "skipLibCheck": true,
127
- "sourceMap": false,
128
- "baseUrl": ".",
129
- "paths": {
130
- "react/jsx-runtime": ["node_modules/@weaver-wow/core/src/types/react-jsx-runtime.d.ts"]
131
- }
132
- },
133
- "tstl": {
134
- "luaTarget": "5.1",
135
- "luaBundle": "dist/App.lua",
136
- "noHeader": true
137
- },
138
- "include": ["src/**/*.tsx", "src/**/*.ts", "src/**/*.d.ts"]
139
- }
140
- `.trim();
141
- if (!existsSync("tsconfig.json")) {
142
- writeFileSync("tsconfig.json", tsconfigContent);
143
- }
144
-
145
- // src/App.tsx (Starter code)
146
- if (!existsSync("src")) {
147
- mkdirSync("src");
148
- }
149
-
150
- if (!existsSync("src/App.tsx") && !existsSync("src/index.tsx")) {
151
- const appContent = `
152
- import {
153
- useState,
154
- useEffect,
155
- Frame,
156
- Texture,
157
- Button,
158
- mount,
159
- createElement
160
- } from '@weaver-wow/core';
161
-
162
- const ${addonName} = () => {
163
- const [clicks, setClicks] = useState("clickCount", 0);
164
-
165
- useEffect(() => {
166
- print("${addonName} initialized!");
167
- }, []);
168
-
169
- return (
170
- <Frame name="${addonName}Frame" size={[220, 100]} setPoint={["CENTER", 0, 0]}>
171
- <Texture setAllPoints={true} color={[0, 0, 0, 0.6]} />
172
- <Button
173
- text={\`Clicks: \${clicks}\`}
174
- onClick={() => setClicks(clicks + 1)}
175
- />
176
- </Frame>
177
- );
178
- };
179
-
180
- mount("${addonName}", () => ${addonName}());
181
- `.trim();
182
- writeFileSync("src/App.tsx", appContent);
183
- }
184
-
185
- // package.json (if missing)
186
- if (!existsSync("package.json")) {
187
- const packageContent = {
188
- name: addonName.toLowerCase(),
189
- version: "0.1.0",
190
- private: true,
191
- type: "module",
192
- scripts: {
193
- "build": "bun x wvr build",
194
- "dev": "bun x wvr dev",
195
- "link": "bun x wvr link"
196
- },
197
- dependencies: {
198
- "@weaver-wow/core": "^1.0.0"
199
- },
200
- devDependencies: {
201
- "typescript-to-lua": "latest"
202
- }
203
- };
204
- writeFileSync("package.json", JSON.stringify(packageContent, null, 2));
205
- }
206
-
207
- // .gitignore
208
- const gitignoreContent = `
209
- node_modules/
210
- dist/
211
- .tstl.build.json
212
- *.log
213
- `.trim();
214
- if (!existsSync(".gitignore")) {
215
- writeFileSync(".gitignore", gitignoreContent);
216
- }
217
-
218
- s.stop("Project initialized successfully!");
219
-
220
- outro(`
221
- ${picocolors.green("Project layout created:")}
222
- - ${picocolors.cyan("src/App.tsx")} (Addon source)
223
- - ${picocolors.cyan("weaver.config.ts")} (Addon metadata)
224
- - ${picocolors.cyan("tsconfig.json")} (Compiler settings)
225
-
226
- Run ${picocolors.bold("bun install")} and then ${picocolors.bold("bun run dev")} to start coding!
227
- `);
228
- }