Skip to content

Commit

Permalink
feat: add dumper and upgrade edge.js with new stacks functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Sep 18, 2024
1 parent b75f122 commit f6e05ba
Show file tree
Hide file tree
Showing 14 changed files with 516 additions and 19 deletions.
3 changes: 2 additions & 1 deletion bin/test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert } from '@japa/assert'
import { snapshot } from '@japa/snapshot'
import { fileSystem } from '@japa/file-system'
import { expectTypeOf } from '@japa/expect-type'
import { processCLIArgs, configure, run } from '@japa/runner'
Expand All @@ -19,7 +20,7 @@ import { processCLIArgs, configure, run } from '@japa/runner'
processCLIArgs(process.argv.slice(2))
configure({
files: ['tests/**/*.spec.ts'],
plugins: [assert(), expectTypeOf(), fileSystem()],
plugins: [assert(), expectTypeOf(), fileSystem(), snapshot()],
})

/*
Expand Down
21 changes: 21 additions & 0 deletions modules/dumper/define_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { ConsoleDumpConfig } from '@poppinss/dumper/console/types'
import { HTMLDumpConfig } from '@poppinss/dumper/html/types'

/**
* Define config for the dumper service exported by
* the "@adonisjs/core/services/dumper" module
*/
export function defineConfig(
dumperConfig: Partial<{ html: HTMLDumpConfig; console: ConsoleDumpConfig }>
) {
return dumperConfig
}
139 changes: 139 additions & 0 deletions modules/dumper/dumper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { dump as consoleDump } from '@poppinss/dumper/console'
import type { HTMLDumpConfig } from '@poppinss/dumper/html/types'
import type { ConsoleDumpConfig } from '@poppinss/dumper/console/types'
import { createScript, createStyleSheet, dump } from '@poppinss/dumper/html'

import type { Application } from '../app.js'
import { E_DUMP_DIE_EXCEPTION } from './errors.js'

const DUMP_TITLE_STYLES = `
.adonisjs-dump-header {
font-family: JetBrains Mono, monaspace argon, Menlo, Monaco, Consolas, monospace;
background: #ff1639;
border-radius: 4px;
color: #fff;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding: 0.4rem 1.2rem;
font-size: 1em;
display: flex;
justify-content: space-between;
}
.adonisjs-dump-header .adonisjs-dump-header-title {
font-weight: bold;
text-transform: uppercase;
}
.adonisjs-dump-header .adonisjs-dump-header-source {
font-weight: bold;
color: inherit;
text-decoration: underline;
}
.dumper-dump pre {
border-radius: 4px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}`

/**
* Dumper exposes the API to dump or die/dump values via your
* AdonisJS application. An singleton instance of the Dumper
* is shared as a service and may use it follows.
*
* ```ts
* dumper.configureHtmlOutput({
* // parser + html formatter config
* })
*
* dumper.configureAnsiOutput({
* // parser + console formatter config
* })
*
* const html = dumper.dumpToHtml(value)
* const ansi = dumper.dumpToAnsi(value)
*
* // Returns style and script tags that must be
* // injeted to the head of the HTML document
* const head = dumper.getHeadElements()
* ```
*/
export class Dumper {
#app: Application<any>
#htmlConfig: HTMLDumpConfig = {}
#consoleConfig: ConsoleDumpConfig = {
collapse: ['DateTime', 'Date'],
}

constructor(app: Application<any>) {
this.#app = app
}

/**
* Configure the HTML formatter output
*/
configureHtmlOutput(config: HTMLDumpConfig): this {
this.#htmlConfig = config
return this
}

/**
* Configure the ANSI formatter output
*/
configureAnsiOutput(config: ConsoleDumpConfig): this {
this.#consoleConfig = config
return this
}

/**
* Returns the style and the script elements for the
* HTML document
*/
getHeadElements(cspNonce?: string): string {
return (
'<style id="dumper-styles">' +
createStyleSheet() +
DUMP_TITLE_STYLES +
'</style>' +
`<script id="dumper-script"${cspNonce ? ` nonce="${cspNonce}"` : ''}>` +
createScript() +
'</script>'
)
}

