-
Notifications
You must be signed in to change notification settings - Fork 1
feat(elysia): add isolated Elysia 2 integration #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4167822
6ae1f25
d3051ea
855717e
0538191
b90f328
76e9a9d
a401a3f
2a2504f
3e327fd
f287aa6
852cdd4
0e22b9a
4a45003
72dfdcd
6f813d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ coverage/ | |
| .cache/ | ||
| .temp/ | ||
| tmp/ | ||
| .bun-test/ | ||
|
|
||
| # Framework build output | ||
| .next/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,14 +16,25 @@ | |
| "types": "./dist/index.d.cts", | ||
| "default": "./dist/index.cjs" | ||
| } | ||
| }, | ||
| "./next": { | ||
| "import": { | ||
| "types": "./dist/next.d.ts", | ||
| "default": "./dist/next.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/next.d.cts", | ||
| "default": "./dist/next.cjs" | ||
| } | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "build": "tsup && node scripts/patch-next-declarations.mjs", | ||
| "test": "vitest run", | ||
| "test:declarations": "tsc --project tests/fixtures/next-declarations-consumer/tsconfig.json && node scripts/validate-next-declarations.mjs", | ||
| "typecheck": "tsc --noEmit", | ||
| "clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"" | ||
| }, | ||
|
|
@@ -46,13 +57,17 @@ | |
| "@logtide/types": "workspace:*" | ||
| }, | ||
| "peerDependencies": { | ||
| "elysia": ">=1.0.0" | ||
| "elysia": ">=1.0.0 <2.0.0 || >=2.0.0-beta.4 <3.0.0-0", | ||
| "typebox": ">=1.3.0" | ||
| }, | ||
| "devDependencies": { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||
| "@sinclair/typebox": "^0.34.48", | ||
| "elysia": "^1.2.0", | ||
| "elysia": "npm:elysia@^1.4.29", | ||
| "elysia-next": "npm:elysia@2.0.0-beta.4", | ||
| "exact-mirror": "^1.2.4", | ||
| "file-type": "^22.0.0", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^5.5.4" | ||
| "typebox": "^1.3.15", | ||
| "typescript": "^5.7.0" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import { readFile, writeFile } from 'node:fs/promises'; | ||
| import { pathToFileURL } from 'node:url'; | ||
| import ts from 'typescript'; | ||
|
|
||
| const declarationPaths = ['dist/next.d.ts', 'dist/next.d.cts']; | ||
| const developmentAlias = 'elysia-next'; | ||
| const publishedSpecifier = 'elysia'; | ||
|
|
||
| function isDevelopmentAlias(specifier) { | ||
| return specifier === developmentAlias || specifier.startsWith(`${developmentAlias}/`); | ||
| } | ||
|
|
||
| function aliasReplacement(specifier) { | ||
| return specifier === developmentAlias | ||
| ? publishedSpecifier | ||
| : `${publishedSpecifier}${specifier.slice(developmentAlias.length)}`; | ||
| } | ||
|
|
||
| function aliasSpecifiers(sourceFile) { | ||
| const specifiers = []; | ||
|
|
||
| function addSpecifier(node) { | ||
| if (!node || !ts.isStringLiteral(node) || !isDevelopmentAlias(node.text)) return; | ||
|
|
||
| const start = node.getStart(sourceFile) + 1; | ||
| const end = node.getEnd() - 1; | ||
| specifiers.push({ start, end, replacement: aliasReplacement(node.text) }); | ||
| } | ||
|
|
||
| function visit(node) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The visitor misses
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||
| if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { | ||
| addSpecifier(node.moduleSpecifier); | ||
| } else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) { | ||
| addSpecifier(node.argument.literal); | ||
| } else if (ts.isExternalModuleReference(node)) { | ||
| addSpecifier(node.expression); | ||
| } | ||
|
|
||
| ts.forEachChild(node, visit); | ||
| } | ||
|
|
||
| ts.forEachChild(sourceFile, visit); | ||
| return specifiers; | ||
| } | ||
|
|
||
| function patchDeclaration(declaration) { | ||
| const sourceFile = ts.createSourceFile('next.d.ts', declaration, ts.ScriptTarget.Latest, false); | ||
| const replacements = aliasSpecifiers(sourceFile); | ||
|
|
||
| return replacements | ||
| .reverse() | ||
| .reduce( | ||
| (patched, { start, end, replacement }) => | ||
| `${patched.slice(0, start)}${replacement}${patched.slice(end)}`, | ||
| declaration | ||
| ); | ||
| } | ||
|
|
||
| export async function patchNextDeclarations(paths = declarationPaths) { | ||
| const patchedDeclarations = []; | ||
|
|
||
| for (const path of paths) { | ||
| const declaration = await readFile(path, 'utf8'); | ||
| const patched = patchDeclaration(declaration); | ||
|
|
||
| if (new RegExp(`\\b${developmentAlias}\\b`).test(patched)) { | ||
| throw new Error(`Expected ${path} to contain no elysia-next development alias after patching`); | ||
| } | ||
|
|
||
| patchedDeclarations.push([path, patched, declaration]); | ||
| } | ||
|
|
||
| for (const [path, patched, declaration] of patchedDeclarations) { | ||
| if (patched !== declaration) { | ||
| await writeFile(path, patched); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { | ||
| await patchNextDeclarations(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { readFileSync } from 'node:fs'; | ||
| import { dirname, resolve } from 'node:path'; | ||
| import ts from 'typescript'; | ||
|
|
||
| const fixtureConfigPath = 'tests/fixtures/next-declarations-consumer/tsconfig.json'; | ||
| const declarationPaths = ['dist/next.d.ts', 'dist/next.d.cts']; | ||
|
|
||
| function readCompilerOptions(configPath) { | ||
| const config = ts.readConfigFile(configPath, ts.sys.readFile); | ||
| if (config.error) throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n')); | ||
|
|
||
| return ts.parseJsonConfigFileContent(config.config, ts.sys, dirname(configPath)).options; | ||
| } | ||
|
|
||
| function moduleSpecifiers(sourceFile) { | ||
| const specifiers = new Set(); | ||
|
|
||
| function addSpecifier(node) { | ||
| if (node && ts.isStringLiteral(node)) specifiers.add(node.text); | ||
| } | ||
|
|
||
| function visit(node) { | ||
| if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { | ||
| addSpecifier(node.moduleSpecifier); | ||
| } else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) { | ||
| addSpecifier(node.argument.literal); | ||
| } else if (ts.isExternalModuleReference(node)) { | ||
| addSpecifier(node.expression); | ||
| } | ||
|
|
||
| ts.forEachChild(node, visit); | ||
| } | ||
|
|
||
| ts.forEachChild(sourceFile, visit); | ||
| return specifiers; | ||
| } | ||
|
|
||
| export function validateNextDeclarations(paths = declarationPaths, fixtureConfig = fixtureConfigPath) { | ||
| const compilerOptions = readCompilerOptions(fixtureConfig); | ||
| const host = ts.sys; | ||
|
|
||
| for (const path of paths) { | ||
| const declaration = readFileSync(path, 'utf8'); | ||
| const sourceFile = ts.createSourceFile(path, declaration, ts.ScriptTarget.Latest, false); | ||
|
|
||
| for (const specifier of moduleSpecifiers(sourceFile)) { | ||
| const resolved = ts.resolveModuleName(specifier, resolve(path), compilerOptions, host).resolvedModule; | ||
| if (!resolved) { | ||
| throw new Error(`Unable to resolve ${JSON.stringify(specifier)} from ${path}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| validateNextDeclarations(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { logtide, type LogtideElysiaOptions } from './plugin-next'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No CI step semantically validates the patched
dist/next.d.ts:skipLibCheckis on repo-wide and the patch test only checks string rewriting, so an unresolvable specifier in the shipped declarations would go unnoticed. A smalltscfixture over the built d.ts (orattw) would catch it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
4a45003.test:declarationscompiles a consumer fixture that imports the builtdist/next.d.ts, then TypeScript's module resolver checks every import, export, and import-type specifier in the ESM and CJS declarations. CI runs it after the build.