@bobschlowinskii/clicraft 0.4.4 → 0.4.6

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/CHANGELOG.md CHANGED
@@ -2,7 +2,29 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.4.4] - [TBA]
5
+ ## [0.4.6] - 2026-02-06
6
+
7
+ ### Added
8
+ - **Added `--last` option to `launch` command**
9
+ - Launches the last ran instance
10
+ - call with `--last` or `-l`
11
+ - **Allow user to launch server with `launch` command**
12
+
13
+ ## [0.4.5] - 2026-01-31
14
+
15
+ ### Added
16
+ - **Added `force` option to `create` command**
17
+ - Overwrites existing instance if folder already exists in path
18
+ - call with `--force` or `-f`
19
+ - **Added `path` option to `create` command**
20
+ - Specifies a path other than the working directory to install the instance
21
+ - call with `--path` or `-p`
22
+ - **Multi-mod installation support**
23
+ - Install multiple mods in a single command: `clicraft install mod1 mod2 mod3`
24
+ - Shows installation summary when installing multiple mods
25
+ - Tracks success/failure count for batch installations
26
+
27
+ ## [0.4.4] - 2026-01-29
6
28
 
7
29
  ### Added
8
30
 
@@ -482,7 +482,7 @@ export async function createInstance(options) {
482
482
  message: 'Instance Name:',
483
483
  validate: (input) => {
484
484
  if (!input.trim()) return 'Instance name is required';
485
- if (fs.existsSync(input.trim())) return 'A folder with this name already exists';
485
+ if (fs.existsSync(input.trim()) && !options.force) return 'A folder with this name already exists';
486
486
  return true;
487
487
  }
488
488
  }]);
@@ -526,7 +526,16 @@ export async function createInstance(options) {
526
526
  loaderVersion = await paginatedSelect('Forge Version:', forgeChoices);
527
527
  }
528
528
 
529
- const instancePath = path.resolve(instanceName.trim());
529
+ const instancePath = options.path
530
+ ? path.resolve(options.path) + path.resolve(instanceName.trim())
531
+ : path.resolve(instanceName.trim())
532
+ ;
533
+ if (fs.existsSync(instancePath) && !options.force) {
534
+ console.log(chalk.red(`A folder already exists at ${instancePath}. Use --force to overwrite.`));
535
+ return;
536
+ } else if (fs.existsSync(instancePath) && options.force) {
537
+ fs.rmSync(instancePath, { recursive: true, force: true });
538
+ }
530
539
  fs.mkdirSync(instancePath, { recursive: true });
531
540
  fs.mkdirSync(path.join(instancePath, 'mods'), { recursive: true });
532
541
 
@@ -5,12 +5,41 @@ import { downloadFile, loadConfig, saveConfig, getInstancePath, requireConfig }
5
5
  import { getProject, getProjectVersions } from '../helpers/modrinth.js';
6
6
  import { callPostCommandActions } from '../helpers/post-command.js';
7
7
 
