sandstone-cli 2.3.2 → 2.4.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.3.2",
3
+ "version": "2.4.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,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.4",
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 ? await 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 ? await 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.4'), sv(CLI_VERSION)], [sv('1.0.0-beta.3'), sv('2.3.2')]] 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
File without changes
package/src/create.ts CHANGED
File without changes
package/src/index.ts CHANGED
File without changes
package/src/shared.ts CHANGED
File without changes
@@ -0,0 +1,194 @@
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 { readFile } from 'fs/promises'
10
+ import { dirname, join, resolve } from 'path'
11
+
12
+ // Cache for loaded source map consumers
13
+ const sourceMapCache = new Map<string, SourceMapConsumer | null>()
14
+
15
+ /**
16
+ * Try to load a source map for a given JS file.
17
+ * Returns null if no source map is found or it's invalid.
18
+ */
19
+ async function loadSourceMap(jsFilePath: string): Promise<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 = await readFile(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
+
39
+ if (sourceMappingURL.startsWith('data:')) {
40
+ // Inline source map (base64 encoded)
41
+ const base64Match = sourceMappingURL.match(/base64,(.+)/)
42
+ if (!base64Match) {
43
+ sourceMapCache.set(jsFilePath, null)
44
+ return null
45
+ }
46
+ const mapContent = Buffer.from(base64Match[1], 'base64').toString('utf8')
47
+ const rawMap: RawSourceMap = JSON.parse(mapContent)
48
+ const consumer = new SourceMapConsumer(rawMap)
49
+ sourceMapCache.set(jsFilePath, consumer)
50
+ return consumer
51
+ } else {
52
+ // External source map file
53
+ mapPath = resolve(dirname(jsFilePath), sourceMappingURL)
54
+ }
55
+
56
+ const mapContent = await readFile(mapPath, 'utf8')
57
+ const rawMap: RawSourceMap = JSON.parse(mapContent)
58
+ const consumer = new SourceMapConsumer(rawMap)
59
+ sourceMapCache.set(jsFilePath, consumer)
60
+ return consumer
61
+ } catch {
62
+ sourceMapCache.set(jsFilePath, null)
63
+ return null
64
+ }
65
+ }
66
+
67
+ interface StackFrame {
68
+ original: string
69
+ filePath?: string
70
+ line?: number
71
+ column?: number
72
+ functionName?: string
73
+ }
74
+
75
+ /**
76
+ * Parse a single stack trace line.
77
+ */
78
+ function parseStackLine(line: string): StackFrame {
79
+ // Match patterns like:
80
+ // " at functionName (/path/to/file.js:123:45)"
81
+ // " at /path/to/file.js:123:45"
82
+ // " at async functionName (/path/to/file.js:123:45)"
83
+ const patterns = [
84
+ // Standard format: at functionName (file:line:col)
85
+ /^\s*at\s+(?:async\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)$/,
86
+ // Anonymous format: at file:line:col
87
+ /^\s*at\s+(?:async\s+)?(.+?):(\d+):(\d+)$/,
88
+ // Format with <anonymous>: at <anonymous> (file:line:col)
89
+ /^\s*at\s+<anonymous>\s+\((.+?):(\d+):(\d+)\)$/,
90
+ ]
91
+
92
+ for (const pattern of patterns) {
93
+ const match = line.match(pattern)
94
+ if (match) {
95
+ if (match.length === 5) {
96
+ // Pattern with function name
97
+ return {
98
+ original: line,
99
+ functionName: match[1],
100
+ filePath: match[2],
101
+ line: parseInt(match[3], 10),
102
+ column: parseInt(match[4], 10),
103
+ }
104
+ } else if (match.length === 4) {
105
+ // Pattern without function name or <anonymous>
106
+ return {
107
+ original: line,
108
+ filePath: match[1],
109
+ line: parseInt(match[2], 10),
110
+ column: parseInt(match[3], 10),
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ return { original: line }
117
+ }
118
+
119
+ /**
120
+ * Resolve a stack frame using source maps.
121
+ */
122
+ async function resolveStackFrame(frame: StackFrame): Promise<string> {
123
+ if (!frame.filePath || !frame.line || !frame.column) {
124
+ return frame.original
125
+ }
126
+
127
+ // Skip native or internal frames
128
+ if (frame.filePath.includes('(native)') || frame.filePath === 'native') {
129
+ return frame.original
130
+ }
131
+
132
+ try {
133
+ const consumer = await loadSourceMap(frame.filePath)
134
+ if (!consumer) {
135
+ return frame.original
136
+ }
137
+
138
+ const pos = consumer.originalPositionFor({
139
+ line: frame.line,
140
+ column: frame.column - 1, // source-map uses 0-based columns
141
+ })
142
+
143
+ if (pos.source && pos.line !== null) {
144
+ // Resolve the source path relative to the JS file
145
+ const sourceDir = dirname(frame.filePath)
146
+ const originalPath = resolve(sourceDir, pos.source)
147
+
148
+ // Use source map name if available, then fall back to frame's function name
149
+ // The frame's function name comes from the runtime stack and is often correct
150
+ // even when the source map doesn't have name mappings
151
+ const functionPart = pos.name ?? frame.functionName ?? '<anonymous>'
152
+ const location = `${originalPath}:${pos.line}:${(pos.column ?? 0) + 1}`
153
+
154
+ if (functionPart) {
155
+ return ` at ${functionPart} (${location})`
156
+ }
157
+ return ` at ${location}`
158
+ }
159
+ } catch {
160
+ // If resolution fails, return original
161
+ }
162
+
163
+ return frame.original
164
+ }
165
+
166
+ /**
167
+ * Resolve source maps in a stack trace string.
168
+ * Returns the stack trace with original source locations where possible.
169
+ */
170
+ export async function resolveStackTrace(stack: string): Promise<string> {
171
+ const lines = stack.split('\n')
172
+ const resolvedLines: string[] = []
173
+
174
+ for (const line of lines) {
175
+ // Only process lines that look like stack frames
176
+ if (line.trimStart().startsWith('at ')) {
177
+ const frame = parseStackLine(line)
178
+ const resolved = await resolveStackFrame(frame)
179
+ resolvedLines.push(resolved)
180
+ } else {
181
+ resolvedLines.push(line)
182
+ }
183
+ }
184
+
185
+ return resolvedLines.join('\n')
186
+ }
187
+
188
+ /**
189
+ * Clear the source map cache.
190
+ * Call this between builds if source maps may have changed.
191
+ */
192
+ export function clearSourceMapCache(): void {
193
+ sourceMapCache.clear()
194
+ }
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.0'
package/tsconfig.json CHANGED
File without changes