sandstone-cli 2.0.8 → 2.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,6 +1,6 @@
1
1
  {
2
2
  "name": "sandstone-cli",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "description": "The CLI for Sandstone - the minecraft pack creation library.",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -9,7 +9,8 @@
9
9
  "create-sandstone": "./lib/create.js"
10
10
  },
11
11
  "scripts": {
12
- "dev:build": "bun tsc"
12
+ "build": "bun scripts/version.ts && bun build src/index.ts --outfile=lib/index.js --target=node --external=@parcel/watcher --external=figlet && bun build src/create.ts --outfile=lib/create.js --target=node --external=@parcel/watcher --external=figlet",
13
+ "dev:build": "tsc && bun run build"
13
14
  },
14
15
  "repository": {
15
16
  "type": "git",
@@ -0,0 +1,4 @@
1
+ import { writeFileSync } from 'fs'
2
+ import pkg from '../package.json'
3
+
4
+ writeFileSync('src/version.ts', `export const CLI_VERSION = '${pkg.version}'\n`)
@@ -7,6 +7,7 @@ import * as child from 'child_process'
7
7
  import { nanoid } from 'nanoid'
8
8
  import { confirm, select, input } from '@inquirer/prompts'
9
9
 
10
+ import { CLI_VERSION } from '../version.js'
10
11
  import { capitalize, getWorldsList, hasBun, hasPnpm, hasYarn } from '../utils.js'
11
12
 
12
13
  type CreateOptions = {
@@ -42,7 +43,7 @@ export async function createCommand(_project: string, opts: CreateOptions) {
42
43
 
43
44
  const sv = (v: string) => new SemVer(v)
44
45
 
45
- const versions = [[sv('1.0.0-beta.1'), sv('2.0.8')]] as const
46
+ const versions = [[sv('1.0.0-beta.1'), sv(CLI_VERSION)]] as const
46
47
 
47
48
  const version = await select({
48
49
  message: 'Which version of Sandstone do you want to use? These are the only supported versions for new projects.',
@@ -10,11 +10,12 @@ import { initLogger, log, logError, setLiveLogCallback } from '../ui/logger.js'
10
10
  import type { TrackedChange, ChangeCategory } from '../ui/types.js'
11
11
  import { hot } from '@sandstone-mc/hot-hook'
12
12
  import fs from 'fs-extra'
13
- import { join } from 'node:path'
13
+ import { join, relative } from 'node:path'
14
14
 
15
15
  export interface WatchOptions extends BuildOptions {
16
16
  manual?: boolean
17
17
  library?: boolean
18
+ ignore?: string[]
18
19
  }
19
20
 
20
21
  export async function watchCommand(opts: WatchOptions) {
@@ -77,7 +78,7 @@ export async function watchCommand(opts: WatchOptions) {
77
78
 
78
79
  api?.setStatus('building')
79
80
  api?.setChangedFiles(changes)
80
- log('Building...', changes.map(c => c.path).join(', '))
81
+ log('Building...', changes.map(c => './' + relative(opts.path, c.path).replace(/\\/g, '/')).join(', '))
81
82
 
82
83
  const libChanges = opts.library && Object.hasOwn(globalThis, 'Bun') ? changes.filter((change) => !change.path.includes('test/')) : []
83
84
 
@@ -283,8 +284,14 @@ export async function watchCommand(opts: WatchOptions) {
283
284
  }
284
285
 
285
286
  if (opts.manual) {
286
- // In manual mode, accumulate changes and wait for user input
287
- pendingChanges = [...pendingChanges, ...changesToProcess]
287
+ // In manual mode, accumulate changes and wait for user input (deduplicated)
288
+ const existingPaths = new Set(pendingChanges.map(c => c.path))
289
+ for (const change of changesToProcess) {
290
+ if (!existingPaths.has(change.path)) {
291
+ pendingChanges.push(change)
292
+ existingPaths.add(change.path)
293
+ }
294
+ }
288
295
  getWatchUIAPI()?.setStatus('pending')
289
296
  getWatchUIAPI()?.setChangedFiles(pendingChanges)
290
297
  } else {
@@ -300,6 +307,10 @@ export async function watchCommand(opts: WatchOptions) {
300
307
  // Initial build
301
308
  await onFilesChange([])
302
309
 
310
+ const defaultIgnore = ['**/.git/**/*', '**/.sandstone/**/*', '**/resources/cache/**/*', '**/*tmp*', '**/*.swp', 'lib/**/*']
311
+ const cliIgnore = (opts.ignore ?? []).flatMap(p => p.split(',').filter(Boolean))
312
+ const ignorePatterns = [...defaultIgnore, ...cliIgnore]
313
+
303
314
  subscription = await subscribe(
304
315
  opts.path,
305
316
  (err, events) => {
@@ -310,7 +321,7 @@ export async function watchCommand(opts: WatchOptions) {
310
321
  handleEvents(events)
311
322
  },
312
323
  {
313
- ignore: ['**/.git/**/*', '**/.sandstone/**/*', '**/resources/cache/**/*', '**/*tmp*', 'lib/**/*'],
324
+ ignore: ignorePatterns,
314
325
  }
315
326
  )
316
327
 
package/src/create.ts CHANGED
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Argument, Command } from 'commander'
3
3
  import figlet from 'figlet'
4
- import { createCommand } from './commands/index.js'
5
- import { BuildDeclares } from './shared.js'
4
+
5
+ import { CLI_VERSION } from './version.js'
6
+ import { createCommand } from './commands/create.js'
7
+ import { BuildOptions } from './shared.js'
6
8
 
7
9
  const commander = new Command()
8
10
 
9
11
  console.log(figlet.textSync('Sandstone'));
10
12
 
11
- const createCLI = commander
12
- .version('2.0.8')
13
+ commander
14
+ .version(CLI_VERSION)
13
15
  .description('Create a new Sandstone project. ⛏')
16
+ .addOption(BuildOptions.get('name'))
17
+ .addOption(BuildOptions.get('namespace'))
18
+ .addOption(BuildOptions.get('world'))
19
+ .addOption(BuildOptions.get('clientPath'))
20
+ .addOption(BuildOptions.get('serverPath'))
14
21
  .action(createCommand)
15
22
  .addArgument(new Argument('<projectName>', 'Not the name of the output pack'))
16
-
17
- createCLI.option.apply(createCLI, BuildDeclares.name)
18
- .option.apply(createCLI, BuildDeclares.namespace)
19
- .option.apply(createCLI, BuildDeclares.world)
20
- .option.apply(createCLI, BuildDeclares.clientPath)
21
- .option.apply(createCLI, BuildDeclares.serverPath)
22
-
23
-
24
- createCLI.parse(process.argv)
23
+ .parse(process.argv)
package/src/index.ts CHANGED
@@ -1,72 +1,66 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Argument, Command } from 'commander'
3
3
  import figlet from 'figlet'
4
+
5
+ import { CLI_VERSION } from './version.js'
4
6
  import { buildCommand, createCommand, watchCommand, installNativeCommand, installVanillaCommand, uninstallVanillaCommand, refreshCommand } from './commands/index.js'
5
- import { BuildDeclares } from './shared.js'
7
+ import { BuildOptions } from './shared.js'
6
8
 
7
9
  const commander = new Command()
8
10
 
9
11
  console.log(figlet.textSync('Sandstone'));
10
12
 
11
13
  const CLI = commander
12
- .version('2.0.8', '-v, --version')
14
+ .version(CLI_VERSION, '-v, --version')
13
15
  .description('The CLI for Sandstone - the minecraft pack creation library.')
14
16
 
15
- const build = CLI
17
+ CLI
16
18
  .command('build')
17
19
  .description('Build the pack(s). ⛏')
18
-
19
- build.option.apply(build, BuildDeclares.dry)
20
- .option.apply(build, BuildDeclares.verbose)
21
- .option.apply(build, BuildDeclares.root)
22
- .option.apply(build, BuildDeclares.fullTrace)
23
- .option.apply(build, BuildDeclares.strictErrors)
24
- .option.apply(build, BuildDeclares.production)
25
-
26
- .option.apply(build, BuildDeclares.path)
27
- .option.apply(build, BuildDeclares.name)
28
- .option.apply(build, BuildDeclares.namespace)
29
- .option.apply(build, BuildDeclares.world)
30
- .option.apply(build, BuildDeclares.clientPath)
31
- .option.apply(build, BuildDeclares.serverPath)
32
-
33
- .option.apply(build, BuildDeclares.enableSymlinks)
20
+ .addOption(BuildOptions.get('dry'))
21
+ .addOption(BuildOptions.get('verbose'))
22
+ .addOption(BuildOptions.get('root'))
23
+ .addOption(BuildOptions.get('fullTrace'))
24
+ .addOption(BuildOptions.get('strictErrors'))
25
+ .addOption(BuildOptions.get('production'))
26
+ .addOption(BuildOptions.get('path'))
27
+ .addOption(BuildOptions.get('name'))
28
+ .addOption(BuildOptions.get('namespace'))
29
+ .addOption(BuildOptions.get('world'))
30
+ .addOption(BuildOptions.get('clientPath'))
31
+ .addOption(BuildOptions.get('serverPath'))
34
32
  .action(buildCommand)
35
33
 
36
- const watch = CLI
34
+ CLI
37
35
  .command('watch')
38
36
  .description('Build the packs, and rebuild them on file change. ⛏')
37
+ .addOption(BuildOptions.get('dry'))
38
+ .addOption(BuildOptions.get('verbose'))
39
+ .addOption(BuildOptions.get('root'))
40
+ .addOption(BuildOptions.get('fullTrace'))
41
+ .addOption(BuildOptions.get('strictErrors'))
42
+ .addOption(BuildOptions.get('path'))
43
+ .addOption(BuildOptions.get('name'))
44
+ .addOption(BuildOptions.get('namespace'))
45
+ .addOption(BuildOptions.get('world'))
46
+ .addOption(BuildOptions.get('clientPath'))
47
+ .addOption(BuildOptions.get('serverPath'))
48
+ .addOption(BuildOptions.get('library'))
49
+ .addOption(BuildOptions.get('manual'))
50
+ .addOption(BuildOptions.get('ignore'))
39
51
  .action(watchCommand)
40
52
 
41
- watch.option.apply(watch, BuildDeclares.dry)
42
- .option.apply(watch, BuildDeclares.verbose)
43
- .option.apply(watch, BuildDeclares.root)
44
- .option.apply(watch, BuildDeclares.fullTrace)
45
- .option.apply(watch, BuildDeclares.strictErrors)
46
-
47
- .option.apply(watch, BuildDeclares.path)
48
- .option.apply(watch, BuildDeclares.name)
49
- .option.apply(watch, BuildDeclares.namespace)
50
- .option.apply(watch, BuildDeclares.world)
51
- .option.apply(watch, BuildDeclares.clientPath)
52
- .option.apply(watch, BuildDeclares.serverPath)
53
-
54
- .option.apply(watch, BuildDeclares.library)
55
- .option.apply(watch, BuildDeclares.manual)
56
- .option.apply(watch, BuildDeclares.enableSymlinks)
57
-
58
- const create = CLI
53
+ CLI
59
54
  .command('create')
60
55
  .description('Create a new Sandstone project. ⛏')
56
+ .addOption(BuildOptions.get('name'))
57
+ .addOption(BuildOptions.get('namespace'))
58
+ .addOption(BuildOptions.get('world'))
59
+ .addOption(BuildOptions.get('clientPath'))
60
+ .addOption(BuildOptions.get('serverPath'))
61
61
  .action(createCommand)
62
62
  .addArgument(new Argument('<projectName>', 'Not the name of the output pack'))
63
63
 
64
- create.option.apply(create, BuildDeclares.name)
65
- .option.apply(create, BuildDeclares.namespace)
66
- .option.apply(create, BuildDeclares.world)
67
- .option.apply(create, BuildDeclares.clientPath)
68
- .option.apply(create, BuildDeclares.serverPath)
69
-
70
64
  const install = CLI
71
65
  .command('install')
72
66
  .alias('add')
package/src/shared.ts CHANGED
@@ -1,23 +1,65 @@
1
- export const BuildDeclares = {
2
- // Flags
3
- dry: ['-d, --dry', 'Do not save the pack. Mostly useful with `verbose`.'],
4
- verbose: ['-f, --verbose', 'Fully log all resulting resources: functions, advancements...'],
5
- root: ['-r, --root', 'Save the pack & resource pack in the .minecraft/datapacks & .minecraft/resource_packs folders. Override the value specified in the configuration file.'],
6
- fullTrace: ['-t, --full-trace', 'Show the full stack trace on errors.'],
7
- strictErrors: ['-s, --strict-errors', 'Stop pack compilation on type errors.'],
8
- production: ['-p, --production', 'Runs Sandstone in production mode. This sets process.env.SANDSTONE_ENV to "production".'],
9
-
10
- // Values
11
- path: ['--path <path>', 'Path of the folder containing your sandstone workspace.', './'],
12
- name: ['-n, --name <name>', 'Name of the datapack. Override the value specified in the configuration file.'],
13
- namespace: ['-ns, --namespace <namespace>', 'The default namespace. Override the value specified in the configuration file.'],
14
- world: ['-w, --world <name>', 'The name of the world to save the packs in. Override the value specified in the configuration file.'],
15
- clientPath: ['-c, --client-path <path>', 'Path of the client folder. Override the value specified in the configuration file.'],
16
- serverPath: ['--server-path <path>', 'Path of the server folder. Override the value specified in the configuration file.'],
17
-
18
- // TODO: ssh
19
-
20
- enableSymlinks: ['--enable-symlinks', 'Force enable/disable symlinks. Defaults to false. Useful if you want to enable symlinks on Windows.'],
21
- manual: ['-m, --manual', 'Manual reload mode - press r or Enter to rebuild after changes.'],
22
- library: ['-l, --library', 'Library mode - watches a library workspace based on the library project template.'],
23
- } as unknown as Record<string, [string, string, RegExp, boolean]> // Haha TypeScript funny
1
+ import { Option } from 'commander'
2
+
3
+ interface OptionDef {
4
+ flags: string
5
+ description: string
6
+ default?: unknown
7
+ env?: string | false // false to disable auto-env
8
+ }
9
+
10
+ function opt(flags: string, description: string, config?: { default?: unknown; env?: string | false }): OptionDef {
11
+ return { flags, description, ...config }
12
+ }
13
+
14
+ /** Parse --long-name from flags like "-s, --long-name <value>" */
15
+ function parseLongFlag(flags: string): string | undefined {
16
+ const match = flags.match(/--([a-z-]+)/)
17
+ return match?.[1]
18
+ }
19
+
20
+ /** Convert kebab-case to SCREAMING_SNAKE_CASE with SANDSTONE_ prefix */
21
+ function toEnvVar(flagName: string): string {
22
+ return 'SANDSTONE_' + flagName.toUpperCase().replace(/-/g, '_')
23
+ }
24
+
25
+ // Option definitions - use BuildOptions.get() to create Commander Option instances
26
+ const options = {
27
+ // Flags
28
+ dry: opt('-d, --dry', 'Do not save the pack. Mostly useful with `verbose`.'),
29
+ verbose: opt('-f, --verbose', 'Fully log all resulting resources: functions, advancements...'),
30
+ root: opt('-r, --root', 'Save the pack & resource pack in the .minecraft/datapacks & .minecraft/resource_packs folders. Override the value specified in the configuration file.'),
31
+ fullTrace: opt('-t, --full-trace', 'Show the full stack trace on errors.'),
32
+ strictErrors: opt('-e, --strict-errors', 'Stop pack compilation on type errors.'),
33
+ production: opt('-p, --production', 'Runs Sandstone in production mode. This sets process.env.SANDSTONE_ENV to "production".'),
34
+
35
+ // Values
36
+ path: opt('-h,--path <path>', 'Path of the folder containing your sandstone workspace.', { default: './' }),
37
+ name: opt('-n, --name <name>', 'Name of the datapack. Override the value specified in the configuration file.'),
38
+ namespace: opt('-ns, --namespace <namespace>', 'The default namespace. Override the value specified in the configuration file.'),
39
+ world: opt('-w, --world <name>', 'The name of the world to save the packs in. Override the value specified in the configuration file.'),
40
+ clientPath: opt('-c, --client-path <path>', 'Path of the client folder. Override the value specified in the configuration file.'),
41
+ serverPath: opt('--server-path <path>', 'Path of the server folder. Override the value specified in the configuration file.'),
42
+
43
+ // TODO: ssh
44
+
45
+ // Watch-specific
46
+ manual: opt('-m, --manual', 'Manual reload mode - press r or Enter to rebuild after changes.', { env: 'WATCH_MANUAL' }),
47
+ library: opt('-l, --library', 'Library mode - watches a library workspace based on the library project template.', { env: 'WATCH_LIBRARY' }),
48
+ ignore: opt('-i, --ignore <globs...>', 'Additional glob patterns to ignore when watching for changes.', { env: 'WATCH_IGNORE_PATTERNS' }),
49
+ } satisfies Record<string, OptionDef>
50
+
51
+ export type OptionName = keyof typeof options
52
+
53
+ export const BuildOptions = {
54
+ /** Get a Commander Option instance for the given option name */
55
+ get(name: OptionName): Option {
56
+ const def = options[name]
57
+ const option = new Option(def.flags, def.description)
58
+ if (def.default !== undefined) option.default(def.default)
59
+ // Auto-generate env var from flag unless explicitly disabled (env: false)
60
+ if (def.env !== false) {
61
+ option.env(def.env === undefined ? toEnvVar(parseLongFlag(def.flags) ?? name) : `SANDSTONE_${def.env}`)
62
+ }
63
+ return option
64
+ },
65
+ }
@@ -0,0 +1 @@
1
+ export default { initialize() {}, connectToDevTools() {} }
@@ -146,6 +146,8 @@ export function WatchUI({ manual, onManualRebuild, exit }: WatchUIProps) {
146
146
  setBuildResultState(result)
147
147
  if (result.success) {
148
148
  setStatusState(manual ? 'pending' : 'watching')
149
+ // Clear changed files so contentMode switches back to 'logs'
150
+ setChangedFilesState([])
149
151
  } else {
150
152
  setStatusState('error')
151
153
  }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const CLI_VERSION = '2.1.0'
package/tsconfig.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "declaration": true,
4
3
  "module": "NodeNext",
5
- "outDir": "lib",
6
- "rootDir": "src",
4
+ "noEmit": true,
5
+ "paths": {
6
+ "*": ["./*"],
7
+ "react-devtools-core": ["./src/stubs/react-devtools-core.js"]
8
+ },
7
9
  "strict": true,
8
10
  "target": "ES2024",
9
11
  "lib": [
@@ -1,34 +0,0 @@
1
- import type { BuildResult } from '../ui/types.js';
2
- import type * as sandstone from 'sandstone';
3
- declare global {
4
- interface RegExpConstructor {
5
- escape(str: string): string;
6
- }
7
- }
8
- export declare function enableConsoleCapture(): void;
9
- export declare function disableConsoleCapture(): void;
10
- export type BuildOptions = {
11
- dry?: boolean;
12
- verbose?: boolean;
13
- root?: boolean;
14
- fullTrace?: boolean;
15
- strictErrors?: boolean;
16
- production?: boolean;
17
- path: string;
18
- name?: string;
19
- namespace?: string;
20
- world?: string;
21
- clientPath?: string;
22
- serverPath?: string;
23
- enableSymlinks?: boolean;
24
- dependencies?: [string, string][];
25
- };
26
- export interface BuildContext {
27
- sandstoneConfig: sandstone.SandstoneConfig;
28
- sandstonePack: sandstone.SandstonePack;
29
- resetSandstonePack: () => void;
30
- }
31
- export declare function loadBuildContext(cliOptions: BuildOptions, folder: string): Promise<BuildContext>;
32
- export declare function _buildCommand(opts: BuildOptions, _folder?: string, existingContext?: BuildContext, watching?: boolean): Promise<BuildResult>;
33
- export declare function buildCommand(opts: BuildOptions, _?: string): Promise<void>;
34
- export declare function buildCommand(opts: BuildOptions, _folder: string | undefined, silent: true): Promise<BuildResult>;