Skip to content

Commit f6e05ba

Browse files
committed
feat: add dumper and upgrade edge.js with new stacks functionality
1 parent b75f122 commit f6e05ba

14 files changed

+516
-19
lines changed

Diff for: bin/test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { assert } from '@japa/assert'
2+
import { snapshot } from '@japa/snapshot'
23
import { fileSystem } from '@japa/file-system'
34
import { expectTypeOf } from '@japa/expect-type'
45
import { processCLIArgs, configure, run } from '@japa/runner'
@@ -19,7 +20,7 @@ import { processCLIArgs, configure, run } from '@japa/runner'
1920
processCLIArgs(process.argv.slice(2))
2021
configure({
2122
files: ['tests/**/*.spec.ts'],
22-
plugins: [assert(), expectTypeOf(), fileSystem()],
23+
plugins: [assert(), expectTypeOf(), fileSystem(), snapshot()],
2324
})
2425

2526
/*

Diff for: modules/dumper/define_config.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* @adonisjs/core
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { ConsoleDumpConfig } from '@poppinss/dumper/console/types'
11+
import { HTMLDumpConfig } from '@poppinss/dumper/html/types'
12+
13+
/**
14+
* Define config for the dumper service exported by
15+
* the "@adonisjs/core/services/dumper" module
16+
*/
17+
export function defineConfig(
18+
dumperConfig: Partial<{ html: HTMLDumpConfig; console: ConsoleDumpConfig }>
19+
) {
20+
return dumperConfig
21+
}

