tsnite 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # tsnite
2
-
3
- [![npm version](https://img.shields.io/npm/v/tsnite.svg)](https://www.npmjs.com/package/tsnite)
4
- [![Eslint](https://img.shields.io/badge/ESLint-3A33D1?logo=eslint)](https://img.shields.io/badge/ESLint-3A33D1?logo=eslint)
5
- [![prettier](https://img.shields.io/badge/Prettier-de9954?logo=prettier&logoColor=ffffff)](https://img.shields.io/badge/Prettier-de9954?logo=prettier&logoColor=ffffff)
6
- [![github license](https://img.shields.io/github/license/luas10c/tsnite)](https://img.shields.io/github/license/luas10c/tsnite)
7
-
8
- TypeScript runner for Node.js with watch mode, `tsconfig` path alias support, and extensionless TypeScript import resolution.
1
+ <h1 align="center">
2
+ <br>
3
+ <img alt="tsnite" src="https://github.com/luas10c/tsnite/blob/main/tsnite.png?raw=true">
4
+ <br><br>
5
+ <a href="https://npm.im/tsnite"><img src="https://badgen.net/npm/v/tsnite"></a>
6
+ <a href="https://npm.im/tsnite"><img src="https://badgen.net/npm/dm/tsnite"></a>
7
+ <a href="https://npm.im/tsnite"><img src="https://img.shields.io/badge/ESLint-3A33D1?logo=eslint" alt="eslint"></a>
8
+ <a href="https://npm.im/tsnite"><img src="https://img.shields.io/badge/Prettier-21323b?logo=prettier&logoColor=ffffff" alt="prettier"></a>
9
+ <a href="https://npm.im/tsnite"><img src="https://img.shields.io/github/license/luas10c/tsnite" alt="github license"></a>
10
+ </h1>
11
+
12
+ <p align="center">
13
+ TypeScript at full throttle—fast, safe, unstoppable. 🚀
14
+ <br><br>
15
+ <a href="https://github.com/luas10c/tsnite">Documentation</a>&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;<a href="https://github.com/luas10c/tsnite">Getting started →</a>
16
+ </p>
9
17
 
10
18
  ## Install
11
19
 
@@ -13,7 +21,15 @@ TypeScript runner for Node.js with watch mode, `tsconfig` path alias support, an
13
21
  npm install --save-dev tsnite
14
22
  ```
15
23
 
16
- ## Usage
24
+ ## Features
25
+
26
+ - ESM-friendly runtime
27
+ - Watch mode with automatic restart
28
+ - Supports `compilerOptions.paths`
29
+ - Resolves extensionless TypeScript imports
30
+ - Decorator support
31
+
32
+ ## Quick Start
17
33
 
18
34
  Run a TypeScript entry file:
19
35
 
@@ -44,16 +60,6 @@ Use in `package.json`:
44
60
  }
45
61
  ```
46
62
 
47
- ## Features
48
-
49
- - Run `.ts`, `.tsx`, `.mts`, and `.cts` files directly in Node.js
50
- - Watch mode with automatic restart
51
- - Reads the current project's `tsconfig.json`
52
- - Resolves `compilerOptions.paths`
53
- - Resolves extensionless TypeScript imports
54
- - ESM-friendly runtime
55
- - Decorator support in transpilation
56
-
57
63
  ## Import Resolution
58
64
 
59
65
  `tsnite` resolves TypeScript files without requiring explicit extensions.
@@ -78,29 +84,6 @@ It will try these TypeScript candidates:
78
84
 
79
85
  Explicit JavaScript imports such as `.js`, `.mjs`, and `.cjs` are delegated to Node.js.
80
86
 
81
- ## `tsconfig` Paths
82
-
83
- `tsnite` supports `compilerOptions.paths` aliases.
84
-
85
- ```json
86
- {
87
- "compilerOptions": {
88
- "baseUrl": ".",
89
- "paths": {
90
- "#/*": ["src/*"]
91
- }
92
- }
93
- }
94
- ```
95
-
96
- Then this works without an extension:
97
-
98
- ```ts
99
- import { getMetadata } from '#/common/metadata'
100
- ```
101
-
102
- If `baseUrl` is not defined, `tsnite` uses the project root by default.
103
-
104
87
  ## Watch Mode
105
88
 
106
89
  Customize watched paths and extensions:
@@ -123,45 +106,14 @@ Defaults:
123
106
  - ext: `ts,tsx,js,jsx,json`
124
107
  - source root: `.`
125
108
 
126
- ## Example
127
-
128
- `tsconfig.json`
129
-
130
- ```json
131
- {
132
- "compilerOptions": {
133
- "target": "es2024",
134
- "module": "es2022",
135
- "moduleResolution": "bundler",
136
- "baseUrl": ".",
137
- "paths": {
138
- "#/*": ["src/*"]
139
- }
140
- }
141
- }
142
- ```
143
-
144
- `src/index.ts`
145
-
146
- ```ts
147
- import { startServer } from '#/server/start'
148
-
149
- await startServer()
150
- ```
151
-
152
- Run it:
153
-
154
- ```bash
155
- npx tsnite src/index.ts
156
- ```
109
+ ## Support
157
110
 
158
- ## Notes
111
+ Enjoying this tool? Consider supporting the project.
159
112
 
160
- - `tsnite` is focused on development-time execution
161
- - the current project's `tsconfig.json` is used
162
- - watch mode clears cached resolution and transpilation state before restart
113
+ <p>
114
+ <a href="https://buymeacoffee.com/luas10c" target="_blank" rel="noopener noreferrer"><img src="https://github.com/luas10c/tsnite/blob/main/buymeacoffe.png?raw=true" alt="Buy me a coffee" width="180" /></a>
115
+ </p>
163
116
 
164
- ## Links
117
+ ## License
165
118
 
166
- - npm: <https://www.npmjs.com/package/tsnite>
167
- - repository: <https://github.com/luas10c/tsnite>
119
+ [MIT](LICENSE)
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { fork } from 'node:child_process';
6
6
  import { watch } from 'chokidar';
7
7
  import { name, description, version } from './metadata.js';
8
8
  import { clearResolveCache, invalidateFileCaches } from './cache.js';
9
- import { debounce, yellow } from './util.js';
9
+ import { debounce, cyan, gray, red, yellow } from './util.js';
10
10
  const DEFAULT_INCLUDE_PATHS = ['.'];
11
11
  const DEFAULT_EXCLUDE_PATHS = ['dist', 'build', 'coverage'];
12
12
  const DEFAULT_WATCH_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'json'];
@@ -20,7 +20,7 @@ program
20
20
  .version(version, '-v, --version', 'Output the current version')
21
21
  .showSuggestionAfterError();
22
22
  function cleanup(signal) {
23
- process.stdout.write(`${yellow(`Received ${signal}. Stopping watcher and child processes...`)}\n`);
23
+ process.stdout.write(`${yellow(`Received`)} ${cyan(signal)}${yellow('. Stopping watcher and child processes...')}\n`);
24
24
  for (const child of children.values()) {
25
25
  try {
26
26
  child.kill('SIGTERM');
@@ -37,9 +37,14 @@ process.on('SIGINT', function () {
37
37
  process.on('SIGTERM', function () {
38
38
  cleanup('SIGTERM');
39
39
  });
40
- function spawn(entry, nodeArgs) {
41
- const child = fork(join(import.meta.dirname, '..', entry), {
40
+ function spawn(entry, nodeArgs, shouldLogTranspile) {
41
+ const entryPath = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
42
+ const child = fork(entryPath, {
42
43
  stdio: 'inherit',
44
+ env: {
45
+ ...process.env,
46
+ TSNITE_LOG_TRANSPILE: shouldLogTranspile ? '1' : '0'
47
+ },
43
48
  execArgv: [
44
49
  '--enable-source-maps',
45
50
  '--no-experimental-strip-types',
@@ -130,10 +135,11 @@ function createWatchConfig(options) {
130
135
  };
131
136
  }
132
137
  async function handler(entry, options, nodeArgs, isWatch) {
138
+ const runtimeEntry = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
133
139
  async function restart(reason) {
134
140
  process.stdout.write('\x1Bc');
135
141
  if (reason) {
136
- console.log(yellow(reason));
142
+ console.log(gray(reason));
137
143
  }
138
144
  for (const child of children.values()) {
139
145
  try {
@@ -145,7 +151,7 @@ async function handler(entry, options, nodeArgs, isWatch) {
145
151
  const exited = await waitForChildExit(child);
146
152
  if (exited)
147
153
  continue;
148
- console.log(yellow(`${reason ?? 'Restart requested.'} Process hasn't exited. Killing process...`));
154
+ console.log(`${yellow('restart')} ${gray('process unresponsive, sending')} ${red('SIGKILL')}`);
149
155
  try {
150
156
  child.kill('SIGKILL');
151
157
  }
@@ -154,14 +160,14 @@ async function handler(entry, options, nodeArgs, isWatch) {
154
160
  }
155
161
  }
156
162
  clearResolveCache();
157
- spawn(entry, nodeArgs);
163
+ spawn(runtimeEntry, nodeArgs, isWatch);
158
164
  }
159
165
  const restartDebounced = debounce(restart, WATCH_DEBOUNCE_MS);
160
166
  process.stdout.write('\x1Bc');
161
167
  if (isWatch) {
162
168
  console.log(yellow('Watching for changes...'));
163
169
  }
164
- spawn(entry, nodeArgs);
170
+ spawn(runtimeEntry, nodeArgs, isWatch);
165
171
  if (!isWatch)
166
172
  return;
167
173
  const { ignored, paths } = createWatchConfig(options);
@@ -176,8 +182,9 @@ async function handler(entry, options, nodeArgs, isWatch) {
176
182
  eventName !== 'unlink') {
177
183
  return;
178
184
  }
179
- await invalidateFileCaches(isAbsolute(changedPath) ? changedPath : (resolve(import.meta.dirname, '..', changedPath)));
180
- restartDebounced(`Change detected (${eventName}): ${changedPath}`);
185
+ const absoluteChangedPath = isAbsolute(changedPath) ? changedPath : (resolve(process.cwd(), changedPath));
186
+ await invalidateFileCaches(absoluteChangedPath);
187
+ restartDebounced();
181
188
  });
182
189
  }
183
190
  function parseCsv(value) {
package/dist/loader.js CHANGED
@@ -1,14 +1,17 @@
1
- import { transformFile } from '@swc/core';
1
+ import { transform } from 'oxc-transform';
2
2
  import { fileURLToPath, pathToFileURL } from 'node:url';
3
3
  import { readFile, stat, writeFile } from 'node:fs/promises';
4
- import { extname, join, dirname } from 'node:path';
4
+ import { writeSync } from 'node:fs';
5
+ import { extname, join, dirname, relative } from 'node:path';
5
6
  import { createHash } from 'node:crypto';
6
7
  import { parse } from './parse.js';
7
8
  import { ensureTranspileCacheDir, existsWithCache, getTranspileCacheFile, resolveCache } from './cache.js';
9
+ import { gray, green, yellow } from './util.js';
8
10
  const tsconfigCache = { paths: null, baseUrl: null };
9
11
  const transpileCache = new Map();
10
12
  const MAX_TRANSPILE_CACHE_ENTRIES = 256;
11
13
  const TS_EXTENSIONS = ['.cts', '.mts', '.tsx', '.ts'];
14
+ const SHOULD_LOG_TRANSPILE = process.env.TSNITE_LOG_TRANSPILE === '1';
12
15
  function hasTypeScriptExtension(value) {
13
16
  return TS_EXTENSIONS.some((extension) => value.endsWith(extension));
14
17
  }
@@ -221,38 +224,26 @@ export async function load(url, ctx, next) {
221
224
  if (cachedCode !== null) {
222
225
  return { format: 'module', source: cachedCode, shortCircuit: true };
223
226
  }
224
- const { code } = await transformFile(filename, {
225
- filename,
226
- jsc: {
227
- baseUrl: baseUrl || process.cwd(),
228
- parser: {
229
- syntax: 'typescript',
230
- tsx: url.endsWith('.tsx'),
231
- decorators: true
232
- },
233
- target: 'es2022',
234
- keepClassNames: true,
235
- experimental: {
236
- emitAssertForImportAttributes: true
237
- },
238
- transform: {
239
- decoratorMetadata: true,
240
- legacyDecorator: true,
241
- react: {
242
- runtime: 'automatic',
243
- development: true
244
- }
245
- },
246
- paths: paths ?? {}
227
+ const source = await readFile(filename, 'utf8');
228
+ const transpileStart = Date.now();
229
+ const { code } = await transform(filename, source, {
230
+ jsx: {
231
+ runtime: 'automatic',
232
+ development: true
247
233
  },
248
- module: {
249
- type: 'es6',
250
- strict: true,
251
- outFileExtension: 'ts',
252
- resolveFully: true
253
- },
254
- sourceMaps: 'inline'
234
+ sourcemap: true,
235
+ sourceType: 'module',
236
+ target: 'es2024',
237
+ decorator: {
238
+ emitDecoratorMetadata: true,
239
+ legacy: true
240
+ }
255
241
  });
242
+ if (SHOULD_LOG_TRANSPILE) {
243
+ const transpileTimeMs = Date.now() - transpileStart;
244
+ const displayPath = relative(process.cwd(), filename);
245
+ writeSync(1, `${gray('Transpiled')} ${yellow(displayPath)} ${gray('in')} ${green(`${transpileTimeMs}ms`)}\n`);
246
+ }
256
247
  await writeCachedTranspile(filename, {
257
248
  code,
258
249
  mtimeMs: fileStats.mtimeMs,
package/dist/util.js CHANGED
@@ -9,6 +9,39 @@ export function debounce(callback, waitMs) {
9
9
  function color(code, value) {
10
10
  return `\u001B[${code}m${value}\u001B[0m`;
11
11
  }
12
+ function style(codes, value) {
13
+ return `\u001B[${codes.join(';')}m${value}\u001B[0m`;
14
+ }
12
15
  export function yellow(value) {
13
16
  return color(33, value);
14
17
  }
18
+ export function purple(value) {
19
+ return color(95, value);
20
+ }
21
+ export function red(value) {
22
+ return color(31, value);
23
+ }
24
+ export function blue(value) {
25
+ return color(34, value);
26
+ }
27
+ export function white(value) {
28
+ return color(97, value);
29
+ }
30
+ export function pink(value) {
31
+ return color(95, value);
32
+ }
33
+ export function cyan(value) {
34
+ return color(96, value);
35
+ }
36
+ export function green(value) {
37
+ return color(32, value);
38
+ }
39
+ export function gray(value) {
40
+ return color(90, value);
41
+ }
42
+ export function bold(value) {
43
+ return style([1], value);
44
+ }
45
+ export function accent(value) {
46
+ return style([1, 36], value);
47
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tsnite",
3
3
  "description": "TypeScript at full throttle—fast, safe, unstoppable. 🚀",
4
- "version": "0.1.4",
4
+ "version": "0.2.0",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "esm",
@@ -20,7 +20,6 @@
20
20
  },
21
21
  "type": "module",
22
22
  "scripts": {
23
- "prebuild": "rimraf dist",
24
23
  "build": "tsc -p tsconfig.build.json",
25
24
  "postbuild": "tsc-alias -f -p tsconfig.build.json",
26
25
  "lint": "eslint . --cache",
@@ -32,9 +31,9 @@
32
31
  "prepare": "husky"
33
32
  },
34
33
  "dependencies": {
35
- "@swc/core": "^1.15.24",
36
34
  "chokidar": "^5.0.0",
37
- "commander": "^14.0.3"
35
+ "commander": "^14.0.3",
36
+ "oxc-transform": "^0.127.0"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@commitlint/cli": "^20.5.0",
@@ -42,7 +41,7 @@
42
41
  "@eslint/js": "^10.0.1",
43
42
  "@jest/globals": "^30.3.0",
44
43
  "@swc/jest": "^0.2.39",
45
- "eslint": "^10.2.0",
44
+ "eslint": "^10.2.1",
46
45
  "eslint-plugin-jest": "^29.15.2",
47
46
  "eslint-plugin-prettier": "^5.5.5",
48
47
  "globals": "^17.5.0",
@@ -50,11 +49,10 @@
50
49
  "jest": "^30.3.0",
51
50
  "jest-environment-node": "^30.3.0",
52
51
  "lint-staged": "^16.4.0",
53
- "prettier": "^3.8.2",
54
- "rimraf": "^6.1.3",
52
+ "prettier": "^3.8.3",
55
53
  "tsc-alias": "^1.8.16",
56
- "typescript": "^6.0.2",
57
- "typescript-eslint": "^8.58.1"
54
+ "typescript": "^6.0.3",
55
+ "typescript-eslint": "^8.59.0"
58
56
  },
59
57
  "engines": {
60
58
  "node": "^20.9.0 || ^22.11.0 || ^24.11.0"