sandstone-cli 2.3.2 → 2.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandstone-cli",
3
- "version": "2.3.2",
3
+ "version": "2.4.1",
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,7 @@
9
9
  "create-sandstone": "./lib/create.js"
10
10
  },
11
11
  "scripts": {
12
- "bundle": "bun scripts/version.ts && bun build src/index.ts --outfile=lib/index.js --target=bun --external=@parcel/watcher --external=figlet --external=@inquirer/prompts && bun build src/create.ts --outfile=lib/create.js --target=bun --external=@parcel/watcher --external=figlet --external=@inquirer/prompts",
12
+ "bundle": "bun scripts/version.ts && bun build src/index.ts --outdir=lib --sourcemap=external --target=bun --external=@parcel/watcher --external=figlet --external=@inquirer/prompts && bun build src/create.ts --outdir=lib --sourcemap=external --target=bun --external=@parcel/watcher --external=figlet --external=@inquirer/prompts",
13
13
  "dev:build": "tsc && bun bundle",
14
14
  "test:harness": "npx tsx scripts/test-harness.ts"
15
15
  },
@@ -59,7 +59,8 @@
59
59
  "node-fetch": "^3.3.2",
60
60
  "obliterator": "^2.0.5",
61
61
  "react": "^19.2.4",
62
- "semver": "^7.5.4"
62
+ "semver": "^7.5.4",
63
+ "source-map-js": "^1.2.1"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@types/adm-zip": "^0.5.2",
@@ -69,10 +70,11 @@
69
70
  "@types/semver": "^7.5.3",
70
71
  "bun-types": "^1.3.9",
71
72
  "node-pty": "^1.1.0",
72
- "sandstone": "^1.0.0-beta.3",
73
+ "sandstone": "^1.0.0-beta.5",
73
74
  "typescript": "^5.2.2"
74
75
  },
75
76
  "trustedDependencies": [
76
- "@parcel/watcher"
77
+ "@parcel/watcher",
78
+ "node-pty"
77
79
  ]
78
80
  }