Diff for: modules/dumper/dumper.ts

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* @adonisjs/core
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { dump as consoleDump } from '@poppinss/dumper/console'
11+
import type { HTMLDumpConfig } from '@poppinss/dumper/html/types'
12+
import type { ConsoleDumpConfig } from '@poppinss/dumper/console/types'
13+
import { createScript, createStyleSheet, dump } from '@poppinss/dumper/html'
14+
15+
import type { Application } from '../app.js'
16+
import { E_DUMP_DIE_EXCEPTION } from './errors.js'
17+
18+
const DUMP_TITLE_STYLES = `
19+
.adonisjs-dump-header {
20+
font-family: JetBrains Mono, monaspace argon, Menlo, Monaco, Consolas, monospace;
21+
background: #ff1639;
22+
border-radius: 4px;
23+
color: #fff;
24+
border-bottom-left-radius: 0;
25+
border-bottom-right-radius: 0;
26+
padding: 0.4rem 1.2rem;
27+
font-size: 1em;
28+
display: flex;
29+
justify-content: space-between;
30+
}
31+
.adonisjs-dump-header .adonisjs-dump-header-title {
32+
font-weight: bold;
33+
text-transform: uppercase;
34+
}
35+
.adonisjs-dump-header .adonisjs-dump-header-source {
36+
font-weight: bold;
37+
color: inherit;
38+
text-decoration: underline;
39+
}
40+
.dumper-dump pre {
41+
border-radius: 4px;
42+
border-top-left-radius: 0;
43+
border-top-right-radius: 0;
44+
}`
45+
46+
/**
47+
* Dumper exposes the API to dump or die/dump values via your
48+
* AdonisJS application. An singleton instance of the Dumper
49+
* is shared as a service and may use it follows.
50+
*
51+
* ```ts
52+
* dumper.configureHtmlOutput({
53+
* // parser + html formatter config
54+
* })
55+
*
56+
* dumper.configureAnsiOutput({
57+
* // parser + console formatter config
58+
* })
59+
*
60+
* const html = dumper.dumpToHtml(value)
61+
* const ansi = dumper.dumpToAnsi(value)
62+
*
63+
* // Returns style and script tags that must be
64+
* // injeted to the head of the HTML document
65+
* const head = dumper.getHeadElements()
66+
* ```
67+
*/
68+
export class Dumper {
69+
#app: Application<any>
70+
#htmlConfig: HTMLDumpConfig = {}
71+
#consoleConfig: ConsoleDumpConfig = {
72+
collapse: ['DateTime', 'Date'],
73+
}
74+
75+
constructor(app: Application<any>) {
76+
this.#app = app
77+
}
78+
79+
/**
80+
* Configure the HTML formatter output
81+
*/
82+
configureHtmlOutput(config: HTMLDumpConfig): this {
83+
this.#htmlConfig = config
84+
return this
85+
}
86+
87+
/**
88+
* Configure the ANSI formatter output
89+
*/
90+
configureAnsiOutput(config: ConsoleDumpConfig): this {
91+
this.#consoleConfig = config
92+
return this
93+
}
94+
95+
/**
96+
* Returns the style and the script elements for the
97+
* HTML document
98+
*/
99+
getHeadElements(cspNonce?: string): string {
100+
return (
101+
'<style id="dumper-styles">' +
102+
createStyleSheet() +
103+
DUMP_TITLE_STYLES +
104+
'</style>' +
105+
`<script id="dumper-script"${cspNonce ? ` nonce="${cspNonce}"` : ''}>` +
106+
createScript() +
107+
'</script>'
108+
)
109+
}
110+
111+
/**
112+
* Dump value to HTML ouput
113+
*/
114+
dumpToHtml(value: unknown, cspNonce?: string) {
115+
return dump(value, { cspNonce, ...this.#htmlConfig })
116+
}
117+
118+
/**
119+
* Dump value to ANSI output
120+
*/
121+
dumpToAnsi(value: unknown) {
122+
return consoleDump(value, this.#consoleConfig)
123+
}
124+
125+
/**
126+
* Dump values and die. The formatter will be picked
127+
* based upon where your app is running.
128+
*
129+
* - In CLI commands, the ANSI output will be printed
130+
* to the console.
131+
* - During an HTTP request, the HTML output will be
132+
* sent to the server.
133+
*/
134+
dd(value: unknown, traceSourceIndex: number = 1) {
135+
const error = new E_DUMP_DIE_EXCEPTION(value, this, this.#app)
136+
error.setTraceSourceIndex(traceSourceIndex)
137+
throw error
138+
}
139+
}

Diff for: modules/dumper/errors.ts

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* @adonisjs/core
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { inspect } from 'node:util'
11+
import { parse } from 'error-stack-parser-es'
12+
import type { Kernel } from '@adonisjs/core/ace'
13+
import { Exception } from '@poppinss/utils/exception'
14+
import type { HttpContext } from '@adonisjs/core/http'
15+
import type { ApplicationService } from '@adonisjs/core/types'
16+
17+
import type { Dumper } from './dumper.js'
18+
19+
const IDE = process.env.ADONIS_IDE ?? process.env.EDITOR ?? ''
20+
21+
/**
22+
* DumpDie exception is raised by the "dd" function. It will
23+
* result in dumping the value in response to an HTTP
24+
* request or printing the value to the console
25+
*/
26+
class DumpDieException extends Exception {
27+
static status: number = 500
28+
static code: string = 'E_DUMP_DIE_EXCEPTION'
29+
30+
#app: ApplicationService
31+
#dumper: Dumper
32+
#traceSourceIndex: number = 1
33+
34+
/**
35+
* A collections of known editors to create URLs to open
36+
* them
37+
*/
38+
#editors: Record<string, string> = {
39+
textmate: 'txmt://open?url=file://%f&line=%l',
40+
macvim: 'mvim://open?url=file://%f&line=%l',
41+
emacs: 'emacs://open?url=file://%f&line=%l',
42+
sublime: 'subl://open?url=file://%f&line=%l',
43+
phpstorm: 'phpstorm://open?file=%f&line=%l',
44+
atom: 'atom://core/open/file?filename=%f&line=%l',
45+
vscode: 'vscode://file/%f:%l',
46+
}
47+
48+
value: unknown
49+
50+
constructor(value: unknown, dumper: Dumper, app: ApplicationService) {
51+
super('Dump and Die exception')
52+
this.#dumper = dumper
53+
this.#app = app
54+
this.value = value
55+
}
56+
57+
/**
58+
* Returns the link to open the file using dd inside one
59+
* of the known code editors
60+
*/
61+
#getEditorLink(): { href: string; text: string } | undefined {
62+
const editorURL = this.#editors[IDE] || IDE
63+
if (!editorURL) {
64+
return
65+
}
66+
67+
const source = parse(this)[this.#traceSourceIndex]
68+
if (!source.fileName || !source.lineNumber) {
69+
return
70+
}
71+
72+
return {
73+
href: editorURL.replace('%f', source.fileName).replace('%l', String(source.lineNumber)),
74+
text: `${this.#app.relativePath(source.fileName)}:${source.lineNumber}`,
75+
}
76+
}
77+
78+
/**
79+
* Set the index for the trace source. This is helpful when
80+
* you build nested helpers on top of Die/Dump
81+
*/
82+
setTraceSourceIndex(index: number) {
83+
this.#traceSourceIndex = index
84+
return this
85+
}
86+
87+
/**
88+
* Preventing itself from getting reported by the
89+
* AdonisJS exception reporter
90+
*/
91+
report() {}
92+
93+
/**
94+
* Handler called by the AdonisJS HTTP exception handler
95+
*/
96+
async handle(error: DumpDieException, ctx: HttpContext) {
97+
const link = this.#getEditorLink()
98+
/**
99+
* Comes from the shield package
100+
*/
101+
const cspNonce = 'nonce' in ctx.response ? ctx.response.nonce : undefined
102+
103+
ctx.response
104+
.status(500)
105+
.send(
106+
'<!DOCTYPE html>' +
107+
'<html>' +
108+
'<head>' +
109+
'<meta charset="utf-8">' +
110+
'<meta name="viewport" content="width=device-width">' +
111+
`${this.#dumper.getHeadElements(cspNonce)}` +
112+
'</head>' +
113+
'<body>' +
114+
'<div class="adonisjs-dump-header">' +
115+
'<span class="adonisjs-dump-header-title">DUMP DIE</span>' +
116+
(link
117+
? `<a href="${link.href}" class="adonisjs-dump-header-source">${link.text}</a>`
118+
: '') +
119+
'</div>' +
120+
`${this.#dumper.dumpToHtml(error.value, cspNonce)}` +
121+
'</body>' +
122+
'</html>'
123+
)
124+
}
125+
126+
/**
127+
* Handler called by the AdonisJS Ace kernel
128+
*/
129+
async render(error: DumpDieException, kernel: Kernel) {
130+
kernel.ui.logger.log(this.#dumper.dumpToAnsi(error.value))
131+
}
132+
133+
/**
134+
* Custom output for the Node.js util inspect
135+
*/
136+
[inspect.custom]() {
137+
return this.#dumper.dumpToAnsi(this.value)
138+
}
139+
}
140+
141+
export const E_DUMP_DIE_EXCEPTION = DumpDieException

Diff for: modules/dumper/main.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* @adonisjs/core
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export * as errors from './errors.js'
11+
export { Dumper } from './dumper.js'
12+
export { defineConfig } from './define_config.js'

Diff for: package.json

+18-15
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,16 @@
8585
"@adonisjs/eslint-config": "^2.0.0-beta.6",
8686
"@adonisjs/prettier-config": "^1.4.0",
8787
"@adonisjs/tsconfig": "^1.4.0",
88-
"@commitlint/cli": "^19.4.1",
89-
"@commitlint/config-conventional": "^19.4.1",
88+
"@commitlint/cli": "^19.5.0",
89+
"@commitlint/config-conventional": "^19.5.0",
9090
"@japa/assert": "^3.0.0",
9191
"@japa/expect-type": "^2.0.2",
9292
"@japa/file-system": "^2.3.0",
9393
"@japa/runner": "^3.1.4",
94-
"@release-it/conventional-changelog": "^8.0.1",
95-
"@swc/core": "^1.7.23",
96-
"@types/node": "^22.5.4",
94+
"@japa/snapshot": "^2.0.5",
95+
"@release-it/conventional-changelog": "^8.0.2",
96+
"@swc/core": "^1.7.26",
97+
"@types/node": "^22.5.5",
9798
"@types/pretty-hrtime": "^1.0.3",
9899
"@types/sinon": "^17.0.3",
99100
"@types/supertest": "^6.0.2",
@@ -105,19 +106,19 @@
105106
"copyfiles": "^2.4.1",
106107
"cross-env": "^7.0.3",
107108
"del-cli": "^5.1.0",
108-
"edge.js": "^6.0.2",
109-
"eslint": "^9.9.1",
110-
"execa": "^9.3.1",
109+
"edge.js": "^6.1.0",
110+
"eslint": "^9.10.0",
111+
"execa": "^9.4.0",
111112
"get-port": "^7.1.0",
112113
"github-label-sync": "^2.3.1",
113-
"husky": "^9.1.5",
114+
"husky": "^9.1.6",
114115
"prettier": "^3.3.3",
115116
"release-it": "^17.6.0",
116-
"sinon": "^18.0.0",
117+
"sinon": "^19.0.2",
117118
"supertest": "^7.0.0",
118119
"test-console": "^2.0.0",
119120
"ts-node-maintained": "^10.9.4",
120-
"typescript": "^5.5.4"
121+
"typescript": "^5.6.2"
121122
},
122123
"dependencies": {
123124
"@adonisjs/ace": "^13.2.0",
@@ -135,12 +136,14 @@
135136
"@adonisjs/repl": "^4.0.1",
136137
"@antfu/install-pkg": "^0.4.1",
137138
"@paralleldrive/cuid2": "^2.2.2",
138-
"@poppinss/macroable": "^1.0.2",
139-
"@poppinss/utils": "^6.7.3",
140-
"@sindresorhus/is": "^7.0.0",
139+
"@poppinss/dumper": "^0.4.0",
140+
"@poppinss/macroable": "^1.0.3",
141+
"@poppinss/utils": "^6.8.1",
142+
"@sindresorhus/is": "^7.0.1",
141143
"@types/he": "^1.2.3",
144+
"error-stack-parser-es": "^0.1.5",
142145
"he": "^1.2.0",
143-
"parse-imports": "^1.2.0",
146+
"parse-imports": "^2.1.1",
144147
"pretty-hrtime": "^1.0.3",
145148
"string-width": "^7.2.0",
146149
"youch": "^3.3.3",

0 commit comments

Comments
 (0)