diff options
Diffstat (limited to 'packages')
75 files changed, 1417 insertions, 2518 deletions
diff --git a/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts b/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts index 51b2765b5..41847c6db 100644 --- a/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts +++ b/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts @@ -5,7 +5,7 @@ import { spawnSync } from "node:child_process"; run().catch(console.error); async function run() { - const cwd = new URL("../protocol/", import.meta.url); + const cwd = new URL("../src/protocol/", import.meta.url); const runner = "Bun" in globalThis ? "bunx" : "npx"; const write = (name: string, data: string) => { const path = new URL(name, cwd); diff --git a/packages/bun-debug-adapter-protocol/src/protocol/index.d.ts b/packages/bun-debug-adapter-protocol/src/protocol/index.d.ts index abf9ecdec..7a8dcc312 100644 --- a/packages/bun-debug-adapter-protocol/src/protocol/index.d.ts +++ b/packages/bun-debug-adapter-protocol/src/protocol/index.d.ts @@ -638,7 +638,7 @@ export namespace DAP { */ export type BreakpointLocationsRequest = { /** - * The source location of the breakpoints; either `source.path` or `source.reference` must be specified. + * The source location of the breakpoints; either `source.path` or `source.sourceReference` must be specified. */ source: Source; /** @@ -1139,6 +1139,12 @@ export namespace DAP { * The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** + * A memory reference to a location appropriate for this result. + * For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + * This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; /** * Arguments for `source` request. @@ -1286,7 +1292,7 @@ export namespace DAP { /** * A memory reference to a location appropriate for this result. * For pointer type eval results, this is generally a reference to the memory address contained in the pointer. - * This attribute should be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + * This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; }; @@ -1344,6 +1350,12 @@ export namespace DAP { * The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** + * A memory reference to a location appropriate for this result. + * For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + * This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; /** * Arguments for `stepInTargets` request. @@ -2064,8 +2076,10 @@ export namespace DAP { */ indexedVariables?: number; /** - * The memory reference for the variable if the variable represents executable code, such as a function pointer. - * This attribute is only required if the corresponding capability `supportsMemoryReferences` is true. + * A memory reference associated with this variable. + * For pointer type variables, this is generally a reference to the memory address contained in the pointer. + * For executable data, this reference may later be used in a `disassemble` request. + * This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; }; diff --git a/packages/bun-debug-adapter-protocol/src/protocol/protocol.json b/packages/bun-debug-adapter-protocol/src/protocol/protocol.json index 00a2ab014..41d65608e 100644 --- a/packages/bun-debug-adapter-protocol/src/protocol/protocol.json +++ b/packages/bun-debug-adapter-protocol/src/protocol/protocol.json @@ -108,7 +108,7 @@ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The `cancel` request is used by the client in two situations:\n- to indicate that it is no longer interested in the result produced by a specific request issued earlier\n- to cancel a progress sequence. Clients should only call this request if the corresponding capability `supportsCancelRequest` is true.\nThis request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honoring this request but there are no guarantees.\nThe `cancel` request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users.\nThe request that got cancelled still needs to send a response back. This can either be a normal result (`success` attribute true) or an error response (`success` attribute false and the `message` set to `cancelled`).\nReturning partial results from a cancelled request is possible but please note that a client has no generic way for detecting that a response is partial or not.\nThe progress that got cancelled still needs to send a `progressEnd` event back.\n A client should not assume that progress just got cancelled after sending the `cancel` request.", + "description": "The `cancel` request is used by the client in two situations:\n- to indicate that it is no longer interested in the result produced by a specific request issued earlier\n- to cancel a progress sequence.\nClients should only call this request if the corresponding capability `supportsCancelRequest` is true.\nThis request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honoring this request but there are no guarantees.\nThe `cancel` request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users.\nThe request that got cancelled still needs to send a response back. This can either be a normal result (`success` attribute true) or an error response (`success` attribute false and the `message` set to `cancelled`).\nReturning partial results from a cancelled request is possible but please note that a client has no generic way for detecting that a response is partial or not.\nThe progress that got cancelled still needs to send a `progressEnd` event back.\n A client should not assume that progress just got cancelled after sending the `cancel` request.", "properties": { "command": { "type": "string", "enum": ["cancel"] }, "arguments": { "$ref": "#/definitions/CancelArguments" } @@ -1074,7 +1074,7 @@ "properties": { "source": { "$ref": "#/definitions/Source", - "description": "The source location of the breakpoints; either `source.path` or `source.reference` must be specified." + "description": "The source location of the breakpoints; either `source.path` or `source.sourceReference` must be specified." }, "line": { "type": "integer", @@ -2035,6 +2035,10 @@ "indexedVariables": { "type": "integer", "description": "The number of indexed child variables.\nThe client can use this information to present the variables in a paged UI and fetch them in chunks.\nThe value should be less than or equal to 2147483647 (2^31-1)." + }, + "memoryReference": { + "type": "string", + "description": "A memory reference to a location appropriate for this result.\nFor pointer type eval results, this is generally a reference to the memory address contained in the pointer.\nThis attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true." } }, "required": ["value"] @@ -2326,7 +2330,7 @@ }, "memoryReference": { "type": "string", - "description": "A memory reference to a location appropriate for this result.\nFor pointer type eval results, this is generally a reference to the memory address contained in the pointer.\nThis attribute should be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true." + "description": "A memory reference to a location appropriate for this result.\nFor pointer type eval results, this is generally a reference to the memory address contained in the pointer.\nThis attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true." } }, "required": ["result", "variablesReference"] @@ -2397,6 +2401,10 @@ "indexedVariables": { "type": "integer", "description": "The number of indexed child variables.\nThe client can use this information to present the variables in a paged UI and fetch them in chunks.\nThe value should be less than or equal to 2147483647 (2^31-1)." + }, + "memoryReference": { + "type": "string", + "description": "A memory reference to a location appropriate for this result.\nFor pointer type eval results, this is generally a reference to the memory address contained in the pointer.\nThis attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true." } }, "required": ["value"] @@ -3240,7 +3248,7 @@ }, "memoryReference": { "type": "string", - "description": "The memory reference for the variable if the variable represents executable code, such as a function pointer.\nThis attribute is only required if the corresponding capability `supportsMemoryReferences` is true." + "description": "A memory reference associated with this variable.\nFor pointer type variables, this is generally a reference to the memory address contained in the pointer.\nFor executable data, this reference may later be used in a `disassemble` request.\nThis attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true." } }, "required": ["name", "value", "variablesReference"] @@ -3299,8 +3307,8 @@ "Indicates that the object is a constant.", "Indicates that the object is read only.", "Indicates that the object is a raw string.", - "Indicates that the object can have an Object ID created for it.", - "Indicates that the object has an Object ID associated with it.", + "Indicates that the object can have an Object ID created for it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol.", + "Indicates that the object has an Object ID associated with it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol.", "Indicates that the evaluation had side effects.", "Indicates that the object has its value tracked by a data breakpoint." ] diff --git a/packages/bun-ecosystem/.gitignore b/packages/bun-ecosystem/.gitignore deleted file mode 100644 index 2817d4e0b..000000000 --- a/packages/bun-ecosystem/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.DS_Store -node_modules -tmp diff --git a/packages/bun-ecosystem/README.md b/packages/bun-ecosystem/README.md deleted file mode 100644 index 2ed8df0c7..000000000 --- a/packages/bun-ecosystem/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# bun-ecosystem - -A registry to test `npm` packages using Bun. This can be used as a tool to find bugs in Bun by running the test suites of these packages. In the future, we will run theses tests to catch regressions between releases. - -```sh -bun run test -``` diff --git a/packages/bun-ecosystem/bun.lockb b/packages/bun-ecosystem/bun.lockb Binary files differdeleted file mode 100755 index 4eb6aded1..000000000 --- a/packages/bun-ecosystem/bun.lockb +++ /dev/null diff --git a/packages/bun-ecosystem/package.json b/packages/bun-ecosystem/package.json deleted file mode 100644 index 190314a31..000000000 --- a/packages/bun-ecosystem/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "bun-ecosystem-ci", - "private": true, - "dependencies": { - "globby": "^13.1.3" - }, - "devDependencies": { - "bun-types": "canary", - "prettier": "^2.8.2" - }, - "scripts": { - "format": "prettier --write src", - "test": "bun run src/runner.ts" - } -} diff --git a/packages/bun-ecosystem/src/packages.ts b/packages/bun-ecosystem/src/packages.ts deleted file mode 100644 index 677bfdf16..000000000 --- a/packages/bun-ecosystem/src/packages.ts +++ /dev/null @@ -1,69 +0,0 @@ -export type Package = { - readonly name: string; - readonly repository: string; - readonly cwd?: string; - readonly tests?: { - readonly style: "jest" | "ava" | "tape" | "custom"; - readonly include: string[]; - readonly exclude?: string[]; - readonly disabled?: boolean; - }; -}; - -export const packages: Package[] = [ - { - name: "lodash", - repository: github("lodash/lodash"), - tests: { - style: "jest", - include: ["test/*.js"], - exclude: [ - "debounce.test.js", // hangs runner - "size.test.js", // require('vm').runInNewContext() - "merge.test.js", // failing - ], - }, - }, - { - name: "chalk", - repository: github("chalk/chalk"), - tests: { - style: "ava", - include: ["test/*.js"], - }, - }, - { - name: "request", - repository: github("request/request"), - tests: { - style: "tape", - include: ["tests/*.js"], - }, - }, - { - name: "commander", - repository: github("tj/commander.js"), - tests: { - style: "jest", - include: ["tests/*.js"], - }, - }, - { - name: "express", - repository: github("expressjs/express"), - tests: { - style: "jest", - include: ["test/**/*.js"], - exclude: [ - "test/res.sendStatus.js", // https://github.com/oven-sh/bun/issues/887 - "test/Route.js", // https://github.com/oven-sh/bun/issues/2030 - ], - // Most tests fail due to lack of "http2" - disabled: true, - }, - }, -]; - -function github(repository: string): string { - return `git@github.com:${repository}.git`; -} diff --git a/packages/bun-ecosystem/src/runner.ts b/packages/bun-ecosystem/src/runner.ts deleted file mode 100644 index f95174808..000000000 --- a/packages/bun-ecosystem/src/runner.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { Package } from "./packages"; -import { packages } from "./packages"; -import { existsSync, copyFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { globby } from "globby"; - -for (const pkg of packages) { - try { - await loadPackage(pkg, "tmp"); - } catch (error) { - console.error(pkg.name, error); - } -} - -async function loadPackage(pkg: Package, cwd?: string): Promise<void> { - await gitClone({ - cwd, - repository: pkg.repository, - name: pkg.name, - }); - const dir = join(cwd ?? "", pkg.name, pkg.cwd ?? ""); - await spawn({ - cwd: dir, - cmd: ["bun", "install"], - }); - if (!pkg.tests || pkg.tests.style !== "jest") { - return; - } - const files = await globby(pkg.tests.include, { - cwd: dir, - ignore: pkg.tests.exclude ?? [crypto.randomUUID()], - onlyFiles: true, - caseSensitiveMatch: false, - }); - if (!files.length) { - throw new Error("No tests found"); - } - for (const file of files) { - let path = file; - if (!file.includes(".test.")) { - const ext = path.lastIndexOf("."); - path = file.substring(0, ext) + ".test" + file.substring(ext); - copyFileSync(join(dir, file), join(dir, path)); - } - await spawn({ - cwd: dir, - cmd: ["bun", "test", path], - }); - } -} - -type GitCloneOptions = { - repository: string; - cwd?: string; - name?: string; -}; - -async function gitClone(options: GitCloneOptions): Promise<void> { - const name = options.name ?? dirname(options.repository); - const cwd = options.cwd ?? process.cwd(); - const path = join(cwd, name); - if (existsSync(path)) { - await spawn({ - cwd: path, - cmd: ["git", "pull"], - }); - } else { - const url = `${options.repository}`; - await spawn({ - cwd, - cmd: ["git", "clone", "--single-branch", "--depth", "1", url, name], - }); - } -} - -type SpawnOptions = { - cwd: string; - cmd: string[]; -}; - -async function spawn({ cwd, cmd }: SpawnOptions) { - const { exited } = await Bun.spawn({ - cwd, - cmd, - stdout: "inherit", - stderr: "inherit", - }); - const exitCode = await exited; - if (exitCode !== 0) { - throw new Error(`"${cmd.join(" ")}" exited with ${exitCode}`); - } -} diff --git a/packages/bun-ecosystem/tsconfig.json b/packages/bun-ecosystem/tsconfig.json deleted file mode 100644 index 1b2f41220..000000000 --- a/packages/bun-ecosystem/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ESNext"], - "module": "ESNext", - "target": "ESNext", - "moduleResolution": "node", - "types": ["bun-types"], - "esModuleInterop": true, - "allowJs": true, - "strict": true, - "resolveJsonModule": true - }, - "include": [ - "src" - ] -} diff --git a/packages/bun-framework-next/.npmignore b/packages/bun-framework-next/.npmignore deleted file mode 100644 index cdb7baa1d..000000000 --- a/packages/bun-framework-next/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -*.bun -node_modules -pnpm-log.yaml -yarn-error.log -yarn.lock
\ No newline at end of file diff --git a/packages/bun-framework-next/README.md b/packages/bun-framework-next/README.md deleted file mode 100644 index 4df8999d1..000000000 --- a/packages/bun-framework-next/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# bun-framework-next - -This package lets you use Next.js 12.2 with bun. This readme assumes you already installed bun. - -To start a new project: - -```bash -bun create next --open -``` - -To use Next.js 12 with an existing project: - -```bash -bun add bun-framework-next -echo "framework = 'next'" > bunfig.toml -bun bun -``` - -Launch the development server: - -```bash -bun dev -``` - -Open http://localhost:3000 with your browser to see the result. diff --git a/packages/bun-framework-next/appInjector.js b/packages/bun-framework-next/appInjector.js deleted file mode 100644 index e8bf22d21..000000000 --- a/packages/bun-framework-next/appInjector.js +++ /dev/null @@ -1,15 +0,0 @@ -export function maybeInjectApp(expr) { - var app; - try { - const path = Bun.routesDir + "/_app"; - app = Bun.resolveSync(path, Bun.cwd + "/"); - } catch (exception) { - return undefined; - } - - return ( - <> - <import path={app} /> - </> - ); -} diff --git a/packages/bun-framework-next/bun.lockb b/packages/bun-framework-next/bun.lockb Binary files differdeleted file mode 100755 index 7cb21bc37..000000000 --- a/packages/bun-framework-next/bun.lockb +++ /dev/null diff --git a/packages/bun-framework-next/client.development.tsx b/packages/bun-framework-next/client.development.tsx deleted file mode 100644 index 470dd2085..000000000 --- a/packages/bun-framework-next/client.development.tsx +++ /dev/null @@ -1,463 +0,0 @@ -globalThis.global = globalThis; -globalThis.Bun_disableCSSImports = true; - -import * as React from "react"; - -var ReactDOM; -try { - ReactDOM = require("react-dom/client"); -} catch (exception) {} - -if (!ReactDOM) { - try { - ReactDOM = require("react-dom"); - } catch (exception) {} -} - -import NextApp from "next/app"; -import mitt, { MittEmitter } from "next/dist/shared/lib/mitt"; -import { RouterContext } from "next/dist/shared/lib/router-context"; -import Router, { - AppComponent, - AppProps, - PrivateRouteInfo, -} from "next/dist/shared/lib/router/router"; - -import NextRouteLoader from "next/dist/client/route-loader"; -import { isDynamicRoute } from "next/dist/shared/lib/router/utils/is-dynamic"; -import { - urlQueryToSearchParams, - assign, -} from "next/dist/shared/lib/router/utils/querystring"; -import { setConfig } from "next/dist/shared/lib/runtime-config"; -import { getURL, NEXT_DATA } from "next/dist/shared/lib/utils"; - -import initHeadManager from "next/dist/client/head-manager"; -import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context"; -import PageLoader from "./page-loader"; -import { - createRouter, - makePublicRouterInstance, -} from "next/dist/client/router"; - -export const emitter: MittEmitter<string> = mitt(); - -declare global { - interface Window { - /* test fns */ - __NEXT_HYDRATED?: boolean; - __NEXT_HYDRATED_CB?: () => void; - - /* prod */ - __NEXT_PRELOADREADY?: (ids?: (string | number)[]) => void; - __NEXT_DATA__: NEXT_DATA; - __NEXT_P: any[]; - } -} - -function nextDataFromBunData() { - const { - router: { routes, route, params: paramsList }, - } = globalThis.__BUN_DATA__; - - const paramsMap = new Map(); - for (let i = 0; i < paramsList.keys.length; i++) { - paramsMap.set( - decodeURIComponent(paramsList.keys[i]), - decodeURIComponent(paramsList.values[i]), - ); - } - - const params = {}; - var url = new URL(location.href); - Object.assign(params, Object.fromEntries(url.searchParams.entries())); - Object.assign(params, Object.fromEntries(paramsMap.entries())); - - const pages = routes.keys.reduce((acc, routeName, i) => { - const routePath = routes.values[i]; - acc[routeName] = [routePath]; - return acc; - }, {}); - - return { - page: routes.keys[route], - buildId: "1234", - assetPrefix: "", - isPreview: false, - locale: null, - locales: [], - isFallback: false, - err: null, - props: {}, - query: params, - pages, - }; -} - -type RenderRouteInfo = PrivateRouteInfo & { - App: AppComponent; - scroll?: { x: number; y: number } | null; -}; - -const nextDataTag = document.getElementById("__NEXT_DATA__"); - -// pages is added at runtime and doesn't exist in Next types -const data: NEXT_DATA & { pages: Record<string, string[]> } = nextDataTag - ? JSON.parse(document.getElementById("__NEXT_DATA__")!.textContent!) - : nextDataFromBunData(); - -window.__NEXT_DATA__ = data; - -const { - props: hydrateProps, - err: hydrateErr, - page, - query, - buildId, - assetPrefix, - runtimeConfig, - // Todo, revist this constant when supporting dynamic() - dynamicIds, - isFallback, - locale, - locales, - domainLocales, - isPreview, - pages, -} = data; - -const prefix: string = assetPrefix || ""; - -setConfig({ - serverRuntimeConfig: {}, - publicRuntimeConfig: runtimeConfig || {}, -}); - -let asPath: string = getURL(); -const basePath = (process.env.__NEXT_ROUTER_BASEPATH as string) || ""; - -function pathNoQueryHash(path: string) { - const queryIndex = path.indexOf("?"); - const hashIndex = path.indexOf("#"); - - if (queryIndex > -1 || hashIndex > -1) { - path = path.substring(0, queryIndex > -1 ? queryIndex : hashIndex); - } - return path; -} - -function hasBasePath(path: string): boolean { - path = pathNoQueryHash(path); - return path === prefix || path.startsWith(prefix + "/"); -} - -function delBasePath(path: string): string { - path = path.slice(basePath.length); - if (!path.startsWith("/")) path = `/${path}`; - return path; -} - -// make sure not to attempt stripping basePath for 404s -if (hasBasePath(asPath)) { - asPath = delBasePath(asPath); -} - -export const pageLoader: PageLoader = new PageLoader(buildId, prefix, pages); - -const headManager: { - mountedInstances: Set<unknown>; - updateHead: (head: JSX.Element[]) => void; -} = initHeadManager(); - -export let router: Router; - -let CachedApp: AppComponent = null; - -export default function boot(EntryPointNamespace) { - _boot(EntryPointNamespace, false); -} - -class Container extends React.Component<{ - fn: (err: Error, info?: any) => void; -}> { - componentDidCatch(componentErr: Error, info: any) { - this.props.fn(componentErr, info); - } - - componentDidMount() { - this.scrollToHash(); - - // We need to replace the router state if: - // - the page was (auto) exported and has a query string or search (hash) - // - it was auto exported and is a dynamic route (to provide params) - // - if it is a client-side skeleton (fallback render) - if ( - router.isSsr && - // We don't update for 404 requests as this can modify - // the asPath unexpectedly e.g. adding basePath when - // it wasn't originally present - page !== "/404" && - page !== "/_error" && - (isFallback || - (data.nextExport && - (isDynamicRoute(router.pathname) || - location.search || - process.env.__NEXT_HAS_REWRITES)) || - (hydrateProps && - hydrateProps.__N_SSG && - (location.search || process.env.__NEXT_HAS_REWRITES))) - ) { - // update query on mount for exported pages - router.replace( - router.pathname + - "?" + - String( - assign( - urlQueryToSearchParams(router.query), - new URLSearchParams(location.search), - ), - ), - asPath, - { - // @ts-ignore - // WARNING: `_h` is an internal option for handing Next.js - // client-side hydration. Your app should _never_ use this property. - // It may change at any time without notice. - _h: 1, - // Fallback pages must trigger the data fetch, so the transition is - // not shallow. - // Other pages (strictly updating query) happens shallowly, as data - // requirements would already be present. - shallow: !isFallback, - }, - ); - } - } - - componentDidUpdate() { - this.scrollToHash(); - } - - scrollToHash() { - let { hash } = location; - hash = hash && hash.substring(1); - if (!hash) return; - - const el: HTMLElement | null = document.getElementById(hash); - if (!el) return; - - // If we call scrollIntoView() in here without a setTimeout - // it won't scroll properly. - setTimeout(() => el.scrollIntoView(), 0); - } - - render() { - return this.props.children; - } -} - -let CachedComponent: React.ComponentType; - -const wrapApp = - (App: AppComponent) => - (wrappedAppProps: Record<string, any>): JSX.Element => { - const appProps: AppProps = { - ...wrappedAppProps, - Component: CachedComponent, - err: hydrateErr, - router, - }; - return ( - <AppContainer> - <App {...appProps} /> - </AppContainer> - ); - }; - -function AppContainer({ - children, -}: React.PropsWithChildren<{}>): React.ReactElement { - return ( - <Container fn={(error) => <div>{JSON.stringify(error)}</div>}> - <RouterContext.Provider value={makePublicRouterInstance(router)}> - <HeadManagerContext.Provider value={headManager}> - {children} - </HeadManagerContext.Provider> - </RouterContext.Provider> - </Container> - ); -} - -let reactRoot: any = null; - -const USE_REACT_18 = "hydrateRoot" in ReactDOM; - -class BootError extends Error { - constructor(message) { - super(message); - this.name = "BootError"; - } -} - -export async function _boot(EntryPointNamespace, isError) { - NextRouteLoader.getClientBuildManifest = () => Promise.resolve({}); - - const PageComponent = EntryPointNamespace.default; - - const appScripts = globalThis.__NEXT_DATA__.pages["/_app"]; - - // Type 'typeof App' is not assignable to type 'ComponentClass<AppProps, any>'. - // Construct signature return types 'App<any, any, any>' and 'Component<AppProps, any, any>' are incompatible. - // @ts-expect-error - CachedApp = NextApp; - CachedComponent = PageComponent; - - if (appScripts && appScripts.length > 0) { - let appSrc; - for (let asset of appScripts) { - if (!asset.endsWith(".css")) { - appSrc = asset; - break; - } - } - - if (appSrc) { - const AppModule = await import(appSrc); - - console.assert( - AppModule.default, - appSrc + " must have a default export'd React component", - ); - - if ("default" in AppModule) { - CachedApp = AppModule.default; - } - } - } - - router = createRouter(page, query, asPath, { - initialProps: hydrateProps, - pageLoader, - App: CachedApp, - Component: CachedComponent, - wrapApp, - err: null, - isFallback: Boolean(isFallback), - subscription: async (info, App, scroll) => { - return render( - Object.assign< - {}, - Omit<RenderRouteInfo, "App" | "scroll" | "Component">, - Pick<RenderRouteInfo, "App" | "scroll" | "Component"> - >({}, info, { - // If we don't have an info.Component, we may be shallow routing, - // fallback to current entry point - Component: info.Component || CachedComponent, - App, - scroll, - }), - ); - }, - locale, - locales, - defaultLocale: "", - domainLocales, - isPreview, - }); - - globalThis.next.router = router; - - var domEl = document.querySelector("#__next"); - - if (!domEl) { - const nextEl = document.createElement("div"); - nextEl.id = "__next"; - document.body.appendChild(nextEl); - domEl = nextEl; - } - - const reactEl = ( - <TopLevelRender - App={CachedApp} - Component={PageComponent} - props={hydrateProps} - /> - ); - - if (USE_REACT_18) { - if (!isError && domEl.hasChildNodes() && !reactRoot) { - try { - // Unlike with createRoot, you don't need a separate root.render() call here - reactRoot = ReactDOM.hydrateRoot(domEl, reactEl); - } catch (exception) { - try { - reactRoot = ReactDOM.createRoot(domEl); - reactRoot.render(reactEl); - } catch { - throw exception; - } - } - } else { - if (!reactRoot) { - reactRoot = ReactDOM.createRoot(domEl); - } - - reactRoot.render(reactEl); - } - } else { - if (isError || !domEl.hasChildNodes() || !("hydrate" in ReactDOM)) { - ReactDOM.render(reactEl, domEl); - } else { - try { - ReactDOM.hydrate(reactEl, domEl); - } catch (e) { - ReactDOM.render(reactEl, domEl); - } - } - } -} - -function TopLevelRender({ App, Component, props }) { - return ( - <AppContainer> - <App Component={Component} {...props}></App> - </AppContainer> - ); -} - -export function render(props) { - if (USE_REACT_18) { - reactRoot.render(<TopLevelRender {...props} />); - } else { - ReactDOM.render( - <TopLevelRender {...props} />, - document.getElementById("__next"), - ); - } -} - -export function renderError(e) { - const reactEl = <AppContainer>{null}</AppContainer>; - - if (USE_REACT_18) { - if (!reactRoot) { - const domEl = document.querySelector("#__next"); - - // Unlike with createRoot, you don't need a separate root.render() call here - reactRoot = ReactDOM.hydrateRoot(domEl, reactEl); - } else { - reactRoot.render(reactEl); - } - } else { - const domEl = document.querySelector("#__next"); - - ReactDOM.render(reactEl, domEl); - } -} - -globalThis.next = { - version: "12.0.4", - emitter, - render, - renderError, -}; diff --git a/packages/bun-framework-next/empty.js b/packages/bun-framework-next/empty.js deleted file mode 100644 index bbf5800ce..000000000 --- a/packages/bun-framework-next/empty.js +++ /dev/null @@ -1 +0,0 @@ -// Keep this file here so that main resolves correctly diff --git a/packages/bun-framework-next/fallback.development.tsx b/packages/bun-framework-next/fallback.development.tsx deleted file mode 100644 index 6e3ff00ff..000000000 --- a/packages/bun-framework-next/fallback.development.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { FallbackMessageContainer } from "../../src/api/schema"; -import { maybeInjectApp } from "macro:./appInjector"; - -var globalStyles = []; -function insertGlobalStyleSheet({ detail: url }) { - globalStyles.push( - new Promise((resolve, reject) => { - const link: HTMLLinkElement = document.createElement("link"); - link.rel = "stylesheet"; - link.href = url; - link.onload = resolve; - link.onabort = reject; - link.onerror = reject; - document.head.appendChild(link); - }), - ); -} - -const nCSS = document.createElement("noscript"); -nCSS.setAttribute("data-n-css", ""); -document.head.appendChild(nCSS); - -document.addEventListener("onimportcss", insertGlobalStyleSheet); - -var once = false; -function insertNextHeadCount() { - if (!once) { - document.head.insertAdjacentHTML( - "beforeend", - `<meta name="next-head-count" content="0">`, - ); - once = true; - } -} - -maybeInjectApp(); - -globalThis.__BUN_APP_STYLES = [...globalThis["__BUN"].allImportedStyles].map( - (style) => { - const url = new URL(style, location.origin); - if (url.origin === location.origin && url.href === style) { - return url.pathname; - } - - return style; - }, -); - -import { _boot, pageLoader } from "./client.development"; - -function renderFallback({ router }: FallbackMessageContainer) { - const route = router.routes.values[router.route]; - - if (!document.getElementById("__next")) { - const next = document.createElement("div"); - next.id = "__next"; - document.body.prepend(next); - } - - document.removeEventListener("onimportcss", insertGlobalStyleSheet); - document.addEventListener("onimportcss", pageLoader.onImportCSS); - - var cssQueue; - return import(route) - .then((Namespace) => { - nCSS.remove(); - document.head.appendChild(nCSS); - cssQueue = [...globalStyles, ...pageLoader.cssQueue]; - pageLoader.cssQueue = []; - insertNextHeadCount(); - return _boot(Namespace, true); - }) - .then(() => { - cssQueue = [...cssQueue, ...pageLoader.cssQueue.slice()]; - pageLoader.cssQueue = []; - return Promise.allSettled(cssQueue); - }) - .finally(() => { - document.body.style.visibility = "visible"; - document.removeEventListener("onimportcss", pageLoader.onImportCSS); - }); -} - -export default function render(props: FallbackMessageContainer) { - // @ts-expect-error bun:error.js is real - return import("/bun:error.js").then(({ renderFallbackError }) => { - return renderFallback(props).then( - () => { - Promise.all(pageLoader.cssQueue).finally(() => { - renderFallbackError(props); - document.body.style.visibility = "visible"; - }); - }, - (err) => { - console.error(err); - Promise.all(pageLoader.cssQueue).finally(() => { - renderFallbackError(props); - }); - }, - ); - }); -} diff --git a/packages/bun-framework-next/next-image-polyfill.tsx b/packages/bun-framework-next/next-image-polyfill.tsx deleted file mode 100644 index edc3775d7..000000000 --- a/packages/bun-framework-next/next-image-polyfill.tsx +++ /dev/null @@ -1,36 +0,0 @@ -function NextImagePolyfill({ - src, - width, - height, - objectFit, - style, - layout, - ...otherProps -}) { - var _style = style; - if (layout === "fit") { - objectFit = "contain"; - } else if (layout === "fill") { - objectFit = "cover"; - } - - if (objectFit) { - if (!_style) { - _style = { objectFit: objectFit }; - } else { - _style.objectFit = objectFit; - } - } - - return ( - <img - src={src} - width={width} - height={height} - style={_style} - {...otherProps} - /> - ); -} - -export default NextImagePolyfill; diff --git a/packages/bun-framework-next/next_document.tsx b/packages/bun-framework-next/next_document.tsx deleted file mode 100644 index e69de29bb..000000000 --- a/packages/bun-framework-next/next_document.tsx +++ /dev/null diff --git a/packages/bun-framework-next/package.json b/packages/bun-framework-next/package.json deleted file mode 100644 index a06294ba5..000000000 --- a/packages/bun-framework-next/package.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "name": "bun-framework-next", - "version": "12.2.5", - "main": "empty.js", - "repository": "https://github.com/oven-sh/bun", - "module": "empty.js", - "description": "bun compatibility layer for Next.js >= v12.2.3", - "homepage": "https://bun.sh", - "bugs": { - "url": "https://github.com/oven-sh/bun/issues" - }, - "scripts": { - "check": "tsc --noEmit" - }, - "license": "MIT", - "dependencies": { - "react-is": "*" - }, - "peerDependencies": { - "next": "~12.2.3" - }, - "devDependencies": { - "@types/react": "^18", - "@types/react-dom": "^18", - "next": "^12.2.3", - "react": "^18", - "react-dom": "^18", - "typescript": "^4" - }, - "framework": { - "displayName": "Next.js", - "static": "public", - "assetPrefix": "_next/", - "router": { - "dir": [ - "pages", - "src/pages" - ], - "extensions": [ - ".js", - ".ts", - ".tsx", - ".jsx" - ] - }, - "css": "onimportcss", - "development": { - "client": "client.development.tsx", - "fallback": "fallback.development.tsx", - "server": "server.development.tsx", - "css": "onimportcss", - "override": { - "next/dist/client/image.js": "next-image-polyfill.tsx" - }, - "define": { - "client": { - ".env": "NEXT_PUBLIC_", - "defaults": { - "process.env.__NEXT_TRAILING_SLASH": "false", - "process.env.NODE_ENV": "'development'", - "process.env.__NEXT_ROUTER_BASEPATH": "''", - "process.env.__NEXT_SCROLL_RESTORATION": "false", - "process.env.__NEXT_I18N_SUPPORT": "false", - "process.env.__NEXT_HAS_REWRITES": "false", - "process.env.__NEXT_ANALYTICS_ID": "null", - "process.env.__NEXT_OPTIMIZE_CSS": "false", - "process.env.__NEXT_CROSS_ORIGIN": "''", - "process.env.__NEXT_STRICT_MODE": "false", - "process.env.__NEXT_IMAGE_OPTS": "null" - } - }, - "server": { - ".env": "NEXT_", - "defaults": { - "process.env.__NEXT_TRAILING_SLASH": "false", - "process.env.__NEXT_OPTIMIZE_FONTS": "false", - "process.env.NODE_ENV": "\"development\"", - "process.env.__NEXT_OPTIMIZE_IMAGES": "false", - "process.env.__NEXT_OPTIMIZE_CSS": "false", - "process.env.__NEXT_ROUTER_BASEPATH": "''", - "process.env.__NEXT_SCROLL_RESTORATION": "false", - "process.env.__NEXT_I18N_SUPPORT": "false", - "process.env.__NEXT_HAS_REWRITES": "false", - "process.env.__NEXT_ANALYTICS_ID": "null", - "process.env.__NEXT_CROSS_ORIGIN": "''", - "process.env.__NEXT_STRICT_MODE": "false", - "process.env.__NEXT_IMAGE_OPTS": "null", - "global": "globalThis", - "window": "undefined" - } - } - } - }, - "production": { - "client": "empty.js", - "server": "empty.js", - "fallback": "empty.js", - "css": "onimportcss" - } - } -} diff --git a/packages/bun-framework-next/packageVersion.ts b/packages/bun-framework-next/packageVersion.ts deleted file mode 100644 index d6ff0b21d..000000000 --- a/packages/bun-framework-next/packageVersion.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { readFileSync } from "fs"; - -var memoizeMap; - -// This gives us a package version inline into the build without pulling in the whole package.json -// it only parses the package.json for a given package name once (relative to the directory of this file) -export function packageVersion(call) { - var name = call.arguments[0].toString(); - - // in the general case, this would break when using multiple versions of the same package - // but people don't use multiple versions of next in the same bundle - // so we don't need to worry about it here - // and it skips resolveSync which is a bit faster - if (memoizeMap) { - const value = memoizeMap.get(name); - if (value) return value; - } - var nextPath; - try { - nextPath = Bun.resolveSync(`${name}/package.json`, import.meta.dir); - } catch (exception) { - throw new Error(`${name} is not a valid package name`); - } - - var json; - try { - // TODO: Add sync methods to FileBlob? - json = JSON.parse(readFileSync(nextPath, "utf8")); - } catch (exc) { - throw new AggregateError([exc], `Error parsing ${name}/package.json`); - } - - if (!json.version) { - throw new Error(`${name}/package.json is missing a version`); - } - - if (!memoizeMap) { - memoizeMap = new Map(); - } - - memoizeMap.set(name, json.version); - - return json.version; -} diff --git a/packages/bun-framework-next/page-loader.ts b/packages/bun-framework-next/page-loader.ts deleted file mode 100644 index c74b22fbf..000000000 --- a/packages/bun-framework-next/page-loader.ts +++ /dev/null @@ -1,138 +0,0 @@ -import NextPageLoader, { - GoodPageCache as NextGoodPageCache, -} from "next/dist/client/page-loader"; -import getAssetPathFromRoute from "next/dist/shared/lib/router/utils/get-asset-path-from-route"; - -export function insertStyleSheet(url: string, isFallback: boolean = false) { - if (document.querySelector(`link[href="${url}"]`)) { - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - const link: HTMLLinkElement = document.createElement("link"); - link.rel = "stylesheet"; - - // marking this resolve as void seems to break other things - link.onload = resolve; - link.onerror = reject; - - link.href = url; - - if (isFallback) { - link.setAttribute("data-href", url); - } - - document.head.appendChild(link); - }); -} - -interface GoodPageCache extends NextGoodPageCache { - __N_SSG: boolean; - __N_SSP: boolean; -} - -export default class PageLoader extends NextPageLoader { - constructor(_, __, pages) { - super(_, __); - - // TODO: assetPrefix? - - // Rewrite the pages object to omit the entry script - // At this point, the entry point has been loaded so we don't want to do that again. - for (let name in pages) { - for (let i = 0; i < pages[name].length; i += 1) { - const lastDot = pages[name][i].lastIndexOf("."); - if (lastDot == -1) continue; - if ( - pages[name][i].substring(lastDot - ".entry".length, lastDot) !== - ".entry" - ) - continue; - - pages[name][i] = - pages[name][i].substring(0, lastDot - ".entry".length) + - pages[name][i].substring(lastDot); - } - } - - this.pages = pages; - this.pageList = Object.keys(this.pages); - } - - pageList: string[]; - pages: Record<string, string[]>; - - getPageList() { - return this.pageList; - } - - async getMiddlewareList() { - return []; - } - - cssQueue = []; - - onImportCSS = (event) => { - this.cssQueue.push( - insertStyleSheet(event.detail).then( - () => {}, - () => {}, - ), - ); - }; - - prefetch() { - return Promise.resolve(); - } - - async loadPage(route: string): Promise<GoodPageCache> { - const assets = - this.pages[route] || this.pages[getAssetPathFromRoute(route)]; - - var src; - for (let asset of assets) { - if (!asset.endsWith(".css")) { - src = asset; - break; - } - } - console.assert(src, "Invalid or unknown route passed to loadPage"); - - if ("__BunClearBuildFailure" in globalThis) { - globalThis.__BunClearBuildFailure(); - } - - document.removeEventListener("onimportcss", this.onImportCSS); - this.cssQueue.length = 0; - document.addEventListener("onimportcss", this.onImportCSS, { - passive: true, - }); - - try { - const res = await import(src); - - if (this.cssQueue.length > 0) { - await Promise.all(this.cssQueue); - this.cssQueue.length = 0; - } - - document.removeEventListener("onimportcss", this.onImportCSS); - - if (this.cssQueue.length > 0) { - await Promise.all(this.cssQueue); - - this.cssQueue.length = 0; - } - - return { - page: res.default, - mod: res, - styleSheets: [], - __N_SSG: false, - __N_SSP: false, - }; - } catch (exception) { - console.error({ exception }); - } - } -} diff --git a/packages/bun-framework-next/renderDocument.tsx b/packages/bun-framework-next/renderDocument.tsx deleted file mode 100644 index 5eae9ebf1..000000000 --- a/packages/bun-framework-next/renderDocument.tsx +++ /dev/null @@ -1,835 +0,0 @@ -import App from "next/app"; -import { AmpStateContext } from "next/dist/shared/lib/amp-context"; -import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context"; -import Loadable from "next/dist/shared/lib/loadable"; -import { LoadableContext } from "next/dist/shared/lib/loadable-context"; -import { RouterContext } from "next/dist/shared/lib/router-context"; -import type { NextRouter } from "next/dist/shared/lib/router/router"; -import { - getDisplayName, - loadGetInitialProps, - type AppType, - type ComponentsEnhancer, - type DocumentInitialProps, - type DocumentProps, - type DocumentType, - type NextComponentType, - type RenderPage, - type RenderPageResult, -} from "next/dist/shared/lib/utils"; -import { HtmlContext } from "next/dist/shared/lib/html-context"; -import type { RenderOpts } from "next/dist/server/render"; -import * as NextDocument from "next/document"; -import * as ReactDOMServer from "react-dom/server.browser"; -import * as React from "react"; -import * as ReactIs from "react-is"; -import packageJson from "next/package.json"; - -const nextVersion = packageJson.version; - -function appendNextBody(documentHTML: string, pageContent: string) { - if (nextVersion.startsWith("12.0")) { - const NEXT_12_0_BODY_RENDER_TARGET = "__NEXT_BODY_RENDER_TARGET__"; - - const bodyRenderIdx = documentHTML.indexOf(NEXT_12_0_BODY_RENDER_TARGET); - - if (!documentHTML.startsWith("<!DOCTYPE html>")) { - documentHTML = "<!DOCTYPE html>" + documentHTML; - } - - return ( - documentHTML.substring(0, bodyRenderIdx) + - pageContent + - documentHTML.substring( - bodyRenderIdx + NEXT_12_0_BODY_RENDER_TARGET.length, - ) - ); - } else { - var [renderTargetPrefix, renderTargetSuffix] = documentHTML.split( - "<next-js-internal-body-render-target></next-js-internal-body-render-target>", - ); - - if (!renderTargetPrefix || !renderTargetSuffix) { - throw new Error( - "Can't find where your <App /> starts or where the <Document /> ends. \nThis is probably a version incompatibility. Please mention this error in Bun's discord\n\n" + - documentHTML, - ); - } - - if (!renderTargetPrefix.startsWith("<!DOCTYPE html>")) { - renderTargetPrefix = "<!DOCTYPE html>" + renderTargetPrefix; - } - - return ( - renderTargetPrefix + - `<div id="__next">${pageContent || ""}</div>` + - renderTargetSuffix - ); - } -} - -const dev = process.env.NODE_ENV === "development"; - -type ParsedUrlQuery = Record<string, string | string[]>; - -const isJSFile = (file: string) => - file.endsWith(".js") || - file.endsWith(".jsx") || - file.endsWith(".mjs") || - file.endsWith(".ts") || - file.endsWith(".tsx"); - -type DocumentFiles = { - sharedFiles: readonly string[]; - pageFiles: readonly string[]; - allFiles: readonly string[]; -}; - -function getScripts(files: DocumentFiles) { - const { context, props } = this; - const { - assetPrefix, - buildManifest, - isDevelopment, - devOnlyCacheBusterQueryString, - } = context; - - const normalScripts = files?.allFiles?.filter(isJSFile) ?? []; - const lowPriorityScripts = - buildManifest?.lowPriorityFiles?.filter(isJSFile) ?? []; - var entryPointIndex = -1; - const scripts = [...normalScripts, ...lowPriorityScripts].map( - (file, index) => { - // if (file.includes(".entry.")) { - // entryPointIndex = index; - // } - - return ( - <script - key={file} - src={`${encodeURI(file)}${devOnlyCacheBusterQueryString}`} - nonce={props.nonce} - async - crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} - type="module" - /> - ); - }, - ); - // if (entryPointIndex > 0) { - // const entry = scripts.splice(entryPointIndex, 1); - // scripts.unshift(...entry); - // } - - return scripts; -} - -interface DomainLocale { - defaultLocale: string; - domain: string; - http?: true; - locales?: string[]; -} - -function renderDocument( - Document: DocumentType, - { - buildManifest, - docComponentsRendered, - props, - docProps, - pathname, - query, - buildId, - page, - canonicalBase, - assetPrefix, - runtimeConfig, - nextExport, - autoExport, - isFallback, - dynamicImportsIds, - dangerousAsPath, - err, - dev, - ampPath, - ampState, - inAmpMode, - hybridAmp, - dynamicImports, - headTags, - gsp, - gssp, - customServer, - gip, - appGip, - unstable_runtimeJS, - unstable_JsPreload, - devOnlyCacheBusterQueryString, - scriptLoader, - locale, - locales, - defaultLocale, - domainLocales, - isPreview, - disableOptimizedLoading, - }: RenderOpts & { - props: any; - // - page: string; - // - docComponentsRendered: DocumentProps["docComponentsRendered"]; - docProps: DocumentInitialProps; - pathname: string; - query: ParsedUrlQuery; - dangerousAsPath: string; - ampState: any; - ampPath: string; - inAmpMode: boolean; - hybridAmp: boolean; - dynamicImportsIds: (string | number)[]; - dynamicImports: string[]; - headTags: any; - isFallback?: boolean; - gsp?: boolean; - gssp?: boolean; - customServer?: boolean; - gip?: boolean; - appGip?: boolean; - devOnlyCacheBusterQueryString: string; - scriptLoader: any; - isPreview?: boolean; - autoExport?: boolean; - }, -): string { - const htmlProps = { - __NEXT_DATA__: { - props, // The result of getInitialProps - page: page, // The rendered page - query, // querystring parsed / passed by the user - buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles - assetPrefix: assetPrefix === "" ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML - runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML - nextExport, // If this is a page exported by `next export` - autoExport, // If this is an auto exported page - isFallback, - dynamicIds: - dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds, - err: err || undefined, // err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML - gsp, // whether the page is getStaticProps - gssp, // whether the page is getServerSideProps - customServer, // whether the user is using a custom server - gip, // whether the page has getInitialProps - appGip, // whether the _app has getInitialProps - locale, - locales, - defaultLocale, - domainLocales, - isPreview, - - pages: buildManifest.pages, - }, - buildManifest, - docComponentsRendered, - dangerousAsPath, - canonicalBase, - ampPath, - inAmpMode, - isDevelopment: !!dev, - hybridAmp, - dynamicImports, - assetPrefix, - headTags, - unstable_runtimeJS, - unstable_JsPreload, - devOnlyCacheBusterQueryString, - scriptLoader, - locale, - disableOptimizedLoading, - useMaybeDeferContent, - ...docProps, - }; - - return ReactDOMServer.renderToStaticMarkup( - <AmpStateContext.Provider value={ampState}> - {/* HTMLContextProvider expects useMainContent */} - {/* @ts-expect-error */} - <HtmlContext.Provider value={htmlProps}> - {/* Document doesn't expect useMaybeDeferContent */} - {/* @ts-expect-error */} - <Document {...htmlProps} {...docProps}></Document> - </HtmlContext.Provider> - </AmpStateContext.Provider>, - ); -} - -class ServerRouter implements NextRouter { - route: string; - pathname: string; - query: ParsedUrlQuery; - asPath: string; - basePath: string; - events: any; - isFallback: boolean; - locale?: string; - isReady: boolean; - locales?: string[]; - defaultLocale?: string; - domainLocales?: DomainLocale[]; - isPreview: boolean; - isLocaleDomain: boolean; - - constructor( - pathname: string, - query: ParsedUrlQuery, - as: string, - { isFallback }: { isFallback: boolean }, - isReady: boolean, - basePath: string, - locale?: string, - locales?: string[], - defaultLocale?: string, - domainLocales?: DomainLocale[], - isPreview?: boolean, - isLocaleDomain?: boolean, - ) { - this.route = pathname.replace(/\/$/, "") || "/"; - this.pathname = new URL( - pathname || "/", - Bun.origin || "http://localhost:3000", - ).href; - - this.query = query; - this.asPath = new URL( - as || "/", - Bun.origin || "http://localhost:3000", - ).href; - this.isFallback = isFallback; - this.basePath = basePath; - this.locale = locale; - this.locales = locales; - this.defaultLocale = defaultLocale; - this.isReady = isReady; - this.domainLocales = domainLocales; - this.isPreview = !!isPreview; - this.isLocaleDomain = !!isLocaleDomain; - } - - push(): any { - noRouter(); - } - replace(): any { - noRouter(); - } - reload() { - noRouter(); - } - back() { - noRouter(); - } - prefetch(): any { - noRouter(); - } - beforePopState() { - noRouter(); - } -} - -function noRouter() { - const message = - 'No router instance found. you should only use "next/router" inside the client side of your app. https://nextjs.org/docs/messages/no-router-instance'; - throw new Error(message); -} - -function enhanceComponents( - options: ComponentsEnhancer, - App: AppType, - Component: NextComponentType, -): { - App: AppType; - Component: NextComponentType; -} { - // For backwards compatibility - if (typeof options === "function") { - return { - App, - Component: options(Component), - }; - } - - return { - App: options.enhanceApp ? options.enhanceApp(App) : App, - Component: options.enhanceComponent - ? options.enhanceComponent(Component) - : Component, - }; -} -const scriptsGetter = { - get() { - return getScripts; - }, -}; - -Object.defineProperty(NextDocument.Head.prototype, "getScripts", scriptsGetter); -Object.defineProperty( - NextDocument.NextScript.prototype, - "getScripts", - scriptsGetter, -); -try { - Object.defineProperty( - NextDocument.default.prototype, - "getScripts", - scriptsGetter, - ); -} catch {} -try { - Object.defineProperty(NextDocument.default, "getScripts", scriptsGetter); -} catch {} - -export async function render({ - route, - request, - PageNamespace, - AppNamespace, - appStylesheets = [], - pageStylesheets = [], - DocumentNamespace = null, - buildId, - routePaths = [], - routeNames = [], -}: { - buildId: number; - route: any; - PageNamespace: { default: NextComponentType<any> }; - AppNamespace: { default: NextComponentType<any> } | null; - DocumentNamespace: Object | null; - appStylesheets: string[]; - pageStylesheets: string[]; - routePaths: string[]; - routeNames: string[]; - request: Request; -}): Promise<Response> { - const { default: Component } = PageNamespace || {}; - const getStaticProps = (PageNamespace as any)?.getStaticProps || null; - const { default: AppComponent_ } = AppNamespace || {}; - var query = Object.assign({}, route.query); - const origin = Bun.origin; - - // These are reversed in our Router versus Next.js...mostly due to personal preference. - const pathname = route.name; - var asPath = route.pathname; - const pages = {}; - - for (let i = 0; i < routeNames.length; i++) { - const filePath = routePaths[i]; - const name = routeNames[i]; - pages[name] = [filePath]; - } - - if (appStylesheets.length > 0) { - if (pages["/_app"]) { - pages["/_app"].push(...appStylesheets); - } else { - pages["/_app"] = appStylesheets; - } - } - pages[pathname] = [route.scriptSrc, ...pageStylesheets]; - - if (!("/_app" in pages)) { - pages["/_app"] = []; - } - - const AppComponent = AppComponent_ || App; - const Document = (DocumentNamespace as any)?.default || NextDocument.default; - - const callMiddleware = async (method: string, args: any[], props = false) => { - let results: any = props ? {} : []; - - if ((Document as any)[`${method}Middleware`]) { - let middlewareFunc = await (Document as any)[`${method}Middleware`]; - middlewareFunc = middlewareFunc.default || middlewareFunc; - - const curResults = await middlewareFunc(...args); - if (props) { - for (const result of curResults) { - results = { - ...results, - ...result, - }; - } - } else { - results = curResults; - } - } - return results; - }; - - const headTags = (...args: any) => callMiddleware("headTags", args); - - if (!ReactIs.isValidElementType(Component)) { - const exportNames = Object.keys(PageNamespace || {}); - - const reactComponents = exportNames.filter(ReactIs.isValidElementType); - if (reactComponents.length > 2) { - throw new Error( - `\"export default\" missing in ${ - route.filePath - }.\nTry exporting one of ${reactComponents.join(", ")}\n`, - ); - } else if (reactComponents.length === 2) { - throw new Error( - `\"export default\" missing in ${route.filePath}.\n\nTry exporting <${reactComponents[0]} /> or <${reactComponents[1]} />\n`, - ); - } else if (reactComponents.length == 1) { - throw new Error( - `\"export default\" missing in ${route.filePath}. Try adding this to the bottom of the file:\n\n export default ${reactComponents[0]};\n`, - ); - } else if (reactComponents.length == 0) { - throw new Error( - `\"export default\" missing in ${route.filePath}. Try exporting a React component.\n`, - ); - } - } - - const isFallback = !!query.__nextFallback; - delete query.__nextFallback; - delete query.__nextLocale; - delete query.__nextDefaultLocale; - - // const isSSG = !!getStaticProps; - - const defaultAppGetInitialProps = - App.getInitialProps === (App as any).origGetInitialProps; - - const hasPageGetInitialProps = !!(Component as any).getInitialProps; - const pageIsDynamic = route.kind === "dynamic"; - const isPreview = false; - const isAutoExport = false; - const nextExport = isAutoExport || isFallback; - - if (isAutoExport || isFallback) { - // // remove query values except ones that will be set during export - // query = { - // ...(query.amp - // ? { - // amp: query.amp, - // } - // : {}), - // }; - asPath = `${asPath}${ - // ensure trailing slash is present for non-dynamic auto-export pages - asPath.endsWith("/") && asPath !== "/" && !pageIsDynamic ? "/" : "" - }`; - } - - let head: JSX.Element[] = [ - <meta charSet="utf-8" />, - <meta name="viewport" content="width=device-width" />, - ]; - - const reactLoadableModules: string[] = []; - var scriptLoader = {}; - const AppContainer = ({ children }: any) => ( - <RouterContext.Provider value={router}> - {/* <AmpStateContext.Provider value={ampState}> */} - <HeadManagerContext.Provider - value={{ - updateHead: (state) => { - head = state; - }, - updateScripts: (scripts) => { - scriptLoader = scripts; - }, - scripts: {}, - mountedInstances: new Set(), - }} - > - <LoadableContext.Provider - value={(moduleName) => reactLoadableModules.push(moduleName)} - > - {children} - </LoadableContext.Provider> - </HeadManagerContext.Provider> - {/* </AmpStateContext.Provider> */} - </RouterContext.Provider> - ); - - // Todo: Double check this when adding support for dynamic() - await Loadable.preloadAll(); // Make sure all dynamic imports are loaded - - const router = new ServerRouter( - pathname, - query, - asPath, - { - isFallback: isFallback, - }, - true, - origin, - null, - [], // renderOpts.locales, - null, //renderOpts.defaultLocale, - [], // renderOpts.domainLocales, - false, - false, - ); - - const ctx = { - err: null, - req: undefined, - res: undefined, - pathname, - query, - asPath, - locale: null, - locales: [], - defaultLocale: null, - AppTree: (props: any) => { - return ( - <AppContainer> - <App {...props} Component={Component} router={router} /> - </AppContainer> - ); - }, - defaultGetInitialProps: async ( - docCtx: NextDocument.DocumentContext, - ): Promise<DocumentInitialProps> => { - const enhanceApp = (AppComp: any) => { - return (props: any) => <AppComp {...props} />; - }; - - const { html, head } = await docCtx.renderPage({ enhanceApp }); - // const styles = jsxStyleRegistry.styles(); - return { html, head }; - }, - }; - - var props: any = await loadGetInitialProps(AppComponent, { - AppTree: ctx.AppTree, - Component, - router, - ctx, - }); - - const pageProps = Object.assign({}, props.pageProps || {}); - // We don't call getServerSideProps on clients. - // @ts-expect-error - const getServerSideProps = PageNamespace.getServerSideProps; - - var responseHeaders: Headers; - - if (typeof getServerSideProps === "function") { - const result = await getServerSideProps({ - params: route.params, - query: route.query, - req: { - destroy() {}, - method: request.method, - httpVersion: "1.1", - rawHeaders: [], - rawTrailers: [], - socket: null, - statusCode: 200, - statusMessage: "OK", - trailers: {}, - url: request.url, - headers: new Proxy( - {}, - { - get(target, name) { - return request.headers.get(name as string); - }, - has(target, name) { - return request.headers.has(name as string); - }, - }, - ), - }, - res: { - getHeaders() { - return {}; - }, - getHeaderNames() { - return {}; - }, - flushHeaders() {}, - getHeader(name) { - if (!responseHeaders) return undefined; - return responseHeaders.get(name); - }, - hasHeader(name) { - if (!responseHeaders) return undefined; - return responseHeaders.has(name); - }, - headersSent: false, - setHeader(name, value) { - responseHeaders = responseHeaders || new Headers(); - responseHeaders.set(name, String(value)); - }, - cork() {}, - end() {}, - finished: false, - }, - resolvedUrl: route.pathname, - preview: false, - previewData: null, - locale: null, - locales: [], - defaultLocale: null, - }); - - if (result) { - if ("props" in result) { - if (typeof result.props === "object") { - Object.assign(pageProps, result.props); - } - } - } - } else if (typeof getStaticProps === "function") { - const result = await getStaticProps({ - params: route.params, - query: route.query, - req: null, - res: null, - resolvedUrl: route.pathname, - preview: false, - previewData: null, - locale: null, - locales: [], - defaultLocale: null, - }); - - if (result) { - if ("props" in result) { - if (typeof result.props === "object") { - Object.assign(pageProps, result.props); - } - } - } - } - - const renderToString = ReactDOMServer.renderToString; - const ErrorDebug = null; - - props.pageProps = pageProps; - - const renderPage: RenderPage = ( - options: ComponentsEnhancer = {}, - ): RenderPageResult | Promise<RenderPageResult> => { - if (ctx.err && ErrorDebug) { - const htmlOrPromise = renderToString(<ErrorDebug error={ctx.err} />); - return { html: htmlOrPromise, head }; - } - - if (dev && (props.router || props.Component)) { - throw new Error( - `'router' and 'Component' can not be returned in getInitialProps from _app.js https://nextjs.org/docs/messages/cant-override-next-props`, - ); - } - - const { App: EnhancedApp, Component: EnhancedComponent } = - // Argument of type 'NextComponentType<any, {}, {}> | typeof App' is not assignable to parameter of type 'AppType'. - // @ts-expect-error - enhanceComponents(options, AppComponent, Component); - - const htmlOrPromise = renderToString( - <AppContainer> - <EnhancedApp - Component={EnhancedComponent} - router={router} - {...props} - pageProps={pageProps} - /> - </AppContainer>, - ); - - return { html: htmlOrPromise, head }; - }; - - const documentCtx = { ...ctx, renderPage }; - const docProps: DocumentInitialProps = await loadGetInitialProps( - Document, - documentCtx, - ); - - if (!docProps || typeof docProps.html !== "string") { - const message = `"${getDisplayName( - Document, - )}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`; - throw new Error(message); - } - - const renderOpts = { - params: route.params, - }; - - const docComponentsRendered: DocumentProps["docComponentsRendered"] = {}; - - let html = renderDocument(Document, { - docComponentsRendered, - ...renderOpts, - disableOptimizedLoading: false, - canonicalBase: origin, - buildManifest: { - devFiles: [], - allFiles: [], - polyfillFiles: [], - lowPriorityFiles: [], - // buildManifest doesn't expect pages, even though its used - // @ts-expect-error - pages, - }, - // Only enabled in production as development mode has features relying on HMR (style injection for example) - // @ts-expect-error - unstable_runtimeJS: true, - // process.env.NODE_ENV === "production" - // ? pageConfig.unstable_runtimeJS - // : undefined, - // unstable_JsPreload: pageConfig.unstable_JsPreload, - // @ts-expect-error - unstable_JsPreload: true, - dangerousAsPath: router.asPath, - ampState: undefined, - props, - assetPrefix: "", - headTags: await headTags(documentCtx), - isFallback, - docProps, - page: pathname, - pathname, - ampPath: undefined, - query, - inAmpMode: false, - hybridAmp: undefined, - dynamicImportsIds: [], // Array.from(dynamicImportsIds), - dynamicImports: [], //Array.from(dynamicImports), - gsp: !!getStaticProps ? true : undefined, - gssp: !!getServerSideProps ? true : undefined, - gip: hasPageGetInitialProps ? true : undefined, - appGip: !defaultAppGetInitialProps ? true : undefined, - devOnlyCacheBusterQueryString: "", - scriptLoader, - isPreview: isPreview, - autoExport: nextExport === true ? true : undefined, - nextExport: nextExport, - useMaybeDeferContent, - }); - // __NEXT_BODY_RENDER_TARGET__ - html = appendNextBody(html, docProps.html); - html = html - .replaceAll('"/_next/http://', '"http://') - .replaceAll('"/_next/https://', '"https://'); - if (responseHeaders) { - return new Response(html, { headers: responseHeaders }); - } else { - return new Response(html); - } -} - -export function useMaybeDeferContent( - _name: string, - contentFn: () => JSX.Element, -): [boolean, JSX.Element] { - return [false, contentFn()]; -} diff --git a/packages/bun-framework-next/server.development.tsx b/packages/bun-framework-next/server.development.tsx deleted file mode 100644 index a19ffd149..000000000 --- a/packages/bun-framework-next/server.development.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { render } from "./renderDocument"; -import packagejson from "next/package.json"; - -const version = packagejson.version; - -if (!version.startsWith("12.2")) { - console.warn( - "Possibly incompatible Next.js version: ", - version, - ". Please upgrade to Next.js 12.2.0+.\n", - ); -} - -let buildId = 0; - -let DocumentLoaded = false; -let DocumentNamespace; - -import(Bun.routesDir + "_document").then( - (doc) => { - DocumentNamespace = doc; - DocumentLoaded = true; - }, - (err) => { - // ResolveError is defined outside of bun-framework-next in ../../src/runtime/errors - // @ts-expect-error - if (err instanceof ResolveError) { - DocumentLoaded = true; - } else { - console.error(err); - } - }, -); - -addEventListener("fetch", async (event: FetchEvent) => { - const route = Bun.match(event); - - // This imports the currently matched route. - let PageNamespace: any; - - try { - PageNamespace = await import(route.filePath); - } catch (exception) { - console.error("Error loading page:", route.filePath); - throw exception; - } - - // This returns all .css files that were imported in the line above. - // It's recursive, so any file that imports a CSS file will be included. - const pageStylesheets = (Bun.getImportedStyles() as string[]).slice(); - - let appRoute: any; - - try { - appRoute = await import(Bun.routesDir + "_app"); - } catch (exception) { - // ResolveError is defined outside of bun-framework-next in ../../src/runtime/errors - // @ts-expect-error - if (exception && !(exception instanceof ResolveError)) { - console.error("Error loading app:", Bun.routesDir + "_app"); - throw exception; - } - } - - const appStylesheets = (Bun.getImportedStyles() as string[]).slice(); - let response: Response; - try { - response = await render({ - route, - PageNamespace, - appStylesheets, - pageStylesheets, - DocumentNamespace, - AppNamespace: appRoute, - buildId, - routePaths: Bun.getRouteFiles(), - routeNames: Bun.getRouteNames(), - request: event.request, - }); - } catch (exception) { - console.error("Error rendering route", route.filePath); - throw exception; - } - - event.respondWith(response); - - buildId++; -}); - -declare let Bun: any; -export {}; diff --git a/packages/bun-framework-next/tsconfig.json b/packages/bun-framework-next/tsconfig.json deleted file mode 100644 index 96e13d1eb..000000000 --- a/packages/bun-framework-next/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "target": "esnext", - "lib": [ - "dom", - "dom.iterable", - "esnext", - "WebWorker" - ], - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "empty.js" - ], - "exclude": [ - "node_modules" - ], -}
\ No newline at end of file diff --git a/packages/bun-inspector-protocol/scripts/generate-protocol.ts b/packages/bun-inspector-protocol/scripts/generate-protocol.ts index c53153bb4..7a0826009 100644 --- a/packages/bun-inspector-protocol/scripts/generate-protocol.ts +++ b/packages/bun-inspector-protocol/scripts/generate-protocol.ts @@ -5,7 +5,7 @@ import { spawnSync } from "node:child_process"; run().catch(console.error); async function run() { - const cwd = new URL("../protocol/", import.meta.url); + const cwd = new URL("../src/protocol/", import.meta.url); const runner = "Bun" in globalThis ? "bunx" : "npx"; const write = (name: string, data: string) => { const path = new URL(name, cwd); diff --git a/packages/bun-inspector-protocol/src/protocol/v8/index.d.ts b/packages/bun-inspector-protocol/src/protocol/v8/index.d.ts index f0e82acb4..579adf70c 100644 --- a/packages/bun-inspector-protocol/src/protocol/v8/index.d.ts +++ b/packages/bun-inspector-protocol/src/protocol/v8/index.d.ts @@ -1136,7 +1136,8 @@ export namespace V8 { | "Canceled" | "RpPageNotVisible" | "SilentMediationFailure" - | "ThirdPartyCookiesBlocked"; + | "ThirdPartyCookiesBlocked" + | "NotSignedInWithIdp"; export type FederatedAuthUserInfoRequestIssueDetails = { federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason; }; @@ -1192,6 +1193,25 @@ export namespace V8 { */ failedRequestInfo?: FailedRequestInfo | undefined; }; + export type PropertyRuleIssueReason = "InvalidSyntax" | "InvalidInitialValue" | "InvalidInherits" | "InvalidName"; + /** + * This issue warns about errors in property rules that lead to property + * registrations being ignored. + */ + export type PropertyRuleIssueDetails = { + /** + * Source code position of the property rule. + */ + sourceCodeLocation: SourceCodeLocation; + /** + * Reason why the property rule was discarded. + */ + propertyRuleIssueReason: PropertyRuleIssueReason; + /** + * The value of the property rule property that failed to parse + */ + propertyValue?: string | undefined; + }; /** * A unique identifier for the type of issue. Each type may use one of the * optional fields in InspectorIssueDetails to convey more specific @@ -1215,7 +1235,8 @@ export namespace V8 { | "FederatedAuthRequestIssue" | "BounceTrackingIssue" | "StylesheetLoadingIssue" - | "FederatedAuthUserInfoRequestIssue"; + | "FederatedAuthUserInfoRequestIssue" + | "PropertyRuleIssue"; /** * This struct holds a list of optional fields with additional information * specific to the kind of issue. When adding a new issue code, please also @@ -1239,6 +1260,7 @@ export namespace V8 { federatedAuthRequestIssueDetails?: FederatedAuthRequestIssueDetails | undefined; bounceTrackingIssueDetails?: BounceTrackingIssueDetails | undefined; stylesheetLoadingIssueDetails?: StylesheetLoadingIssueDetails | undefined; + propertyRuleIssueDetails?: PropertyRuleIssueDetails | undefined; federatedAuthUserInfoRequestIssueDetails?: FederatedAuthUserInfoRequestIssueDetails | undefined; }; /** @@ -1390,17 +1412,83 @@ export namespace V8 { */ name: string; /** - * address field name, for example Jon Doe. + * address field value, for example Jon Doe. */ value: string; }; + /** + * A list of address fields. + */ + export type AddressFields = { + fields: AddressField[]; + }; export type Address = { /** - * fields and values defining a test address. + * fields and values defining an address. */ fields: AddressField[]; }; /** + * Defines how an address can be displayed like in chrome://settings/addresses. + * Address UI is a two dimensional array, each inner array is an "address information line", and when rendered in a UI surface should be displayed as such. + * The following address UI for instance: + * [[{name: "GIVE_NAME", value: "Jon"}, {name: "FAMILY_NAME", value: "Doe"}], [{name: "CITY", value: "Munich"}, {name: "ZIP", value: "81456"}]] + * should allow the receiver to render: + * Jon Doe + * Munich 81456 + */ + export type AddressUI = { + /** + * A two dimension array containing the repesentation of values from an address profile. + */ + addressFields: AddressFields[]; + }; + /** + * Specified whether a filled field was done so by using the html autocomplete attribute or autofill heuristics. + */ + export type FillingStrategy = "autocompleteAttribute" | "autofillInferred"; + export type FilledField = { + /** + * The type of the field, e.g text, password etc. + */ + htmlType: string; + /** + * the html id + */ + id: string; + /** + * the html name + */ + name: string; + /** + * the field value + */ + value: string; + /** + * The actual field type, e.g FAMILY_NAME + */ + autofillType: string; + /** + * The filling strategy + */ + fillingStrategy: FillingStrategy; + }; + /** + * Emitted when an address form is filled. + * @event `Autofill.addressFormFilled` + */ + export type AddressFormFilledEvent = { + /** + * Information about the fields that were filled + */ + filledFields: FilledField[]; + /** + * An UI representation of the address used to fill the form. + * Consists of a 2D array where each child represents an address/profile line. + */ + addressUi: AddressUI; + }; + /** * Trigger autofill on a form identified by the fieldId. * If the field and related form cannot be autofilled, returns an error. * @request `Autofill.trigger` @@ -1437,6 +1525,26 @@ export namespace V8 { * @response `Autofill.setAddresses` */ export type SetAddressesResponse = {}; + /** + * Disables autofill domain notifications. + * @request `Autofill.disable` + */ + export type DisableRequest = {}; + /** + * Disables autofill domain notifications. + * @response `Autofill.disable` + */ + export type DisableResponse = {}; + /** + * Enables autofill domain notifications. + * @request `Autofill.enable` + */ + export type EnableRequest = {}; + /** + * Enables autofill domain notifications. + * @response `Autofill.enable` + */ + export type EnableResponse = {}; } export namespace BackgroundService { /** @@ -3574,6 +3682,25 @@ export namespace V8 { */ export type SetEffectivePropertyValueForNodeResponse = {}; /** + * Modifies the property rule property name. + * @request `CSS.setPropertyRulePropertyName` + */ + export type SetPropertyRulePropertyNameRequest = { + styleSheetId: StyleSheetId; + range: SourceRange; + propertyName: string; + }; + /** + * Modifies the property rule property name. + * @response `CSS.setPropertyRulePropertyName` + */ + export type SetPropertyRulePropertyNameResponse = { + /** + * The resulting key text after modification. + */ + propertyName: Value; + }; + /** * Modifies the keyframe rule key text. * @request `CSS.setKeyframeKey` */ @@ -7168,6 +7295,16 @@ export namespace V8 { * @response `EventBreakpoints.removeInstrumentationBreakpoint` */ export type RemoveInstrumentationBreakpointResponse = {}; + /** + * Removes all breakpoints + * @request `EventBreakpoints.disable` + */ + export type DisableRequest = {}; + /** + * Removes all breakpoints + * @response `EventBreakpoints.disable` + */ + export type DisableResponse = {}; } export namespace FedCm { /** @@ -7178,7 +7315,7 @@ export namespace V8 { /** * Whether the dialog shown is an account chooser or an auto re-authentication dialog. */ - export type DialogType = "AccountChooser" | "AutoReauthn" | "ConfirmIdpSignin"; + export type DialogType = "AccountChooser" | "AutoReauthn" | "ConfirmIdpLogin"; /** * Corresponds to IdentityRequestAccount */ @@ -7189,7 +7326,7 @@ export namespace V8 { givenName: string; pictureUrl: string; idpConfigUrl: string; - idpSigninUrl: string; + idpLoginUrl: string; loginState: LoginState; /** * These two are only set if the loginState is signUp @@ -7253,6 +7390,20 @@ export namespace V8 { */ export type SelectAccountResponse = {}; /** + * Only valid if the dialog type is ConfirmIdpLogin. Acts as if the user had + * clicked the continue button. + * @request `FedCm.confirmIdpLogin` + */ + export type ConfirmIdpLoginRequest = { + dialogId: string; + }; + /** + * Only valid if the dialog type is ConfirmIdpLogin. Acts as if the user had + * clicked the continue button. + * @response `FedCm.confirmIdpLogin` + */ + export type ConfirmIdpLoginResponse = {}; + /** * undefined * @request `FedCm.dismissDialog` */ @@ -10477,6 +10628,7 @@ export namespace V8 { | "ch-ect" | "ch-prefers-color-scheme" | "ch-prefers-reduced-motion" + | "ch-prefers-reduced-transparency" | "ch-rtt" | "ch-save-data" | "ch-ua" @@ -13109,7 +13261,6 @@ export namespace V8 { | "LowEndDevice" | "InvalidSchemeRedirect" | "InvalidSchemeNavigation" - | "InProgressNavigation" | "NavigationRequestBlockedByCsp" | "MainFrameNavigation" | "MojoBinderPolicy" @@ -13121,7 +13272,6 @@ export namespace V8 { | "NavigationBadHttpStatus" | "ClientCertRequested" | "NavigationRequestNetworkError" - | "MaxNumOfRunningPrerendersExceeded" | "CancelAllHostsForTesting" | "DidFailLoad" | "Stop" @@ -13133,9 +13283,8 @@ export namespace V8 { | "MixedContent" | "TriggerBackgrounded" | "MemoryLimitExceeded" - | "FailToGetMemoryUsage" | "DataSaverEnabled" - | "HasEffectiveUrl" + | "TriggerUrlHasEffectiveUrl" | "ActivatedBeforeStarted" | "InactivePageRestriction" | "StartFailed" @@ -13166,7 +13315,13 @@ export namespace V8 { | "PrerenderingDisabledByDevTools" | "ResourceLoadBlockedByClient" | "SpeculationRuleRemoved" - | "ActivatedWithAuxiliaryBrowsingContexts"; + | "ActivatedWithAuxiliaryBrowsingContexts" + | "MaxNumOfRunningEagerPrerendersExceeded" + | "MaxNumOfRunningNonEagerPrerendersExceeded" + | "MaxNumOfRunningEmbedderPrerendersExceeded" + | "PrerenderingUrlHasEffectiveUrl" + | "RedirectedPrerenderingUrlHasEffectiveUrl" + | "ActivationUrlHasEffectiveUrl"; /** * Preloading status values, see also PreloadingTriggeringOutcome. This * status is shared by prefetchStatusUpdated and prerenderStatusUpdated. @@ -13222,24 +13377,6 @@ export namespace V8 { id: RuleSetId; }; /** - * Fired when a prerender attempt is completed. - * @event `Preload.prerenderAttemptCompleted` - */ - export type PrerenderAttemptCompletedEvent = { - key: PreloadingAttemptKey; - /** - * The frame id of the frame initiating prerendering. - */ - initiatingFrameId: Page.FrameId; - prerenderingUrl: string; - finalStatus: PrerenderFinalStatus; - /** - * This is used to give users more information about the name of the API call - * that is incompatible with prerender and has caused the cancellation of the attempt - */ - disallowedApiMethod?: string | undefined; - }; - /** * Fired when a preload enabled state is updated. * @event `Preload.preloadEnabledStateUpdated` */ @@ -13935,12 +14072,21 @@ export namespace V8 { /** * Enum of interest group access types. */ - export type InterestGroupAccessType = "join" | "leave" | "update" | "loaded" | "bid" | "win"; + export type InterestGroupAccessType = + | "join" + | "leave" + | "update" + | "loaded" + | "bid" + | "win" + | "additionalBid" + | "additionalBidWin" + | "clear"; /** * Ad advertising element inside an interest group. */ export type InterestGroupAd = { - renderUrl: string; + renderURL: string; metadata?: string | undefined; }; /** @@ -13951,10 +14097,10 @@ export namespace V8 { name: string; expirationTime: Network.TimeSinceEpoch; joiningOrigin: string; - biddingUrl?: string | undefined; - biddingWasmHelperUrl?: string | undefined; - updateUrl?: string | undefined; - trustedBiddingSignalsUrl?: string | undefined; + biddingLogicURL?: string | undefined; + biddingWasmHelperURL?: string | undefined; + updateURL?: string | undefined; + trustedBiddingSignalsURL?: string | undefined; trustedBiddingSignalsKeys: string[]; userBiddingSignals?: string | undefined; ads: InterestGroupAd[]; @@ -14099,20 +14245,27 @@ export namespace V8 { key: string; value: UnsignedInt128AsBase16; }; - export type AttributionReportingSourceRegistration = { - time: Network.TimeSinceEpoch; + export type AttributionReportingEventReportWindows = { /** * duration in seconds */ - expiry?: number | undefined; + start: number; + /** + * duration in seconds + */ + ends: number[]; + }; + export type AttributionReportingSourceRegistration = { + time: Network.TimeSinceEpoch; /** * duration in seconds */ - eventReportWindow?: number | undefined; + expiry: number; + eventReportWindows: AttributionReportingEventReportWindows; /** * duration in seconds */ - aggregatableReportWindow?: number | undefined; + aggregatableReportWindow: number; type: AttributionReportingSourceType; sourceOrigin: string; reportingOrigin: string; @@ -16381,6 +16534,7 @@ export namespace V8 { "Animation.animationCreated": Animation.AnimationCreatedEvent; "Animation.animationStarted": Animation.AnimationStartedEvent; "Audits.issueAdded": Audits.IssueAddedEvent; + "Autofill.addressFormFilled": Autofill.AddressFormFilledEvent; "BackgroundService.recordingStateChanged": BackgroundService.RecordingStateChangedEvent; "BackgroundService.backgroundServiceEventReceived": BackgroundService.BackgroundServiceEventReceivedEvent; "Browser.downloadWillBegin": Browser.DownloadWillBeginEvent; @@ -16460,7 +16614,6 @@ export namespace V8 { "PerformanceTimeline.timelineEventAdded": PerformanceTimeline.TimelineEventAddedEvent; "Preload.ruleSetUpdated": Preload.RuleSetUpdatedEvent; "Preload.ruleSetRemoved": Preload.RuleSetRemovedEvent; - "Preload.prerenderAttemptCompleted": Preload.PrerenderAttemptCompletedEvent; "Preload.preloadEnabledStateUpdated": Preload.PreloadEnabledStateUpdatedEvent; "Preload.prefetchStatusUpdated": Preload.PrefetchStatusUpdatedEvent; "Preload.prerenderStatusUpdated": Preload.PrerenderStatusUpdatedEvent; @@ -16533,6 +16686,8 @@ export namespace V8 { "Audits.checkFormsIssues": Audits.CheckFormsIssuesRequest; "Autofill.trigger": Autofill.TriggerRequest; "Autofill.setAddresses": Autofill.SetAddressesRequest; + "Autofill.disable": Autofill.DisableRequest; + "Autofill.enable": Autofill.EnableRequest; "BackgroundService.startObserving": BackgroundService.StartObservingRequest; "BackgroundService.stopObserving": BackgroundService.StopObservingRequest; "BackgroundService.setRecording": BackgroundService.SetRecordingRequest; @@ -16583,6 +16738,7 @@ export namespace V8 { "CSS.trackComputedStyleUpdates": CSS.TrackComputedStyleUpdatesRequest; "CSS.takeComputedStyleUpdates": CSS.TakeComputedStyleUpdatesRequest; "CSS.setEffectivePropertyValueForNode": CSS.SetEffectivePropertyValueForNodeRequest; + "CSS.setPropertyRulePropertyName": CSS.SetPropertyRulePropertyNameRequest; "CSS.setKeyframeKey": CSS.SetKeyframeKeyRequest; "CSS.setMediaText": CSS.SetMediaTextRequest; "CSS.setContainerQueryText": CSS.SetContainerQueryTextRequest; @@ -16705,9 +16861,11 @@ export namespace V8 { "Emulation.setAutomationOverride": Emulation.SetAutomationOverrideRequest; "EventBreakpoints.setInstrumentationBreakpoint": EventBreakpoints.SetInstrumentationBreakpointRequest; "EventBreakpoints.removeInstrumentationBreakpoint": EventBreakpoints.RemoveInstrumentationBreakpointRequest; + "EventBreakpoints.disable": EventBreakpoints.DisableRequest; "FedCm.enable": FedCm.EnableRequest; "FedCm.disable": FedCm.DisableRequest; "FedCm.selectAccount": FedCm.SelectAccountRequest; + "FedCm.confirmIdpLogin": FedCm.ConfirmIdpLoginRequest; "FedCm.dismissDialog": FedCm.DismissDialogRequest; "FedCm.resetCooldown": FedCm.ResetCooldownRequest; "Fetch.disable": Fetch.DisableRequest; @@ -16979,6 +17137,8 @@ export namespace V8 { "Audits.checkFormsIssues": Audits.CheckFormsIssuesResponse; "Autofill.trigger": Autofill.TriggerResponse; "Autofill.setAddresses": Autofill.SetAddressesResponse; + "Autofill.disable": Autofill.DisableResponse; + "Autofill.enable": Autofill.EnableResponse; "BackgroundService.startObserving": BackgroundService.StartObservingResponse; "BackgroundService.stopObserving": BackgroundService.StopObservingResponse; "BackgroundService.setRecording": BackgroundService.SetRecordingResponse; @@ -17029,6 +17189,7 @@ export namespace V8 { "CSS.trackComputedStyleUpdates": CSS.TrackComputedStyleUpdatesResponse; "CSS.takeComputedStyleUpdates": CSS.TakeComputedStyleUpdatesResponse; "CSS.setEffectivePropertyValueForNode": CSS.SetEffectivePropertyValueForNodeResponse; + "CSS.setPropertyRulePropertyName": CSS.SetPropertyRulePropertyNameResponse; "CSS.setKeyframeKey": CSS.SetKeyframeKeyResponse; "CSS.setMediaText": CSS.SetMediaTextResponse; "CSS.setContainerQueryText": CSS.SetContainerQueryTextResponse; @@ -17151,9 +17312,11 @@ export namespace V8 { "Emulation.setAutomationOverride": Emulation.SetAutomationOverrideResponse; "EventBreakpoints.setInstrumentationBreakpoint": EventBreakpoints.SetInstrumentationBreakpointResponse; "EventBreakpoints.removeInstrumentationBreakpoint": EventBreakpoints.RemoveInstrumentationBreakpointResponse; + "EventBreakpoints.disable": EventBreakpoints.DisableResponse; "FedCm.enable": FedCm.EnableResponse; "FedCm.disable": FedCm.DisableResponse; "FedCm.selectAccount": FedCm.SelectAccountResponse; + "FedCm.confirmIdpLogin": FedCm.ConfirmIdpLoginResponse; "FedCm.dismissDialog": FedCm.DismissDialogResponse; "FedCm.resetCooldown": FedCm.ResetCooldownResponse; "Fetch.disable": Fetch.DisableResponse; diff --git a/packages/bun-inspector-protocol/src/protocol/v8/protocol.json b/packages/bun-inspector-protocol/src/protocol/v8/protocol.json index 979091bf0..cee8c2ca4 100644 --- a/packages/bun-inspector-protocol/src/protocol/v8/protocol.json +++ b/packages/bun-inspector-protocol/src/protocol/v8/protocol.json @@ -1091,7 +1091,8 @@ "Canceled", "RpPageNotVisible", "SilentMediationFailure", - "ThirdPartyCookiesBlocked" + "ThirdPartyCookiesBlocked", + "NotSignedInWithIdp" ] }, { @@ -1164,6 +1165,34 @@ ] }, { + "id": "PropertyRuleIssueReason", + "type": "string", + "enum": ["InvalidSyntax", "InvalidInitialValue", "InvalidInherits", "InvalidName"] + }, + { + "id": "PropertyRuleIssueDetails", + "description": "This issue warns about errors in property rules that lead to property\nregistrations being ignored.", + "type": "object", + "properties": [ + { + "name": "sourceCodeLocation", + "description": "Source code position of the property rule.", + "$ref": "SourceCodeLocation" + }, + { + "name": "propertyRuleIssueReason", + "description": "Reason why the property rule was discarded.", + "$ref": "PropertyRuleIssueReason" + }, + { + "name": "propertyValue", + "description": "The value of the property rule property that failed to parse", + "optional": true, + "type": "string" + } + ] + }, + { "id": "InspectorIssueCode", "description": "A unique identifier for the type of issue. Each type may use one of the\noptional fields in InspectorIssueDetails to convey more specific\ninformation about the kind of issue.", "type": "string", @@ -1185,7 +1214,8 @@ "FederatedAuthRequestIssue", "BounceTrackingIssue", "StylesheetLoadingIssue", - "FederatedAuthUserInfoRequestIssue" + "FederatedAuthUserInfoRequestIssue", + "PropertyRuleIssue" ] }, { @@ -1227,6 +1257,7 @@ }, { "name": "bounceTrackingIssueDetails", "optional": true, "$ref": "BounceTrackingIssueDetails" }, { "name": "stylesheetLoadingIssueDetails", "optional": true, "$ref": "StylesheetLoadingIssueDetails" }, + { "name": "propertyRuleIssueDetails", "optional": true, "$ref": "PropertyRuleIssueDetails" }, { "name": "federatedAuthUserInfoRequestIssueDetails", "optional": true, @@ -1344,20 +1375,76 @@ "type": "object", "properties": [ { "name": "name", "description": "address field name, for example GIVEN_NAME.", "type": "string" }, - { "name": "value", "description": "address field name, for example Jon Doe.", "type": "string" } + { "name": "value", "description": "address field value, for example Jon Doe.", "type": "string" } ] }, { + "id": "AddressFields", + "description": "A list of address fields.", + "type": "object", + "properties": [{ "name": "fields", "type": "array", "items": { "$ref": "AddressField" } }] + }, + { "id": "Address", "type": "object", "properties": [ { "name": "fields", - "description": "fields and values defining a test address.", + "description": "fields and values defining an address.", "type": "array", "items": { "$ref": "AddressField" } } ] + }, + { + "id": "AddressUI", + "description": "Defines how an address can be displayed like in chrome://settings/addresses.\nAddress UI is a two dimensional array, each inner array is an \"address information line\", and when rendered in a UI surface should be displayed as such.\nThe following address UI for instance:\n[[{name: \"GIVE_NAME\", value: \"Jon\"}, {name: \"FAMILY_NAME\", value: \"Doe\"}], [{name: \"CITY\", value: \"Munich\"}, {name: \"ZIP\", value: \"81456\"}]]\nshould allow the receiver to render:\nJon Doe\nMunich 81456", + "type": "object", + "properties": [ + { + "name": "addressFields", + "description": "A two dimension array containing the repesentation of values from an address profile.", + "type": "array", + "items": { "$ref": "AddressFields" } + } + ] + }, + { + "id": "FillingStrategy", + "description": "Specified whether a filled field was done so by using the html autocomplete attribute or autofill heuristics.", + "type": "string", + "enum": ["autocompleteAttribute", "autofillInferred"] + }, + { + "id": "FilledField", + "type": "object", + "properties": [ + { "name": "htmlType", "description": "The type of the field, e.g text, password etc.", "type": "string" }, + { "name": "id", "description": "the html id", "type": "string" }, + { "name": "name", "description": "the html name", "type": "string" }, + { "name": "value", "description": "the field value", "type": "string" }, + { "name": "autofillType", "description": "The actual field type, e.g FAMILY_NAME", "type": "string" }, + { "name": "fillingStrategy", "description": "The filling strategy", "$ref": "FillingStrategy" } + ] + } + ], + "events": [ + { + "name": "addressFormFilled", + "description": "Emitted when an address form is filled.", + "parameters": [ + { + "name": "filledFields", + "description": "Information about the fields that were filled", + "type": "array", + "items": { "$ref": "FilledField" } + }, + { + "name": "addressUi", + "description": "An UI representation of the address used to fill the form.\nConsists of a 2D array where each child represents an address/profile line.", + "$ref": "AddressUI" + } + ] } ], "commands": [ @@ -1387,7 +1474,9 @@ "name": "setAddresses", "description": "Set addresses so that developers can verify their forms implementation.", "parameters": [{ "name": "addresses", "type": "array", "items": { "$ref": "Address" } }] - } + }, + { "name": "disable", "description": "Disables autofill domain notifications." }, + { "name": "enable", "description": "Enables autofill domain notifications." } ] }, { @@ -3212,6 +3301,18 @@ ] }, { + "name": "setPropertyRulePropertyName", + "description": "Modifies the property rule property name.", + "parameters": [ + { "name": "styleSheetId", "$ref": "StyleSheetId" }, + { "name": "range", "$ref": "SourceRange" }, + { "name": "propertyName", "type": "string" } + ], + "returns": [ + { "name": "propertyName", "description": "The resulting key text after modification.", "$ref": "Value" } + ] + }, + { "name": "setKeyframeKey", "description": "Modifies the keyframe rule key text.", "parameters": [ @@ -4628,7 +4729,7 @@ { "domain": "DOMDebugger", "description": "DOM debugging allows setting breakpoints on particular DOM operations and events. JavaScript\nexecution will stop on these operations as if there was a regular breakpoint set.", - "dependencies": ["DOM", "Debugger", "Runtime"], + "dependencies": ["DOM", "Runtime"], "types": [ { "id": "DOMBreakpointType", @@ -4738,6 +4839,8 @@ "name": "removeInstrumentationBreakpoint", "description": "Removes breakpoint on particular native event.", "experimental": true, + "deprecated": true, + "redirect": "EventBreakpoints", "parameters": [{ "name": "eventName", "description": "Instrumentation name to stop on.", "type": "string" }] }, { @@ -4788,6 +4891,8 @@ "name": "setInstrumentationBreakpoint", "description": "Sets breakpoint on particular native event.", "experimental": true, + "deprecated": true, + "redirect": "EventBreakpoints", "parameters": [{ "name": "eventName", "description": "Instrumentation name to stop on.", "type": "string" }] }, { @@ -6069,7 +6174,7 @@ }, { "domain": "EventBreakpoints", - "description": "EventBreakpoints permits setting breakpoints on particular operations and\nevents in targets that run JavaScript but do not have a DOM.\nJavaScript execution will stop on these operations as if there was a regular\nbreakpoint set.", + "description": "EventBreakpoints permits setting JavaScript breakpoints on operations and events\noccurring in native code invoked from JavaScript. Once breakpoint is hit, it is\nreported through Debugger domain, similarly to regular breakpoints being hit.", "experimental": true, "commands": [ { @@ -6081,7 +6186,8 @@ "name": "removeInstrumentationBreakpoint", "description": "Removes breakpoint on particular native event.", "parameters": [{ "name": "eventName", "description": "Instrumentation name to stop on.", "type": "string" }] - } + }, + { "name": "disable", "description": "Removes all breakpoints" } ] }, { @@ -6099,7 +6205,7 @@ "id": "DialogType", "description": "Whether the dialog shown is an account chooser or an auto re-authentication dialog.", "type": "string", - "enum": ["AccountChooser", "AutoReauthn", "ConfirmIdpSignin"] + "enum": ["AccountChooser", "AutoReauthn", "ConfirmIdpLogin"] }, { "id": "Account", @@ -6112,7 +6218,7 @@ { "name": "givenName", "type": "string" }, { "name": "pictureUrl", "type": "string" }, { "name": "idpConfigUrl", "type": "string" }, - { "name": "idpSigninUrl", "type": "string" }, + { "name": "idpLoginUrl", "type": "string" }, { "name": "loginState", "$ref": "LoginState" }, { "name": "termsOfServiceUrl", @@ -6161,6 +6267,11 @@ ] }, { + "name": "confirmIdpLogin", + "description": "Only valid if the dialog type is ConfirmIdpLogin. Acts as if the user had\nclicked the continue button.", + "parameters": [{ "name": "dialogId", "type": "string" }] + }, + { "name": "dismissDialog", "parameters": [ { "name": "dialogId", "type": "string" }, @@ -6987,16 +7098,14 @@ { "name": "tiltX", "description": "The plane angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis, in degrees of the range [-90,90], a positive tiltX is to the right (default: 0)", - "experimental": true, "optional": true, - "type": "integer" + "type": "number" }, { "name": "tiltY", "description": "The plane angle between the X-Z plane and the plane containing both the stylus axis and the X axis, in degrees of the range [-90,90], a positive tiltY is towards the user (default: 0).", - "experimental": true, "optional": true, - "type": "integer" + "type": "number" }, { "name": "twist", @@ -7280,16 +7389,14 @@ { "name": "tiltX", "description": "The plane angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis, in degrees of the range [-90,90], a positive tiltX is to the right (default: 0).", - "experimental": true, "optional": true, - "type": "integer" + "type": "number" }, { "name": "tiltY", "description": "The plane angle between the X-Z plane and the plane containing both the stylus axis and the X axis, in degrees of the range [-90,90], a positive tiltY is towards the user (default: 0).", - "experimental": true, "optional": true, - "type": "integer" + "type": "number" }, { "name": "twist", @@ -9158,6 +9265,7 @@ "ch-ect", "ch-prefers-color-scheme", "ch-prefers-reduced-motion", + "ch-prefers-reduced-transparency", "ch-rtt", "ch-save-data", "ch-ua", @@ -11403,7 +11511,6 @@ "LowEndDevice", "InvalidSchemeRedirect", "InvalidSchemeNavigation", - "InProgressNavigation", "NavigationRequestBlockedByCsp", "MainFrameNavigation", "MojoBinderPolicy", @@ -11415,7 +11522,6 @@ "NavigationBadHttpStatus", "ClientCertRequested", "NavigationRequestNetworkError", - "MaxNumOfRunningPrerendersExceeded", "CancelAllHostsForTesting", "DidFailLoad", "Stop", @@ -11427,9 +11533,8 @@ "MixedContent", "TriggerBackgrounded", "MemoryLimitExceeded", - "FailToGetMemoryUsage", "DataSaverEnabled", - "HasEffectiveUrl", + "TriggerUrlHasEffectiveUrl", "ActivatedBeforeStarted", "InactivePageRestriction", "StartFailed", @@ -11460,7 +11565,13 @@ "PrerenderingDisabledByDevTools", "ResourceLoadBlockedByClient", "SpeculationRuleRemoved", - "ActivatedWithAuxiliaryBrowsingContexts" + "ActivatedWithAuxiliaryBrowsingContexts", + "MaxNumOfRunningEagerPrerendersExceeded", + "MaxNumOfRunningNonEagerPrerendersExceeded", + "MaxNumOfRunningEmbedderPrerendersExceeded", + "PrerenderingUrlHasEffectiveUrl", + "RedirectedPrerenderingUrlHasEffectiveUrl", + "ActivationUrlHasEffectiveUrl" ] }, { @@ -11516,26 +11627,6 @@ }, { "name": "ruleSetRemoved", "parameters": [{ "name": "id", "$ref": "RuleSetId" }] }, { - "name": "prerenderAttemptCompleted", - "description": "Fired when a prerender attempt is completed.", - "parameters": [ - { "name": "key", "$ref": "PreloadingAttemptKey" }, - { - "name": "initiatingFrameId", - "description": "The frame id of the frame initiating prerendering.", - "$ref": "Page.FrameId" - }, - { "name": "prerenderingUrl", "type": "string" }, - { "name": "finalStatus", "$ref": "PrerenderFinalStatus" }, - { - "name": "disallowedApiMethod", - "description": "This is used to give users more information about the name of the API call\nthat is incompatible with prerender and has caused the cancellation of the attempt", - "optional": true, - "type": "string" - } - ] - }, - { "name": "preloadEnabledStateUpdated", "description": "Fired when a preload enabled state is updated.", "parameters": [ @@ -12077,14 +12168,14 @@ "id": "InterestGroupAccessType", "description": "Enum of interest group access types.", "type": "string", - "enum": ["join", "leave", "update", "loaded", "bid", "win"] + "enum": ["join", "leave", "update", "loaded", "bid", "win", "additionalBid", "additionalBidWin", "clear"] }, { "id": "InterestGroupAd", "description": "Ad advertising element inside an interest group.", "type": "object", "properties": [ - { "name": "renderUrl", "type": "string" }, + { "name": "renderURL", "type": "string" }, { "name": "metadata", "optional": true, "type": "string" } ] }, @@ -12097,10 +12188,10 @@ { "name": "name", "type": "string" }, { "name": "expirationTime", "$ref": "Network.TimeSinceEpoch" }, { "name": "joiningOrigin", "type": "string" }, - { "name": "biddingUrl", "optional": true, "type": "string" }, - { "name": "biddingWasmHelperUrl", "optional": true, "type": "string" }, - { "name": "updateUrl", "optional": true, "type": "string" }, - { "name": "trustedBiddingSignalsUrl", "optional": true, "type": "string" }, + { "name": "biddingLogicURL", "optional": true, "type": "string" }, + { "name": "biddingWasmHelperURL", "optional": true, "type": "string" }, + { "name": "updateURL", "optional": true, "type": "string" }, + { "name": "trustedBiddingSignalsURL", "optional": true, "type": "string" }, { "name": "trustedBiddingSignalsKeys", "type": "array", "items": { "type": "string" } }, { "name": "userBiddingSignals", "optional": true, "type": "string" }, { "name": "ads", "type": "array", "items": { "$ref": "InterestGroupAd" } }, @@ -12276,19 +12367,23 @@ ] }, { + "id": "AttributionReportingEventReportWindows", + "experimental": true, + "type": "object", + "properties": [ + { "name": "start", "description": "duration in seconds", "type": "integer" }, + { "name": "ends", "description": "duration in seconds", "type": "array", "items": { "type": "integer" } } + ] + }, + { "id": "AttributionReportingSourceRegistration", "experimental": true, "type": "object", "properties": [ { "name": "time", "$ref": "Network.TimeSinceEpoch" }, - { "name": "expiry", "description": "duration in seconds", "optional": true, "type": "integer" }, - { "name": "eventReportWindow", "description": "duration in seconds", "optional": true, "type": "integer" }, - { - "name": "aggregatableReportWindow", - "description": "duration in seconds", - "optional": true, - "type": "integer" - }, + { "name": "expiry", "description": "duration in seconds", "type": "integer" }, + { "name": "eventReportWindows", "$ref": "AttributionReportingEventReportWindows" }, + { "name": "aggregatableReportWindow", "description": "duration in seconds", "type": "integer" }, { "name": "type", "$ref": "AttributionReportingSourceType" }, { "name": "sourceOrigin", "type": "string" }, { "name": "reportingOrigin", "type": "string" }, diff --git a/packages/bun-inspector-protocol/src/util/preview.ts b/packages/bun-inspector-protocol/src/util/preview.ts index c6d748304..78b1484ed 100644 --- a/packages/bun-inspector-protocol/src/util/preview.ts +++ b/packages/bun-inspector-protocol/src/util/preview.ts @@ -46,7 +46,10 @@ export function objectPreviewToString(objectPreview: JSC.Runtime.ObjectPreview): items = entries.map(entryPreviewToString).sort(); } else if (properties) { if (isIndexed(subtype)) { - items = properties.map(indexedPropertyPreviewToString).sort(); + items = properties.map(indexedPropertyPreviewToString); + if (subtype !== "array") { + items.sort(); + } } else { items = properties.map(namedPropertyPreviewToString).sort(); } diff --git a/packages/bun-internal-test/bun.lockb b/packages/bun-internal-test/bun.lockb Binary files differindex f0d6d2032..cef9d5e7e 100755 --- a/packages/bun-internal-test/bun.lockb +++ b/packages/bun-internal-test/bun.lockb diff --git a/packages/bun-lambda/README.md b/packages/bun-lambda/README.md index 1553714ae..87d4fbb53 100644 --- a/packages/bun-lambda/README.md +++ b/packages/bun-lambda/README.md @@ -8,7 +8,7 @@ First, you will need to deploy the layer to your AWS account. Clone this reposit ```sh git clone git@github.com:oven-sh/bun.git -cd packages/bun-lambda +cd bun/packages/bun-lambda bun install bun run publish-layer ``` diff --git a/packages/bun-lambda/package.json b/packages/bun-lambda/package.json index f670787eb..9643d5463 100644 --- a/packages/bun-lambda/package.json +++ b/packages/bun-lambda/package.json @@ -2,6 +2,7 @@ "name": "bun-lambda", "private": true, "devDependencies": { + "@oclif/plugin-plugins": "^3.5.0", "bun-types": "^0.7.0", "jszip": "^3.10.1", "oclif": "^3.6.5", diff --git a/packages/bun-types/README.md b/packages/bun-types/README.md index 19964c6f8..c11c1371d 100644 --- a/packages/bun-types/README.md +++ b/packages/bun-types/README.md @@ -12,7 +12,7 @@ Install the `bun-types` npm package: ```bash # yarn/npm/pnpm work too, "bun-types" is an ordinary npm package -bun add bun-types +bun add -d bun-types ``` # Usage @@ -21,9 +21,11 @@ Add this to your `tsconfig.json` or `jsconfig.json`: ```jsonc-diff { - -+ "types": ["bun-types"], - + "compilerOptions": { ++ "types": ["bun-types"] + // other options... + } + // other options... } ``` diff --git a/packages/bun-types/buffer.d.ts b/packages/bun-types/buffer.d.ts index fd7201677..6b9cc78c1 100644 --- a/packages/bun-types/buffer.d.ts +++ b/packages/bun-types/buffer.d.ts @@ -2084,31 +2084,31 @@ declare module "buffer" { values(): IterableIterator<number>; } var Buffer: BufferConstructor; + } - /** - * This function returns `true` if `input` contains only valid UTF-8-encoded data, - * including the case in which `input` is empty. - * - * Throws if the `input` is a detached array buffer. - * @since Bun v0.6.13 - * @param input The input to validate. - */ - export function isUtf8( - input: TypedArray | ArrayBufferLike | DataView, - ): boolean; + /** + * This function returns `true` if `input` contains only valid UTF-8-encoded data, + * including the case in which `input` is empty. + * + * Throws if the `input` is a detached array buffer. + * @since Bun v0.6.13 + * @param input The input to validate. + */ + export function isUtf8( + input: TypedArray | ArrayBufferLike | DataView, + ): boolean; - /** - * This function returns `true` if `input` contains only valid ASCII-encoded data, - * including the case in which `input` is empty. - * - * Throws if the `input` is a detached array buffer. - * @since Bun v0.6.13 - * @param input The input to validate. - */ - export function isAscii( - input: TypedArray | ArrayBufferLike | DataView, - ): boolean; - } + /** + * This function returns `true` if `input` contains only valid ASCII-encoded data, + * including the case in which `input` is empty. + * + * Throws if the `input` is a detached array buffer. + * @since Bun v0.6.13 + * @param input The input to validate. + */ + export function isAscii( + input: TypedArray | ArrayBufferLike | DataView, + ): boolean; } declare module "node:buffer" { export * from "buffer"; diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index 03a067dc2..57814d2aa 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -114,7 +114,7 @@ declare module "bun:test" { export function spyOn<T extends object, K extends keyof T>( obj: T, methodOrPropertyValue: K, - ): Mock<() => T[K]>; + ): Mock<T[K] extends AnyFunction ? T[K] : never>; /** * Describes a group of related tests. @@ -174,18 +174,26 @@ declare module "bun:test" { * * @param table Array of Arrays with the arguments that are passed into the test fn for each row. */ - each<T extends ReadonlyArray<unknown>>( + + each<T extends Readonly<[any, ...any[]]>>( table: ReadonlyArray<T>, ): ( label: string, - fn: (...args: T) => void | Promise<unknown>, + fn: (...args: [...T]) => void | Promise<unknown>, options?: number | TestOptions, ) => void; - each<T>( + each<T extends Array<any>>( table: ReadonlyArray<T>, ): ( label: string, - fn: (arg: T) => void | Promise<unknown>, + fn: (...args: Readonly<T>) => void | Promise<unknown>, + options?: number | TestOptions, + ) => void; + each<T>( + table: Array<T>, + ): ( + label: string, + fn: (...args: T[]) => void | Promise<unknown>, options?: number | TestOptions, ) => void; }; @@ -419,18 +427,25 @@ declare module "bun:test" { * * @param table Array of Arrays with the arguments that are passed into the test fn for each row. */ - each<T extends ReadonlyArray<unknown>>( + each<T extends Readonly<[any, ...any[]]>>( table: ReadonlyArray<T>, ): ( label: string, - fn: (...args: T) => void | Promise<unknown>, + fn: (...args: [...T]) => void | Promise<unknown>, options?: number | TestOptions, ) => void; - each<T>( + each<T extends Array<any>>( table: ReadonlyArray<T>, ): ( label: string, - fn: (arg: T, done: (err?: unknown) => void) => void | Promise<unknown>, + fn: (...args: Readonly<T>) => void | Promise<unknown>, + options?: number | TestOptions, + ) => void; + each<T>( + table: Array<T>, + ): ( + label: string, + fn: (...args: T[]) => void | Promise<unknown>, options?: number | TestOptions, ) => void; }; @@ -464,13 +479,13 @@ declare module "bun:test" { * @param actual the actual value */ export const expect: { - (actual?: unknown): Expect; + <T = unknown>(actual?: T): Expect<T>; any: ( constructor: ((..._: any[]) => any) | { new (..._: any[]): any }, ) => Expect; anything: () => Expect; - stringContaining: (str: string) => Expect; - stringMatching: (regex: RegExp | string) => Expect; + stringContaining: (str: string) => Expect<string>; + stringMatching: <T extends RegExp | string>(regex: T) => Expect<T>; }; /** * Asserts that a value matches some criteria. @@ -968,6 +983,16 @@ declare module "bun:test" { */ toBeWithin(start: number, end: number): void; /** + * Asserts that a value is equal to the expected string, ignoring any whitespace. + * + * @example + * expect(" foo ").toEqualIgnoringWhitespace("foo"); + * expect("bar").toEqualIgnoringWhitespace(" bar "); + * + * @param expected the expected string + */ + toEqualIgnoringWhitespace(expected: string): void; + /** * Asserts that a value is a `symbol`. * * @example diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index f0161ef88..d183f1b33 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -70,6 +70,19 @@ declare module "bun" { options?: { PATH?: string; cwd?: string }, ): string | null; + interface TOML { + /** + * Parse a TOML string into a JavaScript object. + * + * @param {string} command The name of the executable or script + * @param {string} options.PATH Overrides the PATH environment variable + * @param {string} options.cwd Limits the search to a particular directory in which to searc + * + */ + parse(input: string): object; + } + export const TOML: TOML; + export type Serve<WebSocketDataType = undefined> = | ServeOptions | TLSServeOptions @@ -150,6 +163,10 @@ declare module "bun" { export function write( destination: BunFile | PathLike, input: Blob | TypedArray | ArrayBufferLike | string | BlobPart[], + options?: { + /** If writing to a PathLike, set the permissions of the file. */ + mode?: number; + }, ): Promise<number>; /** @@ -908,6 +925,14 @@ declare module "bun" { * Minify whitespace and comments from the output. */ minifyWhitespace?: boolean; + /** + * **Experimental** + * + * Enabled by default, use this to disable dead code elimination. + * + * Some other transpiler options may still do some specific dead code elimination. + */ + deadCodeElimination?: boolean; /** * This does two things (and possibly more in the future): @@ -1846,6 +1871,15 @@ declare module "bun" { port?: string | number; /** + * If the `SO_REUSEPORT` flag should be set. + * + * This allows multiple processes to bind to the same port, which is useful for load balancing. + * + * @default false + */ + reusePort?: boolean; + + /** * What hostname should the server listen on? * * @default @@ -2184,6 +2218,21 @@ declare module "bun" { tls?: TLSOptions; } + export interface SocketAddress { + /** + * The IP address of the client. + */ + address: string; + /** + * The port of the client. + */ + port: number; + /** + * The IP family ("IPv4" or "IPv6"). + */ + family: "IPv4" | "IPv6"; + } + /** * HTTP & HTTPS Server * @@ -2223,7 +2272,7 @@ declare module "bun" { * }); * * // Update the server to return a different response - * server.update({ + * server.reload({ * fetch(request) { * return new Response("Hello World v2") * } @@ -2332,6 +2381,20 @@ declare module "bun" { ): ServerWebSocketSendStatus; /** + * Returns the client IP address and port of the given Request. If the request was closed or is a unix socket, returns null. + * + * @example + * ```js + * export default { + * async fetch(request, server) { + * return new Response(server.requestIP(request)); + * } + * } + * ``` + */ + requestIP(request: Request): SocketAddress | null; + + /** * How many requests are in-flight right now? */ readonly pendingRequests: number; @@ -3160,7 +3223,7 @@ declare module "bun" { loader: Loader; } - type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject; + type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject | undefined; type OnLoadCallback = ( args: OnLoadArgs, ) => OnLoadResult | Promise<OnLoadResult>; @@ -3252,6 +3315,37 @@ declare module "bun" { * The config object passed to `Bun.build` as is. Can be mutated. */ config: BuildConfig & { plugins: BunPlugin[] }; + + /** + * Create a lazy-loaded virtual module that can be `import`ed or `require`d from other modules + * + * @param specifier The module specifier to register the callback for + * @param callback The function to run when the module is imported or required + * + * ### Example + * @example + * ```ts + * Bun.plugin({ + * setup(builder) { + * builder.module("hello:world", () => { + * return { exports: { foo: "bar" }, loader: "object" }; + * }); + * }, + * }); + * + * // sometime later + * const { foo } = await import("hello:world"); + * console.log(foo); // "bar" + * + * // or + * const { foo } = require("hello:world"); + * console.log(foo); // "bar" + * ``` + */ + module( + specifier: string, + callback: () => OnLoadResult | Promise<OnLoadResult>, + ): void; } interface BunPlugin { @@ -3815,7 +3909,7 @@ declare module "bun" { : undefined; type ReadableToSyncIO<X extends Readable> = X extends "pipe" | undefined - ? Uint8Array + ? Buffer : undefined; type WritableIO = FileSink | number | undefined; diff --git a/packages/bun-types/dns.d.ts b/packages/bun-types/dns.d.ts index 247227fc2..8eee83361 100644 --- a/packages/bun-types/dns.d.ts +++ b/packages/bun-types/dns.d.ts @@ -701,10 +701,10 @@ declare module "dns" { * one of the `DNS error codes`. * @since v0.1.16 */ - // export function reverse( - // ip: string, - // callback: (err: ErrnoException | null, hostnames: string[]) => void, - // ): void; + export function reverse( + ip: string, + callback: (err: ErrnoException | null, hostnames: string[]) => void, + ): void; /** * Sets the IP address and port of servers to be used when performing DNS * resolution. The `servers` argument is an array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted @@ -749,7 +749,7 @@ declare module "dns" { * ``` * @since v0.11.3 */ - // export function getServers(): string[]; + export function getServers(): string[]; /** * Set the default value of `verbatim` in {@link lookup} and `dnsPromises.lookup()`. The value could be: * @@ -841,7 +841,7 @@ declare module "dns" { * @since v8.3.0 */ cancel(): void; - // getServers: typeof getServers; + getServers: typeof getServers; resolve: typeof resolve; resolve4: typeof resolve4; resolve6: typeof resolve6; @@ -854,7 +854,7 @@ declare module "dns" { resolveSoa: typeof resolveSoa; resolveSrv: typeof resolveSrv; resolveTxt: typeof resolveTxt; - // reverse: typeof reverse; + reverse: typeof reverse; /** * The resolver instance will send its requests from the specified IP address. * This allows programs to specify outbound interfaces when used on multi-homed diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index d43ee78bd..57dd4aafa 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -1,5 +1,3 @@ -// import * as tls from 'node:tls'; - /** * "blob" is not supported yet */ @@ -105,6 +103,15 @@ type MultipleResolveListener = ( ) => void; // type WorkerListener = (worker: Worker) => void; +interface ConsoleOptions { + stdout: import("stream").Writable; + stderr?: import("stream").Writable; + ignoreErrors?: boolean; + colorMode?: boolean | "auto"; + inspectOptions?: import("util").InspectOptions; + groupIndentation?: number; +} + interface Console { /** * Asynchronously read lines from standard input (fd 0) @@ -205,7 +212,19 @@ interface Console { warn(...data: any[]): void; } -declare var console: Console; +declare var console: Console & { + /** + * Creates a new Console with one or two writable stream instances. stdout is a writable stream to print log or info output. stderr is used for warning or error output. If stderr is not provided, stdout is used for stderr. + */ + Console: { + new (options: ConsoleOptions): Console; + new ( + stdout: import("stream").Writable, + stderr?: import("stream").Writable, + ignoreErrors?: boolean, + ): Console; + }; +}; declare namespace NodeJS { interface RequireResolve { @@ -527,6 +546,12 @@ interface Worker extends EventTarget, AbstractWorker { */ unref(): void; + /** + * An integer identifier for the referenced thread. Inside the worker thread, + * it is available as `require('node:worker_threads').threadId`. + * This value is unique for each `Worker` instance inside a single process. + * @since v10.5.0 + */ threadId: number; } @@ -662,7 +687,7 @@ interface Process { */ setSourceMapsEnabled(enabled: boolean): void; - kill(pid: number, signal?: string | number): void; + kill(pid: number, signal?: string | number): true; on(event: "beforeExit", listener: BeforeExitListener): this; // on(event: "disconnect", listener: DisconnectListener): this; @@ -706,6 +731,16 @@ interface Process { * @param listener The event handler function */ listenerCount(eventName: string | symbol, listener?: Function): number; + + /** + * Get the constrained memory size for the process. + * + * On Linux, this is the memory limit for the process, accounting for cgroups 1 and 2. + * On other operating systems, this returns `undefined`. + */ + constrainedMemory(): number | undefined; + + send(data: any): void; } interface MemoryUsageObject { @@ -1290,7 +1325,6 @@ interface RequestInit { } interface FetchRequestInit extends RequestInit { - /** * Log the raw HTTP request & response to stdout. This API may be * removed in a future version of Bun without notice. @@ -2448,6 +2482,14 @@ declare var URL: { createObjectURL(obj: Blob): string; /** Not implemented yet */ revokeObjectURL(url: string): void; + + /** + * Check if `url` is a valid URL string + * + * @param url URL string to parse + * @param base URL to resolve against + */ + canParse(url: string, base?: string): boolean; }; type TimerHandler = (...args: any[]) => void; @@ -3500,8 +3542,9 @@ interface CallSite { } interface ArrayBufferConstructor { - new (params: { byteLength: number; maxByteLength?: number }): ArrayBuffer; + new (byteLength: number, options: { maxByteLength?: number }): ArrayBuffer; } + interface ArrayBuffer { /** * Read-only. The length of the ArrayBuffer (in bytes). @@ -3800,3 +3843,11 @@ interface PromiseConstructor { reject: (reason?: any) => void; }; } + +interface Navigator { + readonly userAgent: string; + readonly platform: "MacIntel" | "Win32" | "Linux x86_64"; + readonly hardwareConcurrency: number; +} + +declare var navigator: Navigator; diff --git a/packages/bun-types/package.json b/packages/bun-types/package.json index 059ab0bfc..40ce131e4 100644 --- a/packages/bun-types/package.json +++ b/packages/bun-types/package.json @@ -1,6 +1,7 @@ { "name": "bun-types", "repository": "https://github.com/oven-sh/bun", + "license": "MIT", "devDependencies": { "conditional-type-checks": "^1.0.6", "prettier": "^2.4.1", @@ -12,7 +13,7 @@ "prebuild": "echo $(pwd)", "build": "rm -rf ./dist && bun run bundle && bun run fmt", "bundle": "bun scripts/bundle.ts ./dist", - "test": "tsd", + "test": "tsc", "fmt": "echo $(which prettier) && prettier --write './**/*.{ts,tsx,js,jsx}'" }, "tsd": { diff --git a/packages/bun-types/scripts/bundle.ts b/packages/bun-types/scripts/bundle.ts index 031a90359..4b9109882 100644 --- a/packages/bun-types/scripts/bundle.ts +++ b/packages/bun-types/scripts/bundle.ts @@ -23,7 +23,7 @@ try { const header = await file(join(import.meta.dir, "..", "header.txt")).text(); const filesToCat = (await getDotTsFiles("./")).filter( - f => !["./index.d.ts"].some(tf => f === tf), + f => f !== "./index.d.ts", ); const fileContents: string[] = []; diff --git a/packages/bun-types/tests/array-buffer.test-d.ts b/packages/bun-types/tests/array-buffer.test-d.ts index 7d28e2097..a3253138e 100644 --- a/packages/bun-types/tests/array-buffer.test-d.ts +++ b/packages/bun-types/tests/array-buffer.test-d.ts @@ -1,5 +1,4 @@ -const buffer = new ArrayBuffer({ - byteLength: 1024, +const buffer = new ArrayBuffer(1024, { maxByteLength: 2048, }); diff --git a/packages/bun-types/tests/bun.test-d.ts b/packages/bun-types/tests/bun.test-d.ts index 03301c13d..cd5a40eca 100644 --- a/packages/bun-types/tests/bun.test-d.ts +++ b/packages/bun-types/tests/bun.test-d.ts @@ -41,3 +41,6 @@ import * as tsd from "tsd"; env: { ...process.env, dummy: "" }, }); } +{ + Bun.TOML.parse("asdf = asdf"); +} diff --git a/packages/bun-types/tests/dns.test-d.ts b/packages/bun-types/tests/dns.test-d.ts new file mode 100644 index 000000000..3f701f346 --- /dev/null +++ b/packages/bun-types/tests/dns.test-d.ts @@ -0,0 +1,5 @@ +import * as dns from "node:dns"; + +dns.resolve("asdf", "A", () => {}); +dns.reverse("asdf", () => {}); +dns.getServers(); diff --git a/packages/bun-types/tests/test.test-d.ts b/packages/bun-types/tests/test.test-d.ts index 1d504272d..b385c3051 100644 --- a/packages/bun-types/tests/test.test-d.ts +++ b/packages/bun-types/tests/test.test-d.ts @@ -6,7 +6,12 @@ import { afterAll, beforeEach, afterEach, + spyOn, } from "bun:test"; +import { expectType } from "tsd"; + +const spy = spyOn(console, "log"); +expectType<any[][]>(spy.mock.calls); const hooks = [beforeAll, beforeEach, afterAll, afterEach]; @@ -40,6 +45,7 @@ describe("bun:test", () => { test("expect()", () => { expect(1).toBe(1); expect(1).not.toBe(2); + // @ts-expect-error expect({ a: 1 }).toEqual({ a: 1, b: undefined }); expect({ a: 1 }).toStrictEqual({ a: 1 }); expect(new Set()).toHaveProperty("size"); @@ -58,3 +64,69 @@ describe("bun:test", () => { expect(undefined).not.toBeDefined(); }); }); + +// inference should work when data is passed directly in +test.each([ + ["a", true, 5], + ["b", false, 1234], +])("test.each", (a, b, c) => { + expectType<string>(a); + expectType<boolean>(b); + expectType<number | string>(c); +}); +describe.each([ + ["a", true, 5], + ["b", false, 5], +])("test.each", (a, b, c) => { + expectType<string>(a); + expectType<boolean>(b); + expectType<number | string>(c); +}); +describe.each([ + ["a", true, 5], + ["b", false, "asdf"], +])("test.each", (a, b, c) => { + expectType<string>(a); + expectType<boolean>(b); + expectType<number | string>(c); +}); +describe.each([{ asdf: "asdf" }, { asdf: "asdf" }])("test.each", (a, b, c) => { + expectType<{ asdf: string }>(a); + expectType<{ asdf: string }>(c); +}); + +// no inference on data +const data = [ + ["a", true, 5], + ["b", false, "asdf"], +]; +test.each(data)("test.each", (...args) => { + expectType<string | number | boolean>(args[0]); +}); +describe.each(data)("test.each", (a, b, c) => { + expectType<string | number | boolean>(a); + expectType<string | number | boolean>(b); + expectType<string | number | boolean>(c); +}); + +// as const +const dataAsConst = [ + ["a", true, 5], + ["b", false, "asdf"], +] as const; + +test.each(dataAsConst)("test.each", (...args) => { + expectType<string>(args[0]); + expectType<boolean>(args[1]); + expectType<string | number>(args[2]); +}); +describe.each(dataAsConst)("test.each", (...args) => { + expectType<string>(args[0]); + expectType<boolean>(args[1]); + expectType<string | number>(args[2]); +}); +describe.each(dataAsConst)("test.each", (a, b, c) => { + expectType<"a" | "b">(a); + expectType<boolean>(b); + expectType<5 | "asdf">(c); +}); diff --git a/packages/bun-types/tsconfig.json b/packages/bun-types/tsconfig.json index ad946c2e0..ed37def14 100644 --- a/packages/bun-types/tsconfig.json +++ b/packages/bun-types/tsconfig.json @@ -9,7 +9,9 @@ "allowSyntheticDefaultImports": true, "disableSolutionSearching": true, "noUnusedLocals": true, - "outDir": "build" + "outDir": "build", + "noEmit": true, + "resolveJsonModule": true }, "exclude": [ "dist", diff --git a/packages/bun-types/ws.d.ts b/packages/bun-types/ws.d.ts index a14f87edd..fc489006f 100644 --- a/packages/bun-types/ws.d.ts +++ b/packages/bun-types/ws.d.ts @@ -74,6 +74,8 @@ declare module "ws" { WebSocket?: U | undefined; } + interface ServerOption extends WebSocketServerOptions {} + interface AddressInfo { address: string; family: string; @@ -219,4 +221,6 @@ declare module "ws" { listener: (...args: any[]) => void, ): this; } + + var Server: typeof WebSocketServer; } diff --git a/packages/bun-usockets/src/bsd.c b/packages/bun-usockets/src/bsd.c index 7683acd7d..fc501e4d9 100644 --- a/packages/bun-usockets/src/bsd.c +++ b/packages/bun-usockets/src/bsd.c @@ -665,9 +665,39 @@ int bsd_udp_packet_buffer_ecn(void *msgvec, int index) { return 0; // no ecn defaults to 0 } -static int bsd_do_connect(struct addrinfo *result, int fd) +static int bsd_do_connect_raw(struct addrinfo *rp, int fd) { - return connect(fd, result->ai_addr, (socklen_t) result->ai_addrlen); + do { + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0 || errno == EINPROGRESS) { + return 0; + } + } while (errno == EINTR); + + return LIBUS_SOCKET_ERROR; +} + +static int bsd_do_connect(struct addrinfo *rp, int *fd) +{ + while (rp != NULL) { + if (bsd_do_connect_raw(rp, *fd) == 0) { + return 0; + } + + rp = rp->ai_next; + bsd_close_socket(*fd); + + if (rp == NULL) { + return LIBUS_SOCKET_ERROR; + } + + int resultFd = bsd_create_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (resultFd < 0) { + return LIBUS_SOCKET_ERROR; + } + *fd = resultFd; + } + + return LIBUS_SOCKET_ERROR; } LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, const char *source_host, int options) { @@ -700,18 +730,21 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, co return LIBUS_SOCKET_ERROR; } } - } - - do { - if (bsd_do_connect(result, fd) != 0 && errno != EINPROGRESS) { + + if (bsd_do_connect_raw(result, fd) != 0) { bsd_close_socket(fd); freeaddrinfo(result); return LIBUS_SOCKET_ERROR; } - } while (errno == EINTR); - + } else { + if (bsd_do_connect(result, &fd) != 0) { + freeaddrinfo(result); + return LIBUS_SOCKET_ERROR; + } + } + + freeaddrinfo(result); - return fd; } diff --git a/packages/bun-usockets/src/context.c b/packages/bun-usockets/src/context.c index a4d243e85..9e0dd5356 100644 --- a/packages/bun-usockets/src/context.c +++ b/packages/bun-usockets/src/context.c @@ -29,6 +29,7 @@ int default_is_low_prio_handler(struct us_socket_t *s) { unsigned short us_socket_context_timestamp(int ssl, struct us_socket_context_t *context) { return context->timestamp; } +int us_internal_raw_root_certs(struct us_cert_string_t** out); int us_raw_root_certs(struct us_cert_string_t**out){ return us_internal_raw_root_certs(out); } @@ -568,7 +569,7 @@ void *us_socket_context_ext(int ssl, struct us_socket_context_t *context) { void us_socket_context_on_handshake(int ssl, struct us_socket_context_t *context, void (*on_handshake)(struct us_socket_context_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data), void* custom_data) { #ifndef LIBUS_NO_SSL if (ssl) { - us_internal_on_ssl_handshake((struct us_internal_ssl_socket_context_t *) context, on_handshake, custom_data); + us_internal_on_ssl_handshake((struct us_internal_ssl_socket_context_t *) context, (void (*)(struct us_internal_ssl_socket_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data))on_handshake, custom_data); return; } #endif diff --git a/packages/bun-usockets/src/crypto/openssl.c b/packages/bun-usockets/src/crypto/openssl.c index 0b55ca866..a03bf3520 100644 --- a/packages/bun-usockets/src/crypto/openssl.c +++ b/packages/bun-usockets/src/crypto/openssl.c @@ -45,7 +45,7 @@ void *sni_find(void *sni, const char *hostname); #include "./root_certs.h" #include <stdatomic.h> -static const root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]); +static const size_t root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]); static X509* root_cert_instances[root_certs_size] = {NULL}; static atomic_flag root_cert_instances_lock = ATOMIC_FLAG_INIT; static atomic_bool root_cert_instances_initialized = 0; diff --git a/packages/bun-usockets/src/eventing/epoll_kqueue.c b/packages/bun-usockets/src/eventing/epoll_kqueue.c index 7ab2be826..d051f5b5a 100644 --- a/packages/bun-usockets/src/eventing/epoll_kqueue.c +++ b/packages/bun-usockets/src/eventing/epoll_kqueue.c @@ -30,13 +30,13 @@ void Bun__internal_dispatch_ready_poll(void* loop, void* poll); #include <stdint.h> #endif -void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs); +void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs, void*); /* Pointer tags are used to indicate a Bun pointer versus a uSockets pointer */ #define UNSET_BITS_49_UNTIL_64 0x0000FFFFFFFFFFFF #define CLEAR_POINTER_TAG(p) ((void *) ((uintptr_t) (p) & UNSET_BITS_49_UNTIL_64)) -#define LIKELY(cond) __builtin_expect((uint64_t)(void*)cond, 1) -#define UNLIKELY(cond) __builtin_expect((uint64_t)(void*)cond, 0) +#define LIKELY(cond) __builtin_expect((uint64_t)(void*)(cond), 1) +#define UNLIKELY(cond) __builtin_expect((uint64_t)(void*)(cond), 0) #ifdef LIBUS_USE_EPOLL #define GET_READY_POLL(loop, index) (struct us_poll_t *) loop->ready_polls[index].data.ptr @@ -174,13 +174,20 @@ void us_loop_run(struct us_loop_t *loop) { } } +void bun_on_tick_before(void* ctx); +void bun_on_tick_after(void* ctx); -void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs) { + +void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs, void* tickCallbackContext) { us_loop_integrate(loop); if (loop->num_polls == 0) return; + if (tickCallbackContext) { + bun_on_tick_before(tickCallbackContext); + } + /* Emit pre callback */ us_internal_loop_pre(loop); @@ -202,6 +209,10 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs) { } #endif + if (tickCallbackContext) { + bun_on_tick_after(tickCallbackContext); + } + /* Iterate ready polls, dispatching them by type */ for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) { struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll); @@ -403,14 +414,18 @@ struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsi #endif #ifdef LIBUS_USE_EPOLL -void us_timer_close(struct us_timer_t *timer) { +void us_timer_close(struct us_timer_t *timer, int fallthrough) { struct us_internal_callback_t *cb = (struct us_internal_callback_t *) timer; us_poll_stop(&cb->p, cb->loop); close(us_poll_fd(&cb->p)); - /* (regular) sockets are the only polls which are not freed immediately */ - us_poll_free((struct us_poll_t *) timer, cb->loop); + /* (regular) sockets are the only polls which are not freed immediately */ + if(fallthrough){ + us_free(timer); + }else { + us_poll_free((struct us_poll_t *) timer, cb->loop); + } } void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms) { @@ -427,7 +442,7 @@ void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms us_poll_start((struct us_poll_t *) t, internal_cb->loop, LIBUS_SOCKET_READABLE); } #else -void us_timer_close(struct us_timer_t *timer) { +void us_timer_close(struct us_timer_t *timer, int fallthrough) { struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) timer; struct kevent64_s event; @@ -435,7 +450,11 @@ void us_timer_close(struct us_timer_t *timer) { kevent64(internal_cb->loop->fd, &event, 1, NULL, 0, 0, NULL); /* (regular) sockets are the only polls which are not freed immediately */ - us_poll_free((struct us_poll_t *) timer, internal_cb->loop); + if(fallthrough){ + us_free(timer); + }else { + us_poll_free((struct us_poll_t *) timer, internal_cb->loop); + } } void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms) { diff --git a/packages/bun-usockets/src/libusockets.h b/packages/bun-usockets/src/libusockets.h index 5f4563605..cff9a1bd2 100644 --- a/packages/bun-usockets/src/libusockets.h +++ b/packages/bun-usockets/src/libusockets.h @@ -132,7 +132,7 @@ struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsi void *us_timer_ext(struct us_timer_t *timer); /* */ -void us_timer_close(struct us_timer_t *timer); +void us_timer_close(struct us_timer_t *timer, int fallthrough); /* Arm a timer with a delay from now and eventually a repeat delay. * Specify 0 as repeat delay to disable repeating. Specify both 0 to disarm. */ @@ -382,16 +382,17 @@ int us_socket_local_port(int ssl, struct us_socket_t *s); /* Copy remote (IP) address of socket, or fail with zero length. */ void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length); +void us_socket_local_address(int ssl, struct us_socket_t *s, char *buf, int *length); /* Bun extras */ struct us_socket_t *us_socket_pair(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR* fds); struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR fd); -struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s); struct us_socket_t *us_socket_attach(int ssl, LIBUS_SOCKET_DESCRIPTOR client_fd, struct us_socket_context_t *ctx, int flags, int socket_ext_size); struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size); int us_socket_raw_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more); struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length); int us_raw_root_certs(struct us_cert_string_t**out); +unsigned int us_get_remote_address_info(char *buf, struct us_socket_t *s, const char **dest, int *port, int *is_ipv6); #ifdef __cplusplus } diff --git a/packages/bun-usockets/src/loop.c b/packages/bun-usockets/src/loop.c index 9ad1e64bf..e230fa29b 100644 --- a/packages/bun-usockets/src/loop.c +++ b/packages/bun-usockets/src/loop.c @@ -47,7 +47,7 @@ void us_internal_loop_data_free(struct us_loop_t *loop) { free(loop->data.recv_buf); - us_timer_close(loop->data.sweep_timer); + us_timer_close(loop->data.sweep_timer, 0); us_internal_async_close(loop->data.wakeup_async); } diff --git a/packages/bun-usockets/src/socket.c b/packages/bun-usockets/src/socket.c index 5f5a91acb..d8371c2ff 100644 --- a/packages/bun-usockets/src/socket.c +++ b/packages/bun-usockets/src/socket.c @@ -48,6 +48,16 @@ void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *le } } +void us_socket_local_address(int ssl, struct us_socket_t *s, char *buf, int *length) { + struct bsd_addr_t addr; + if (bsd_local_addr(us_poll_fd(&s->p), &addr) || *length < bsd_addr_get_ip_length(&addr)) { + *length = 0; + } else { + *length = bsd_addr_get_ip_length(&addr); + memcpy(buf, bsd_addr_get_ip(&addr), *length); + } +} + struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s) { return s->context; } @@ -140,59 +150,6 @@ struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s, int code, vo return s; } -// This function is the same as us_socket_close but: -// - does not emit on_close event -// - does not close -struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s) { - if (!us_socket_is_closed(0, s)) { - if (s->low_prio_state == 1) { - /* Unlink this socket from the low-priority queue */ - if (!s->prev) s->context->loop->data.low_prio_head = s->next; - else s->prev->next = s->next; - - if (s->next) s->next->prev = s->prev; - - s->prev = 0; - s->next = 0; - s->low_prio_state = 0; - } else { - us_internal_socket_context_unlink(s->context, s); - } - us_poll_stop((struct us_poll_t *) s, s->context->loop); - - /* Link this socket to the close-list and let it be deleted after this iteration */ - s->next = s->context->loop->data.closed_head; - s->context->loop->data.closed_head = s; - - /* Any socket with prev = context is marked as closed */ - s->prev = (struct us_socket_t *) s->context; - - return s; - } - return s; -} - -// This function is used for moving a socket between two different event loops -struct us_socket_t *us_socket_attach(int ssl, LIBUS_SOCKET_DESCRIPTOR client_fd, struct us_socket_context_t *ctx, int flags, int socket_ext_size) { - struct us_poll_t *accepted_p = us_create_poll(ctx->loop, 0, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + socket_ext_size); - us_poll_init(accepted_p, client_fd, POLL_TYPE_SOCKET); - us_poll_start(accepted_p, ctx->loop, flags); - - struct us_socket_t *s = (struct us_socket_t *) accepted_p; - - s->context = ctx; - s->timeout = 0; - s->low_prio_state = 0; - - /* We always use nodelay */ - bsd_socket_nodelay(client_fd, 1); - us_internal_socket_context_link(ctx, s); - - if (ctx->on_open) ctx->on_open(s, 0, 0, 0); - - return s; -} - struct us_socket_t *us_socket_pair(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR* fds) { #ifdef LIBUS_USE_LIBUV return 0; @@ -333,3 +290,25 @@ int us_socket_raw_write(int ssl, struct us_socket_t *s, const char *data, int le // non-TLS is always raw return us_socket_write(ssl, s, data, length, msg_more); } + +unsigned int us_get_remote_address_info(char *buf, struct us_socket_t *s, const char **dest, int *port, int *is_ipv6) +{ + // This function is manual inlining + modification of + // us_socket_remote_address + // AsyncSocket::getRemoteAddress + // To get { ip, port, is_ipv6 } for Bun.serve().requestIP() + struct bsd_addr_t addr; + if (bsd_remote_addr(us_poll_fd(&s->p), &addr)) { + return 0; + } + + int length = bsd_addr_get_ip_length(&addr); + if (!length) { + return 0; + } + + memcpy(buf, bsd_addr_get_ip(&addr), length); + *port = bsd_addr_get_port(&addr); + + return length; +} diff --git a/packages/bun-uws/capi/examples/Broadcast.c b/packages/bun-uws/capi/examples/Broadcast.c index dc6787349..ef1b09101 100644 --- a/packages/bun-uws/capi/examples/Broadcast.c +++ b/packages/bun-uws/capi/examples/Broadcast.c @@ -14,7 +14,7 @@ void uws_timer_close(struct us_timer_t *timer) struct timer_handler_data *data; memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *)); free(data); - us_timer_close(t); + us_timer_close(t, 0); } //Timer create helper struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data) @@ -47,7 +47,7 @@ struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void if (!data->repeat) { free(data); - us_timer_close(t); + us_timer_close(t, 0); } }, ms, repeat_ms); diff --git a/packages/bun-uws/capi/examples/HelloWorldAsync.c b/packages/bun-uws/capi/examples/HelloWorldAsync.c index b8549b07e..e22dd44c1 100644 --- a/packages/bun-uws/capi/examples/HelloWorldAsync.c +++ b/packages/bun-uws/capi/examples/HelloWorldAsync.c @@ -19,7 +19,7 @@ void uws_timer_close(struct us_timer_t *timer) struct timer_handler_data *data; memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *)); free(data); - us_timer_close(t); + us_timer_close(t, 0); } //Timer create helper struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data) @@ -52,7 +52,7 @@ struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void if (!data->repeat) { free(data); - us_timer_close(t); + us_timer_close(t, 0); } }, ms, repeat_ms); diff --git a/packages/bun-uws/capi/examples/UpgradeAsync.c b/packages/bun-uws/capi/examples/UpgradeAsync.c index 3dacd8c8e..8c6e73542 100644 --- a/packages/bun-uws/capi/examples/UpgradeAsync.c +++ b/packages/bun-uws/capi/examples/UpgradeAsync.c @@ -62,7 +62,7 @@ void uws_timer_close(struct us_timer_t *timer) struct timer_handler_data *data; memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *)); free(data); - us_timer_close(t); + us_timer_close(t, 0); } //Timer create helper struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data) @@ -95,7 +95,7 @@ struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void if (!data->repeat) { free(data); - us_timer_close(t); + us_timer_close(t, 0); } }, ms, repeat_ms); diff --git a/packages/bun-uws/capi/libuwebsockets.h b/packages/bun-uws/capi/libuwebsockets.h index 25c330af7..3008d1992 100644 --- a/packages/bun-uws/capi/libuwebsockets.h +++ b/packages/bun-uws/capi/libuwebsockets.h @@ -205,6 +205,7 @@ extern "C" DLL_EXPORT unsigned int uws_ws_get_buffered_amount(int ssl, uws_websocket_t *ws); DLL_EXPORT size_t uws_ws_get_remote_address(int ssl, uws_websocket_t *ws, const char **dest); DLL_EXPORT size_t uws_ws_get_remote_address_as_text(int ssl, uws_websocket_t *ws, const char **dest); + DLL_EXPORT void uws_res_get_remote_address_info(uws_res_t *res, const char **dest, size_t *length, unsigned int *port); //Response DLL_EXPORT void uws_res_end(int ssl, uws_res_t *res, const char *data, size_t length, bool close_connection); diff --git a/packages/bun-uws/examples/UpgradeAsync.cpp b/packages/bun-uws/examples/UpgradeAsync.cpp index 0c5301be4..045605831 100644 --- a/packages/bun-uws/examples/UpgradeAsync.cpp +++ b/packages/bun-uws/examples/UpgradeAsync.cpp @@ -90,7 +90,7 @@ int main() { delete upgradeData; - us_timer_close(t); + us_timer_close(t, 0); }, 5000, 0); }, diff --git a/packages/bun-uws/src/AsyncSocket.h b/packages/bun-uws/src/AsyncSocket.h index 8e3301f24..1051271a2 100644 --- a/packages/bun-uws/src/AsyncSocket.h +++ b/packages/bun-uws/src/AsyncSocket.h @@ -121,18 +121,25 @@ public: void corkUnchecked() { /* What if another socket is corked? */ getLoopData()->corkedSocket = this; + getLoopData()->corkedSocketIsSSL = SSL; } /* Cork this socket. Only one socket may ever be corked per-loop at any given time */ void cork() { /* Extra check for invalid corking of others */ if (getLoopData()->corkOffset && getLoopData()->corkedSocket != this) { - std::cerr << "Error: Cork buffer must not be acquired without checking canCork!" << std::endl; - std::terminate(); + // We uncork the other socket early instead of terminating the program + // is unlikely to be cause any issues and is better than crashing + if(getLoopData()->corkedSocketIsSSL) { + ((AsyncSocket<true> *) getLoopData()->corkedSocket)->uncork(); + } else { + ((AsyncSocket<false> *) getLoopData()->corkedSocket)->uncork(); + } } /* What if another socket is corked? */ getLoopData()->corkedSocket = this; + getLoopData()->corkedSocketIsSSL = SSL; } /* Returns the corked socket or nullptr */ diff --git a/packages/bun-uws/src/HttpResponse.h b/packages/bun-uws/src/HttpResponse.h index d73f98152..aa372e4e3 100644 --- a/packages/bun-uws/src/HttpResponse.h +++ b/packages/bun-uws/src/HttpResponse.h @@ -77,12 +77,12 @@ public: writeHeader("Date", std::string_view(((LoopData *) us_loop_ext(us_socket_context_loop(SSL, (us_socket_context(SSL, (us_socket_t *) this)))))->date, 29)); /* You can disable this altogether */ -#ifndef UWS_HTTPRESPONSE_NO_WRITEMARK - if (!Super::getLoopData()->noMark) { - /* We only expose major version */ - writeHeader("uWebSockets", "20"); - } -#endif +// #ifndef UWS_HTTPRESPONSE_NO_WRITEMARK +// if (!Super::getLoopData()->noMark) { +// /* We only expose major version */ +// writeHeader("uWebSockets", "20"); +// } +// #endif } /* Returns true on success, indicating that it might be feasible to write more data. @@ -439,6 +439,24 @@ public: return {internalEnd(data, totalSize, true, true, closeConnection), hasResponded()}; } + /* Write the end of chunked encoded stream */ + bool sendTerminatingChunk(bool closeConnection = false) { + writeStatus(HTTP_200_OK); + HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); + if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) { + /* Write mark on first call to write */ + writeMark(); + + writeHeader("Transfer-Encoding", "chunked"); + httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED; + } + + /* Terminating 0 chunk */ + Super::write("\r\n0\r\n\r\n", 7); + + return internalEnd({nullptr, 0}, 0, false, false, closeConnection); + } + /* Write parts of the response in chunking fashion. Starts timeout if failed. */ bool write(std::string_view data) { writeStatus(HTTP_200_OK); diff --git a/packages/bun-uws/src/Loop.h b/packages/bun-uws/src/Loop.h index 6c7bdfc9e..7311dd976 100644 --- a/packages/bun-uws/src/Loop.h +++ b/packages/bun-uws/src/Loop.h @@ -58,12 +58,6 @@ private: for (auto &p : loopData->postHandlers) { p.second((Loop *) loop); } - - /* After every event loop iteration, we must not hold the cork buffer */ - if (loopData->corkedSocket) { - std::cerr << "Error: Cork buffer must not be held across event loop iterations!" << std::endl; - std::terminate(); - } } Loop() = delete; @@ -132,7 +126,7 @@ public: LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); /* Stop and free dateTimer first */ - us_timer_close(loopData->dateTimer); + us_timer_close(loopData->dateTimer, 1); loopData->~LoopData(); /* uSockets will track whether this loop is owned by us or a borrowed alien loop */ diff --git a/packages/bun-uws/src/LoopData.h b/packages/bun-uws/src/LoopData.h index 986bf0cbd..92bd9ffff 100644 --- a/packages/bun-uws/src/LoopData.h +++ b/packages/bun-uws/src/LoopData.h @@ -98,6 +98,7 @@ public: char *corkBuffer = new char[CORK_BUFFER_SIZE]; unsigned int corkOffset = 0; void *corkedSocket = nullptr; + bool corkedSocketIsSSL = false; /* Per message deflate data */ ZlibContext *zlibContext = nullptr; diff --git a/packages/bun-vscode/README.md b/packages/bun-vscode/README.md index c3d94924c..6848f8977 100644 --- a/packages/bun-vscode/README.md +++ b/packages/bun-vscode/README.md @@ -73,7 +73,7 @@ You can use the following configurations to debug JavaScript and TypeScript file "name": "Attach to Bun", // The URL of the WebSocket inspector to attach to. - // This value can be retreived by using `bun --inspect`. + // This value can be retrieved by using `bun --inspect`. "url": "ws://localhost:6499/", } ] @@ -89,12 +89,10 @@ You can use the following configurations to customize the behavior of the Bun ex // The path to the `bun` executable. "bun.runtime": "/path/to/bun", - "bun.debugTerminal": { - // If support for Bun should be added to the default "JavaScript Debug Terminal". - "enabled": true, - - // If the debugger should stop on the first line of the program. - "stopOnEntry": false, - } + // If support for Bun should be added to the default "JavaScript Debug Terminal". + "bun.debugTerminal.enabled": true, + + // If the debugger should stop on the first line of the program. + "bun.debugTerminal.stopOnEntry": false, } ```
\ No newline at end of file diff --git a/packages/bun-vscode/assets/vscode.css b/packages/bun-vscode/assets/vscode.css new file mode 100644 index 000000000..32bdcc5b2 --- /dev/null +++ b/packages/bun-vscode/assets/vscode.css @@ -0,0 +1,69 @@ +.mtk1 { color: #cccccc; } +.mtk2 { color: #1f1f1f; } +.mtk3 { color: #d4d4d4; } +.mtk4 { color: #000080; } +.mtk5 { color: #6a9955; } +.mtk6 { color: #569cd6; } +.mtk7 { color: #b5cea8; } +.mtk8 { color: #646695; } +.mtk9 { color: #d7ba7d; } +.mtk10 { color: #9cdcfe; } +.mtk11 { color: #f44747; } +.mtk12 { color: #ce9178; } +.mtk13 { color: #6796e6; } +.mtk14 { color: #808080; } +.mtk15 { color: #d16969; } +.mtk16 { color: #dcdcaa; } +.mtk17 { color: #4ec9b0; } +.mtk18 { color: #c586c0; } +.mtk19 { color: #4fc1ff; } +.mtk20 { color: #c8c8c8; } +.mtk21 { color: #606060; } +.mtk22 { color: #ffffff; } +.mtk23 { color: #cd9731; } +.mtk24 { color: #b267e6; } +.mtki { font-style: italic; } +.mtkb { font-weight: bold; } +.mtku { text-decoration: underline; text-underline-position: under; } +.mtks { text-decoration: line-through; } +.mtks.mtku { text-decoration: underline line-through; text-underline-position: under; } + +.bunlock { + display: flex; + flex-direction: row; +} + +.lines { + display: flex; + flex-direction: column; + width: 30px; + margin-right: 15px; + text-align: right; + opacity: 0.5; + + font-size: var(--vscode-editor-font-size); + font-weight: var(--vscode-editor-font-weight); + font-family: var(--vscode-editor-font-family); + background-color: var(--vscode-editor-background); +} + +.lines > span { + margin-top: 1px; + margin-bottom: 1px; +} + +code { + white-space: pre; + + font-size: var(--vscode-editor-font-size); + font-weight: var(--vscode-editor-font-weight); + font-family: var(--vscode-editor-font-family); + background-color: var(--vscode-editor-background); +} + +code > span { + display: inline-block; + width: 100%; + margin-top: 1px; + margin-bottom: 1px; +} diff --git a/packages/bun-vscode/bun.lockb b/packages/bun-vscode/bun.lockb Binary files differindex c879b518e..c879b518e 100755..100644 --- a/packages/bun-vscode/bun.lockb +++ b/packages/bun-vscode/bun.lockb diff --git a/packages/bun-vscode/example/package.json b/packages/bun-vscode/example/package.json index 602fba159..eed10159d 100644 --- a/packages/bun-vscode/example/package.json +++ b/packages/bun-vscode/example/package.json @@ -7,6 +7,12 @@ "mime": "^3.0.0", "mime-db": "^1.52.0" }, + "scripts": { + "run": "node hello.js", + "start": "hello.js", + "start:bun": "bun hello.js", + "start:bun:quotes": "bun run hello.js" + }, "trustedDependencies": [ "mime" ], diff --git a/packages/bun-vscode/package.json b/packages/bun-vscode/package.json index 39b5d37de..501257eb0 100644 --- a/packages/bun-vscode/package.json +++ b/packages/bun-vscode/package.json @@ -54,13 +54,7 @@ "../bun-inspector-protocol" ], "activationEvents": [ - "onLanguage:javascript", - "onLanguage:javascriptreact", - "onLanguage:typescript", - "onLanguage:typescriptreact", - "workspaceContains:**/.lockb", - "onDebugResolve:bun", - "onDebugDynamicConfigurations:bun" + "onStartupFinished" ], "browser": "dist/web-extension.js", "bugs": { @@ -95,7 +89,7 @@ }, "bun.debugTerminal.stopOnEntry": { "type": "boolean", - "description": "If Bun should stop on entry when used in the JavaScript Debug Terminal.", + "description": "If the debugger should stop on the first line when used in the JavaScript Debug Terminal.", "scope": "window", "default": false } @@ -177,12 +171,12 @@ "properties": { "runtime": { "type": "string", - "description": "The path to Bun.", + "description": "The path to the `bun` executable. Defaults to `PATH` environment variable.", "default": "bun" }, "runtimeArgs": { "type": "array", - "description": "The command-line arguments passed to Bun.", + "description": "The command-line arguments passed to the `bun` executable. Unlike `args`, these arguments are not passed to the program, but to the `bun` executable itself.", "items": { "type": "string" }, @@ -208,7 +202,7 @@ }, "env": { "type": "object", - "description": "The environment variables passed to Bun.", + "description": "The environment variables to pass to Bun.", "default": {} }, "strictEnv": { @@ -218,7 +212,7 @@ }, "stopOnEntry": { "type": "boolean", - "description": "If a breakpoint should be set at the first line.", + "description": "If the debugger should stop on the first line of the program.", "default": false }, "noDebug": { @@ -231,7 +225,7 @@ "boolean", "string" ], - "description": "If the process should be restarted when files change.", + "description": "If the process should be restarted when files change. Equivalent to passing `--watch` or `--hot` to the `bun` executable.", "enum": [ true, false, @@ -245,7 +239,7 @@ "properties": { "url": { "type": "string", - "description": "The URL of the Bun process to attach to." + "description": "The URL of the WebSocket inspector to attach to." }, "noDebug": { "type": "boolean", @@ -254,7 +248,7 @@ }, "stopOnEntry": { "type": "boolean", - "description": "If a breakpoint should when the program is attached.", + "description": "If the debugger should stop on the first line of the program.", "default": false } } @@ -294,6 +288,20 @@ ], "priority": "default" } + ], + "taskDefinitions": [ + { + "type": "bun", + "required": [ + "script" + ], + "properties": { + "script": { + "type": "string", + "description": "The script to execute" + } + } + } ] } } diff --git a/packages/bun-vscode/scripts/build.mjs b/packages/bun-vscode/scripts/build.mjs index 261965840..4f2292599 100644 --- a/packages/bun-vscode/scripts/build.mjs +++ b/packages/bun-vscode/scripts/build.mjs @@ -12,6 +12,9 @@ buildSync({ external: ["vscode"], platform: "node", format: "cjs", + // The following settings are required to allow for extension debugging + minify: false, + sourcemap: true, }); rmSync("extension", { recursive: true, force: true }); diff --git a/packages/bun-vscode/src/extension.ts b/packages/bun-vscode/src/extension.ts index e333aedd7..175165fa7 100644 --- a/packages/bun-vscode/src/extension.ts +++ b/packages/bun-vscode/src/extension.ts @@ -1,10 +1,14 @@ import * as vscode from "vscode"; -import activateLockfile from "./features/lockfile"; -import activateDebug from "./features/debug"; +import { registerTaskProvider } from "./features/tasks/tasks"; +import { registerDebugger } from "./features/debug"; +import { registerPackageJsonProviders } from "./features/tasks/package.json"; +import { registerBunlockEditor } from "./features/lockfile"; export function activate(context: vscode.ExtensionContext) { - activateLockfile(context); - activateDebug(context); + registerBunlockEditor(context); + registerDebugger(context); + registerTaskProvider(context); + registerPackageJsonProviders(context); } export function deactivate() {} diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index 2ea21dbe8..caa0c9378 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -4,43 +4,43 @@ import { DebugAdapter, UnixSignal } from "../../../bun-debug-adapter-protocol"; import { DebugSession } from "@vscode/debugadapter"; import { tmpdir } from "node:os"; -const debugConfiguration: vscode.DebugConfiguration = { +export const DEBUG_CONFIGURATION: vscode.DebugConfiguration = { type: "bun", + internalConsoleOptions: "neverOpen", request: "launch", name: "Debug File", program: "${file}", cwd: "${workspaceFolder}", stopOnEntry: false, watchMode: false, - internalConsoleOptions: "neverOpen", }; -const runConfiguration: vscode.DebugConfiguration = { +export const RUN_CONFIGURATION: vscode.DebugConfiguration = { type: "bun", + internalConsoleOptions: "neverOpen", request: "launch", name: "Run File", program: "${file}", cwd: "${workspaceFolder}", noDebug: true, watchMode: false, - internalConsoleOptions: "neverOpen", }; -const attachConfiguration: vscode.DebugConfiguration = { +const ATTACH_CONFIGURATION: vscode.DebugConfiguration = { type: "bun", + internalConsoleOptions: "neverOpen", request: "attach", name: "Attach Bun", url: "ws://localhost:6499/", stopOnEntry: false, - internalConsoleOptions: "neverOpen", }; const adapters = new Map<string, FileDebugSession>(); -export default function (context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) { +export function registerDebugger(context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) { context.subscriptions.push( - vscode.commands.registerCommand("extension.bun.runFile", RunFileCommand), - vscode.commands.registerCommand("extension.bun.debugFile", DebugFileCommand), + vscode.commands.registerCommand("extension.bun.runFile", runFileCommand), + vscode.commands.registerCommand("extension.bun.debugFile", debugFileCommand), vscode.debug.registerDebugConfigurationProvider( "bun", new DebugConfigurationProvider(), @@ -52,15 +52,15 @@ export default function (context: vscode.ExtensionContext, factory?: vscode.Debu vscode.DebugConfigurationProviderTriggerKind.Dynamic, ), vscode.debug.registerDebugAdapterDescriptorFactory("bun", factory ?? new InlineDebugAdapterFactory()), - vscode.window.onDidOpenTerminal(InjectDebugTerminal), + vscode.window.onDidOpenTerminal(injectDebugTerminal), ); } -function RunFileCommand(resource?: vscode.Uri): void { +function runFileCommand(resource?: vscode.Uri): void { const path = getActivePath(resource); if (path) { vscode.debug.startDebugging(undefined, { - ...runConfiguration, + ...RUN_CONFIGURATION, noDebug: true, program: path, runtime: getRuntime(resource), @@ -68,22 +68,21 @@ function RunFileCommand(resource?: vscode.Uri): void { } } -function DebugFileCommand(resource?: vscode.Uri): void { +export function debugCommand(command: string) { + vscode.debug.startDebugging(undefined, { + ...DEBUG_CONFIGURATION, + program: command, + runtime: getRuntime(), + }); +} + +function debugFileCommand(resource?: vscode.Uri) { const path = getActivePath(resource); - if (path) { - vscode.debug.startDebugging(undefined, { - ...debugConfiguration, - program: path, - runtime: getRuntime(resource), - }); - } + if (path) debugCommand(path); } -function InjectDebugTerminal(terminal: vscode.Terminal): void { - const enabled = getConfig("debugTerminal.enabled"); - if (enabled === false) { - return; - } +function injectDebugTerminal(terminal: vscode.Terminal): void { + if (!getConfig("debugTerminal.enabled")) return; const { name, creationOptions } = terminal; if (name !== "JavaScript Debug Terminal") { @@ -118,16 +117,9 @@ function InjectDebugTerminal(terminal: vscode.Terminal): void { setTimeout(() => terminal.dispose(), 100); } -class TerminalProfileProvider implements vscode.TerminalProfileProvider { - provideTerminalProfile(token: vscode.CancellationToken): vscode.ProviderResult<vscode.TerminalProfile> { - const { terminalProfile } = new TerminalDebugSession(); - return terminalProfile; - } -} - class DebugConfigurationProvider implements vscode.DebugConfigurationProvider { provideDebugConfigurations(folder?: vscode.WorkspaceFolder): vscode.ProviderResult<vscode.DebugConfiguration[]> { - return [debugConfiguration, runConfiguration, attachConfiguration]; + return [DEBUG_CONFIGURATION, RUN_CONFIGURATION, ATTACH_CONFIGURATION]; } resolveDebugConfiguration( @@ -139,9 +131,9 @@ class DebugConfigurationProvider implements vscode.DebugConfigurationProvider { const { request } = config; if (request === "attach") { - target = attachConfiguration; + target = ATTACH_CONFIGURATION; } else { - target = debugConfiguration; + target = DEBUG_CONFIGURATION; } // If the configuration is missing a default property, copy it from the template. @@ -219,7 +211,7 @@ class TerminalDebugSession extends FileDebugSession { this.signal = new UnixSignal(); this.signal.on("Signal.received", () => { vscode.debug.startDebugging(undefined, { - ...attachConfiguration, + ...ATTACH_CONFIGURATION, url: this.adapter.url, }); }); @@ -238,34 +230,18 @@ class TerminalDebugSession extends FileDebugSession { } } -function getActiveDocument(): vscode.TextDocument | undefined { - return vscode.window.activeTextEditor?.document; -} - function getActivePath(target?: vscode.Uri): string | undefined { - if (!target) { - target = getActiveDocument()?.uri; - } - return target?.fsPath; -} - -function isJavaScript(languageId?: string): boolean { - return ( - languageId === "javascript" || - languageId === "javascriptreact" || - languageId === "typescript" || - languageId === "typescriptreact" - ); + return target?.fsPath ?? vscode.window.activeTextEditor?.document?.uri.fsPath; } function getRuntime(scope?: vscode.ConfigurationScope): string { - const value = getConfig("runtime", scope); + const value = getConfig<string>("runtime", scope); if (typeof value === "string" && value.trim().length > 0) { return value; } return "bun"; } -function getConfig<T>(path: string, scope?: vscode.ConfigurationScope): unknown { - return vscode.workspace.getConfiguration("bun", scope).get(path); +function getConfig<T>(path: string, scope?: vscode.ConfigurationScope) { + return vscode.workspace.getConfiguration("bun", scope).get<T>(path); } diff --git a/packages/bun-vscode/src/features/lockfile.ts b/packages/bun-vscode/src/features/lockfile.ts deleted file mode 100644 index 81adf5b9e..000000000 --- a/packages/bun-vscode/src/features/lockfile.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as vscode from "vscode"; -import { spawn } from "node:child_process"; - -export type BunLockfile = vscode.CustomDocument & { - readonly preview: string; -}; - -export class BunLockfileEditorProvider implements vscode.CustomReadonlyEditorProvider { - constructor(context: vscode.ExtensionContext) {} - - async openCustomDocument( - uri: vscode.Uri, - openContext: vscode.CustomDocumentOpenContext, - token: vscode.CancellationToken, - ): Promise<BunLockfile> { - const preview = await previewLockfile(uri, token); - return { - uri, - preview, - dispose() {}, - }; - } - - async resolveCustomEditor( - document: BunLockfile, - webviewPanel: vscode.WebviewPanel, - token: vscode.CancellationToken, - ): Promise<void> { - const { preview } = document; - renderLockfile(webviewPanel, preview); - } -} - -function renderLockfile(webviewPanel: vscode.WebviewPanel, preview: string): void { - // TODO: Improve syntax highlighting to match that of yarn.lock - webviewPanel.webview.html = `<pre><code class="language-yaml">${preview}</code></pre>`; -} - -function previewLockfile(uri: vscode.Uri, token?: vscode.CancellationToken): Promise<string> { - return new Promise((resolve, reject) => { - const process = spawn("bun", [uri.fsPath], { - stdio: ["ignore", "pipe", "pipe"], - }); - token.onCancellationRequested(() => { - process.kill(); - }); - let stdout = ""; - process.stdout.on("data", (data: Buffer) => { - stdout += data.toString(); - }); - let stderr = ""; - process.stderr.on("data", (data: Buffer) => { - stderr += data.toString(); - }); - process.on("error", error => { - reject(error); - }); - process.on("exit", code => { - if (code === 0) { - resolve(stdout); - } else { - reject(new Error(`Bun exited with code: ${code}\n${stderr}`)); - } - }); - }); -} - -export default function (context: vscode.ExtensionContext): void { - const viewType = "bun.lockb"; - const provider = new BunLockfileEditorProvider(context); - - vscode.window.registerCustomEditorProvider(viewType, provider, { - supportsMultipleEditorsPerDocument: true, - webviewOptions: { - enableFindWidget: true, - retainContextWhenHidden: true, - }, - }); -} diff --git a/packages/bun-vscode/src/features/lockfile/index.ts b/packages/bun-vscode/src/features/lockfile/index.ts new file mode 100644 index 000000000..cef9a1768 --- /dev/null +++ b/packages/bun-vscode/src/features/lockfile/index.ts @@ -0,0 +1,109 @@ +import * as vscode from "vscode"; +import { spawn } from "node:child_process"; +import { styleLockfile } from "./lockfile.style"; + +export type BunLockfile = vscode.CustomDocument & { + readonly preview: string; +}; + +export class BunLockfileEditorProvider implements vscode.CustomReadonlyEditorProvider { + constructor(private context: vscode.ExtensionContext) {} + + async openCustomDocument( + uri: vscode.Uri, + openContext: vscode.CustomDocumentOpenContext, + token: vscode.CancellationToken, + ): Promise<BunLockfile> { + const preview = await previewLockfile(uri, token); + return { + uri, + preview, + dispose() {}, + }; + } + + async resolveCustomEditor( + document: BunLockfile, + webviewPanel: vscode.WebviewPanel, + token: vscode.CancellationToken, + ): Promise<void> { + const { preview } = document; + webviewPanel.webview.options = { + localResourceRoots: [this.context.extensionUri], + }; + renderLockfile(webviewPanel, preview, this.context.extensionUri); + } +} + +function renderLockfile({ webview }: vscode.WebviewPanel, preview: string, extensionUri: vscode.Uri): void { + const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, "assets", "vscode.css")); + const lockfileContent = styleLockfile(preview); + + const lineNumbers: string[] = []; + for (let i = 0; i < lockfileContent.split("\n").length; i++) { + lineNumbers.push(`<span class="line-number">${i + 1}</span>`); + } + + webview.html = ` +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource};"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="${styleVSCodeUri}" rel="stylesheet" /> + </head> + <body> + <div class="bunlock"> + <div class="lines"> + ${lineNumbers.join("\n")} + </div> + <code>${lockfileContent}</code> + </div> + </body> +</html>`; +} + +function previewLockfile(uri: vscode.Uri, token?: vscode.CancellationToken): Promise<string> { + return new Promise((resolve, reject) => { + const process = spawn("bun", [uri.fsPath], { + stdio: ["ignore", "pipe", "pipe"], + }); + token?.onCancellationRequested(() => { + process.kill(); + }); + let stdout = ""; + process.stdout.on("data", (data: Buffer) => { + stdout += data.toString(); + }); + let stderr = ""; + process.stderr.on("data", (data: Buffer) => { + stderr += data.toString(); + }); + process.on("error", error => { + reject(error); + }); + process.on("exit", code => { + if (code === 0) { + resolve(stdout); + } else { + reject(new Error(`Bun exited with code: ${code}\n${stderr}`)); + } + }); + }); +} + +export function registerBunlockEditor(context: vscode.ExtensionContext): void { + const viewType = "bun.lockb"; + const provider = new BunLockfileEditorProvider(context); + + context.subscriptions.push( + vscode.window.registerCustomEditorProvider(viewType, provider, { + supportsMultipleEditorsPerDocument: true, + webviewOptions: { + enableFindWidget: true, + retainContextWhenHidden: true, + }, + }), + ); +} diff --git a/packages/bun-vscode/src/features/lockfile/lockfile.style.ts b/packages/bun-vscode/src/features/lockfile/lockfile.style.ts new file mode 100644 index 000000000..7c4650497 --- /dev/null +++ b/packages/bun-vscode/src/features/lockfile/lockfile.style.ts @@ -0,0 +1,35 @@ +export function styleLockfile(preview: string) { + // Match all lines that don't start with a whitespace character + const lines = preview.split(/\n(?!\s)/); + + return lines.map(styleSection).join("\n"); +} + +function styleSection(section: string) { + const lines = section.split(/\n/); + + return lines.map(styleLine).join("\n"); +} + +function styleLine(line: string) { + if (line.startsWith("#")) { + return `<span class="mtk5">${line}</span>`; + } + + const parts = line.trim().split(" "); + if (line.startsWith(" ")) { + return `<span><span class="mtk1"> ${parts[0]} </span><span class="mtk16">${parts[1]}</span></span>`; + } + if (line.startsWith(" ")) { + const leftPart = `<span class="mtk6"> ${parts[0]} </span>`; + + if (parts.length === 1) return `<span>${leftPart}</span>`; + + if (parts[1].startsWith('"http://') || parts[1].startsWith('"https://')) + return `<span>${leftPart}<span class="mtk12 detected-link">${parts[1]}</span></span>`; + if (parts[1].startsWith('"')) return `<span>${leftPart}<span class="mtk16">${parts[1]}</span></span>`; + + return `<span>${leftPart}<span class="mtk6">${parts[1]}</span></span>`; + } + return `<span class="mtk1">${line} </span>`; +} diff --git a/packages/bun-vscode/src/features/tasks/package.json.ts b/packages/bun-vscode/src/features/tasks/package.json.ts new file mode 100644 index 000000000..55947a4a1 --- /dev/null +++ b/packages/bun-vscode/src/features/tasks/package.json.ts @@ -0,0 +1,201 @@ +/** + * Automatically generates tasks from package.json scripts. + */ +import * as vscode from "vscode"; +import { BunTask } from "./tasks"; +import { debugCommand } from "../debug"; + +/** + * Parses tasks defined in the package.json. + */ +export async function providePackageJsonTasks(): Promise<BunTask[]> { + // + const scripts: Record<string, string> = await (async () => { + try { + const file = vscode.Uri.file(vscode.workspace.workspaceFolders[0]?.uri.fsPath + "/package.json"); + + // Load contents of package.json, no need to check if file exists, we return null if it doesn't + const contents = await vscode.workspace.fs.readFile(file); + return JSON.parse(contents.toString()).scripts; + } catch { + return null; + } + })(); + if (!scripts) return []; + + return Object.entries(scripts).map(([name, script]) => { + // Prefix script with bun if it doesn't already start with bun + const shellCommand = script.startsWith("bun run ") ? script : `bun run ${script}`; + + const task = new BunTask({ + script, + name, + detail: `${shellCommand} - package.json`, + execution: new vscode.ShellExecution(shellCommand), + }); + return task; + }); +} + +export function registerPackageJsonProviders(context: vscode.ExtensionContext) { + registerCodeLensProvider(context); + registerHoverProvider(context); +} + +/** + * Utility function to extract the scripts from a package.json file, including their name and position in the document. + */ +function extractScriptsFromPackageJson(document: vscode.TextDocument) { + const content = document.getText(); + const matches = content.match(/"scripts"\s*:\s*{([\s\S]*?)}/); + if (!matches || matches.length < 2) return null; + + const startIndex = content.indexOf(matches[0]); + const endIndex = startIndex + matches[0].length; + const range = new vscode.Range(document.positionAt(startIndex), document.positionAt(endIndex)); + + const scripts = matches[1].split(/,\s*/).map(script => { + const elements = script.match(/"([^"\\]|\\.|\\\n)*"/g); + if (elements?.length != 2) return null; + const [name, command] = elements; + return { + name: name.replace('"', "").trim(), + command: command.replace(/(?<!\\)"/g, "").trim(), + range: new vscode.Range( + document.positionAt(startIndex + matches[0].indexOf(name)), + document.positionAt(startIndex + matches[0].indexOf(name) + name.length + command.length), + ), + }; + }); + + return { + range, + scripts, + }; +} + +/** + * This function registers a CodeLens provider for package.json files. It is used to display the "Run" and "Debug" buttons + * above the scripts properties in package.json (inline). + */ +function registerCodeLensProvider(context: vscode.ExtensionContext) { + context.subscriptions.push( + // Register CodeLens provider for package.json files + vscode.languages.registerCodeLensProvider( + { + language: "json", + scheme: "file", + pattern: "**/package.json", + }, + { + provideCodeLenses(document: vscode.TextDocument) { + const { range } = extractScriptsFromPackageJson(document); + + const codeLenses: vscode.CodeLens[] = []; + codeLenses.push( + new vscode.CodeLens(range, { + title: "$(breakpoints-view-icon) Bun: Debug", + tooltip: "Debug a script using bun", + command: "extension.bun.codelens.run", + arguments: [{ type: "debug" }], + }), + new vscode.CodeLens(range, { + title: "$(debug-start) Bun: Run", + tooltip: "Run a script using bun", + command: "extension.bun.codelens.run", + arguments: [{ type: "run" }], + }), + ); + return codeLenses; + }, + resolveCodeLens(codeLens) { + return codeLens; + }, + }, + ), + // Register the commands that are executed when clicking the CodeLens buttons + vscode.commands.registerCommand("extension.bun.codelens.run", async ({ type }: { type: "debug" | "run" }) => { + const tasks = (await vscode.tasks.fetchTasks({ type: "bun" })) as BunTask[]; + if (tasks.length === 0) return; + + const pick = await vscode.window.showQuickPick( + tasks + .filter(task => task.detail.endsWith("package.json")) + .map(task => ({ + label: task.name, + detail: task.detail, + })), + ); + if (!pick) return; + + const task = tasks.find(task => task.name === pick.label); + if (!task) return; + + const command = type === "debug" ? "extension.bun.codelens.debug.task" : "extension.bun.codelens.run.task"; + + vscode.commands.executeCommand(command, { + script: task.definition.script, + name: task.name, + }); + }), + ); +} + +function getActiveTerminal(name: string) { + return vscode.window.terminals.filter(terminal => terminal.name === name); +} + +interface CommandArgs { + script: string; + name: string; +} + +/** + * This function registers a Hover language feature provider for package.json files. It is used to display the + * "Run" and "Debug" buttons when hovering over a script property in package.json. + */ +function registerHoverProvider(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.languages.registerHoverProvider("json", { + provideHover(document, position) { + const { scripts } = extractScriptsFromPackageJson(document); + + return { + contents: scripts.map(script => { + if (!script.range.contains(position)) return null; + + const command = encodeURI(JSON.stringify({ script: script.command, name: script.name })); + + const markdownString = new vscode.MarkdownString( + `[Debug](command:extension.bun.codelens.debug.task?${command}) | [Run](command:extension.bun.codelens.run.task?${command})`, + ); + markdownString.isTrusted = true; + + return markdownString; + }), + }; + }, + }), + vscode.commands.registerCommand("extension.bun.codelens.debug.task", async ({ script, name }: CommandArgs) => { + if (script.startsWith("bun run ")) script = script.slice(8); + if (script.startsWith("bun ")) script = script.slice(4); + + debugCommand(script); + }), + vscode.commands.registerCommand("extension.bun.codelens.run.task", async ({ script, name }: CommandArgs) => { + if (script.startsWith("bun run ")) script = script.slice(8); + + name = `Bun Task: ${name}`; + const terminals = getActiveTerminal(name); + if (terminals.length > 0) { + terminals[0].show(); + terminals[0].sendText(`bun run ${script}`); + return; + } + + const terminal = vscode.window.createTerminal({ name }); + terminal.show(); + terminal.sendText(`bun run ${script}`); + }), + ); +} diff --git a/packages/bun-vscode/src/features/tasks/tasks.ts b/packages/bun-vscode/src/features/tasks/tasks.ts new file mode 100644 index 000000000..aabeb3920 --- /dev/null +++ b/packages/bun-vscode/src/features/tasks/tasks.ts @@ -0,0 +1,59 @@ +import * as vscode from "vscode"; +import { providePackageJsonTasks } from "./package.json"; + +interface BunTaskDefinition extends vscode.TaskDefinition { + script: string; +} + +export class BunTask extends vscode.Task { + declare definition: BunTaskDefinition; + + constructor({ + script, + name, + detail, + execution, + scope = vscode.TaskScope.Workspace, + }: { + script: string; + name: string; + detail?: string; + scope?: vscode.WorkspaceFolder | vscode.TaskScope.Global | vscode.TaskScope.Workspace; + execution?: vscode.ProcessExecution | vscode.ShellExecution | vscode.CustomExecution; + }) { + super({ type: "bun", script }, scope, name, "bun", execution); + this.detail = detail; + } +} + +/** + * Registers the task provider for the bun extension. + */ +export function registerTaskProvider(context: vscode.ExtensionContext) { + const taskProvider: vscode.TaskProvider<BunTask> = { + provideTasks: async () => await providePackageJsonTasks(), + resolveTask: task => resolveTask(task), + }; + context.subscriptions.push(vscode.tasks.registerTaskProvider("bun", taskProvider)); +} + +/** + * Parses tasks defined in the vscode tasks.json file. + * For more information, see https://code.visualstudio.com/api/extension-guides/task-provider + */ +export function resolveTask(task: BunTask): BunTask | undefined { + // Make sure the task has a script defined + const definition: BunTask["definition"] = task.definition; + if (!definition.script) return task; + const shellCommand = definition.script.startsWith("bun ") ? definition.script : `bun ${definition.script}`; + + const newTask = new vscode.Task( + definition, + task.scope ?? vscode.TaskScope.Workspace, + task.name, + "bun", + new vscode.ShellExecution(shellCommand), + ) as BunTask; + newTask.detail = `${shellCommand} - tasks.json`; + return newTask; +} |