@@ -101,9 +101,9 @@ export async function createSymlink(
101
101
 
102
102
  const comment = `# Sandstone Pack: ${packName}\n`
103
103
  try {
104
- const currentlyAllowed = await fs.readFile(allowedList, 'utf-8')
104
+ const currentlyAllowed = (await fs.readFile(allowedList, 'utf-8')).replace(/\r/g, '')
105
105
 
106
- if (!currentlyAllowed.includes(allowPath)) {
106
+ if (currentlyAllowed.match(new RegExp(`^${allowPath}$`, 'm')) === null) {
107
107
  log('[symlink] Adding workspace to allowed_symlinks.txt. If the game is running please restart it.')
108
108
  await fs.writeFile(allowedList, `${currentlyAllowed}\n#\n${comment}${allowPath}`)
109
109
  } else {
@@ -7,6 +7,7 @@ import { split } from 'obliterator'
7
7
  import type { BuildResult, ResourceCounts } from '../../ui/types.js'
8
8
  import { log, initLoggerNoFile, setSilent } from '../../ui/logger.js'
9
9
  import { hash } from '../../utils.js'
10
+ import { resolveStackTrace } from '../../utils/source-map.js'
10
11
 
11
12
  import {
12
13
  type SandstoneCache,
@@ -173,7 +174,7 @@ export async function loadBuildContext(
173
174
  }
174
175
  }
175
176
 
176
- const sandstoneUrl = pathToFileURL(path.join(folder, 'node_modules', 'sandstone', 'dist', 'index.js'))
177
+ const sandstoneUrl = pathToFileURL(path.join(folder, 'node_modules', 'sandstone', 'dist', 'exports', 'index.js'))
177
178
  /* @ts-ignore */
178
179
  const { createSandstonePack, resetSandstonePack } = (await import(sandstoneUrl)) as typeof sandstone
179
180
 
@@ -512,7 +513,10 @@ export async function _buildCommand(
512
513
  const stackLines = cleanedStack.split('\n')
513
514
  const traceStart = stackLines.findIndex(line => line.trimStart().startsWith('at '))
514
515
  const stackTrace = traceStart >= 0 ? stackLines.slice(traceStart).join('\n') : ''
515
- const formattedError = stackTrace ? `${errorMessage}\n${stackTrace}` : errorMessage
516
+
517
+ // Resolve source maps for better error locations
518
+ const resolvedStackTrace = stackTrace ? resolveStackTrace(stackTrace) : ''
519
+ const formattedError = resolvedStackTrace ? `${errorMessage}\n${resolvedStackTrace}` : errorMessage
516
520
  return {
517
521
  success: false,
518
522
  error: formattedError,
@@ -553,7 +557,10 @@ export async function buildCommand(opts: BuildOptions, _folder?: string, silent
553
557
  const stackLines = cleanedStack.split('\n')
554
558
  const traceStart = stackLines.findIndex(line => line.trimStart().startsWith('at '))
555
559
  const stackTrace = traceStart >= 0 ? stackLines.slice(traceStart).join('\n') : ''
556
- const formattedError = stackTrace ? `${errorMessage}\n${stackTrace}` : errorMessage
560
+
561
+ // Resolve source maps for better error locations
562
+ const resolvedStackTrace = stackTrace ? resolveStackTrace(stackTrace) : ''
563
+ const formattedError = resolvedStackTrace ? `${errorMessage}\n${resolvedStackTrace}` : errorMessage
557
564
  if (!silent) {
558
565
  log(chalk.bgRed.white('BuildError') + chalk.gray(':'), formattedError)
559
566
  process.exit(1)
@@ -130,7 +130,7 @@ export async function createCommand(_project: string, opts: CreateOptions) {
130
130
 
131
131
  const sv = (v: string) => new SemVer(v)
132
132
 
133
- const versions = [[sv('1.0.0-beta.3'), sv(CLI_VERSION)]] as const
133
+ const versions = [[sv('1.0.0-beta.5'), sv(CLI_VERSION)]] as const
134
134
 
135
135
  const version = await select({
136
136
  message: 'Which version of Sandstone do you want to use? These are the only supported versions for new projects.',
File without changes
File without changes
@@ -9,6 +9,7 @@ import { WatchUI, getWatchUIAPI } from '../ui/WatchUI.js'
9
9
  import { initLogger, log, logInfo, logWarn, logError, logDebug, logTrace, 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
+ import { resolveStackTrace } from '../utils/source-map.js'
12
13
  import fs from 'fs-extra'
13
14
  import { join, relative } from 'node:path'
14
15
 
@@ -33,7 +34,14 @@ function enableConsoleCapture() {
33
34
  .replace(/^Error\n/, '')
34
35
  .replace(/\?hot-hook=\d+/g, '')
35
36
  .replace(/file:\/\/\/?/g, '')
36
- logTrace(...args, '\n' + cleanedStack)
37
+
38
+ // Resolve source maps for stack frames
39
+ const stackLines = cleanedStack.split('\n')
40
+ const traceStart = stackLines.findIndex(line => line.trimStart().startsWith('at '))
41
+ const stackTrace = traceStart >= 0 ? stackLines.slice(traceStart).join('\n') : ''
42
+ const resolvedStack = stackTrace ? resolveStackTrace(stackTrace) : cleanedStack
43
+
44
+ logTrace(...args, '\n' + resolvedStack)
37
45
  }
38
46
  }
39
47
 
package/src/create.ts CHANGED
File without changes
package/src/index.ts CHANGED
File without changes
package/src/shared.ts CHANGED
File without changes
package/src/ui/logger.ts CHANGED
@@ -88,13 +88,14 @@ async function logWorkerMain(level: string | false, ...args: unknown[]) {
88
88
  const chunks: (string | Buffer | Uint8Array)[] = []
89
89
 
90
90
  // Timestamp and level prefix
91
- chunks.push(`[${new Date().toISOString()}]${level !== false ? ` [${level}]` : ''} `)
91
+ const prefix = `[${new Date().toISOString()}]${level !== false ? ` [${level}]` : ''} `
92
+ chunks.push(prefix)
92
93
 
93
94
  // Process each argument
94
95
  for (let i = 0; i < args.length; i++) {
95
96
  const arg = args[i]
96
97
  if (typeof arg === 'string') {
97
- chunks.push(stripVTControlCharacters(arg))
98
+ chunks.push(stripVTControlCharacters(arg).replaceAll('\n', `\n${' '.repeat(prefix.length)}`))
98
99
  } else if (Buffer.isBuffer(arg) || arg instanceof Uint8Array) {
99
100
  chunks.push(arg)
100
101
  } else if (arg instanceof ArrayBuffer) {
@@ -103,7 +104,7 @@ async function logWorkerMain(level: string | false, ...args: unknown[]) {
103
104
  chunks.push(new Uint8Array(await arg.arrayBuffer()))
104
105
  } else {
105
106
  // Use util.format for objects, numbers, etc.
106
- chunks.push(format('%O', arg))
107
+ chunks.push(stripVTControlCharacters(format('%O', arg)).replaceAll('\n', `\n${' '.repeat(prefix.length)}`))
107
108
  }
108
109
  // Add space between args (but not after last)
109
110
  if (i < args.length - 1) {
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Source map resolution for stack traces.
3
+ *
4
+ * Bun doesn't automatically resolve source maps for bundled dependencies,
5
+ * so we manually parse and resolve them for better error messages.
6
+ */
7
+
8
+ import { SourceMapConsumer, type RawSourceMap } from 'source-map-js'
9
+ import { readFileSync } from 'fs'
10
+ import { dirname, resolve } from 'path'
11
+
12
+ // Cache for loaded source map consumers
13
+ const sourceMapCache = new Map<string, SourceMapConsumer | null>()
14
+
15
+ /**
16
+ * Synchronously load a source map for a given JS file.
17
+ * Returns null if no source map is found or it's invalid.
18
+ */
19
+ function loadSourceMapSync(jsFilePath: string): SourceMapConsumer | null {
20
+ // Check cache first
21
+ if (sourceMapCache.has(jsFilePath)) {
22
+ return sourceMapCache.get(jsFilePath) ?? null
23
+ }
24
+
25
+ try {
26
+ // Read the JS file to find the sourceMappingURL
27
+ const jsContent = readFileSync(jsFilePath, 'utf8')
28
+
29
+ // Look for sourceMappingURL comment
30
+ const match = jsContent.match(/\/\/[#@]\s*sourceMappingURL=(.+)$/m)
31
+ if (!match) {
32
+ sourceMapCache.set(jsFilePath, null)
33
+ return null
34
+ }
35
+
36
+ const sourceMappingURL = match[1].trim()
37
+ let mapPath: string
38
+ let mapContent: string
39
+
40
+ if (sourceMappingURL.startsWith('data:')) {
41
+ // Inline source map (base64 encoded)
42
+ const base64Match = sourceMappingURL.match(/base64,(.+)/)
43
+ if (!base64Match) {
44
+ sourceMapCache.set(jsFilePath, null)
45
+ return null
46
+ }
47
+ mapContent = Buffer.from(base64Match[1], 'base64').toString('utf8')
48
+ } else {
49
+ // External source map file
50
+ mapPath = resolve(dirname(jsFilePath), sourceMappingURL)
51
+ mapContent = readFileSync(mapPath, 'utf8')
52
+ }
53
+
54
+ const rawMap: RawSourceMap = JSON.parse(mapContent)
55
+ const consumer = new SourceMapConsumer(rawMap)
56
+ sourceMapCache.set(jsFilePath, consumer)
57
+ return consumer
58
+ } catch {
59
+ sourceMapCache.set(jsFilePath, null)
60
+ return null
61
+ }
62
+ }
63
+
64
+ interface StackFrame {
65
+ original: string
66
+ filePath?: string
67
+ line?: number
68
+ column?: number
69
+ functionName?: string
70
+ }
71
+
72
+ /**
73
+ * Parse a single stack trace line.
74
+ */
75
+ function parseStackLine(line: string): StackFrame {
76
+ // Match patterns like:
77
+ // " at functionName (/path/to/file.js:123:45)"
78
+ // " at /path/to/file.js:123:45"
79
+ // " at async functionName (/path/to/file.js:123:45)"
80
+ const patterns = [
81
+ // Standard format: at functionName (file:line:col)
82
+ /^\s*at\s+(?:async\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)$/,
83
+ // Anonymous format: at file:line:col
84
+ /^\s*at\s+(?:async\s+)?(.+?):(\d+):(\d+)$/,
85
+ // Format with <anonymous>: at <anonymous> (file:line:col)
86
+ /^\s*at\s+<anonymous>\s+\((.+?):(\d+):(\d+)\)$/,
87
+ ]
88
+
89
+ for (const pattern of patterns) {
90
+ const match = line.match(pattern)
91
+ if (match) {
92
+ if (match.length === 5) {
93
+ // Pattern with function name
94
+ return {
95
+ original: line,
96
+ functionName: match[1],
97
+ filePath: match[2],
98
+ line: parseInt(match[3], 10),
99
+ column: parseInt(match[4], 10),
100
+ }
101
+ } else if (match.length === 4) {
102
+ // Pattern without function name or <anonymous>
103
+ return {
104
+ original: line,
105
+ filePath: match[1],
106
+ line: parseInt(match[2], 10),
107
+ column: parseInt(match[3], 10),
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ return { original: line }
114
+ }
115
+
116
+ /**
117
+ * Resolve a stack frame using source maps (synchronous).
118
+ */
119
+ function resolveStackFrame(frame: StackFrame): string {
120
+ if (!frame.filePath || !frame.line || !frame.column) {
121
+ return frame.original
122
+ }
123
+
124
+ // Skip native or internal frames
125
+ if (frame.filePath.includes('(native)') || frame.filePath === 'native') {
126
+ return frame.original
127
+ }
128
+
129
+ try {
130
+ const consumer = loadSourceMapSync(frame.filePath)
131
+ if (!consumer) {
132
+ return frame.original
133
+ }
134
+
135
+ const pos = consumer.originalPositionFor({
136
+ line: frame.line,
137
+ column: frame.column - 1, // source-map uses 0-based columns
138
+ })
139
+
140
+ if (pos.source && pos.line !== null) {
141
+ // Resolve the source path relative to the JS file
142
+ const sourceDir = dirname(frame.filePath)
143
+ const originalPath = resolve(sourceDir, pos.source)
144
+
145
+ // Use source map name if available, then fall back to frame's function name
146
+ // The frame's function name comes from the runtime stack and is often correct
147
+ // even when the source map doesn't have name mappings
148
+ const functionPart = pos.name ?? frame.functionName ?? '<anonymous>'
149
+ const location = `${originalPath}:${pos.line}:${(pos.column ?? 0) + 1}`
150
+
151
+ if (functionPart) {
152
+ return ` at ${functionPart} (${location})`
153
+ }
154
+ return ` at ${location}`
155
+ }
156
+ } catch {
157
+ // If resolution fails, return original
158
+ }
159
+
160
+ return frame.original
161
+ }
162
+
163
+ /**
164
+ * Resolve source maps in a stack trace string (synchronous).
165
+ * Returns the stack trace with original source locations where possible.
166
+ */
167
+ export function resolveStackTrace(stack: string): string {
168
+ const lines = stack.split('\n')
169
+ const resolvedLines: string[] = []
170
+
171
+ for (const line of lines) {
172
+ // Only process lines that look like stack frames
173
+ if (line.trimStart().startsWith('at ')) {
174
+ const frame = parseStackLine(line)
175
+ const resolved = resolveStackFrame(frame)
176
+ resolvedLines.push(resolved)
177
+ } else {
178
+ resolvedLines.push(line)
179
+ }
180
+ }
181
+
182
+ return resolvedLines.join('\n')
183
+ }
184
+
185
+ /**
186
+ * Clear the source map cache.
187
+ * Call this between builds if source maps may have changed.
188
+ */
189
+ export function clearSourceMapCache(): void {
190
+ sourceMapCache.clear()
191
+ }
package/src/utils.ts CHANGED
File without changes
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '2.3.2'
1
+ export const CLI_VERSION = '2.4.1'
package/tsconfig.json CHANGED
File without changes