Skip to content

Commit

Permalink
🗞️ Declaration maps for local development (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Oct 25, 2024
1 parent 6daefb4 commit 9b70e96
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 2 deletions.
3 changes: 3 additions & 0 deletions packages/build-devtools/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.turbo
dist
node_modules
3 changes: 3 additions & 0 deletions packages/build-devtools/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json"
}
31 changes: 31 additions & 0 deletions packages/build-devtools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>

<h1 align="center">@layerzerolabs/build-devtools</h1>

<!-- The badges section -->
<p align="center">
<!-- Shields.io NPM published package version -->
<a href="https://www.npmjs.com/package/@layerzerolabs/build-devtools"><img alt="NPM Version" src="https://img.shields.io/npm/v/@layerzerolabs/build-devtools"/></a>
<!-- Shields.io NPM downloads -->
<a href="https://www.npmjs.com/package/@layerzerolabs/build-devtools"><img alt="Downloads" src="https://img.shields.io/npm/dm/@layerzerolabs/build-devtools"/></a>
<!-- Shields.io license badge -->
<a href="https://www.npmjs.com/package/@layerzerolabs/build-devtools"><img alt="NPM License" src="https://img.shields.io/npm/l/@layerzerolabs/build-devtools"/></a>
</p>

## Installation

```bash
pnpm install @layerzerolabs/build-devtools
```

```bash
pnpm install @layerzerolabs/build-devtools
```

```bash
npm install @layerzerolabs/build-devtools
```
12 changes: 12 additions & 0 deletions packages/build-devtools/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
cache: false,
reporters: [['github-actions', { silent: false }], 'default'],
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
};
55 changes: 55 additions & 0 deletions packages/build-devtools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@layerzerolabs/build-devtools",
"version": "0.0.1",
"private": true,
"description": "Utilities devtools build system",
"repository": {
"type": "git",
"url": "git+https://github.com/LayerZero-Labs/devtools.git",
"directory": "packages/build-devtools"
},
"license": "MIT",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./*": {
"types": "./dist/*/index.d.ts",
"require": "./dist/*/index.js",
"import": "./dist/*/index.mjs"
}
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist",
"swag"
],
"scripts": {
"prebuild": "tsc -noEmit",
"build": "$npm_execpath tsup",
"clean": "rm -rf dist",
"dev": "$npm_execpath tsup --watch",
"lint": "$npm_execpath eslint '**/*.{js,ts,json}'",
"lint:fix": "eslint --fix '**/*.{js,ts,json}'",
"test": "jest --ci --runInBand"
},
"devDependencies": {
"@swc/core": "^1.4.0",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"fast-check": "^3.15.1",
"jest": "^29.7.0",
"ts-node": "^10.9.2",
"tslib": "~2.6.2",
"tsup": "~8.0.1",
"typescript": "^5.4.4"
},
"peerDependencies": {
"tsup": "^8.0.1",
"typescript": "^5.4.4"
}
}
1 change: 1 addition & 0 deletions packages/build-devtools/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tsup'
71 changes: 71 additions & 0 deletions packages/build-devtools/src/tsup/declarations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { spawnSync } from 'child_process'
import { type Options } from 'tsup'

export interface CreateDeclarationBuildOptions {
/**
* Enable this plugin.
*
* @default process.env.NODE_ENV === 'production'
*/
enabled?: boolean
/**
* tsc binary to use
*
* @default 'tsc'
*/
tsc?: string
/**
* tsconfig.json to use
*
* @default 'tsconfig.build.json'
*/
tsConfig?: string
outDir?: string
}

/**
* Helper type to make sure the return value matches what tsup expects
*/
type Plugin = Exclude<Options['plugins'], undefined>[number]

const LOG_LABEL = 'DMAP'

export const createDeclarationBuild = ({
enabled = process.env.NODE_ENV === 'production',
tsc = 'tsc',
tsConfig = 'tsconfig.build.json',
outDir: outDirOption,
}: CreateDeclarationBuildOptions = {}) =>
({
name: 'Generate Local Declaration Maps',
async buildEnd() {
if (!enabled) {
this.logger.info(LOG_LABEL, `Skipping declaration map generation in production mode`)

return
}

const outDir = outDirOption ?? this.options.outDir
this.logger.info(LOG_LABEL, `Generating declaration maps for based on ${tsConfig} into ${outDir}`)

// tsc does not have a pretty programmatic interface
// so we use spawnSync to run it as an external command
const result = spawnSync(
tsc,
['-p', tsConfig, '--noEmit', 'false', '--emitDeclarationOnly', '-outDir', outDir],
{
encoding: 'utf8',
stdio: 'inherit',
}
)

// Check the exit status of tsc to make sure it succeeded
if (result.status !== 0) {
this.logger.error(LOG_LABEL, `Declaration map generation failed with exit code ${result.status}`)

throw new Error(`Declaration map generation failed with exit code ${result.status}`)
}

this.logger.info(LOG_LABEL, `⚡️ Generating declaration maps successful`)
},
}) satisfies Plugin
1 change: 1 addition & 0 deletions packages/build-devtools/src/tsup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './declarations'
110 changes: 110 additions & 0 deletions packages/build-devtools/test/tsup/declarations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { createDeclarationBuild } from '@/tsup'
import fc from 'fast-check'
import { spawnSync } from 'child_process'

