sandstone-cli 2.1.3 → 2.2.1

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/CLAUDE.md ADDED
@@ -0,0 +1,107 @@
1
+ # Sandstone CLI
2
+
3
+ Command-line interface for Sandstone projects. Provides `sand` (project commands) and `create-sandstone` (project scaffolding).
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ |---------|-------------|
9
+ | `sand create <name>` | Create a new Sandstone project |
10
+ | `sand build` | Build the datapack/resourcepack |
11
+ | `sand watch` | Build and rebuild on file changes |
12
+ | `sand install` | Install Smithed libraries |
13
+ | `sand uninstall` | Remove Smithed libraries |
14
+ | `sand refresh` | Clear library cache |
15
+
16
+ ## Project Structure
17
+
18
+ ```
19
+ sandstone-cli/
20
+ ├── src/
21
+ │ ├── commands/ # CLI command implementations
22
+ │ │ ├── create.ts # create-sandstone command
23
+ │ │ ├── build.ts # sand build
24
+ │ │ ├── watch.ts # sand watch (uses hot-hook for HMR in Node, modifies require.cache in Bun)
25
+ │ │ └── dependency.ts # install/uninstall/refresh
26
+ │ ├── launchers/ # Minecraft installation detection
27
+ │ │ ├── types.ts # LauncherProvider interface
28
+ │ │ ├── registry.ts # Provider registry
29
+ │ │ ├── index.ts # Auto-registers all providers
30
+ │ │ └── providers/ # Vanilla, Prism, Modrinth
31
+ │ ├── ui/ # Terminal UI components (Ink/React)
32
+ │ ├── utils.ts # Shared utilities
33
+ │ └── index.ts # Main CLI entry (Commander.js)
34
+ ├── lib/ # Built output (gitignored)
35
+ ├── scripts/
36
+ │ └── test-harness.ts # Interactive CLI testing tool
37
+ └── .test-runs/ # Test output directory (gitignored)
38
+ ```
39
+
40
+ ## Build Commands
41
+
42
+ ```bash
43
+ bun dev:build # Compile TypeScript and bundle
44
+ bun bundle # Bundle only (after tsc, don't use this one directly)
45
+ ```
46
+
47
+ ## Test Harness
48
+
49
+ The test harness allows programmatic testing of interactive CLI prompts using node-pty.
50
+
51
+ ```bash
52
+ # Create a project with pre-programmed responses
53
+ bun test:harness create my-pack --responses '[
54
+ ["n", "enter"], # Not a library
55
+ ["enter"], # Default version
56
+ ["My Pack", "enter"], # Pack name
57
+ ["mypack", "enter"], # Namespace
58
+ ["up", "enter"], # Save location: None
59
+ ["enter"] # Package manager: bun
60
+ ]'
61
+
62
+ # View test runs
63
+ bun test:harness list
64
+
65
+ # Clean up
66
+ bun test:harness cleanup
67
+ ```
68
+
69
+ Test runs are saved to `.test-runs/<name>/` with a `test-run.log` containing cleaned output.
70
+
71
+ **Response format:**
72
+ - `["text", "enter"]` - Type text and press enter
73
+ - `["enter"]` - Accept default
74
+ - `["down", "enter"]` - Navigate and select
75
+ - Special keys: `enter`, `up`, `down`, `space`, `tab`, `escape`, `backspace`
76
+
77
+ ## Launcher Detection
78
+
79
+ The CLI detects Minecraft installations across multiple launchers.
80
+
81
+ Paths are platform-aware (Windows, macOS, Linux including Flatpak).
82
+
83
+ ### Adding a New Launcher
84
+
85
+ 1. Create `src/launchers/providers/<name>.ts` implementing `LauncherProvider`
86
+ 2. Export and register in `src/launchers/index.ts`
87
+
88
+ ## Dependencies
89
+
90
+ - `commander` - CLI argument parsing
91
+ - `@inquirer/prompts` - Interactive prompts
92
+ - `ink` + `react` - Terminal UI components
93
+ - `@sandstone-mc/hot-hook` - HMR for watch mode
94
+ - `@parcel/watcher` - File system watching
95
+
96
+ ## Create Command Flow
97
+
98
+ 1. Ask if library or pack
99
+ 2. Select Sandstone version
100
+ 3. Enter pack name and namespace
101
+ 4. Select save location (triggers launcher detection)
102
+ 5. Select Minecraft instance (if root/world chosen)
103
+ 6. Select world (if world chosen)
104
+ 7. Select package manager
105
+ 8. Clone template, install deps, configure
106
+
107
+ The `--root`, `--world`, `--client-path`, `--server-path` flags skip relevant prompts.
package/bun.lock CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
- "configVersion": 0,
3
+ "configVersion": 1,
4
4
  "workspaces": {
5
5
  "": {
6
6
  "name": "sandstone-cli",
@@ -29,11 +29,16 @@
29
29
  "@types/fs-extra": "^11.0.2",
30
30
  "@types/react": "^19.2.10",
31
31
  "@types/semver": "^7.5.3",
32
+ "bun-types": "^1.3.9",
33
+ "node-pty": "^1.1.0",
32
34
  "sandstone": "^1.0.0-beta.1",
33
35
  "typescript": "^5.2.2",
34
36
  },
35
37
  },
