@idajs/create-mod 0.1.44-dev.51

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.
@@ -0,0 +1,100 @@
1
+ const { execSync } = require("child_process");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+
6
+ // Function to read IdaJS installation directory from .idajs.json
7
+ function getIdaJsPath() {
8
+ // Try local config first, then user home config
9
+ const configPaths = [path.join(__dirname, ".idajs.json"), path.join(os.homedir(), ".idajs.json")];
10
+
11
+ for (const configPath of configPaths) {
12
+ if (fs.existsSync(configPath)) {
13
+ try {
14
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
15
+ if (config.installDir) {
16
+ return config.installDir;
17
+ }
18
+ } catch (err) {
19
+ // Ignore parse errors and try next config
20
+ }
21
+ }
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ // Get package name from command line argument
28
+ const packageName = process.argv[2];
29
+
30
+ if (!packageName) {
31
+ console.error("Error: Package name is required as an argument");
32
+ console.error("Usage: node sync.js <package-name>");
33
+ process.exit(1);
34
+ }
35
+
36
+ // Get IdaJS installation path
37
+ const idaJsPath = getIdaJsPath();
38
+ if (!idaJsPath) {
39
+ console.error("Error: .idajs.json not found in current directory or user home directory.");
40
+ console.error("Please build IdaJS first or create .idajs.json with 'installDir' property.");
41
+ process.exit(1);
42
+ }
43
+
44
+ if (!fs.existsSync(idaJsPath)) {
45
+ console.error(`Error: IdaJS installation directory does not exist: ${idaJsPath}`);
46
+ console.error("Please build IdaJS first.");
47
+ process.exit(1);
48
+ }
49
+
50
+ const isTypeScriptProject = (() => {
51
+ // Check if tsconfig.json exists
52
+ if (!fs.existsSync("tsconfig.json")) {
53
+ return false;
54
+ }
55
+
56
+ // Check if there are any .ts files in src/
57
+ if (!fs.existsSync("src")) {
58
+ return false;
59
+ }
60
+
61
+ const files = fs.readdirSync("src");
62
+ return files.some((file) => file.endsWith(".ts"));
63
+ })();
64
+
65
+ const targetBase = path.join(idaJsPath, "GameRun", "mods", packageName);
66
+ const deleteExcludeArgs = '--delete-exclude "*.ida" --delete-exclude "*.md5"';
67
+
68
+ console.log(`Syncing ${packageName}...`);
69
+
70
+ // If TypeScript project, compile first
71
+ if (isTypeScriptProject) {
72
+ console.log("TypeScript project detected, compiling...");
73
+
74
+ // Clean dist folder
75
+ if (fs.existsSync("dist")) {
76
+ console.log("Cleaning dist folder...");
77
+ fs.rmSync("dist", { recursive: true, force: true });
78
+ }
79
+
80
+ // Compile TypeScript
81
+ execSync("tsc", { stdio: "inherit" });
82
+ console.log("Compilation complete!");
83
+ }
84
+
85
+ // Sync media folder if it exists
86
+ if (fs.existsSync("media")) {
87
+ const mediaSyncCmd = `idasync media ${path.join(targetBase, "media")} ${deleteExcludeArgs}`;
88
+ console.log(`Running: ${mediaSyncCmd}`);
89
+ execSync(mediaSyncCmd, { stdio: "inherit" });
90
+ } else {
91
+ console.log("No media folder found, skipping media sync");
92
+ }
93
+
94
+ // Sync src folder
95
+ const sourceFolder = isTypeScriptProject ? "dist" : "src";
96
+ const srcSyncCmd = `idasync ${sourceFolder} ${targetBase} ${deleteExcludeArgs} --delete-exclude "media/*"`;
97
+ console.log(`Running: ${srcSyncCmd}`);
98
+ execSync(srcSyncCmd, { stdio: "inherit" });
99
+
100
+ console.log("Sync completed!");
@@ -0,0 +1,11 @@
1
+ {
2
+ "printWidth": 100,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": false,
7
+ "trailingComma": "es5",
8
+ "bracketSpacing": true,
9
+ "arrowParens": "always",
10
+ "endOfLine": "lf"
11
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2021",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2021", "DOM"],
6
+ "outDir": "dist",
7
+ "removeComments": true,
8
+ "allowJs": true,
9
+ "checkJs": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "baseUrl": ".",
13
+ "types": ["@idajs/types"],
14
+ "typeRoots": ["./node_modules/@types", "./node_modules"],
15
+ "moduleResolution": "node",
16
+ "esModuleInterop": true,
17
+ "forceConsistentCasingInFileNames": true
18
+ },
19
+ "include": ["src/**/*.ts", "src/**/*.js"],
20
+ "exclude": ["node_modules"]
21
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Watches for file changes in the src/ and media/ directories
3
+ * and runs the sync script to copy changed files to the game mod directory.
4
+ * Exits when the LBA2.exe process is no longer running.
5
+ */
6
+
7
+ const chokidar = require("chokidar");
8
+ const { spawn, exec } = require("child_process");
9
+ const { promisify } = require("util");
10
+
11
+ const execAsync = promisify(exec);
12
+ const PROC_NAME = "LBA2.exe";
13
+
14
+ let syncing = null;
15
+ function runSync() {
16
+ if (syncing) return; // prevent overlapping syncs
17
+ syncing = spawn("npm", ["run", "sync"], { stdio: "inherit", shell: true });
18
+ syncing.on("exit", () => (syncing = null));
19
+ }
20
+
21
+ const watcher = chokidar.watch(["src/**/*.js", "media/**/*.png"], {
22
+ ignoreInitial: true,
23
+ });
24
+
25
+ watcher.on("all", () => runSync());
26
+
27
+ async function isProcRunning() {
28
+ try {
29
+ const { stdout } = await execAsync(
30
+ `powershell -Command "Get-Process | Where-Object {$_.ProcessName -eq '${PROC_NAME.replace(".exe", "")}'} | Select-Object -First 1"`,
31
+ {
32
+ timeout: 5000,
33
+ }
34
+ );
35
+ return stdout.trim().length > 0;
36
+ } catch (error) {
37
+ // Process not found or command failed
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function killGameProc() {
43
+ try {
44
+ await execAsync(
45
+ `powershell -Command "Stop-Process -Name '${PROC_NAME.replace(".exe", "")}' -Force -ErrorAction SilentlyContinue"`
46
+ );
47
+ } catch (e) {
48
+ // Process may already be stopped
49
+ }
50
+ }
51
+
52
+ const interval = setInterval(async () => {
53
+ try {
54
+ const alive = await isProcRunning();
55
+ if (!alive) {
56
+ console.log(`${PROC_NAME} not running. Exiting watcher.`);
57
+
58
+ clearInterval(interval);
59
+ await watcher.close();
60
+ process.exit(0);
61
+ }
62
+ } catch (e) {
63
+ // if process listing fails, you can decide to ignore or stop
64
+ }
65
+ }, 1000);
66
+
67
+ // Ctrl+C cleanup
68
+ process.on("SIGINT", async () => {
69
+ console.log("Caught interrupt signal. Exiting watcher.");
70
+
71
+ clearInterval(interval);
72
+ await watcher.close();
73
+ await killGameProc();
74
+
75
+ process.exit(0);
76
+ });
@@ -0,0 +1,26 @@
1
+ # Media Assets
2
+
3
+ This folder contains custom media assets for your mod.
4
+
5
+ ## Directory Structure
6
+
7
+ You can create `images` and `sprites` folders here to have custom PNG files for your IdaJS mod:
8
+
9
+ ```
10
+ media/
11
+ ├── images/
12
+ │ ├── my-dialog-bg.png
13
+ │ ├── cutscene-01.png
14
+ │ └── ...
15
+ └── sprites/
16
+ ├── item-icon.png
17
+ ├── character-portrait.png
18
+ └── ...
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ - **`images/`** - Full-screen dialog backgrounds and standalone images
24
+ - **`sprites/`** - In-dialog pictures and smaller graphics used within dialogs
25
+
26
+ All PNG files will be automatically synced to your IdaJS installation when you run `npm start` or `npm run sync`.
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "PROJECT_NAME",
3
+ "description": "Do not run the code in this folder. It's a template for creating new IdaJS projects."
4
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Welcome to IdaJS modding!
3
+ * Read the documentation here: https://ida.innerbytes.com
4
+ */
5
+ console.log("Welcome to the IdaJS project!\n");
6
+
7
+ /**
8
+ * Start with this event handler to setup every scene, the mod needs to modify
9
+ */
10
+ scene.addEventListener(scene.Events.afterLoadScene, (sceneId, sceneLoadMode) => {
11
+ // Setup initial behavior for the Twinsen house scene here
12
+ if (sceneId === 0) {
13
+ // Do something with Twinsen and Zoe objects here.
14
+ // See Samples for mod examples
15
+ const twinsen = scene.getObject(0);
16
+ const zoe = scene.getObject(4);
17
+
18
+ // Handle move script if you will run custom coroutines on the object
19
+ // twinsen.handleMoveScript();
20
+
21
+ // Handle life script if you want to have custom behavior on the object
22
+ // twinsen.handleLifeScript(myLifeScriptFunction);
23
+
24
+ // Register your coroutines
25
+ registerCoroutine("myCoroutine", myCoroutineFunction);
26
+
27
+ // Do more setup - add and modify Zones, Waypoints, Objects, etc - as needed
28
+
29
+ // If new game started, do initialization of your game variables here as well, as needed:
30
+ if (sceneLoadMode === scene.LoadModes.NewGameStarted) {
31
+ const gameStore = useGameStore();
32
+ gameStore.myVariable = "some value";
33
+ }
34
+
35
+ // If it's any other load mode, except loading from saved state, do scene state initialization here - init scene variables, start some coroutines, that should run from scene start, etc
36
+ if (sceneLoadMode !== scene.LoadModes.WillLoadSavedState) {
37
+ const sceneStore = useSceneStore();
38
+ sceneStore.mySceneVariable = 42;
39
+
40
+ // Start the previously registered "myCoroutine" on object 0 (Twinsen), with some arguments
41
+ // startCoroutine(0, "myCoroutine", 1, 2, 3, 4);
42
+ }
43
+ }
44
+ // Setup initial behavior for the outside Twinsen house scene here
45
+ else if (sceneId === 49) {
46
+ // Scene setup
47
+ }
48
+
49
+ // Etc
50
+ // Look at https://lba2remake.net to see the needed scene ids
51
+ // Teleport to the desired scene in the Editor, the id will be shown in the inspector
52
+ /*
53
+ else if (sceneId === 1) {
54
+ // Scene setup
55
+ }
56
+ */
57
+ });
58
+
59
+ // Example of a coroutine function with some arguments
60
+ function* myCoroutineFunction(arg1, arg2, arg3, etc) {
61
+ // Your coroutine code goes here
62
+ yield doMove(ida.Move.TM_ANIM, 0);
63
+ }
@@ -0,0 +1,66 @@
1
+ import { SceneLoadMode } from "@idajs/types";
2
+
3
+ /**
4
+ * Welcome to IdaJS modding!
5
+ * Read the documentation here: https://ida.innerbytes.com
6
+ */
7
+ console.log("Welcome to the IdaJS project!\n");
8
+
9
+ /**
10
+ * Start with this event handler to setup every scene, the mod needs to modify
11
+ */
12
+ scene.addEventListener(
13
+ scene.Events.afterLoadScene,
14
+ (sceneId: number, sceneLoadMode: SceneLoadMode) => {
15
+ // Setup initial behavior for the Twinsen house scene here
16
+ if (sceneId === 0) {
17
+ // Do something with Twinsen and Zoe objects here.
18
+ // See Samples for mod examples
19
+ const twinsen = scene.getObject(0);
20
+ const zoe = scene.getObject(4);
21
+
22
+ // Handle move script if you will run custom coroutines on the object
23
+ // twinsen.handleMoveScript();
24
+
25
+ // Handle life script if you want to have custom behavior on the object
26
+ // twinsen.handleLifeScript(myLifeScriptFunction);
27
+
28
+ // Register your coroutines
29
+ registerCoroutine("myCoroutine", myCoroutineFunction);
30
+
31
+ // Do more setup - add and modify Zones, Waypoints, Objects, etc - as needed
32
+
33
+ // If new game started, do initialization of your game variables here as well, as needed:
34
+ if (sceneLoadMode === scene.LoadModes.NewGameStarted) {
35
+ const gameStore = useGameStore();
36
+ gameStore.myVariable = "some value";
37
+ }
38
+
39
+ // If it's any other load mode, except loading from saved state, do scene state initialization here - init scene variables, start some coroutines, that should run from scene start, etc
40
+ if (sceneLoadMode !== scene.LoadModes.WillLoadSavedState) {
41
+ const sceneStore = useSceneStore();
42
+ sceneStore.mySceneVariable = 42;
43
+
44
+ // Start the previously registered "myCoroutine" on object 0 (Twinsen), with some arguments
45
+ // startCoroutine(0, "myCoroutine", 1, true, "three", [4]);
46
+ }
47
+ }
48
+ // Setup initial behavior for the outside Twinsen house scene here
49
+ else if (sceneId === 49) {
50
+ // Scene setup
51
+ }
52
+
53
+ // Etc
54
+ // Look at https://lba2remake.net to see the needed scene ids
55
+ // Teleport to the desired scene in the Editor, the id will be shown in the inspector
56
+ // else if (sceneId === <neededSceneId>) {
57
+ // // Scene setup
58
+ // }
59
+ }
60
+ );
61
+
62
+ // Example of a coroutine function with some arguments
63
+ function* myCoroutineFunction(arg1: number, arg2: boolean, arg3: string, etc: number[]) {
64
+ // Your coroutine code goes here
65
+ yield doMove(ida.Move.TM_ANIM, 0);
66
+ }
package/index.js ADDED
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const { execSync } = require("child_process");
7
+ const IdaSync = require("@idajs/sync");
8
+
9
+ // Parse command line arguments
10
+ const args = process.argv.slice(2);
11
+
12
+ // Helper to get argument value
13
+ function getArgValue(argName) {
14
+ const index = args.findIndex((arg) => arg === argName || arg.startsWith(`${argName}=`));
15
+ if (index === -1) return null;
16
+ const arg = args[index];
17
+ if (arg.includes("=")) {
18
+ return arg.split("=")[1];
19
+ }
20
+ return args[index + 1];
21
+ }
22
+
23
+ const projectName = args.find((arg) => !arg.startsWith("--"));
24
+ const targetDirectory = getArgValue("--dir") || getArgValue("--directory");
25
+ const idajsDir = getArgValue("--idajs-dir") || getArgValue("--idajs");
26
+ const useTypeScript = args.includes("--typescript") || args.includes("--ts");
27
+ const useJavaScript = args.includes("--javascript") || args.includes("--js");
28
+ const skipInstall = args.includes("--skip-install");
29
+ const helpRequested = args.includes("--help") || args.includes("-h");
30
+
31
+ // Show help if requested
32
+ if (helpRequested) {
33
+ console.log(`
34
+ Usage: npx @idajs/create-mod [project-name] [options]
35
+
36
+ Arguments:
37
+ project-name Name of your mod project (default: my-ida-mod)
38
+
39
+ Options:
40
+ --dir, --directory <path> Target directory (default: project name)
41
+ --idajs-dir, --idajs <path> IdaJS installation directory
42
+ --typescript, --ts Use TypeScript
43
+ --javascript, --js Use JavaScript
44
+ --skip-install Skip npm install
45
+ -h, --help Show this help message
46
+
47
+ Examples:
48
+ npx @idajs/create-mod
49
+ npx @idajs/create-mod my-awesome-mod
50
+ npx @idajs/create-mod my-mod --typescript
51
+ npx @idajs/create-mod my-mod --dir ./projects/my-mod --idajs C:/Projects/idajs --js
52
+ npx @idajs/create-mod my-mod --skip-install
53
+ `);
54
+ process.exit(0);
55
+ }
56
+
57
+ // Function to read IdaJS directory from config file
58
+ function getIdaJsDirFromConfig() {
59
+ const configPath = path.join(os.homedir(), ".idajs.json");
60
+ try {
61
+ if (fs.existsSync(configPath)) {
62
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
63
+ if (config.installDir) {
64
+ return config.installDir;
65
+ }
66
+ }
67
+ } catch (err) {
68
+ // Ignore errors, will prompt user
69
+ }
70
+ return null;
71
+ }
72
+
73
+ async function main() {
74
+ console.log("Welcome to IdaJS Mod Creator!\n");
75
+
76
+ // Determine if we need prompts
77
+ const needsPrompts =
78
+ !projectName || (!useTypeScript && !useJavaScript) || (!idajsDir && !getIdaJsDirFromConfig());
79
+
80
+ let config = {
81
+ projectName: projectName || "my-ida-mod",
82
+ targetDirectory: targetDirectory,
83
+ idajsDir: idajsDir,
84
+ language: useTypeScript ? "ts" : useJavaScript ? "js" : null,
85
+ install: !skipInstall,
86
+ };
87
+
88
+ // Try to get IdaJS directory from config if not provided via CLI
89
+ if (!config.idajsDir) {
90
+ const configDir = getIdaJsDirFromConfig();
91
+ if (configDir) {
92
+ config.idajsDir = configDir;
93
+ console.log(`Found IdaJS installation: ${configDir}\n`);
94
+ } else {
95
+ console.log("⚠️ Warning: ~/.idajs.json not found. Make sure you have built IdaJS first.\n");
96
+ }
97
+ }
98
+
99
+ // Use prompts only if needed
100
+ if (needsPrompts) {
101
+ const prompts = require("prompts");
102
+
103
+ const questions = [];
104
+
105
+ if (!projectName) {
106
+ questions.push({
107
+ type: "text",
108
+ name: "projectName",
109
+ message: "Project name:",
110
+ initial: "my-ida-mod",
111
+ validate: (value) => (value.length > 0 ? true : "Project name cannot be empty"),
112
+ });
113
+ }
114
+
115
+ if (!config.targetDirectory) {
116
+ questions.push({
117
+ type: "text",
118
+ name: "targetDirectory",
119
+ message: "Target directory:",
120
+ initial: (prev) => prev || config.projectName,
121
+ });
122
+ }
123
+
124
+ if (!config.idajsDir) {
125
+ questions.push({
126
+ type: "text",
127
+ name: "idajsDir",
128
+ message: "IdaJS installation directory:",
129
+ validate: (value) => {
130
+ if (!value || value.length === 0) {
131
+ return "IdaJS installation directory is required";
132
+ }
133
+ if (!fs.existsSync(value)) {
134
+ return `Directory does not exist: ${value}`;
135
+ }
136
+ return true;
137
+ },
138
+ });
139
+ }
140
+
141
+ if (!config.language) {
142
+ questions.push({
143
+ type: "select",
144
+ name: "language",
145
+ message: "Select language:",
146
+ choices: [
147
+ { title: "JavaScript", value: "js" },
148
+ { title: "TypeScript", value: "ts" },
149
+ ],
150
+ initial: 0,
151
+ });
152
+ }
153
+
154
+ const response = await prompts(questions, {
155
+ onCancel: () => {
156
+ console.log("\nCancelled by user");
157
+ process.exit(1);
158
+ },
159
+ });
160
+
161
+ config = { ...config, ...response };
162
+ }
163
+
164
+ // Set target directory to project name if not specified
165
+ if (!config.targetDirectory) {
166
+ config.targetDirectory = config.projectName;
167
+ }
168
+
169
+ const targetDir = path.isAbsolute(config.targetDirectory)
170
+ ? config.targetDirectory
171
+ : path.join(process.cwd(), config.targetDirectory);
172
+
173
+ // Validate IdaJS directory exists
174
+ if (!fs.existsSync(config.idajsDir)) {
175
+ console.error(`\n❌ Error: IdaJS installation directory does not exist: ${config.idajsDir}`);
176
+ process.exit(1);
177
+ }
178
+
179
+ // Check if directory already exists
180
+ if (fs.existsSync(targetDir)) {
181
+ console.error(`\n❌ Error: Directory already exists: ${targetDir}`);
182
+ process.exit(1);
183
+ }
184
+
185
+ console.log(`\nCreating IdaJS mod in ${targetDir}...`);
186
+ console.log(`Language: ${config.language === "js" ? "JavaScript" : "TypeScript"}`);
187
+ console.log(`IdaJS: ${config.idajsDir}\n`);
188
+
189
+ try {
190
+ // Create project directory
191
+ fs.mkdirSync(targetDir, { recursive: true });
192
+
193
+ // Get paths
194
+ const templatesDir = path.join(__dirname, "_project.template");
195
+
196
+ // Copy all files and folders from _project.template using IdaSync
197
+ console.log("Copying template files...");
198
+
199
+ // Exclude files based on selected language
200
+ const copyExclusions =
201
+ config.language === "js"
202
+ ? ["src/index.ts", "tsconfig.json"]
203
+ : ["src/index.js", "jsconfig.json"];
204
+
205
+ const sync = new IdaSync({
206
+ verbose: false,
207
+ copyExclusions: copyExclusions,
208
+ });
209
+
210
+ await sync.sync(templatesDir, targetDir);
211
+ console.log("✓ Copied template structure");
212
+
213
+ // Modify package.template.json
214
+ const packageTemplatePath = path.join(targetDir, "package.template.json");
215
+ const packageTemplate = JSON.parse(fs.readFileSync(packageTemplatePath, "utf8"));
216
+ packageTemplate.name = config.projectName;
217
+ packageTemplate.description = "This is a new IdaJS project";
218
+ fs.writeFileSync(packageTemplatePath, JSON.stringify(packageTemplate, null, 2) + "\n");
219
+ console.log("✓ Updated package.template.json");
220
+
221
+ // Create .idajs.json config file
222
+ const idajsConfig = {
223
+ installDir: config.idajsDir,
224
+ };
225
+ fs.writeFileSync(
226
+ path.join(targetDir, ".idajs.json"),
227
+ JSON.stringify(idajsConfig, null, 2) + "\n"
228
+ );
229
+ console.log("✓ Created .idajs.json");
230
+
231
+ // Call Samples/install.js to set up the project
232
+ console.log("\nSetting up development environment...");
233
+ const installScript = path.join(__dirname, "install.js");
234
+ const installArgs = [installScript, targetDir];
235
+
236
+ if (!config.install) {
237
+ installArgs.push("--skip-install");
238
+ }
239
+
240
+ execSync(`node ${installArgs.map((arg) => `"${arg}"`).join(" ")}`, {
241
+ stdio: "inherit",
242
+ });
243
+
244
+ // Post-process package.json to remove setup script, author, and private properties
245
+ const finalPackageJsonPath = path.join(targetDir, "package.json");
246
+ const finalPackageJson = JSON.parse(fs.readFileSync(finalPackageJsonPath, "utf8"));
247
+
248
+ if (finalPackageJson.scripts && finalPackageJson.scripts.setup) {
249
+ delete finalPackageJson.scripts.setup;
250
+ }
251
+ if (finalPackageJson.author !== undefined) {
252
+ delete finalPackageJson.author;
253
+ }
254
+ if (finalPackageJson.private !== undefined) {
255
+ delete finalPackageJson.private;
256
+ }
257
+
258
+ // Remove TypeScript from devDependencies if it's a JavaScript project
259
+ if (
260
+ config.language === "js" &&
261
+ finalPackageJson.devDependencies &&
262
+ finalPackageJson.devDependencies.typescript
263
+ ) {
264
+ delete finalPackageJson.devDependencies.typescript;
265
+ }
266
+
267
+ fs.writeFileSync(finalPackageJsonPath, JSON.stringify(finalPackageJson, null, 2) + "\n");
268
+ console.log("✓ Configured package.json");
269
+
270
+ // Remove package.template.json as it's no longer needed
271
+ fs.unlinkSync(packageTemplatePath);
272
+ console.log("✓ Cleaned up template files");
273
+
274
+ // Success message
275
+ console.log(`\n✅ Successfully created "${config.projectName}"!\n`);
276
+ console.log("Next steps:");
277
+ console.log(` cd ${path.relative(process.cwd(), targetDir) || config.targetDirectory}`);
278
+ if (!config.install) {
279
+ console.log(" npm install");
280
+ }
281
+ console.log(" npm start\n");
282
+ console.log("Happy modding! 🎮");
283
+ } catch (error) {
284
+ console.error(`\n❌ Error creating project: ${error.message}`);
285
+ process.exit(1);
286
+ }
287
+ }
288
+
289
+ main().catch((error) => {
290
+ console.error("Unexpected error:", error);
291
+ process.exit(1);
292
+ });