/**
* Dump value to HTML ouput
*/
dumpToHtml(value: unknown, cspNonce?: string) {
return dump(value, { cspNonce, ...this.#htmlConfig })
}

/**
* Dump value to ANSI output
*/
dumpToAnsi(value: unknown) {
return consoleDump(value, this.#consoleConfig)
}

/**
* Dump values and die. The formatter will be picked
* based upon where your app is running.
*
* - In CLI commands, the ANSI output will be printed
* to the console.
* - During an HTTP request, the HTML output will be
* sent to the server.
*/
dd(value: unknown, traceSourceIndex: number = 1) {
const error = new E_DUMP_DIE_EXCEPTION(value, this, this.#app)
error.setTraceSourceIndex(traceSourceIndex)
throw error
}
}
141 changes: 141 additions & 0 deletions modules/dumper/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { inspect } from 'node:util'
import { parse } from 'error-stack-parser-es'
import type { Kernel } from '@adonisjs/core/ace'
import { Exception } from '@poppinss/utils/exception'
import type { HttpContext } from '@adonisjs/core/http'
import type { ApplicationService } from '@adonisjs/core/types'

import type { Dumper } from './dumper.js'

const IDE = process.env.ADONIS_IDE ?? process.env.EDITOR ?? ''

/**
* DumpDie exception is raised by the "dd" function. It will
* result in dumping the value in response to an HTTP
* request or printing the value to the console
*/
class DumpDieException extends Exception {
static status: number = 500
static code: string = 'E_DUMP_DIE_EXCEPTION'

#app: ApplicationService
#dumper: Dumper
#traceSourceIndex: number = 1

/**
* A collections of known editors to create URLs to open
* them
*/
#editors: Record<string, string> = {
textmate: 'txmt://open?url=file://%f&line=%l',
macvim: 'mvim://open?url=file://%f&line=%l',
emacs: 'emacs://open?url=file://%f&line=%l',
sublime: 'subl://open?url=file://%f&line=%l',
phpstorm: 'phpstorm://open?file=%f&line=%l',
atom: 'atom://core/open/file?filename=%f&line=%l',
vscode: 'vscode://file/%f:%l',
}

value: unknown

constructor(value: unknown, dumper: Dumper, app: ApplicationService) {
super('Dump and Die exception')
this.#dumper = dumper
this.#app = app
this.value = value
}

/**
* Returns the link to open the file using dd inside one
* of the known code editors
*/
#getEditorLink(): { href: string; text: string } | undefined {
const editorURL = this.#editors[IDE] || IDE
if (!editorURL) {
return
}

const source = parse(this)[this.#traceSourceIndex]
if (!source.fileName || !source.lineNumber) {
return
}

return {
href: editorURL.replace('%f', source.fileName).replace('%l', String(source.lineNumber)),
text: `${this.#app.relativePath(source.fileName)}:${source.lineNumber}`,
}
}

/**
* Set the index for the trace source. This is helpful when
* you build nested helpers on top of Die/Dump
*/
setTraceSourceIndex(index: number) {
this.#traceSourceIndex = index
return this
}

/**
* Preventing itself from getting reported by the
* AdonisJS exception reporter
*/
report() {}

/**
* Handler called by the AdonisJS HTTP exception handler
*/
async handle(error: DumpDieException, ctx: HttpContext) {
const link = this.#getEditorLink()
/**
* Comes from the shield package
*/
const cspNonce = 'nonce' in ctx.response ? ctx.response.nonce : undefined

ctx.response
.status(500)
.send(
'<!DOCTYPE html>' +
'<html>' +
'<head>' +
'<meta charset="utf-8">' +
'<meta name="viewport" content="width=device-width">' +
`${this.#dumper.getHeadElements(cspNonce)}` +

Check failure on line 111 in modules/dumper/errors.ts

View workflow job for this annotation

GitHub Actions / typecheck / typecheck

Argument of type 'unknown' is not assignable to parameter of type 'string | undefined'.
'</head>' +
'<body>' +
'<div class="adonisjs-dump-header">' +
'<span class="adonisjs-dump-header-title">DUMP DIE</span>' +
(link
? `<a href="${link.href}" class="adonisjs-dump-header-source">${link.text}</a>`
: '') +
'</div>' +
`${this.#dumper.dumpToHtml(error.value, cspNonce)}` +

Check failure on line 120 in modules/dumper/errors.ts

View workflow job for this annotation

GitHub Actions / typecheck / typecheck

Argument of type 'unknown' is not assignable to parameter of type 'string | undefined'.
'</body>' +
'</html>'
)
}

/**
* Handler called by the AdonisJS Ace kernel
*/
async render(error: DumpDieException, kernel: Kernel) {
kernel.ui.logger.log(this.#dumper.dumpToAnsi(error.value))
}

/**
* Custom output for the Node.js util inspect
*/
[inspect.custom]() {
return this.#dumper.dumpToAnsi(this.value)
}
}

export const E_DUMP_DIE_EXCEPTION = DumpDieException
12 changes: 12 additions & 0 deletions modules/dumper/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export * as errors from './errors.js'
export { Dumper } from './dumper.js'
export { defineConfig } from './define_config.js'
33 changes: 18 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@
"@adonisjs/eslint-config": "^2.0.0-beta.6",
"@adonisjs/prettier-config": "^1.4.0",
"@adonisjs/tsconfig": "^1.4.0",
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@japa/assert": "^3.0.0",
"@japa/expect-type": "^2.0.2",
"@japa/file-system": "^2.3.0",
"@japa/runner": "^3.1.4",
"@release-it/conventional-changelog": "^8.0.1",
"@swc/core": "^1.7.23",
"@types/node": "^22.5.4",
"@japa/snapshot": "^2.0.5",
"@release-it/conventional-changelog": "^8.0.2",
"@swc/core": "^1.7.26",
"@types/node": "^22.5.5",
"@types/pretty-hrtime": "^1.0.3",
"@types/sinon": "^17.0.3",
"@types/supertest": "^6.0.2",
Expand All @@ -105,19 +106,19 @@
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"del-cli": "^5.1.0",
"edge.js": "^6.0.2",
"eslint": "^9.9.1",
"execa": "^9.3.1",
"edge.js": "^6.1.0",
"eslint": "^9.10.0",
"execa": "^9.4.0",
"get-port": "^7.1.0",
"github-label-sync": "^2.3.1",
"husky": "^9.1.5",
"husky": "^9.1.6",
"prettier": "^3.3.3",
"release-it": "^17.6.0",
"sinon": "^18.0.0",
"sinon": "^19.0.2",
"supertest": "^7.0.0",
"test-console": "^2.0.0",
"ts-node-maintained": "^10.9.4",
"typescript": "^5.5.4"
"typescript": "^5.6.2"
},
"dependencies": {
"@adonisjs/ace": "^13.2.0",
Expand All @@ -135,12 +136,14 @@
"@adonisjs/repl": "^4.0.1",
"@antfu/install-pkg": "^0.4.1",
"@paralleldrive/cuid2": "^2.2.2",
"@poppinss/macroable": "^1.0.2",
"@poppinss/utils": "^6.7.3",
"@sindresorhus/is": "^7.0.0",
"@poppinss/dumper": "^0.4.0",
"@poppinss/macroable": "^1.0.3",
"@poppinss/utils": "^6.8.1",
"@sindresorhus/is": "^7.0.1",
"@types/he": "^1.2.3",
"error-stack-parser-es": "^0.1.5",
"he": "^1.2.0",
"parse-imports": "^1.2.0",
"parse-imports": "^2.1.1",
"pretty-hrtime": "^1.0.3",
"string-width": "^7.2.0",
"youch": "^3.3.3",
Expand Down
Loading

0 comments on commit f6e05ba

Please sign in to comment.