36
38
  },
39
+ "trustedDependencies": [
40
+ "@parcel/watcher",
41
+ ],
37
42
  "packages": {
38
43
  "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ=="],
39
44
 
@@ -155,6 +160,8 @@
155
160
 
156
161
  "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
157
162
 
163
+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
164
+
158
165
  "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
159
166
 
160
167
  "chalk-template": ["chalk-template@1.1.2", "", { "dependencies": { "chalk": "^5.2.0" } }, "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA=="],
@@ -301,6 +308,8 @@
301
308
 
302
309
  "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
303
310
 
311
+ "node-pty": ["node-pty@1.1.0", "", { "dependencies": { "node-addon-api": "^7.1.0" } }, "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg=="],
312
+
304
313
  "normalize-package-data": ["normalize-package-data@8.0.0", "", { "dependencies": { "hosted-git-info": "^9.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ=="],
305
314
 
306
315
  "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="],
package/lib/create.js CHANGED
@@ -5680,12 +5680,12 @@ var {
5680
5680
  import figlet from "figlet";
5681
5681
 
5682
5682
  // src/version.ts
5683
- var CLI_VERSION = "2.1.3";
5683
+ var CLI_VERSION = "2.2.1";
5684
5684
 
5685
5685
  // src/commands/create.ts
5686
5686
  var import_semver = __toESM(require_semver2(), 1);
5687
5687
  var import_fs_extra = __toESM(require_lib(), 1);
5688
- import path2 from "path";
5688
+ import path5 from "path";
5689
5689
 
5690
5690
  // node_modules/chalk/source/vendor/ansi-styles/index.js
5691
5691
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -6410,6 +6410,298 @@ function getWorldsList(clientPath) {
6410
6410
  return fs.readdirSync(savesPath, { withFileTypes: true }).filter((f) => f.isDirectory).map((f) => f.name);
6411
6411
  }
6412
6412
 
6413
+ // src/launchers/registry.ts
6414
+ var providers = new Map;
6415
+ function registerProvider(provider) {
6416
+ providers.set(provider.type, provider);
6417
+ }
6418
+ function getProviders() {
6419
+ return Array.from(providers.values());
6420
+ }
6421
+ async function discoverAllInstances() {
6422
+ const result = {
6423
+ instances: [],
6424
+ errors: new Map
6425
+ };
6426
+ const discoveries = await Promise.allSettled(getProviders().map(async (provider) => {
6427
+ const instances = await provider.discoverInstances();
6428
+ return { type: provider.type, instances };
6429
+ }));
6430
+ for (const discovery of discoveries) {
6431
+ if (discovery.status === "fulfilled") {
6432
+ result.instances.push(...discovery.value.instances);
6433
+ } else {
6434
+ const error = discovery.reason;
6435
+ console.error("Discovery error:", error.message);
6436
+ }
6437
+ }
6438
+ return result;
6439
+ }
6440
+ // src/launchers/providers/vanilla.ts
6441
+ import fs2 from "fs";
6442
+ import path2 from "path";
6443
+ import os3 from "os";
6444
+ function getVanillaPath() {
6445
+ let mcPath;
6446
+ switch (os3.platform()) {
6447
+ case "win32":
6448
+ mcPath = path2.join(os3.homedir(), "AppData/Roaming/.minecraft");
6449
+ break;
6450
+ case "darwin":
6451
+ mcPath = path2.join(os3.homedir(), "Library/Application Support/minecraft");
6452
+ break;
6453
+ case "linux":
6454
+ default:
6455
+ mcPath = path2.join(os3.homedir(), ".minecraft");
6456
+ break;
6457
+ }
6458
+ return fs2.existsSync(mcPath) ? mcPath : null;
6459
+ }
6460
+ var vanillaProvider = {
6461
+ type: "vanilla",
6462
+ displayName: "Vanilla Minecraft",
6463
+ async isInstalled() {
6464
+ return getVanillaPath() !== null;
6465
+ },
6466
+ getDataPath() {
6467
+ return getVanillaPath();
6468
+ },
6469
+ async discoverInstances() {
6470
+ const dataPath = getVanillaPath();
6471
+ if (!dataPath)
6472
+ return [];
6473
+ return [{
6474
+ id: "vanilla",
6475
+ name: "Vanilla Minecraft",
6476
+ launcher: "vanilla",
6477
+ minecraftPath: dataPath
6478
+ }];
6479
+ }
6480
+ };
6481
+
6482
+ // src/launchers/providers/prism.ts
6483
+ import fs3 from "fs";
6484
+ import path3 from "path";
6485
+ import os4 from "os";
6486
+ function getPrismCandidatePaths() {
6487
+ const home = os4.homedir();
6488
+ const paths = [];
6489
+ switch (os4.platform()) {
6490
+ case "win32":
6491
+ paths.push(path3.join(os4.homedir(), "AppData/Roaming/PrismLauncher"));
6492
+ break;
6493
+ case "darwin":
6494
+ paths.push(path3.join(home, "Library/Application Support/PrismLauncher"));
6495
+ break;
6496
+ case "linux":
6497
+ default: {
6498
+ const xdgDataHome = process.env.XDG_DATA_HOME;
6499
+ if (xdgDataHome) {
6500
+ paths.push(path3.join(xdgDataHome, "PrismLauncher"));
6501
+ }
6502
+ paths.push(path3.join(home, ".local/share/PrismLauncher"));
6503
+ paths.push(path3.join(home, ".var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher"));
6504
+ break;
6505
+ }
6506
+ }
6507
+ return paths;
6508
+ }
6509
+ function getPrismDataPath() {
6510
+ for (const candidate of getPrismCandidatePaths()) {
6511
+ if (fs3.existsSync(candidate)) {
6512
+ return candidate;
6513
+ }
6514
+ }
6515
+ return null;
6516
+ }
6517
+ function parseInstanceConfig(configPath) {
6518
+ try {
6519
+ const content = fs3.readFileSync(configPath, "utf-8");
6520
+ const result = {};
6521
+ for (const line of content.split(`
6522
+ `)) {
6523
+ const trimmed = line.trim();
6524
+ if (trimmed.startsWith("name=")) {
6525
+ result.name = trimmed.slice(5);
6526
+ break;
6527
+ }
6528
+ }
6529
+ return result;
6530
+ } catch {
6531
+ return {};
6532
+ }
6533
+ }
6534
+ function parsePackJson(packPath) {
6535
+ try {
6536
+ const content = fs3.readFileSync(packPath, "utf-8");
6537
+ const pack = JSON.parse(content);
6538
+ const components = pack.components;
6539
+ if (components) {
6540
+ const minecraft = components.find((c) => c.uid === "net.minecraft");
6541
+ if (minecraft?.version) {
6542
+ return { version: minecraft.version };
6543
+ }
6544
+ }
6545
+ return {};
6546
+ } catch {
6547
+ return {};
6548
+ }
6549
+ }
6550
+ var prismProvider = {
6551
+ type: "prism",
6552
+ displayName: "Prism Launcher",
6553
+ async isInstalled() {
6554
+ return getPrismDataPath() !== null;
6555
+ },
6556
+ getDataPath() {
6557
+ return getPrismDataPath();
6558
+ },
6559
+ async discoverInstances() {
6560
+ const dataPath = getPrismDataPath();
6561
+ if (!dataPath)
6562
+ return [];
6563
+ const instancesDir = path3.join(dataPath, "instances");
6564
+ if (!fs3.existsSync(instancesDir))
6565
+ return [];
6566
+ const instances = [];
6567
+ try {
6568
+ const entries = fs3.readdirSync(instancesDir, { withFileTypes: true });
6569
+ for (const entry of entries) {
6570
+ if (!entry.isDirectory())
6571
+ continue;
6572
+ if (entry.name.startsWith(".") || entry.name.startsWith("_"))
6573
+ continue;
6574
+ const instanceDir = path3.join(instancesDir, entry.name);
6575
+ const minecraftDir = path3.join(instanceDir, "minecraft");
6576
+ const dotMinecraftDir = path3.join(instanceDir, ".minecraft");
6577
+ let minecraftPath = null;
6578
+ if (fs3.existsSync(minecraftDir)) {
6579
+ minecraftPath = minecraftDir;
6580
+ } else if (fs3.existsSync(dotMinecraftDir)) {
6581
+ minecraftPath = dotMinecraftDir;
6582
+ }
6583
+ if (!minecraftPath)
6584
+ continue;
6585
+ const configPath = path3.join(instanceDir, "instance.cfg");
6586
+ const config = parseInstanceConfig(configPath);
6587
+ const packPath = path3.join(instanceDir, "mmc-pack.json");
6588
+ const pack = parsePackJson(packPath);
6589
+ instances.push({
6590
+ id: `prism-${entry.name}`,
6591
+ name: config.name || entry.name,
6592
+ launcher: "prism",
6593
+ minecraftPath,
6594
+ version: pack.version
6595
+ });
6596
+ }
6597
+ } catch {}
6598
+ return instances;
6599
+ }
6600
+ };
6601
+
6602
+ // src/launchers/providers/modrinth.ts
6603
+ import fs4 from "fs";
6604
+ import path4 from "path";
6605
+ import os5 from "os";
6606
+ import { Database } from "bun:sqlite";
6607
+ function getModrinthCandidatePaths() {
6608
+ const home = os5.homedir();
6609
+ const paths = [];
6610
+ switch (os5.platform()) {
6611
+ case "win32":
6612
+ paths.push(path4.join(os5.homedir(), "AppData/Roaming/ModrinthApp"));
6613
+ break;
6614
+ case "darwin":
6615
+ paths.push(path4.join(home, "Library/Application Support/ModrinthApp"));
6616
+ break;
6617
+ case "linux":
6618
+ default: {
6619
+ const xdgDataHome = process.env.XDG_DATA_HOME;
6620
+ if (xdgDataHome) {
6621
+ paths.push(path4.join(xdgDataHome, "ModrinthApp"));
6622
+ }
6623
+ paths.push(path4.join(home, ".local/share/ModrinthApp"));
6624
+ paths.push(path4.join(home, ".var/app/com.modrinth.ModrinthApp/data/ModrinthApp"));
6625
+ break;
6626
+ }
6627
+ }
6628
+ return paths;
6629
+ }
6630
+ function getModrinthDataPath() {
6631
+ for (const candidate of getModrinthCandidatePaths()) {
6632
+ if (fs4.existsSync(candidate)) {
6633
+ return candidate;
6634
+ }
6635
+ }
6636
+ return null;
6637
+ }
6638
+ function getProfilesFromDb(dataPath) {
6639
+ const profiles = new Map;
6640
+ const dbPath = path4.join(dataPath, "app.db");
6641
+ if (!fs4.existsSync(dbPath))
6642
+ return profiles;
6643
+ try {
6644
+ const db = new Database(dbPath, { readonly: true });
6645
+ const rows = db.query("SELECT path, name, game_version FROM profiles").all();
6646
+ for (const row of rows) {
6647
+ profiles.set(row.path, {
6648
+ name: row.name,
6649
+ version: row.game_version ?? undefined
6650
+ });
6651
+ }
6652
+ db.close();
6653
+ } catch {}
6654
+ return profiles;
6655
+ }
6656
+ var modrinthProvider = {
6657
+ type: "modrinth",
6658
+ displayName: "Modrinth App",
6659
+ async isInstalled() {
6660
+ return getModrinthDataPath() !== null;
6661
+ },
6662
+ getDataPath() {
6663
+ return getModrinthDataPath();
6664
+ },
6665
+ async discoverInstances() {
6666
+ const dataPath = getModrinthDataPath();
6667
+ if (!dataPath)
6668
+ return [];
6669
+ const profilesDir = path4.join(dataPath, "profiles");
6670
+ if (!fs4.existsSync(profilesDir))
6671
+ return [];
6672
+ const profileMetadata = getProfilesFromDb(dataPath);
6673
+ const instances = [];
6674
+ try {
6675
+ const entries = fs4.readdirSync(profilesDir, { withFileTypes: true });
6676
+ for (const entry of entries) {
6677
+ if (!entry.isDirectory())
6678
+ continue;
6679
+ if (entry.name.startsWith("."))
6680
+ continue;
6681
+ const minecraftPath = path4.join(profilesDir, entry.name);
6682
+ const hasSaves = fs4.existsSync(path4.join(minecraftPath, "saves"));
6683
+ const hasMods = fs4.existsSync(path4.join(minecraftPath, "mods"));
6684
+ if (!hasSaves && !hasMods)
6685
+ continue;
6686
+ const metadata = profileMetadata.get(entry.name);
6687
+ instances.push({
6688
+ id: `modrinth-${entry.name}`,
6689
+ name: metadata?.name || entry.name,
6690
+ launcher: "modrinth",
6691
+ minecraftPath,
6692
+ version: metadata?.version
6693
+ });
6694
+ }
6695
+ } catch {}
6696
+ return instances;
6697
+ }
6698
+ };
6699
+
6700
+ // src/launchers/index.ts
6701
+ registerProvider(vanillaProvider);
6702
+ registerProvider(prismProvider);
6703
+ registerProvider(modrinthProvider);
6704
+
6413
6705
  // src/commands/create.ts
6414
6706
  function toJson(obj, pretty = false) {
6415
6707
  return util.inspect(obj, {
@@ -6420,9 +6712,78 @@ function toJson(obj, pretty = false) {
6420
6712
  maxArrayLength: Number(Infinity)
6421
6713
  });
6422
6714
  }
6715
+ function parseVersion(version) {
6716
+ if (!version)
6717
+ return null;
6718
+ const match = version.match(/^(\d+)\.(\d+)(?:\.(\d+))?/);
6719
+ if (match) {
6720
+ return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3] || "0")];
6721
+ }
6722
+ const snapshotMatch = version.match(/^(\d+)w(\d+)/);
6723
+ if (snapshotMatch) {
6724
+ return [1, parseInt(snapshotMatch[1]), parseInt(snapshotMatch[2])];
6725
+ }
6726
+ return null;
6727
+ }
6728
+ function compareVersions(a, b) {
6729
+ if (!a && !b)
6730
+ return 0;
6731
+ if (!a)
6732
+ return 1;
6733
+ if (!b)
6734
+ return -1;
6735
+ for (let i = 0;i < 3; i++) {
6736
+ if (a[i] !== b[i])
6737
+ return b[i] - a[i];
6738
+ }
6739
+ return 0;
6740
+ }
6741
+ async function selectClientInstance() {
6742
+ const { instances } = await discoverAllInstances();
6743
+ if (instances.length === 0) {
6744
+ return await input({ message: "No Minecraft installations detected. Enter path to .minecraft folder:" });
6745
+ }
6746
+ const vanilla = instances.find((i) => i.launcher === "vanilla");
6747
+ const otherInstances = instances.filter((i) => i.launcher !== "vanilla");
6748
+ otherInstances.sort((a, b) => {
6749
+ const versionCmp = compareVersions(parseVersion(a.version), parseVersion(b.version));
6750
+ if (versionCmp !== 0)
6751
+ return versionCmp;
6752
+ return a.name.localeCompare(b.name);
6753
+ });
6754
+ const choices = [];
6755
+ choices.push({ name: "Custom path...", value: "custom", short: "Custom" });
6756
+ choices.push({ name: "None (configure later)", value: "none", short: "None" });
6757
+ if (vanilla) {
6758
+ choices.push({
6759
+ name: `${vanilla.name} [${vanilla.launcher}]`,
6760
+ value: vanilla,
6761
+ short: vanilla.name
6762
+ });
6763
+ }
6764
+ for (const i of otherInstances) {
6765
+ choices.push({
6766
+ name: `${i.name}${i.version ? ` (${i.version})` : ""} [${i.launcher}]`,
6767
+ value: i,
6768
+ short: i.name
6769
+ });
6770
+ }
6771
+ const selected = await select({
6772
+ message: "Select Minecraft installation:",
6773
+ choices,
6774
+ default: vanilla ?? "none"
6775
+ });
6776
+ if (selected === "none") {
6777
+ return;
6778
+ }
6779
+ if (selected === "custom") {
6780
+ return await input({ message: "Enter path to .minecraft folder:" });
6781
+ }
6782
+ return selected.minecraftPath;
6783
+ }
6423
6784
  async function createCommand2(_project, opts) {
6424
- const projectPath = path2.resolve(_project);
6425
- const projectName = path2.basename(projectPath);
6785
+ const projectPath = path5.resolve(_project);
6786
+ const projectName = path5.basename(projectPath);
6426
6787
  const projectType = await confirm({
6427
6788
  message: "Whether your project will be a library for use in other Sandstone projects >",
6428
6789
  default: false
@@ -6490,22 +6851,33 @@ async function createCommand2(_project, opts) {
6490
6851
  }]
6491
6852
  });
