@stacksjs/tinker 0.70.55 → 0.70.56

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/tinker",
3
3
  "type": "module",
4
- "version": "0.70.55",
4
+ "version": "0.70.56",
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
- "bun": "./src/index.ts",
33
- "import": "./dist/index.js"
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
- "bun": "./src/*",
37
- "import": "./dist/*"
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