rajt 0.0.96 → 0.0.98
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/bin/rajt.js +2 -2
- package/package.json +14 -24
- package/src/auth/ability.ts +0 -6
- package/src/auth/token.ts +46 -12
- package/src/cli/commands/build.ts +1 -1
- package/src/cli/commands/deploy.ts +2 -1
- package/src/cli/commands/dev.ts +6 -3
- package/src/cli/commands/make.ts +3 -2
- package/src/cli/commands/migrate.ts +3 -2
- package/src/cli/commands/routes.ts +6 -33
- package/src/cli/index.ts +1 -1
- package/src/cli/utils.ts +40 -9
- package/src/config.ts +1 -0
- package/src/create-app.ts +1 -1
- package/src/dev.ts +2 -2
- package/src/oas.ts +79 -84
- package/src/prod.ts +4 -4
- package/src/request.ts +6 -4
- package/src/routes.ts +54 -26
- package/src/utils/log.ts +1 -1
- package/src/utils/paths.ts +5 -0
- package/src/validator.ts +1 -1
- package/src/utils/colors.ts +0 -102
package/bin/rajt.js
CHANGED
|
@@ -93,8 +93,8 @@ function semiver(a, b, bool) {
|
|
|
93
93
|
function directly() {
|
|
94
94
|
try {
|
|
95
95
|
const arg = (process.argv[1] || '')?.replace(/\\/g, '/');
|
|
96
|
-
return arg?.endsWith('
|
|
97
|
-
|| arg?.endsWith('
|
|
96
|
+
return arg?.endsWith('.bin/rajt')
|
|
97
|
+
|| arg?.endsWith('rajt/bin/rajt.js')
|
|
98
98
|
} catch {
|
|
99
99
|
return false
|
|
100
100
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rajt",
|
|
3
3
|
"description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.98",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
|
+
"files": ["bin", "src"],
|
|
7
8
|
"bin": {
|
|
8
9
|
"rajt": "./bin/rajt.js"
|
|
9
10
|
},
|
|
@@ -22,10 +23,6 @@
|
|
|
22
23
|
"./env": "./src/utils/environment.ts",
|
|
23
24
|
"./length": "./src/utils/length.ts"
|
|
24
25
|
},
|
|
25
|
-
"files": [
|
|
26
|
-
"bin",
|
|
27
|
-
"src"
|
|
28
|
-
],
|
|
29
26
|
"scripts": {
|
|
30
27
|
"aws:local": "bun run --silent aws:build && bun run --silent sam:local",
|
|
31
28
|
"aws:package": "bun run --silent aws:build && bun run --silent sam:package",
|
|
@@ -38,44 +35,37 @@
|
|
|
38
35
|
"zip": "zip -j ../../lambda.zip ../../.rajt/dist/index.js"
|
|
39
36
|
},
|
|
40
37
|
"dependencies": {
|
|
41
|
-
"cripta": "^0.1",
|
|
42
|
-
"forj": "^0.1",
|
|
43
|
-
"t0n": "^0.1",
|
|
44
38
|
"@hono/node-server": "^1.19.9",
|
|
45
39
|
"@hono/standard-validator": "^0.2.2",
|
|
46
40
|
"@scalar/hono-api-reference": "^0.9.40",
|
|
47
|
-
"chokidar
|
|
41
|
+
"chokidar": "^3.5.2",
|
|
48
42
|
"citty": "^0.1.6",
|
|
43
|
+
"consola": "^3.4.2",
|
|
44
|
+
"cripta": "~0.1.10",
|
|
49
45
|
"dotenv": "^16.5.0",
|
|
50
46
|
"esbuild": "^0.25.2",
|
|
47
|
+
"forj": "~0.1.8",
|
|
51
48
|
"hono": "^4.11.7",
|
|
52
49
|
"hono-openapi": "^1.2.0",
|
|
53
50
|
"localflare-api": "^0.4.2",
|
|
54
51
|
"localflare-core": "^0.4.2",
|
|
55
|
-
"miniflare": "^4.
|
|
52
|
+
"miniflare": "^4.20260301.1",
|
|
56
53
|
"pathe": "^2.0",
|
|
57
54
|
"quansync": "^0.2.11",
|
|
55
|
+
"t0n": "~0.1.11",
|
|
58
56
|
"tiny-glob": "^0.2",
|
|
59
|
-
"tsx": "^4.19.
|
|
60
|
-
"ua-parser-js": "^2.0.8",
|
|
57
|
+
"tsx": "^4.19.4",
|
|
61
58
|
"wrangler": "^4.61.0",
|
|
59
|
+
"zod": "^4.3.6",
|
|
62
60
|
"zod-openapi": "4"
|
|
63
61
|
},
|
|
64
62
|
"devDependencies": {
|
|
65
|
-
"@cloudflare/workers-types": "^4.
|
|
66
|
-
"@
|
|
67
|
-
"
|
|
68
|
-
"@miniflare/durable-objects": "^2.14.4",
|
|
69
|
-
"@miniflare/kv": "^2.14.4",
|
|
70
|
-
"@miniflare/r2": "^2.14.4",
|
|
71
|
-
"@miniflare/scheduler": "^2.14.4",
|
|
72
|
-
"@miniflare/sites": "^2.14.4",
|
|
73
|
-
"@miniflare/storage-file": "^2.14.4",
|
|
74
|
-
"@miniflare/web-sockets": "^2.14.4",
|
|
75
|
-
"@types/node": "^20.11.0",
|
|
76
|
-
"bun-types": "^1.2.14",
|
|
63
|
+
"@cloudflare/workers-types": "^4.20260113.0",
|
|
64
|
+
"@types/node": "^25.1.0",
|
|
65
|
+
"bun-types": "^1.3.8",
|
|
77
66
|
"typescript": "^5.9.3"
|
|
78
67
|
},
|
|
68
|
+
"packageManager": "bun@1.3.10",
|
|
79
69
|
"engines": {
|
|
80
70
|
"node": ">=18.0.0"
|
|
81
71
|
},
|
package/src/auth/ability.ts
CHANGED
|
@@ -13,12 +13,6 @@ export class Ability {
|
|
|
13
13
|
static fromRoutes(actions: Routes) {
|
|
14
14
|
if (!actions?.length) return
|
|
15
15
|
|
|
16
|
-
const paths = actions?.map(a => Array.isArray(a) ? a[0] + a[1] : a.method + a.path) || []
|
|
17
|
-
const items = new Set(paths)
|
|
18
|
-
|
|
19
|
-
if (items.size != actions.length)
|
|
20
|
-
throw new Error(`Duplicate routes detected: "${paths.filter((path, index) => paths.indexOf(path) != index).join('", "')}"`)
|
|
21
|
-
|
|
22
16
|
this.#abilities = Array.from(new Set(actions?.map(a => Array.isArray(a) ? a[3] : a.name) || []))
|
|
23
17
|
.map(a => this.format(a))
|
|
24
18
|
.filter(Boolean)
|
package/src/auth/token.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import { Envir } from 't0n'
|
|
2
|
-
import { Token as Factory } from 'cripta'
|
|
3
|
-
import { UAParser } from 'ua-parser-js'
|
|
1
|
+
import { Envir, parseUA } from 't0n'
|
|
2
|
+
import { Token as Factory, sha256 } from 'cripta'
|
|
4
3
|
import type { IRequest } from '@/types'
|
|
5
4
|
|
|
5
|
+
const STABLE_HEADERS = [
|
|
6
|
+
'host',
|
|
7
|
+
'connection',
|
|
8
|
+
'sec-ch-ua',
|
|
9
|
+
'sec-ch-ua-mobile',
|
|
10
|
+
'sec-ch-ua-platform',
|
|
11
|
+
'upgrade-insecure-requests',
|
|
12
|
+
'user-agent',
|
|
13
|
+
'accept',
|
|
14
|
+
'sec-fetch-site',
|
|
15
|
+
'sec-fetch-mode',
|
|
16
|
+
'sec-fetch-user',
|
|
17
|
+
'sec-fetch-dest',
|
|
18
|
+
'accept-encoding',
|
|
19
|
+
'accept-language',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
function headerOrdering(headers: Record<string, string>) {
|
|
23
|
+
const order: string[] = []
|
|
24
|
+
|
|
25
|
+
for (const name in headers) {
|
|
26
|
+
const key = name.toLowerCase()
|
|
27
|
+
|
|
28
|
+
if (STABLE_HEADERS.includes(key))
|
|
29
|
+
order.push(key)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return order
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
export class Token {
|
|
7
36
|
static #cookieName: string = '__auth_'
|
|
8
37
|
static #name: string = 'Authorization'
|
|
@@ -48,8 +77,7 @@ export class Token {
|
|
|
48
77
|
return Factory.parse(token)
|
|
49
78
|
.issuedBy(serveHost)
|
|
50
79
|
.permittedFor(host)
|
|
51
|
-
.withClaim('
|
|
52
|
-
.withClaim('i', this.ip(req))
|
|
80
|
+
.withClaim('_', this.fingerprint(req))
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
static create(req: IRequest, user: any, exp: number = 7200) {
|
|
@@ -61,8 +89,7 @@ export class Token {
|
|
|
61
89
|
.permittedFor(host)
|
|
62
90
|
.issuedAt(time)
|
|
63
91
|
.expiresAt(time + exp)
|
|
64
|
-
.withClaim('
|
|
65
|
-
.withClaim('i', this.ip(req))
|
|
92
|
+
.withClaim('_', this.fingerprint(req))
|
|
66
93
|
.body(user)
|
|
67
94
|
}
|
|
68
95
|
|
|
@@ -90,11 +117,18 @@ export class Token {
|
|
|
90
117
|
}
|
|
91
118
|
}
|
|
92
119
|
|
|
93
|
-
static
|
|
94
|
-
const ua = req
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
120
|
+
static fingerprint(req: IRequest) {
|
|
121
|
+
const ua = parseUA(req.header('user-agent'))
|
|
122
|
+
|
|
123
|
+
const id = sha256(
|
|
124
|
+
// (req.header('accept-language') || '')
|
|
125
|
+
this.ip(req)
|
|
126
|
+
+ ua.browser.name + ua.browser.version.split('.')[0]
|
|
127
|
+
+ ua.os.type + ua.os.name
|
|
128
|
+
+ headerOrdering(req.header()).join('')
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return id.substring(0, 8) + id.substring(56, 64)
|
|
98
132
|
}
|
|
99
133
|
|
|
100
134
|
static ip(req: IRequest) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
2
|
import { defineCommand } from 'citty'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { normalizePlatform, platformError, getRuntime } from '../utils'
|
|
5
5
|
import { error } from '../../utils/log'
|
|
6
|
+
import { _root } from '../../utils/paths'
|
|
6
7
|
import { platforms } from '../constants'
|
|
7
8
|
|
|
8
9
|
import build from './build'
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { spawn, type ChildProcess } from 'node:child_process'
|
|
|
4
4
|
import { defineCommand } from 'citty'
|
|
5
5
|
import type { Miniflare } from 'miniflare'
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
build, wait, watch, normalizePlatform, platformError, getRuntime,
|
|
8
8
|
wranglerConfig, createMiniflare, localflareManifest,
|
|
9
9
|
getDockerHost
|
|
10
10
|
} from '../utils'
|
|
11
11
|
import { error, event, log, rn, warn } from '../../utils/log'
|
|
12
|
+
import { _root } from '../../utils/paths'
|
|
12
13
|
import { withPort } from '../../utils/port'
|
|
13
14
|
import shutdown from '../../utils/shutdown'
|
|
14
15
|
|
|
@@ -165,7 +166,9 @@ export default defineCommand({
|
|
|
165
166
|
let localflare: Miniflare | null = null
|
|
166
167
|
const startWorker = async () => {
|
|
167
168
|
if (worker) await worker.dispose()
|
|
168
|
-
|
|
169
|
+
if (localflare) await localflare.dispose()
|
|
170
|
+
|
|
171
|
+
// await wait(500)
|
|
169
172
|
|
|
170
173
|
const workerConfig = await wranglerConfig()
|
|
171
174
|
workerConfig.host = host
|
|
@@ -179,7 +182,7 @@ export default defineCommand({
|
|
|
179
182
|
...workerConfig.vars,
|
|
180
183
|
LOCALFLARE_MANIFEST: JSON.stringify(localflareManifest(workerConfig)),
|
|
181
184
|
},
|
|
182
|
-
main: '
|
|
185
|
+
main: '.rajt/localfire.js',
|
|
183
186
|
port: 8788,
|
|
184
187
|
inspectorPort: 9230,
|
|
185
188
|
})
|
package/src/cli/commands/make.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineCommand } from 'citty'
|
|
2
2
|
import { join, relative } from 'pathe'
|
|
3
3
|
import { Migrator } from 'forj'
|
|
4
|
-
import {
|
|
4
|
+
import { makeFile, hasExt, camelCase, kebabCase } from '../utils'
|
|
5
|
+
import { _root } from '../../utils/paths'
|
|
5
6
|
import { event, error } from '../../utils/log'
|
|
6
|
-
import { dim } from '
|
|
7
|
+
import { dim } from 't0n/color'
|
|
7
8
|
import * as stub from '../stubs'
|
|
8
9
|
|
|
9
10
|
export default defineCommand({
|
|
@@ -2,8 +2,9 @@ import { defineCommand } from 'citty'
|
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
3
|
import { join } from 'pathe'
|
|
4
4
|
import { Migrator } from 'forj'
|
|
5
|
-
import { gray } from '
|
|
6
|
-
import {
|
|
5
|
+
import { gray } from 't0n/color'
|
|
6
|
+
import { getRuntime, cleanDB, cleanDir } from '../utils'
|
|
7
|
+
import { _root } from '../../utils/paths'
|
|
7
8
|
import { wait, info, event, rn, error, log } from '../../utils/log'
|
|
8
9
|
|
|
9
10
|
export default defineCommand({
|
|
@@ -2,9 +2,9 @@ import { join } from 'pathe'
|
|
|
2
2
|
import { defineCommand } from 'citty'
|
|
3
3
|
import { inspectRoutes } from 'hono/dev'
|
|
4
4
|
import { IMPORT } from 't0n'
|
|
5
|
-
import {
|
|
6
|
-
import { __rajt } from '../utils'
|
|
5
|
+
import { _rajt } from '../../utils/paths'
|
|
7
6
|
import { rn } from '../../utils/log'
|
|
7
|
+
import { highlightedURI, highlightedMethod } from '../utils'
|
|
8
8
|
|
|
9
9
|
export default defineCommand({
|
|
10
10
|
meta: {
|
|
@@ -26,7 +26,7 @@ export default defineCommand({
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
async run({ args }) {
|
|
29
|
-
const mod = await IMPORT(join(
|
|
29
|
+
const mod = await IMPORT(join(_rajt, 'dev.ts'))
|
|
30
30
|
const app = mod.default
|
|
31
31
|
|
|
32
32
|
const opts = {
|
|
@@ -39,28 +39,6 @@ export default defineCommand({
|
|
|
39
39
|
let maxMethodLength = 0
|
|
40
40
|
let maxPathLength = 0
|
|
41
41
|
|
|
42
|
-
const colorMethod = (method: string, str?: string) => {
|
|
43
|
-
const val = str || method
|
|
44
|
-
|
|
45
|
-
switch (method) {
|
|
46
|
-
case 'HEAD':
|
|
47
|
-
case 'OPTIONS':
|
|
48
|
-
case 'CONNECT':
|
|
49
|
-
case 'TRACE':
|
|
50
|
-
return gray(val)
|
|
51
|
-
case 'GET':
|
|
52
|
-
return purple(val)
|
|
53
|
-
case 'POST':
|
|
54
|
-
case 'PUT':
|
|
55
|
-
case 'PATCH':
|
|
56
|
-
return yellow(val)
|
|
57
|
-
case 'DELETE':
|
|
58
|
-
return red(val)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return val
|
|
62
|
-
}
|
|
63
|
-
|
|
64
42
|
let routes = inspectRoutes(app)
|
|
65
43
|
.filter(({ method, path, isMiddleware }) => {
|
|
66
44
|
const key = method + '-' + path
|
|
@@ -88,17 +66,12 @@ export default defineCommand({
|
|
|
88
66
|
const { method, path } = route
|
|
89
67
|
|
|
90
68
|
let mLength = method.length
|
|
91
|
-
let str =
|
|
69
|
+
let str = highlightedMethod(method, null, true)
|
|
92
70
|
|
|
93
|
-
if (method == 'GET')
|
|
71
|
+
if (method == 'GET')
|
|
94
72
|
mLength += 5
|
|
95
|
-
str += gray('|') + colorMethod('HEAD')
|
|
96
|
-
}
|
|
97
73
|
|
|
98
|
-
console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ path
|
|
99
|
-
/(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
|
|
100
|
-
_ => colorMethod(method, _)
|
|
101
|
-
))
|
|
74
|
+
console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ highlightedURI(path, method))
|
|
102
75
|
})
|
|
103
76
|
|
|
104
77
|
rn()
|
package/src/cli/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineCommand, runMain, renderUsage } from 'citty'
|
|
|
2
2
|
import type { ArgsDef, CommandDef } from 'citty'
|
|
3
3
|
import { createConsola } from 'consola'
|
|
4
4
|
import { logo } from '../utils/log'
|
|
5
|
-
import { isColorSupported, gray } from '
|
|
5
|
+
import { isColorSupported, gray } from 't0n/color'
|
|
6
6
|
import { version as rajtVersion } from '../../package.json'
|
|
7
7
|
|
|
8
8
|
import dev from './commands/dev'
|
package/src/cli/utils.ts
CHANGED
|
@@ -3,21 +3,19 @@ import { Miniflare } from 'miniflare'
|
|
|
3
3
|
import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
|
|
4
4
|
import { readFile, stat, writeFile } from 'node:fs/promises'
|
|
5
5
|
import { basename, dirname, join, relative } from 'pathe'
|
|
6
|
-
import
|
|
6
|
+
import { createHash, createHmac } from 'node:crypto'
|
|
7
7
|
|
|
8
8
|
import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from 'localflare-core'
|
|
9
9
|
import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
|
|
10
10
|
|
|
11
11
|
import chokidar from 'chokidar'
|
|
12
|
-
import { gray, bold, italic } from '
|
|
12
|
+
import { gray, bold, italic, purple, yellow, red } from 't0n/color'
|
|
13
13
|
import type { ChokidarEventName, Platform } from './types'
|
|
14
14
|
|
|
15
15
|
import { cacheRoutes } from '../routes'
|
|
16
16
|
import { substep, event, error, wait as wwait, warn, log } from '../utils/log'
|
|
17
17
|
import { platforms } from './constants'
|
|
18
|
-
|
|
19
|
-
export const _root = join(dirname(new URL(import.meta.url).pathname), '../../../../')
|
|
20
|
-
export const __rajt = join(_root, 'node_modules/rajt/src')
|
|
18
|
+
import { _rajt, _root } from '../utils/paths'
|
|
21
19
|
|
|
22
20
|
export function normalizePlatform(platform: Platform) {
|
|
23
21
|
platform = platform?.toLowerCase() as Platform
|
|
@@ -91,7 +89,7 @@ export const build = async (platform: Platform) => {
|
|
|
91
89
|
// @ts-ignore
|
|
92
90
|
platform = platform != 'node' ? '-'+ platform : ''
|
|
93
91
|
const opts = {
|
|
94
|
-
entryPoints: [join(
|
|
92
|
+
entryPoints: [join(_rajt, `prod${platform}.ts`)],
|
|
95
93
|
bundle: true,
|
|
96
94
|
minify: true,
|
|
97
95
|
outfile: join(_root, dist +'/index.js'),
|
|
@@ -122,6 +120,12 @@ export const build = async (platform: Platform) => {
|
|
|
122
120
|
// sourcemap: true,
|
|
123
121
|
// logLevel: 'info',
|
|
124
122
|
plugins: [
|
|
123
|
+
{
|
|
124
|
+
name: 'rajt-resolver',
|
|
125
|
+
setup(build) {
|
|
126
|
+
build.onResolve({ filter: /\.rajt[\/\\]/ }, args => ({ path: join(_root, args.path) }))
|
|
127
|
+
}
|
|
128
|
+
},
|
|
125
129
|
{
|
|
126
130
|
name: 'preserve-class-names',
|
|
127
131
|
setup(build) {
|
|
@@ -199,11 +203,11 @@ export function cleanDir(path: string) {
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
function durableObjectNamespace(name: string, uniqueKey: string) {
|
|
202
|
-
const key =
|
|
203
|
-
const nameHmac =
|
|
206
|
+
const key = createHash('sha256').update(uniqueKey).digest()
|
|
207
|
+
const nameHmac = createHmac('sha256', key).update(name).digest().subarray(0, 16)
|
|
204
208
|
return Buffer.concat([
|
|
205
209
|
nameHmac,
|
|
206
|
-
|
|
210
|
+
createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
|
|
207
211
|
]).toString('hex')
|
|
208
212
|
}
|
|
209
213
|
|
|
@@ -474,3 +478,30 @@ export const camelCase = (text: string) =>
|
|
|
474
478
|
.filter(word => word.length > 0)
|
|
475
479
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
476
480
|
.join('')
|
|
481
|
+
|
|
482
|
+
export const highlightedMethod = (method: string, str?: string | null, siblings: boolean = false): string => {
|
|
483
|
+
const val = str || method
|
|
484
|
+
|
|
485
|
+
switch (method) {
|
|
486
|
+
case 'HEAD':
|
|
487
|
+
case 'OPTIONS':
|
|
488
|
+
case 'CONNECT':
|
|
489
|
+
case 'TRACE':
|
|
490
|
+
return gray(val)
|
|
491
|
+
case 'GET':
|
|
492
|
+
return purple(val) + (siblings ? gray('|') + highlightedMethod('HEAD') : '')
|
|
493
|
+
case 'POST':
|
|
494
|
+
case 'PUT':
|
|
495
|
+
case 'PATCH':
|
|
496
|
+
return yellow(val)
|
|
497
|
+
case 'DELETE':
|
|
498
|
+
return red(val)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return val
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export const highlightedURI = (uri: string, method: string) => uri.replace(
|
|
505
|
+
/(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
|
|
506
|
+
_ => highlightedMethod(method, _)
|
|
507
|
+
)
|
package/src/config.ts
CHANGED
package/src/create-app.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { getMiddlewares, getHandler } from './register'
|
|
|
13
13
|
import request, { GET_REQUEST } from './request'
|
|
14
14
|
import response from './response'
|
|
15
15
|
import { isDev } from './utils/environment'
|
|
16
|
-
import { gray } from '
|
|
16
|
+
import { gray } from 't0n/color'
|
|
17
17
|
|
|
18
18
|
const NFHandler = () => response.notFound()
|
|
19
19
|
const EHandler = async (e: Error | HTTPResponseError) => {
|
package/src/dev.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { config } from 'dotenv'
|
|
|
3
3
|
import { getRoutes, getMiddlewares, getConfigs } from './routes'
|
|
4
4
|
import { registerHandler, registerMiddleware } from './register'
|
|
5
5
|
import Config from './config'
|
|
6
|
-
import
|
|
6
|
+
import { registerOpenAPI } from './oas'
|
|
7
7
|
import createApp from './create-app'
|
|
8
8
|
import { Ability } from 'rajt/auth'
|
|
9
9
|
import { setEnv, detectEnvironment } from 'rajt/env'
|
|
@@ -28,6 +28,6 @@ Ability.roles = Config.get('roles', {})
|
|
|
28
28
|
|
|
29
29
|
// @ts-ignore
|
|
30
30
|
const app = createApp({ routes })
|
|
31
|
-
|
|
31
|
+
registerOpenAPI(app, Config.get('rajt', {}))
|
|
32
32
|
|
|
33
33
|
export default app
|
package/src/oas.ts
CHANGED
|
@@ -8,103 +8,98 @@ import response from './response'
|
|
|
8
8
|
import { getHandler } from './register'
|
|
9
9
|
import type { Hono } from './types'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export function config(opts: any) {
|
|
12
|
+
const docs = opts?.docs ?? {}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const docs = config?.docs ?? {}
|
|
14
|
+
const appName = Envir.get('APP_NAME', 'API Docs')
|
|
15
|
+
const appVersion = Envir.get('APP_VERSION') || Envir.get('VERSION_HASH') || '1.0.0'
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
auth: docs?.auth || {},
|
|
26
|
-
agent: !docs?.agent,
|
|
27
|
-
appName, appVersion,
|
|
28
|
-
}
|
|
17
|
+
return {
|
|
18
|
+
...docs,
|
|
19
|
+
disable: !!docs?.disable,
|
|
20
|
+
path: docs?.path || '/docs',
|
|
21
|
+
auth: docs?.auth || {},
|
|
22
|
+
agent: !docs?.agent,
|
|
23
|
+
appName, appVersion,
|
|
29
24
|
}
|
|
25
|
+
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
export function registerOpenAPI(app: Hono, conf: any) {
|
|
28
|
+
const opts = config(conf)
|
|
29
|
+
if (opts.disable) return
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
if (opts?.auth?.username && opts?.auth?.password) {
|
|
32
|
+
app.use(opts.path +'/*', async (c, next) => {
|
|
33
|
+
const realm = opts.auth?.realm || 'Docs'
|
|
34
|
+
const unauthorized = response.unauthorized(
|
|
35
|
+
null,
|
|
36
|
+
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
37
|
+
)
|
|
38
|
+
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
39
|
+
const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
try {
|
|
42
|
+
await auth(c, next)
|
|
43
|
+
} catch {
|
|
44
|
+
return unauthorized
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
app.get(opts.path + '/openapi', async () => response.json(getHandler('RAJT_OPENAPI')))
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
51
|
+
app.get(
|
|
52
|
+
opts.path,
|
|
53
|
+
Scalar({
|
|
54
|
+
theme: 'saturn',
|
|
55
|
+
url: opts.path +'/openapi',
|
|
56
|
+
showDeveloperTools: 'never',
|
|
57
|
+
telemetry: false,
|
|
58
|
+
documentDownloadType: 'json', //'direct',
|
|
59
|
+
isLoading: true,
|
|
60
|
+
persistAuth: true,
|
|
61
|
+
hideClientButton: true,
|
|
62
|
+
pageTitle: opts.appName,
|
|
63
|
+
agent: { disabled: opts.agent },
|
|
64
|
+
slug: opts.appName?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ opts.appVersion,
|
|
65
|
+
// hideDownloadButton: true,
|
|
66
|
+
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
67
|
+
customCss: `[href="https://www.scalar.com"]{display:none}`,
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
}
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
export async function generateOpenAPI(app: Hono, conf: any) {
|
|
73
|
+
const opts = config(conf)
|
|
74
|
+
if (opts.disable) return {}
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
76
|
+
return await generateSpecs(app, {
|
|
77
|
+
documentation: {
|
|
78
|
+
info: {
|
|
79
|
+
title: opts.appName,
|
|
80
|
+
version: opts.appVersion,
|
|
81
|
+
description: Envir.get('APP_DESCRIPTION', ''),
|
|
82
|
+
},
|
|
83
|
+
components: {
|
|
84
|
+
securitySchemes: {
|
|
85
|
+
JWT: {
|
|
86
|
+
type: 'http',
|
|
87
|
+
scheme: 'bearer',
|
|
88
|
+
bearerFormat: 'JWT',
|
|
94
89
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
},
|
|
90
|
+
},
|
|
91
|
+
responses: {
|
|
92
|
+
500: {
|
|
93
|
+
description: STATUS_CODES[500],
|
|
94
|
+
content: { // @ts-ignore
|
|
95
|
+
'application/json': await resolver(z.object({
|
|
96
|
+
m: z.array(z.string()),
|
|
97
|
+
})).toOpenAPISchema(),
|
|
103
98
|
},
|
|
104
|
-
...opts?.responses,
|
|
105
99
|
},
|
|
100
|
+
...opts?.responses,
|
|
106
101
|
},
|
|
107
102
|
},
|
|
108
|
-
}
|
|
109
|
-
}
|
|
103
|
+
},
|
|
104
|
+
})
|
|
110
105
|
}
|
package/src/prod.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import Config from './config'
|
|
2
|
-
import
|
|
2
|
+
import { registerOpenAPI } from './oas'
|
|
3
3
|
import { Ability } from './auth'
|
|
4
4
|
import createApp from './create-app'
|
|
5
5
|
|
|
6
6
|
// @ts-ignore
|
|
7
|
-
import '
|
|
7
|
+
import '.rajt/imports.mjs'
|
|
8
8
|
// @ts-ignore
|
|
9
|
-
import routes from '
|
|
9
|
+
import routes from '.rajt/routes.json'
|
|
10
10
|
|
|
11
11
|
// @ts-ignore
|
|
12
12
|
Ability.fromRoutes(routes)
|
|
@@ -14,6 +14,6 @@ Ability.roles = Config.get('roles', {})
|
|
|
14
14
|
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
const app = createApp({ routes })
|
|
17
|
-
|
|
17
|
+
registerOpenAPI(app, Config.get('rajt', {}))
|
|
18
18
|
|
|
19
19
|
export { app }
|
package/src/request.ts
CHANGED
|
@@ -78,11 +78,13 @@ export default class $Request {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
get ip(): string | undefined {
|
|
81
|
-
return this.#c.req.header('cf-connecting-ip')
|
|
82
|
-
|| this.#c.req.header('x-forwarded-for')?.split(',')
|
|
81
|
+
return this.#c.req.header('cf-connecting-ip') // cf
|
|
82
|
+
|| this.#c.req.header('x-forwarded-for')?.split(',').at(-1)?.trim() // aws lambda
|
|
83
83
|
|| this.#c.env?.aws?.lambda?.event?.requestContext?.identity?.sourceIp
|
|
84
|
-
|| this.#c.
|
|
85
|
-
|| this.#c.
|
|
84
|
+
|| this.#c.env?.event?.Records[0]?.cf?.request?.clientIp // aws lambda@edge
|
|
85
|
+
|| this.#c.req.header('x-real-ip') // vercel
|
|
86
|
+
|| this.#c.env?.context?.ip // netlify
|
|
87
|
+
|| this.#c.env?.remoteAddr?.hostname // deno
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
get userAgent(): string | undefined {
|
package/src/routes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import {
|
|
1
|
+
import { copyFileSync, existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve, relative } from 'pathe'
|
|
3
3
|
|
|
4
4
|
import glob from 'tiny-glob'
|
|
5
5
|
import { config } from 'dotenv'
|
|
@@ -11,16 +11,16 @@ import { isAnonFn } from './utils/func'
|
|
|
11
11
|
import ensureDir from './utils/ensuredir'
|
|
12
12
|
import versionSHA from './utils/version-sha'
|
|
13
13
|
import type { Routes, StandardSchemaV1 } from './types'
|
|
14
|
-
import { substep, warn } from './utils/log'
|
|
14
|
+
import { rn, substep, warn } from './utils/log'
|
|
15
|
+
import { _root } from './utils/paths'
|
|
15
16
|
|
|
16
|
-
import
|
|
17
|
+
import { generateOpenAPI } from './oas'
|
|
17
18
|
import z from 'zod'
|
|
18
19
|
import { resolver } from 'hono-openapi'
|
|
19
20
|
import { mimes } from 'hono/utils/mime'
|
|
20
21
|
import { STATUS_CODES } from 'node:http'
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
const __root = resolve(dirname(__filename), '../../..')
|
|
23
|
+
import { highlightedMethod, highlightedURI } from './cli/utils'
|
|
24
24
|
|
|
25
25
|
const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
|
|
26
26
|
const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
|
|
@@ -105,9 +105,17 @@ export async function getRoutes(
|
|
|
105
105
|
let length = 0
|
|
106
106
|
const keys: Set<string> = new Set()
|
|
107
107
|
const bag: Record<string, string[]> = {}
|
|
108
|
+
const _route = (key: string, _: string) => {
|
|
109
|
+
if (!keys.has(key)) {
|
|
110
|
+
keys.add(key)
|
|
111
|
+
} else {
|
|
112
|
+
;(bag[key] ||= []).push(_)
|
|
113
|
+
length++
|
|
114
|
+
}
|
|
115
|
+
}
|
|
108
116
|
|
|
109
117
|
await Promise.all(dirs.map(dir => walk(
|
|
110
|
-
resolve(
|
|
118
|
+
resolve(_root, dir),
|
|
111
119
|
dir,
|
|
112
120
|
(path: string, baseDir: string, handle: any, middlewares: string[]) => {
|
|
113
121
|
const name = importName(handle?.name)
|
|
@@ -136,21 +144,26 @@ export async function getRoutes(
|
|
|
136
144
|
desc,
|
|
137
145
|
})
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
;(bag[name] ||= []).push(file)
|
|
143
|
-
length++
|
|
144
|
-
}
|
|
147
|
+
const routeKey = method +'|'+ uri
|
|
148
|
+
_route(name, file)
|
|
149
|
+
_route(routeKey, file)
|
|
145
150
|
}
|
|
146
151
|
)))
|
|
147
152
|
|
|
148
153
|
if (length) {
|
|
149
154
|
hasDuplicatedRoutes = true
|
|
150
155
|
Object.entries(bag).forEach(([name, paths]) => {
|
|
151
|
-
|
|
156
|
+
if (name.includes('|')) {
|
|
157
|
+
let [method, uri] = name.split('|')
|
|
158
|
+
method = method.toUpperCase()
|
|
159
|
+
name = `${highlightedMethod(method, null, true)} "${highlightedURI(uri, method)}"`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
warn(`Route ${name} has `+ (paths.length > 1 ? `registered ${paths.length} times:` : 'already been registered:'))
|
|
152
163
|
substep(...paths)
|
|
153
164
|
})
|
|
165
|
+
|
|
166
|
+
rn()
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
return sortRoutes(routes)
|
|
@@ -224,7 +237,7 @@ export async function getMiddlewares(
|
|
|
224
237
|
const mw: Routes = []
|
|
225
238
|
|
|
226
239
|
await Promise.all(dirs.map(dir => walk(
|
|
227
|
-
resolve(
|
|
240
|
+
resolve(_root, dir),
|
|
228
241
|
dir,
|
|
229
242
|
(fullPath: string, baseDir: string, handle: any) => {
|
|
230
243
|
// @ts-ignore
|
|
@@ -246,15 +259,15 @@ function extractName(file: string) {
|
|
|
246
259
|
export async function getConfigs(
|
|
247
260
|
dirs: string[] = ['configs']
|
|
248
261
|
): Promise<Record<string, any>> {
|
|
249
|
-
dirs = dirs.filter(dir => existsSync(join(
|
|
262
|
+
dirs = dirs.filter(dir => existsSync(join(_root, dir)))
|
|
250
263
|
if (!dirs?.length) return {}
|
|
251
264
|
const configs: Record<string, any> = {}
|
|
252
265
|
|
|
253
|
-
const files = (await glob(join(
|
|
266
|
+
const files = (await glob(join(_root, dirs?.length > 1 ? `{${dirs.join(',')}}` : dirs[0], '/**/*.{ts,js,cjs,mjs,json}')))
|
|
254
267
|
.filter(file => !file.includes('.d.'))
|
|
255
268
|
|
|
256
269
|
for (const file of files) {
|
|
257
|
-
const mod = await IMPORT(join(
|
|
270
|
+
const mod = await IMPORT(join(_root, file))
|
|
258
271
|
const keyPath = extractName(file).split('.')
|
|
259
272
|
|
|
260
273
|
keyPath.reduce((acc, key, index) => {
|
|
@@ -296,16 +309,26 @@ function stringifyToJS(value: unknown): string {
|
|
|
296
309
|
return 'undefined'
|
|
297
310
|
}
|
|
298
311
|
|
|
312
|
+
export async function dependencyEntry(lib: string, root: string) {
|
|
313
|
+
const path = await import.meta.resolve(lib)
|
|
314
|
+
return relative(root, path.replace('file://', ''))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function dependencyPath(lib: string) {
|
|
318
|
+
const entry = await dependencyEntry(lib, join(_root, '.rajt'))
|
|
319
|
+
return entry.substring(0, entry.lastIndexOf(lib) + lib.length)
|
|
320
|
+
}
|
|
321
|
+
|
|
299
322
|
export async function cacheRoutes() {
|
|
300
323
|
const env = Object.entries(
|
|
301
|
-
config({ path: '
|
|
324
|
+
config({ path: join(_root, '.env.prod') })?.parsed || {}
|
|
302
325
|
).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
|
|
303
326
|
|
|
304
327
|
const version = versionSHA('../../.git') // @ts-ignore
|
|
305
328
|
env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version]) // @ts-ignore
|
|
306
329
|
env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
|
|
307
330
|
|
|
308
|
-
const rolePath = join(
|
|
331
|
+
const rolePath = join(_root, 'configs/roles.ts')
|
|
309
332
|
ensureDir(rolePath)
|
|
310
333
|
if (!existsSync(rolePath))
|
|
311
334
|
writeFileSync(rolePath, `export default {\n\n}`)
|
|
@@ -321,15 +344,20 @@ export async function cacheRoutes() {
|
|
|
321
344
|
middlewares.forEach(mw => registerMiddleware(mw.handle))
|
|
322
345
|
|
|
323
346
|
// @ts-ignore
|
|
324
|
-
const openApi = await
|
|
347
|
+
const openApi = await generateOpenAPI(createApp({ routes }), configs?.rajt || {})
|
|
325
348
|
|
|
326
|
-
const iPath = join(
|
|
349
|
+
const iPath = join(_root, '.rajt/imports.mjs')
|
|
327
350
|
ensureDir(iPath)
|
|
351
|
+
|
|
352
|
+
const localfireEntry = await dependencyEntry('localflare-api', _root)
|
|
353
|
+
copyFileSync(localfireEntry, join(_root, '.rajt/localfire.js'))
|
|
354
|
+
|
|
355
|
+
const _rajtDir = await dependencyPath('rajt')
|
|
328
356
|
writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
|
|
329
|
-
${env?.length ? `import { Envir } from '
|
|
330
|
-
${Object.entries(configs)?.length ? `import Config from '
|
|
357
|
+
${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
|
|
358
|
+
${Object.entries(configs)?.length ? `import Config from '${_rajtDir}/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
|
|
331
359
|
|
|
332
|
-
import { registerHandler, registerMiddleware } from '
|
|
360
|
+
import { registerHandler, registerMiddleware } from '${_rajtDir}/src/register'
|
|
333
361
|
|
|
334
362
|
${Object.entries(openApi)?.length ? `registerHandler('RAJT_OPENAPI', ${stringifyToJS(openApi)})` : ''}
|
|
335
363
|
|
|
@@ -355,7 +383,7 @@ try {
|
|
|
355
383
|
}
|
|
356
384
|
`)
|
|
357
385
|
|
|
358
|
-
const rPath = join(
|
|
386
|
+
const rPath = join(_root, '.rajt/routes.json')
|
|
359
387
|
ensureDir(rPath)
|
|
360
388
|
writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
|
|
361
389
|
route.method,
|
package/src/utils/log.ts
CHANGED
package/src/validator.ts
CHANGED
package/src/utils/colors.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// ISC License
|
|
2
|
-
|
|
3
|
-
// Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov
|
|
4
|
-
|
|
5
|
-
// Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
-
// purpose with or without fee is hereby granted, provided that the above
|
|
7
|
-
// copyright notice and this permission notice appear in all copies.
|
|
8
|
-
|
|
9
|
-
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
-
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
-
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
-
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
-
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
-
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
-
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
-
//
|
|
17
|
-
// https://github.com/alexeyraspopov/picocolors/commit/6f0a4638348ed20633d623ee973f9c9a96f65104
|
|
18
|
-
|
|
19
|
-
import { getColorEnabledAsync } from 'hono/utils/color'
|
|
20
|
-
|
|
21
|
-
export const enabled = await getColorEnabledAsync()
|
|
22
|
-
export const isColorSupported = enabled
|
|
23
|
-
|
|
24
|
-
// const { env, stdout } = globalThis?.process ?? {}
|
|
25
|
-
// const enabled =
|
|
26
|
-
// env &&
|
|
27
|
-
// !env.NO_COLOR &&
|
|
28
|
-
// (env.FORCE_COLOR || (stdout?.isTTY && !env.CI && env.TERM !== 'dumb'))
|
|
29
|
-
|
|
30
|
-
const replaceClose = (
|
|
31
|
-
str: string,
|
|
32
|
-
close: string,
|
|
33
|
-
replace: string,
|
|
34
|
-
index: number
|
|
35
|
-
): string => {
|
|
36
|
-
const start = str.substring(0, index) + replace
|
|
37
|
-
const end = str.substring(index + close.length)
|
|
38
|
-
const nextIndex = end.indexOf(close)
|
|
39
|
-
return ~nextIndex
|
|
40
|
-
? start + replaceClose(end, close, replace, nextIndex)
|
|
41
|
-
: start + end
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const formatter = (open: string, close: string, replace = open) => {
|
|
45
|
-
if (!enabled) return String
|
|
46
|
-
return (input: string) => {
|
|
47
|
-
const string = '' + input
|
|
48
|
-
const index = string.indexOf(close, open.length)
|
|
49
|
-
return ~index
|
|
50
|
-
? open + replaceClose(string, close, replace, index) + close
|
|
51
|
-
: open + string + close
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const reset = enabled ? (s: string) => `\x1b[0m${s}\x1b[0m` : String
|
|
56
|
-
export const bold = formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m')
|
|
57
|
-
export const dim = formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m')
|
|
58
|
-
export const italic = formatter('\x1b[3m', '\x1b[23m')
|
|
59
|
-
export const underline = formatter('\x1b[4m', '\x1b[24m')
|
|
60
|
-
export const inverse = formatter('\x1b[7m', '\x1b[27m')
|
|
61
|
-
export const hidden = formatter('\x1b[8m', '\x1b[28m')
|
|
62
|
-
export const strikethrough = formatter('\x1b[9m', '\x1b[29m')
|
|
63
|
-
|
|
64
|
-
const endText = '\x1b[39m'
|
|
65
|
-
export const black = formatter('\x1b[30m', endText)
|
|
66
|
-
export const red = formatter('\x1b[31m', endText)
|
|
67
|
-
export const green = formatter('\x1b[32m', endText)
|
|
68
|
-
export const yellow = formatter('\x1b[33m', endText)
|
|
69
|
-
export const blue = formatter('\x1b[34m', endText)
|
|
70
|
-
export const magenta = formatter('\x1b[35m', endText)
|
|
71
|
-
export const purple = formatter('\x1b[38;2;173;127;168m', endText)
|
|
72
|
-
export const cyan = formatter('\x1b[36m', endText)
|
|
73
|
-
export const white = formatter('\x1b[37m', endText)
|
|
74
|
-
export const gray = formatter('\x1b[90m', endText)
|
|
75
|
-
|
|
76
|
-
const endBg = '\x1b[49m'
|
|
77
|
-
export const bgBlack = formatter('\x1b[40m', endBg)
|
|
78
|
-
export const bgRed = formatter('\x1b[41m', endBg)
|
|
79
|
-
export const bgGreen = formatter('\x1b[42m', endBg)
|
|
80
|
-
export const bgYellow = formatter('\x1b[43m', endBg)
|
|
81
|
-
export const bgBlue = formatter('\x1b[44m', endBg)
|
|
82
|
-
export const bgMagenta = formatter('\x1b[45m', endBg)
|
|
83
|
-
export const bgCyan = formatter('\x1b[46m', endBg)
|
|
84
|
-
export const bgWhite = formatter('\x1b[47m', endBg)
|
|
85
|
-
|
|
86
|
-
export const blackBright = formatter("\x1b[90m", endText)
|
|
87
|
-
export const redBright = formatter("\x1b[91m", endText)
|
|
88
|
-
export const greenBright = formatter("\x1b[92m", endText)
|
|
89
|
-
export const yellowBright = formatter("\x1b[93m", endText)
|
|
90
|
-
export const blueBright = formatter("\x1b[94m", endText)
|
|
91
|
-
export const magentaBright = formatter("\x1b[95m", endText)
|
|
92
|
-
export const cyanBright = formatter("\x1b[96m", endText)
|
|
93
|
-
export const whiteBright = formatter("\x1b[97m", endText)
|
|
94
|
-
|
|
95
|
-
export const bgBlackBright = formatter("\x1b[100m", endBg)
|
|
96
|
-
export const bgRedBright = formatter("\x1b[101m", endBg)
|
|
97
|
-
export const bgGreenBright = formatter("\x1b[102m", endBg)
|
|
98
|
-
export const bgYellowBright = formatter("\x1b[103m", endBg)
|
|
99
|
-
export const bgBlueBright = formatter("\x1b[104m", endBg)
|
|
100
|
-
export const bgMagentaBright = formatter("\x1b[105m", endBg)
|
|
101
|
-
export const bgCyanBright = formatter("\x1b[106m", endBg)
|
|
102
|
-
export const bgWhiteBright = formatter("\x1b[107m", endBg)
|