6492
6853
  switch (saveChoice) {
6493
- case "root":
6854
+ case "root": {
6855
+ const clientPath = await selectClientInstance();
6856
+ if (clientPath) {
6857
+ saveOptions.clientPath = clientPath;
6858
+ }
6494
6859
  saveOptions.root = true;
6495
6860
  break;
6496
- case "world":
6861
+ }
6862
+ case "world": {
6863
+ const clientPath = await selectClientInstance();
6864
+ if (clientPath) {
6865
+ saveOptions.clientPath = clientPath;
6866
+ }
6497
6867
  const world = await select({
6498
6868
  message: "What world do you want to save the packs in? >",
6499
6869
  choices: getWorldsList(saveOptions.clientPath)
6500
6870
  });
6501
6871
  saveOptions.world = world;
6502
6872
  break;
6503
- case "server-path":
6873
+ }
6874
+ case "server-path": {
6504
6875
  const serverPath = await input({
6505
6876
  message: "Where is the server to save the packs in? Relative paths are accepted. >"
6506
6877
  });
6507
6878
  saveOptions.serverPath = serverPath;
6508
6879
  break;
6880
+ }
6509
6881
  case "none":
6510
6882
  break;
6511
6883
  }
@@ -6533,9 +6905,9 @@ async function createCommand2(_project, opts) {
6533
6905
  const exec = (cmd) => child.execSync(cmd, { cwd: projectPath });
6534
6906
  exec("git clone https://github.com/sandstone-mc/sandstone-template.git .");
6535
6907
  exec(`git checkout ${projectType}-${version[0]}`);
6536
- await import_fs_extra.default.rm(path2.join(projectPath, ".git"), { force: true, recursive: true });
6908
+ await import_fs_extra.default.rm(path5.join(projectPath, ".git"), { force: true, recursive: true });
6537
6909
  exec(`${packageManager} install`);
6538
- const configPath = path2.join(projectPath, `${projectType === "library" ? "test/" : ""}sandstone.config.ts`);
6910
+ const configPath = path5.join(projectPath, `${projectType === "library" ? "test/" : ""}sandstone.config.ts`);
6539
6911
  let templateConfig = await import_fs_extra.default.readFile(configPath, "utf8");
6540
6912
  templateConfig = templateConfig.replace("packUid: 'kZZpDK67'", `packUid: ${toJson(nanoid(8))}`);
6541
6913
  templateConfig = templateConfig.replace("name: 'template'", `name: ${toJson(packName)}`);