@stacksjs/tinker 0.70.55 → 0.70.57
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 +10 -7
- package/src/index.ts +0 -371
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/tinker",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.57",
|
|
5
5
|
"description": "Interactive REPL with Stacks framework preloaded.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -29,19 +29,22 @@
|
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
31
31
|
"types": "./dist/index.d.ts",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"development": "./src/index.ts",
|
|
33
|
+
"bun": "./dist/index.js",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"default": "./dist/index.js"
|
|
34
36
|
},
|
|
35
37
|
"./*": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
+
"development": "./src/*",
|
|
39
|
+
"bun": "./dist/*",
|
|
40
|
+
"import": "./dist/*",
|
|
41
|
+
"default": "./dist/*"
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
"module": "dist/index.js",
|
|
41
45
|
"files": [
|
|
42
46
|
"README.md",
|
|
43
|
-
"dist"
|
|
44
|
-
"src"
|
|
47
|
+
"dist"
|
|
45
48
|
],
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "bun build.ts",
|
package/src/index.ts
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import type { Subprocess } from 'bun'
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, appendFileSync, chmodSync } from 'node:fs'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import process from 'node:process'
|
|
6
|
-
|
|
7
|
-
export interface TinkerConfig {
|
|
8
|
-
/** Additional modules to preload into the REPL session */
|
|
9
|
-
preload?: string[]
|
|
10
|
-
/** Custom history file path (default: ~/.stacks_tinker_history) */
|
|
11
|
-
historyFile?: string
|
|
12
|
-
/** Maximum history entries to persist (default: 5000) */
|
|
13
|
-
historySize?: number
|
|
14
|
-
/** Working directory for the REPL (default: process.cwd()) */
|
|
15
|
-
cwd?: string
|
|
16
|
-
/** Whether to show the welcome banner (default: true) */
|
|
17
|
-
banner?: boolean
|
|
18
|
-
/** Whether to preload Stacks framework modules (default: true) */
|
|
19
|
-
stacksPreload?: boolean
|
|
20
|
-
/** Evaluate a single expression and exit */
|
|
21
|
-
eval?: string
|
|
22
|
-
/** Evaluate and print a single expression, then exit */
|
|
23
|
-
print?: string
|
|
24
|
-
/** Enable verbose/debug output */
|
|
25
|
-
verbose?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Generate the preload script that makes all Stacks framework
|
|
30
|
-
* modules available in the REPL session.
|
|
31
|
-
*/
|
|
32
|
-
function generatePreloadScript(config: TinkerConfig): string {
|
|
33
|
-
const lines: string[] = []
|
|
34
|
-
|
|
35
|
-
if (config.stacksPreload !== false) {
|
|
36
|
-
lines.push(`
|
|
37
|
-
// ---- Stacks Framework Preload ----
|
|
38
|
-
// ORM & Database
|
|
39
|
-
try {
|
|
40
|
-
const { db } = await import('@stacksjs/database')
|
|
41
|
-
globalThis.db = db
|
|
42
|
-
} catch {}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const orm = await import('@stacksjs/orm')
|
|
46
|
-
// Expose all ORM exports globally (User, Post, etc.)
|
|
47
|
-
for (const [key, value] of Object.entries(orm)) {
|
|
48
|
-
if (key !== 'default' && key !== 'db') {
|
|
49
|
-
globalThis[key] = value
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
} catch {}
|
|
53
|
-
|
|
54
|
-
// Config
|
|
55
|
-
try {
|
|
56
|
-
const config = await import('@stacksjs/config')
|
|
57
|
-
globalThis.config = config
|
|
58
|
-
} catch {}
|
|
59
|
-
|
|
60
|
-
// Path utilities
|
|
61
|
-
try {
|
|
62
|
-
const path = await import('@stacksjs/path')
|
|
63
|
-
globalThis.path = path
|
|
64
|
-
} catch {}
|
|
65
|
-
|
|
66
|
-
// Storage
|
|
67
|
-
try {
|
|
68
|
-
const storage = await import('@stacksjs/storage')
|
|
69
|
-
globalThis.storage = storage
|
|
70
|
-
} catch {}
|
|
71
|
-
|
|
72
|
-
// Validation
|
|
73
|
-
try {
|
|
74
|
-
const validation = await import('@stacksjs/validation')
|
|
75
|
-
globalThis.validation = validation
|
|
76
|
-
} catch {}
|
|
77
|
-
|
|
78
|
-
// Logging
|
|
79
|
-
try {
|
|
80
|
-
const { log } = await import('@stacksjs/cli')
|
|
81
|
-
globalThis.log = log
|
|
82
|
-
} catch {}
|
|
83
|
-
|
|
84
|
-
// Collections
|
|
85
|
-
try {
|
|
86
|
-
const collections = await import('@stacksjs/collections')
|
|
87
|
-
globalThis.collect = collections.collect ?? collections.default
|
|
88
|
-
} catch {}
|
|
89
|
-
|
|
90
|
-
// Strings
|
|
91
|
-
try {
|
|
92
|
-
const strings = await import('@stacksjs/strings')
|
|
93
|
-
globalThis.Str = strings
|
|
94
|
-
} catch {}
|
|
95
|
-
|
|
96
|
-
// Cache
|
|
97
|
-
try {
|
|
98
|
-
const cache = await import('@stacksjs/cache')
|
|
99
|
-
globalThis.cache = cache
|
|
100
|
-
} catch {}
|
|
101
|
-
|
|
102
|
-
// Queue / Jobs
|
|
103
|
-
try {
|
|
104
|
-
const queue = await import('@stacksjs/queue')
|
|
105
|
-
globalThis.queue = queue
|
|
106
|
-
} catch {}
|
|
107
|
-
|
|
108
|
-
// Events
|
|
109
|
-
try {
|
|
110
|
-
const events = await import('@stacksjs/events')
|
|
111
|
-
globalThis.events = events
|
|
112
|
-
} catch {}
|
|
113
|
-
|
|
114
|
-
// Router
|
|
115
|
-
try {
|
|
116
|
-
const router = await import('@stacksjs/router')
|
|
117
|
-
globalThis.router = router
|
|
118
|
-
} catch {}
|
|
119
|
-
|
|
120
|
-
// Auth
|
|
121
|
-
try {
|
|
122
|
-
const auth = await import('@stacksjs/auth')
|
|
123
|
-
globalThis.auth = auth
|
|
124
|
-
} catch {}
|
|
125
|
-
|
|
126
|
-
// Notifications
|
|
127
|
-
try {
|
|
128
|
-
const notifications = await import('@stacksjs/notifications')
|
|
129
|
-
globalThis.notifications = notifications
|
|
130
|
-
} catch {}
|
|
131
|
-
|
|
132
|
-
// Env / environment helpers
|
|
133
|
-
try {
|
|
134
|
-
const env = await import('@stacksjs/env')
|
|
135
|
-
globalThis.env = env
|
|
136
|
-
} catch {}
|
|
137
|
-
|
|
138
|
-
// validate() shortcut — paired with schema for fluent validation in REPL.
|
|
139
|
-
try {
|
|
140
|
-
const { validate } = await import('@stacksjs/validation')
|
|
141
|
-
globalThis.validate = validate
|
|
142
|
-
} catch {}
|
|
143
|
-
|
|
144
|
-
// Jobs facade — lets you fire jobs without remembering the dispatch builder.
|
|
145
|
-
try {
|
|
146
|
-
const { Jobs } = await import('@stacksjs/queue')
|
|
147
|
-
globalThis.Jobs = Jobs
|
|
148
|
-
} catch {}
|
|
149
|
-
|
|
150
|
-
// Request introspection — only useful inside an active request, but
|
|
151
|
-
// the proxy is safe to expose: outside a request scope every property
|
|
152
|
-
// returns a typed default (see request-context.ts).
|
|
153
|
-
try {
|
|
154
|
-
const { request, listRegisteredRoutes } = await import('@stacksjs/router')
|
|
155
|
-
globalThis.request = request
|
|
156
|
-
globalThis.routes = listRegisteredRoutes
|
|
157
|
-
} catch {}
|
|
158
|
-
|
|
159
|
-
// dump/dd helpers for quick inspection — a Laravel-ism that's always
|
|
160
|
-
// nice to have at the REPL when investigating a value.
|
|
161
|
-
globalThis.dump = (...args) => { console.dir(args.length === 1 ? args[0] : args, { depth: 6, colors: true }) }
|
|
162
|
-
globalThis.dd = (...args) => { console.dir(args.length === 1 ? args[0] : args, { depth: 6, colors: true }); process.exit(0) }
|
|
163
|
-
`)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Add custom preload modules
|
|
167
|
-
if (config.preload?.length) {
|
|
168
|
-
for (const mod of config.preload) {
|
|
169
|
-
const varName = mod.replace(/[@/\-\.]/g, '_').replace(/^_+/, '')
|
|
170
|
-
lines.push(`try { globalThis.${varName} = await import('${mod}') } catch {}`)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return lines.join('\n')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get the path to the tinker history file.
|
|
179
|
-
*/
|
|
180
|
-
export function getHistoryPath(config?: TinkerConfig): string {
|
|
181
|
-
return config?.historyFile ?? join(homedir(), '.stacks_tinker_history')
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Read history entries from file.
|
|
186
|
-
*/
|
|
187
|
-
export function readHistory(config?: TinkerConfig): string[] {
|
|
188
|
-
const historyPath = getHistoryPath(config)
|
|
189
|
-
|
|
190
|
-
if (!existsSync(historyPath)) {
|
|
191
|
-
return []
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const content = readFileSync(historyPath, 'utf-8')
|
|
195
|
-
return content.split('\n').filter(Boolean)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Append a single entry to the history file.
|
|
200
|
-
*
|
|
201
|
-
* The history file lives at `~/.stacks_tinker_history` and accumulates
|
|
202
|
-
* every expression a developer types — including any one-off pasted
|
|
203
|
-
* tokens, API keys, or DB credentials. Forcing 0600 permissions keeps
|
|
204
|
-
* the file readable only by the file's owner so other users on a shared
|
|
205
|
-
* machine can't grep it for accidentally-committed secrets.
|
|
206
|
-
*/
|
|
207
|
-
export function appendHistory(entry: string, config?: TinkerConfig): void {
|
|
208
|
-
const historyPath = getHistoryPath(config)
|
|
209
|
-
const isNew = !existsSync(historyPath)
|
|
210
|
-
appendFileSync(historyPath, `${entry}\n`)
|
|
211
|
-
if (isNew) {
|
|
212
|
-
try { chmodSync(historyPath, 0o600) } catch { /* best-effort */ }
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Trim history if it exceeds max size
|
|
216
|
-
const maxSize = config?.historySize ?? 5000
|
|
217
|
-
const entries = readHistory(config)
|
|
218
|
-
|
|
219
|
-
if (entries.length > maxSize) {
|
|
220
|
-
const trimmed = entries.slice(entries.length - maxSize)
|
|
221
|
-
writeFileSync(historyPath, trimmed.join('\n') + '\n')
|
|
222
|
-
try { chmodSync(historyPath, 0o600) } catch { /* best-effort */ }
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Clear the tinker history file.
|
|
228
|
-
*/
|
|
229
|
-
export function clearHistory(config?: TinkerConfig): void {
|
|
230
|
-
const historyPath = getHistoryPath(config)
|
|
231
|
-
writeFileSync(historyPath, '')
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Build the welcome banner string.
|
|
236
|
-
*/
|
|
237
|
-
function buildBanner(): string {
|
|
238
|
-
const bunVersion = typeof Bun !== 'undefined' ? Bun.version : 'unknown'
|
|
239
|
-
|
|
240
|
-
return [
|
|
241
|
-
'',
|
|
242
|
-
` \x1b[36mStacks Tinker\x1b[0m (Bun v${bunVersion})`,
|
|
243
|
-
' Interactive REPL with Stacks framework preloaded.',
|
|
244
|
-
'',
|
|
245
|
-
' \x1b[2mAvailable globals: db, config, path, storage, log, Str,',
|
|
246
|
-
' cache, queue, events, router, auth, collect, env, request,',
|
|
247
|
-
' validate, Jobs, routes, dump, dd',
|
|
248
|
-
' + all ORM models (User, Post, etc.)\x1b[0m',
|
|
249
|
-
'',
|
|
250
|
-
' \x1b[2mType .help for REPL commands. Press Ctrl+D to exit.\x1b[0m',
|
|
251
|
-
'',
|
|
252
|
-
].join('\n')
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Start an interactive Stacks tinker session.
|
|
257
|
-
*
|
|
258
|
-
* Launches Bun's built-in REPL with all Stacks framework modules
|
|
259
|
-
* preloaded into the global scope. ORM models, database, config,
|
|
260
|
-
* logging, and other framework utilities are immediately available.
|
|
261
|
-
*
|
|
262
|
-
* @example
|
|
263
|
-
* ```ts
|
|
264
|
-
* import { startTinker } from '@stacksjs/tinker'
|
|
265
|
-
*
|
|
266
|
-
* await startTinker()
|
|
267
|
-
* ```
|
|
268
|
-
*
|
|
269
|
-
* @example
|
|
270
|
-
* ```ts
|
|
271
|
-
* // Evaluate a single expression
|
|
272
|
-
* await startTinker({ eval: 'await User.count()' })
|
|
273
|
-
* ```
|
|
274
|
-
*
|
|
275
|
-
* @example
|
|
276
|
-
* ```ts
|
|
277
|
-
* // Evaluate and print
|
|
278
|
-
* await startTinker({ print: 'await User.all()' })
|
|
279
|
-
* ```
|
|
280
|
-
*/
|
|
281
|
-
export async function startTinker(config: TinkerConfig = {}): Promise<{ exitCode: number }> {
|
|
282
|
-
const cwd = config.cwd ?? process.cwd()
|
|
283
|
-
|
|
284
|
-
// Handle --eval / --print (non-interactive) mode
|
|
285
|
-
if (config.eval || config.print) {
|
|
286
|
-
return runNonInteractive(config, cwd)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Interactive mode — show banner
|
|
290
|
-
if (config.banner !== false) {
|
|
291
|
-
process.stdout.write(buildBanner())
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Write a temporary preload script
|
|
295
|
-
const preloadScript = generatePreloadScript(config)
|
|
296
|
-
const preloadPath = join(cwd, '.tinker-preload.ts')
|
|
297
|
-
writeFileSync(preloadPath, preloadScript)
|
|
298
|
-
|
|
299
|
-
const args = ['repl']
|
|
300
|
-
|
|
301
|
-
const proc: Subprocess = Bun.spawn(['bun', ...args], {
|
|
302
|
-
cwd,
|
|
303
|
-
stdin: 'inherit',
|
|
304
|
-
stdout: 'inherit',
|
|
305
|
-
stderr: 'inherit',
|
|
306
|
-
env: {
|
|
307
|
-
...process.env,
|
|
308
|
-
BUN_PRELOAD: preloadPath,
|
|
309
|
-
},
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
const exitCode = await proc.exited
|
|
313
|
-
|
|
314
|
-
// Cleanup temp preload file
|
|
315
|
-
try {
|
|
316
|
-
const { unlinkSync } = await import('node:fs')
|
|
317
|
-
unlinkSync(preloadPath)
|
|
318
|
-
}
|
|
319
|
-
catch {}
|
|
320
|
-
|
|
321
|
-
return { exitCode }
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Run tinker in non-interactive mode (eval/print).
|
|
326
|
-
*/
|
|
327
|
-
async function runNonInteractive(config: TinkerConfig, cwd: string): Promise<{ exitCode: number }> {
|
|
328
|
-
const preloadScript = generatePreloadScript(config)
|
|
329
|
-
const expression = config.print ?? config.eval ?? ''
|
|
330
|
-
|
|
331
|
-
// Build a script that preloads modules then evaluates the expression
|
|
332
|
-
const script = `${preloadScript}\n\nconst __result__ = await (async () => { return ${expression} })()\n${config.print ? 'console.log(__result__)' : ''}`
|
|
333
|
-
|
|
334
|
-
const tmpPath = join(cwd, '.tinker-eval.ts')
|
|
335
|
-
writeFileSync(tmpPath, script)
|
|
336
|
-
|
|
337
|
-
const proc = Bun.spawn(['bun', 'run', tmpPath], {
|
|
338
|
-
cwd,
|
|
339
|
-
stdin: 'inherit',
|
|
340
|
-
stdout: 'inherit',
|
|
341
|
-
stderr: 'inherit',
|
|
342
|
-
env: process.env,
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
const exitCode = await proc.exited
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
const { unlinkSync } = await import('node:fs')
|
|
349
|
-
unlinkSync(tmpPath)
|
|
350
|
-
}
|
|
351
|
-
catch {}
|
|
352
|
-
|
|
353
|
-
return { exitCode }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Convenience function to evaluate a single expression in tinker context
|
|
358
|
-
* and return the result.
|
|
359
|
-
*/
|
|
360
|
-
export async function tinkerEval(expression: string, config: TinkerConfig = {}): Promise<{ exitCode: number }> {
|
|
361
|
-
return startTinker({ ...config, eval: expression })
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Convenience function to evaluate and print a single expression.
|
|
366
|
-
*/
|
|
367
|
-
export async function tinkerPrint(expression: string, config: TinkerConfig = {}): Promise<{ exitCode: number }> {
|
|
368
|
-
return startTinker({ ...config, print: expression })
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export type { Subprocess }
|
|
File without changes
|