jest.mock('child_process', () => ({
...jest.requireActual('child_process'),
spawnSync: jest.fn().mockReturnValue(undefined),
}))

const spawnSyncMock = spawnSync as jest.Mock

describe('tsup/declarations', () => {
let pluginContext: any

beforeEach(() => {
pluginContext = {
logger: {
info: jest.fn(),
error: jest.fn(),
},
options: {},
}

spawnSyncMock.mockReset()
})

describe('if not enabled explicitly', () => {
it('should not run tsc', async () => {
const plugin = createDeclarationBuild({ enabled: false })

await plugin.buildEnd.call(pluginContext)

expect(spawnSyncMock).not.toHaveBeenCalled()
})
})

describe('if not enabled in production environment', () => {
let NODE_ENV: string | undefined

beforeAll(() => {
NODE_ENV = process.env.NODE_ENV
})

afterAll(() => {
process.env.NODE_ENV = NODE_ENV
})

it('should not run tsc', async () => {
await fc.assert(
fc.asyncProperty(fc.oneof(fc.string(), fc.constantFrom(undefined)), async (env) => {
fc.pre(env !== 'production')

const plugin = createDeclarationBuild({ enabled: false })

await plugin.buildEnd.call(pluginContext)

expect(spawnSyncMock).not.toHaveBeenCalled()
})
)
})
})

describe('if enabled explicitly', () => {
it('should run tsc', async () => {
const plugin = createDeclarationBuild({ enabled: true })

spawnSyncMock.mockReturnValue({ status: 0 })

await plugin.buildEnd.call(pluginContext)

expect(spawnSyncMock).toHaveBeenCalledTimes(1)
})
})

describe('if enabled in production environment', () => {
let NODE_ENV: string | undefined

beforeAll(() => {
NODE_ENV = process.env.NODE_ENV

process.env.NODE_ENV = 'production'
})

afterAll(() => {
process.env.NODE_ENV = NODE_ENV
})

it('should run tsc', async () => {
const plugin = createDeclarationBuild({ enabled: undefined })

spawnSyncMock.mockReturnValue({ status: 0 })

await plugin.buildEnd.call(pluginContext)

expect(spawnSyncMock).toHaveBeenCalledTimes(1)
})

it('should run tsc and throw if the process fails', async () => {
const plugin = createDeclarationBuild({ enabled: undefined })

spawnSyncMock.mockReturnValue({ status: 1 })

await expect(plugin.buildEnd.call(pluginContext)).rejects.toThrow(
`Declaration map generation failed with exit code 1`
)

expect(spawnSyncMock).toHaveBeenCalledTimes(1)
})
})
})
4 changes: 4 additions & 0 deletions packages/build-devtools/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["src"]
}
12 changes: 12 additions & 0 deletions packages/build-devtools/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"exclude": ["dist", "node_modules"],
"include": ["src", "test", "*.config.ts"],
"compilerOptions": {
"jsx": "react",
"types": ["node", "jest"],
"paths": {
"@/*": ["./src/*"]
}
}
}
14 changes: 14 additions & 0 deletions packages/build-devtools/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'tsup'
import { createDeclarationBuild } from '@/tsup'

export default defineConfig({
entry: ['src/index.ts'],
outDir: './dist',
clean: true,
dts: true,
sourcemap: true,
splitting: false,
treeshake: true,
format: ['esm', 'cjs'],
plugins: [createDeclarationBuild({})],
})
32 changes: 31 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"importHelpers": true,
"noEmit": true,
"declaration": true,
"declarationMap": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"strict": true,
Expand Down
4 changes: 3 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
"CI",

"RPC_URL_SOLANA_MAINNET",
"RPC_URL_SOLANA_TESTNET"
"RPC_URL_SOLANA_TESTNET",

"NODE_ENV"
]
}

0 comments on commit 9b70e96

Please sign in to comment.