8
- export async function installMod(modSlug, options) {
8
+ export async function installMod(modSlugs, options) {
9
9
  const instancePath = getInstancePath(options);
10
10
 
11
11
  const config = requireConfig(instancePath);
12
12
  if (!config) return;
13
13
 
14
+ // Handle multiple mods
15
+ const slugs = Array.isArray(modSlugs) ? modSlugs : [modSlugs];
16
+
17
+ let successCount = 0;
18
+ let failCount = 0;
19
+
20
+ for (const modSlug of slugs) {
21
+ const success = await installSingleMod(modSlug, instancePath, config, options);
22
+ if (success) {
23
+ successCount++;
24
+ } else {
25
+ failCount++;
26
+ }
27
+ }
28
+
29
+ // Show summary if multiple mods were requested
30
+ if (slugs.length > 1) {
31
+ console.log(chalk.cyan('\nšŸ“Š Installation Summary:'));
32
+ console.log(chalk.green(` āœ… ${successCount} mod(s) installed successfully`));
33
+ if (failCount > 0) {
34
+ console.log(chalk.red(` āŒ ${failCount} mod(s) failed to install`));
35
+ }
36
+ }
37
+
38
+ callPostCommandActions();
39
+ }
40
+
41
+ async function installSingleMod(modSlug, instancePath, config, options) {
42
+
14
43
  console.log(chalk.cyan(`\nšŸ“¦ Installing "${modSlug}" to ${config.name}...\n`));
15
44
 
16
45
  try {
@@ -19,12 +48,12 @@ export async function installMod(modSlug, options) {
19
48
  if (!project) {
20
49
  console.log(chalk.red(`Error: Mod "${modSlug}" not found on Modrinth.`));
21
50
  console.log(chalk.gray('Use "clicraft search <query>" to find available mods.'));
22
- return;
51
+ return false;
23
52
  }
24
53
 
25
54
  if (project.project_type !== 'mod') {
26
55
  console.log(chalk.red(`Error: "${modSlug}" is a ${project.project_type}, not a mod.`));
27
- return;
56
+ return false;
28
57
  }
29
58
 
30
59
  console.log(chalk.gray(`Found: ${project.title}`));
@@ -44,7 +73,7 @@ export async function installMod(modSlug, options) {
44
73
  console.log(chalk.gray(`\nAvailable loaders: ${loaders.join(', ')}`));
45
74
  console.log(chalk.gray(`Recent game versions: ${gameVersions.join(', ')}`));
46
75
  }
47
- return;
76
+ return false;
48
77
  }
49
78
 
50
79
  // Use the latest compatible version
@@ -53,7 +82,7 @@ export async function installMod(modSlug, options) {
53
82
 
54
83
  if (!file) {
55
84
  console.log(chalk.red('Error: No downloadable file found for this version.'));
56
- return;
85
+ return false;
57
86
  }
58
87
 
59
88
  // Check if already installed
@@ -61,7 +90,7 @@ export async function installMod(modSlug, options) {
61
90
  if (existingMod && !options.force) {
62
91
  console.log(chalk.yellow(`\nāš ļø ${project.title} is already installed (version ${existingMod.versionNumber})`));
63
92
  console.log(chalk.gray('Use --force to reinstall or update.'));
64
- return;
93
+ return false;
65
94
  }
66
95
 
67
96
  // Create mods folder if needed
@@ -104,30 +133,52 @@ export async function installMod(modSlug, options) {
104
133
  // Show dependencies if any
105
134
  if (version.dependencies?.length > 0) {
106
135
  const requiredDeps = version.dependencies.filter(d => d.dependency_type === 'required');
107
- if (requiredDeps.length > 0) {
108
- console.log(chalk.yellow(`\nāš ļø This mod has ${requiredDeps.length} required dependencies:`));
109
- for (const dep of requiredDeps) {
110
- if (dep.project_id) {
111
- const depProject = await getProject(dep.project_id);
112
- if (depProject) {
113
- const isInstalled = config.mods.some(m => m.projectId === dep.project_id);
114
- const status = isInstalled ? chalk.green('āœ“') : chalk.red('āœ—');
115
- console.log(chalk.gray(` ${status} ${depProject.title} (${depProject.slug})`));
116
- }
136
+ let totalDeps = [];
137
+ let notInstalledDeps = [];
138
+
139
+ for (const dep of requiredDeps) {
140
+ if (dep.project_id) {
141
+ const depProject = await getProject(dep.project_id);
142
+ if (depProject) {
143
+ const isInstalled = config.mods.some(m => m.projectId === dep.project_id);
144
+ // const status = isInstalled ? chalk.green('āœ“') : chalk.red('āœ—');
145
+ totalDeps.push({ project: depProject, title: depProject.title, slug: depProject.slug, installed: isInstalled });
117
146
  }
118
147
  }
148
+ }
149
+
150
+ for (const dep of totalDeps) {
151
+ if (!dep.installed) {
152
+ notInstalledDeps.push(dep);
153
+ }
154
+ }
155
+
156
+ if (notInstalledDeps.length > 0) {
157
+ console.log(chalk.yellow(`\nāš ļø This mod has ${notInstalledDeps.length} dependencies which are not installed:`));
158
+ totalDeps.forEach(dep => {
159
+ if(dep.installed)
160
+ console.log(chalk.green(` - ${dep.title} (${dep.slug})`));
161
+ if(!dep.installed)
162
+ console.log(chalk.yellow(` - ${dep.title} (${dep.slug})`));
163
+ });
119
164
  console.log(chalk.gray('\nInstall dependencies with: clicraft install <slug>'));
120
165
  }
166
+
167
+ if (notInstalledDeps.length === 0) {
168
+ console.log(chalk.green('\nāœ… All dependencies are already installed.'));
169
+
170
+ }
121
171
  }
122
172
 
173
+ return true;
174
+
123
175
  } catch (error) {
124
176
  console.error(chalk.red('Error installing mod:'), error.message);
125
177
  if (options.verbose) {
126
178
  console.error(error);
127
179
  }
180
+ return false;
128
181
  }
129
-
130
- callPostCommandActions();
131
182
  }
132
183
 
133
184
  export default { installMod };
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import { spawn } from 'child_process';
5
5
  import { refreshAuth } from './auth.js';
6
6
  import { captureGameSettings } from '../commands/config.js';
7
- import { loadSettings, writeGameSettings, getAliasByName } from '../helpers/config.js';
7
+ import { loadSettings, writeGameSettings, getAliasByName, saveSettings } from '../helpers/config.js';
8
8
  import {
9
9
  loadConfig,
10
10
  getInstancePath,
@@ -12,6 +12,7 @@ import {
12
12
  mavenToPath
13
13
  } from '../helpers/utils.js';
14
14
  import { callPostCommandActions } from '../helpers/post-command.js';
15
+ import { startServer } from '../helpers/server.js';
15
16
 
16
17
  // Find Java executable
17
18
  function findJava() {
@@ -126,7 +127,7 @@ export async function launchInstance(aliasOrOptions, options) {
126
127
  }
127
128
 
128
129
  const settings = loadSettings();
129
-
130
+
130
131
  // Resolve instance path from alias, --instance flag, or current directory
131
132
  let instancePath;
132
133
  if (alias) {
@@ -145,7 +146,23 @@ export async function launchInstance(aliasOrOptions, options) {
145
146
  }
146
147
  }
147
148
  } else {
148
- instancePath = getInstancePath(opts);
149
+ if (opts.last) {
150
+ if (!settings.lastInstance) {
151
+ console.log(chalk.red('Error: No last instance found in settings.'));
152
+ return;
153
+ }
154
+
155
+ if (!fs.existsSync(settings.lastInstance)) {
156
+ console.log(chalk.red('Error: Last instance path does not exist.'));
157
+ return;
158
+ }
159
+
160
+ instancePath = settings.lastInstance;
161
+ console.log(chalk.gray(`Using last instance → ${instancePath}`));
162
+ }
163
+ else {
164
+ instancePath = getInstancePath(opts);
165
+ }
149
166
  }
150
167
 
151
168
  // Apply saved game settings if enabled
@@ -163,11 +180,14 @@ export async function launchInstance(aliasOrOptions, options) {
163
180
  if (!config) return;
164
181
 
165
182
  if (config.type === 'server') {
166
- console.log(chalk.red('Error: This is a server instance. Use ./start.sh to start the server.'));
183
+ console.log(chalk.yellow(`\n Detected server instance "${config.name}". Starting server...\n`));
184
+ startServer(instancePath);
167
185
  return;
168
186
  }
169
187
 
170
188
  console.log(chalk.cyan(`\nšŸŽ® Launching ${config.name}...\n`));
189
+ settings.lastInstance = instancePath;
190
+ saveSettings(settings);
171
191
 
172
192
  try {
173
193
  // Get authentication
@@ -47,6 +47,8 @@ This is perfect for sharing modpack configurations or replicating setups across
47
47
  | Option | Description |
48
48
  |--------|-------------|
49
49
  | `--verbose` | Enable verbose output to see detailed progress |
50
+ | `--force, -f` | Overwrites existing instance folder if necessary |
51
+ | `--path, -p <path>` | Specifies path in which to install instance |
50
52
 
51
53
  ## šŸ’” Interactive Prompts
52
54
 
@@ -14,12 +14,12 @@ Install mods from Modrinth directly to your Minecraft instance.
14
14
  ## šŸ“ Synopsis
15
15
 
16
16
  ```bash
17
- clicraft install <mod> [options]
17
+ clicraft install <mods...> [options]
18
18
  ```
19
19
 
20
20
  ## šŸ“– Description
21
21
 
22
- The `install` command downloads and installs mods from Modrinth to your Minecraft instance. It automatically:
22
+ The `install` command downloads and installs one or more mods from Modrinth to your Minecraft instance. It automatically:
23
23
 
24
24
  1. Searches for the mod on Modrinth
25
25
  2. Finds the correct version for your Minecraft version and loader
@@ -31,7 +31,7 @@ The `install` command downloads and installs mods from Modrinth to your Minecraf
31
31
 
32
32
  | Argument | Description | Required |
33
33
  |----------|-------------|----------|
34
- | `<mod>` | Mod name or Modrinth project ID | Yes |
34
+ | `<mods...>` | One or more mod names or Modrinth project IDs | Yes (at least one) |
35
35
 
36
36
  ## šŸŽÆ Options
37
37
 
@@ -69,9 +69,13 @@ Use the project ID from `clicraft search` results.
69
69
  ### Install multiple mods
70
70
  ```bash
71
71
  cd my-instance
72
- clicraft install sodium
73
- clicraft install lithium
74
- clicraft install iris
72
+ clicraft install sodium lithium iris
73
+ ```
74
+
75
+ When installing multiple mods, you'll see a summary at the end:
76
+ ```
77
+ šŸ“Š Installation Summary:
78
+ āœ… 3 mod(s) installed successfully
75
79
  ```
76
80
 
77
81
  ### Verbose installation
@@ -130,33 +134,25 @@ instance-directory/
130
134
  ### Building a Performance Pack
131
135
  ```bash
132
136
  cd my-instance
133
- clicraft install sodium # Better FPS
134
- clicraft install lithium # Server optimization
135
- clicraft install starlight # Lighting engine
136
- clicraft install ferritecore # Memory optimization
137
+ clicraft install sodium lithium starlight ferritecore
137
138
  ```
138
139
 
139
140
  ### Adding Quality of Life Mods
140
141
  ```bash
141
142
  cd my-instance
142
- clicraft install "just enough items" # Recipe viewer
143
- clicraft install "journeymap" # Minimap
144
- clicraft install "appleskin" # Food info
143
+ clicraft install jei journeymap appleskin
145
144
  ```
146
145
 
147
146
  ### Installing Shader Support
148
147
  ```bash
149
148
  cd my-instance
150
- clicraft install sodium # Required for Iris
151
- clicraft install iris # Shader loader
149
+ clicraft install sodium iris
152
150
  ```
153
151
 
154
152
  ### Server Mods
155
153
  ```bash
156
154
  cd my-server
157
- clicraft install lithium
158
- clicraft install starlight
159
- clicraft install "fabric api"
155
+ clicraft install lithium starlight fabric-api
160
156
  ```
161
157
 
162
158
  ## āš ļø Important Notes
@@ -33,8 +33,9 @@ The `launch` command starts your Minecraft instance with all the correct Java ar
33
33
  | Option | Description | Default |
34
34
  |--------|-------------|---------|
35
35
  | `-i, --instance <path>` | Path to instance directory | Current directory |
36
- | `--offline` | Launch in offline mode (no authentication) | false |
37
- | `--verbose` | Show detailed launch information | false |
36
+ | `--offline` | Launch in offline mode (no authentication) | `false` |
37
+ | `--verbose` | Show detailed launch information | `false` |
38
+ | `-l, --last` | Launch last ran instance | `false` |
38
39
 
39
40
  ## šŸ“‹ Examples
40
41
 
@@ -70,7 +70,7 @@ This should display the current version of CLIcraft.
70
70
 
71
71
  ```bash
72
72
  cd clicraft
73
- git pull origin main
73
+ git pull origin live
74
74
  npm install -g
75
75
  ```
76
76
 
package/helpers/config.js CHANGED
@@ -29,6 +29,7 @@ const DEFAULT_SETTINGS = {
29
29
  checkUpdates: true,
30
30
  autoSaveToConfig: true,
31
31
  autoLoadConfigOnLaunch: true,
32
+ lastInstance: "",
32
33
  };
33
34
 
34
35
  // Default game settings to apply to new instances
@@ -0,0 +1,32 @@
1
+ import path from "path";
2
+ import { spawn } from "child_process";
3
+ import fs from "fs";
4
+ import chalk from "chalk";
5
+
6
+ export function startServer(instancePath) {
7
+ const startScript = process.platform === 'win32' ? 'start.bat' : 'start.sh';
8
+ const startPath = path.join(instancePath, startScript);
9
+
10
+ if (!fs.existsSync(startPath)) {
11
+ console.error(chalk.red(`Error: Start script not found at ${startPath}`));
12
+ return;
13
+ }
14
+
15
+ const serverProcess = spawn(startPath, [], {
16
+ cwd: instancePath,
17
+ shell: true,
18
+ stdio: 'inherit'
19
+ });
20
+
21
+ serverProcess.on('error', (err) => {
22
+ console.error(chalk.red(`Failed to start server: ${err.message}`));
23
+ });
24
+
25
+ serverProcess.on('exit', (code) => {
26
+ if (code === 0) {
27
+ console.log(chalk.green('Server stopped successfully.'));
28
+ } else {
29
+ console.error(chalk.red(`Server exited with code ${code}`));
30
+ }
31
+ });
32
+ }
@@ -1,16 +1,8 @@
1
1
  const versions = [
2
2
  '0.1.0',
3
- '0.2.0',
4
- '0.2.1',
5
- '0.2.2',
6
- '0.3.0',
7
- '0.3.1',
8
- '0.3.2',
9
- '0.4.0',
10
- '0.4.1',
11
- '0.4.2',
12
- '0.4.3',
13
- '0.4.4',
3
+ '0.2.0', '0.2.1', '0.2.2',
4
+ '0.3.0', '0.3.1', '0.3.2',
5
+ '0.4.0', '0.4.1', '0.4.2', '0.4.3', '0.4.4', '0.4.5', '0.4.6'
14
6
  ];
15
7
 
16
8
 
package/index.js CHANGED
@@ -31,11 +31,13 @@ program
31
31
  .command('create')
32
32
  .description('Create a new Minecraft instance')
33
33
  .option('--verbose', 'Enable verbose output')
34
+ .option('-f, --force', 'Overwrite existing instance if it exists')
35
+ .option('-p, --path <path>', 'Path to create the instance in')
34
36
  .action(createInstance);
35
37
 
36
38
  program
37
- .command('install <mod>')
38
- .description('Install a mod to the current Minecraft instance')
39
+ .command('install <mods...>')
40
+ .description('Install one or more mods to the current Minecraft instance')
39
41
  .option('-i, --instance <path>', 'Path to the instance directory')
40
42
  .option('-f, --force', 'Force reinstall/update if already installed')
41
43
  .option('--verbose', 'Enable verbose output')
@@ -62,6 +64,7 @@ program
62
64
  .option('-i, --instance <path>', 'Path to the instance directory')
63
65
  .option('--offline', 'Launch in offline mode')
64
66
  .option('--verbose', 'Enable verbose output')
67
+ .option('-l, --last', 'Launch the last used instance')
65
68
  .action(launchInstance);
66
69
 
67
70
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobschlowinskii/clicraft",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "A simple Minecraft Mod Package Manager written in Node.JS",
5
5
  "main": "index.js",
6
6
  "type": "module",