diff --git a/bun.lock b/bun.lock index 3f28e8e..542aa2f 100644 --- a/bun.lock +++ b/bun.lock @@ -13,8 +13,8 @@ "@biomejs/biome": "^2.1.3", "@parcel/watcher": "^2.5.1", "@react-native-clipboard/clipboard": "^1.16.3", + "@revenge-mod/types": "workspace:*", "@types/bun": "^1.2.19", - "@uwu/treetype": "^1.1.1", "rolldown-plugin-dts": "^0.15.4", }, "peerDependencies": { @@ -313,8 +313,6 @@ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], - "@uwu/treetype": ["@uwu/treetype@1.1.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "treetype": "dist/cli.js" } }, "sha512-sts8RcR8DzG6B2gnt3tbeSrn2N/ShI+3wVOWZHWtFT1fgrw/aDBbV6ePvXFobG0AKUrt/AQ5zV5E3JeX0P37eQ=="], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="], diff --git a/lib/types/package.json b/lib/types/package.json deleted file mode 100644 index dd2e7fc..0000000 --- a/lib/types/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@revenge-mod/types", - "private": true, - "type": "module", - "exports": { - ".": { - "types": "./types.d.ts" - }, - "./tsconfig": { - "default": "./tsconfig.json" - } - } -} diff --git a/lib/types/tsconfig.json b/lib/types/tsconfig.json deleted file mode 100644 index eb4a0b8..0000000 --- a/lib/types/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - // Features - "lib": ["ESNext"], - "module": "ESNext", - "target": "ES2015", - // Bundler - "moduleResolution": "bundler", - "verbatimModuleSyntax": true, - "noEmit": true, - "jsx": "preserve", - // Type checking - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noPropertyAccessFromIndexSignature": false, - "types": ["@revenge-mod/types", "react-native/types/index"] - } -} diff --git a/package.json b/package.json index d204541..c696493 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "@biomejs/biome": "^2.1.3", "@parcel/watcher": "^2.5.1", "@react-native-clipboard/clipboard": "^1.16.3", + "@revenge-mod/types": "workspace:*", "@types/bun": "^1.2.19", - "@uwu/treetype": "^1.1.1", "rolldown-plugin-dts": "^0.15.4" }, "peerDependencies": { @@ -57,4 +57,4 @@ "patchedDependencies": { "react-native-click-outside@0.1.1": "patches/react-native-click-outside@0.1.1.patch" } -} +} \ No newline at end of file diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 7420160..f059d9a 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,8 +1,10 @@ { - "extends": "@revenge-mod/types/tsconfig", "compilerOptions": { "module": "ESNext", "target": "ESNext", - "types": ["node/timers"] + "moduleResolution": "Bundler", + "paths": { + "pkg:*": ["../lib/*/package.json"] + } } } diff --git a/scripts/types.ts b/scripts/types.ts index 472baa5..8b5d740 100644 --- a/scripts/types.ts +++ b/scripts/types.ts @@ -1,89 +1,247 @@ import { main } from 'bun' import chalk from 'chalk' -import { existsSync, readdirSync } from 'fs' -import { rm } from 'fs/promises' +import { exists, mkdir, rm } from 'fs/promises' import { rolldown } from 'rolldown' import { dts } from 'rolldown-plugin-dts' import pkg from '../package.json' -import type { Dirent } from 'fs' -const OutputDir = 'dist/types' +const PATHS = { + lib: 'lib', + output: 'dist/types', + outputTemp: 'dist/types/.build', + tsconfig: 'tsconfig.json', +} as const -await rm(OutputDir, { recursive: true, force: true }) - .then(() => - console.debug(chalk.gray('\u{1F5BB} Deleted generated types...')), - ) - .catch() - -// If this file is being run directly, build the project -if (main === import.meta.filename) buildTypes() - -function hasTypesExports(node: Dirent) { - return ( - node.isDirectory() && - (existsSync(`./lib/${node.name}/src/types.ts`) || - existsSync(`./lib/${node.name}/src/types/index.ts`)) - ) +async function cleanup(dir: string, description: string): Promise { + try { + if (await exists(dir)) { + await rm(dir, { recursive: true, force: true }) + console.debug(chalk.gray(`🗑️ Deleted ${description}...`)) + } + } catch (error) { + console.warn( + chalk.yellow(`⚠️ Failed to delete ${description}: ${error}`), + ) + } } -export default async function buildTypes(log = true) { - let start = performance.now() +await cleanup(PATHS.output, 'generated types') +await mkdir(PATHS.outputTemp, { recursive: true }) - if (log) console.info(chalk.gray('\u{1F5CE} Generating types...')) +export default async function buildTypes(log = true): Promise { + const start = performance.now() - const bundle = await rolldown({ - input: { - ...Object.fromEntries( - readdirSync('./lib', { withFileTypes: true }) - .filter(hasTypesExports) - .map(node => [ - `lib/${node.name}/types`, - `./lib/${node.name}/src/types`, - ]), - ), - 'lib/modules/finders/filters': - './lib/modules/src/finders/filters.ts', - types: './types/lib.ts', - }, - external: Object.keys({ - ...pkg.dependencies, - ...pkg.devDependencies, - ...pkg.peerDependencies, - }), - plugins: [ - dts({ - tsconfig: 'tsconfig.json', - parallel: true, - emitDtsOnly: true, - eager: true, - resolve: true, + if (log) console.info(chalk.gray('🏗️ Generating types...')) + + try { + const bundle = await rolldown({ + input: { + ...Object.fromEntries( + Libraries.flatMap(([libName, exports]) => + exports.map(([exportName, path]) => [ + `lib/${libName}${exportName && `/${exportName}`}`, + path, + ]), + ), + ), + globals: './types/globals.consumers.ts', + }, + external: Object.keys({ + ...pkg.dependencies, + ...pkg.devDependencies, + ...pkg.peerDependencies, }), - ], - }) + plugins: [ + dts({ + tsconfig: PATHS.tsconfig, + parallel: true, + emitDtsOnly: true, + eager: true, + resolve: true, + }), + ], + }) - await bundle.write({ - dir: 'dist/types', - }) + await bundle.write({ + dir: PATHS.output, + }) - if (log) - console.info( - chalk.greenBright( - `\u{2714} Generated library types! ${chalk.gray(`(took ${(performance.now() - start).toFixed(2)}ms)`)}`, - ), - ) - - start = performance.now() - - Bun.spawnSync({ - cmd: ['treetype', './types/lib.tt', './dist/types/modules.d.ts'], - stdout: 'inherit', - stderr: 'inherit', - }) - - if (log) - console.info( - chalk.greenBright( - `\u{2714} Generated import types! ${chalk.gray(`(took ${(performance.now() - start).toFixed(2)}ms)`)}`, - ), - ) + if (log) { + const duration = (performance.now() - start).toFixed(2) + console.info( + chalk.greenBright( + `✅ Generated library types! ${chalk.gray(`(took ${duration}ms)`)}`, + ), + ) + } + } catch (error) { + console.error(chalk.red('❌ Failed to generate types:'), error) + throw error + } finally { + await cleanup(PATHS.outputTemp, 'temporary build files') + } } + +import assets from 'pkg:assets' +import components from 'pkg:components' +import discord from 'pkg:discord' +import externals from 'pkg:externals' +import modules from 'pkg:modules' +import patcher from 'pkg:patcher' +import plugins from 'pkg:plugins' +import react from 'pkg:react' +import storage from 'pkg:storage' +import utils from 'pkg:utils' +import { writeFileSync } from 'fs' + +type TrimLeadingDotAndMaybeSlash = T extends `.${infer R}` + ? R extends `/${infer S}` + ? S + : R + : T + +type NameBinding = [original: Exp, to: string] +type Exports = Exp | NameBinding + +function library< + E extends Record<`.${string}`, { default?: string; types?: string }>, +>( + pkg: { name: string; exports: E }, + exports: Exports>>[], +): [name: string, exports: [exportName: string, path: string][]] { + const mapped: [exportName: string, path: string][] = [] + const bindings: Record = {} + const pkgName = pkg.name.split('/').at(-1)! + + for (const nameOrBinding of exports) { + const isBinding = Array.isArray(nameOrBinding) + const name = isBinding ? nameOrBinding[0] : nameOrBinding + + const exportEntry = pkg.exports[`.${name ? `/${name}` : ''}`] + if (!exportEntry) { + console.warn( + chalk.yellow( + `⚠️ Export "${name}" not found in "${pkg.name}" package.json`, + ), + ) + continue + } + + const { default: defaultPath, types } = exportEntry + const actualName = isBinding ? nameOrBinding[1] : name + const path = defaultPath ?? types + + if (!path) { + console.warn( + chalk.yellow( + `⚠️ No path found for export "${name}" in "${pkg.name}"`, + ), + ) + continue + } + + if (isBinding) (bindings[actualName] ??= []).push([actualName, name]) + else { + console.debug( + chalk.gray( + `📄 ${pkgName}${name && `/${name}`} -> ${pkg.name}${name && `/${name}`}`, + ), + ) + mapped.push([actualName, `${PATHS.lib}/${pkgName}/${path}`]) + } + } + + for (const [to, froms] of Object.entries(bindings)) { + if (froms.length > 1) { + const tempFileName = Array.from( + crypto.getRandomValues(new Uint8Array(4)), + ) + .map(b => b.toString(16).padStart(2, '0')) + .join('') + + const tempPath = `${PATHS.outputTemp}/${tempFileName}.ts` + const content = froms + .map(([, from]) => { + const path = `${pkg.name}/${from}` + console.debug( + chalk.gray(`↔️ ${pkgName}${to && `/${to}`} -> ${path}`), + ) + + return `export * from '${path}'` + }) + .join('\n') + + try { + writeFileSync(tempPath, content) + mapped.push([to, tempPath]) + } catch (error) { + console.error( + chalk.red(`❌ Failed to write temp file for ${to}:`), + error, + ) + } + } else if (froms[0]) { + mapped.push(froms[0]) + } + } + + return [pkgName, mapped] +} + +const Libraries = [ + library(assets, ['', 'types']), + library(components, [ + 'FormSwitch', + 'Page', + 'SearchInput', + 'TableRowAssetIcon', + 'types', + ]), + library(discord, [ + 'actions', + 'common', + 'common/flux', + 'common/utils', + 'design', + 'flux', + 'modules/main_tabs_v2', + 'modules/settings', + 'modules/settings/renderer', + 'native', + 'types', + ]), + library(externals, [ + 'browserify', + 'react-native-clipboard', + 'react-navigation', + 'shopify', + 'types', + ]), + library(modules, [ + 'finders/filters', + ['finders/get', 'finders'], + ['finders/lookup', 'finders'], + ['finders/wait', 'finders'], + ['metro/subscriptions', 'metro'], + ['metro/utils', 'metro'], + 'native', + 'types', + ]), + library(patcher, ['', 'types']), + library(plugins, ['constants', 'types']), + library(react, ['', 'jsx-runtime', 'native', 'types']), + library(storage, ['', 'types']), + library(utils, [ + 'callback', + 'discord', + 'error', + 'object', + 'promise', + 'proxy', + 'react', + 'tree', + 'types', + ]), +] as const + +if (main === import.meta.filename) await buildTypes() diff --git a/tsconfig.json b/tsconfig.json index 4e4f242..3135f2b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,33 @@ { - "extends": "@revenge-mod/types/tsconfig", "compilerOptions": { - "types": [ - "@revenge-mod/types", - "./types", - "./types.modules", - "node/util", - "node/events" - ], + "lib": ["ESNext"], + "module": "ESNext", + "target": "ES2015", + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "noEmit": true, + "jsx": "preserve", "allowJs": true, + "strict": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noPropertyAccessFromIndexSignature": false, + "types": [], + "typeRoots": [], "paths": { - "~/*": ["./src/*"], + "~*": ["./src/*"], "~assets/*": ["./dist/assets/generated/*"] } }, - "include": ["lib", "src", "types"], + "include": [ + "lib", + "src", + "types", + "./types/*.d.ts", + "node_modules/@types/node/events.d.ts", + "node_modules/@types/node/util.d.ts", + "node_modules/react-native/types/index.d.ts" + ], "exclude": ["**/*.test.ts"] } diff --git a/types.d.ts b/types/build.d.ts similarity index 100% rename from types.d.ts rename to types/build.d.ts diff --git a/types/lib.ts b/types/globals.consumers.ts similarity index 87% rename from types/lib.ts rename to types/globals.consumers.ts index d6dd5de..246d827 100644 --- a/types/lib.ts +++ b/types/globals.consumers.ts @@ -11,4 +11,4 @@ declare global { ): void } -export type * from '@revenge-mod/types' +export * from './globals' diff --git a/lib/types/types.d.ts b/types/globals.d.ts similarity index 92% rename from lib/types/types.d.ts rename to types/globals.d.ts index 64f9d59..ce6360d 100644 --- a/lib/types/types.d.ts +++ b/types/globals.d.ts @@ -1,5 +1,3 @@ -// All of these typings are exported, be careful what you export here! - import type { Metro } from '@revenge-mod/modules/types' import type { ImageProps, @@ -25,6 +23,8 @@ declare module 'react' { /// HERMES declare global { + const HermesInternal: HermesInternalObject + function setTimeout( cb: (...args: unknown[]) => unknown, timeout?: number, @@ -56,6 +56,10 @@ declare global { /// PROMISE POLYFILLS FROM: https://github.com/then/promise /// AND: https://github.com/facebook/hermes/blob/main/lib/InternalBytecode/01-Promise.js _h: 0 | 1 | 2 + /** + * The resolved value of the promise, if it has been resolved. + */ + _j: any } type HermesPromiseRejectionHandler = ( @@ -103,7 +107,7 @@ declare global { declare global { var nativeModuleProxy: Record - function __turboModuleProxy(name: string): unknown + var __turboModuleProxy: ((name: string) => unknown) | undefined function nativeLoggingHook(str: string, level: number): void function alert(message: unknown): void @@ -111,6 +115,4 @@ declare global { var performance: { now(): number } - - var globalEvalWithSourceUrl: (code: string, sourceURL: string) => any } diff --git a/types/lib.tt b/types/lib.tt deleted file mode 100644 index f16b6c6..0000000 --- a/types/lib.tt +++ /dev/null @@ -1,51 +0,0 @@ -\source ../lib/plugins/src/types.ts -\import ./lib/plugins/types.d.ts - -@revenge-mod from UnscopedPluginApi { - assets - components - discord { - common { - flux - utils - } - modules { - settings { - renderer - } - mainTabsV2 - } - actions - design - flux - native - } - externals { - Browserify - ReactNativeClipboard - ReactNavigation - Shopify - } - modules { - finders - metro - native - } - patcher - plugins { - constants - } - react { - native - } - utils { - callback - discord - error - object - promise - proxy - react - tree - } -} \ No newline at end of file diff --git a/types.modules.d.ts b/types/modules.d.ts similarity index 100% rename from types.modules.d.ts rename to types/modules.d.ts