@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/dist/wvr.mjs +3920 -0
- package/package.json +28 -29
- package/src/cli/builder.ts +0 -280
- package/src/cli/commands/build.ts +0 -34
- package/src/cli/commands/dev.ts +0 -92
- package/src/cli/commands/init.ts +0 -228
- package/src/cli/commands/link.ts +0 -126
- package/src/cli/index.ts +0 -31
- package/src/cli/utils/config.ts +0 -16
- package/src/cli/utils/wow-path.ts +0 -44
- package/src/schema.ts +0 -18
package/package.json
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@weaver-wow/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Command line toolchain for building Weaver addons",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
}
|
package/src/cli/builder.ts
DELETED
|
@@ -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
|
-
}
|
package/src/cli/commands/dev.ts
DELETED
|
@@ -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
|
-
}
|
package/src/cli/commands/init.ts
DELETED
|
@@ -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
|
-
}
|