tsnite 0.1.2 → 0.1.5

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
@@ -5,63 +5,163 @@
5
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
6
  [![github license](https://img.shields.io/github/license/luas10c/tsnite)](https://img.shields.io/github/license/luas10c/tsnite)
7
7
 
8
- ```
9
- TypeScript at full throttle—fast, safe, unstoppable. 🚀
8
+ TypeScript runner for Node.js with watch mode, `tsconfig` path alias support, and extensionless TypeScript import resolution.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install --save-dev tsnite
10
14
  ```
11
15
 
12
- `tsnite` is a tool that accelerates TypeScript project development, offering a streamlined build and run experience. Ideal for developers seeking productivity without compromising security and performance.
16
+ ## Usage
13
17
 
14
- ### 🚀 Installation
18
+ Run a TypeScript entry file:
15
19
 
16
- To add `tsnite` to your project as a development dependency, run:
20
+ ```bash
21
+ npx tsnite src/index.ts
22
+ ```
23
+
24
+ Run in watch mode:
17
25
 
18
26
  ```bash
19
- npm install tsnite -D
27
+ npx tsnite watch src/index.ts
20
28
  ```
21
29
 
22
- Or, if you're using Yarn:
30
+ Pass extra Node.js arguments after the entry file:
23
31
 
24
32
  ```bash
25
- yarn add --dev tsnite
33
+ npx tsnite src/index.ts --env-file=.env
34
+ ```
35
+
36
+ Use in `package.json`:
37
+
38
+ ```json
39
+ {
40
+ "scripts": {
41
+ "dev": "tsnite src/index.ts",
42
+ "dev:watch": "tsnite watch src/index.ts"
43
+ }
44
+ }
26
45
  ```
27
46
 
28
- ### Features
47
+ ## Features
29
48
 
30
- - **Decorators** first-class support for TypeScript decorators.
31
- - **Simple integration** with existing projects.
32
- - **ESM Support** native support for modern JavaScript modules.
33
- - **Automatic `tsconfig.json` loading** – respects your project configuration.
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
34
56
 
35
- ### 🛠️ How to use
57
+ ## Import Resolution
36
58
 
37
- With `tsnite` installed, you can use it directly in your terminal to run TypeScript files without needing to compile them first.
59
+ `tsnite` resolves TypeScript files without requiring explicit extensions.
38
60
 
39
- ### Run a TypeScript file
61
+ These imports work:
62
+
63
+ ```ts
64
+ import './server'
65
+ import './components/App'
66
+ ```
67
+
68
+ It will try these TypeScript candidates:
69
+
70
+ - `./server.ts`
71
+ - `./server.tsx`
72
+ - `./server.mts`
73
+ - `./server.cts`
74
+ - `./server/index.ts`
75
+ - `./server/index.tsx`
76
+ - `./server/index.mts`
77
+ - `./server/index.cts`
78
+
79
+ Explicit JavaScript imports such as `.js`, `.mjs`, and `.cjs` are delegated to Node.js.
80
+
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
+ ## Watch Mode
105
+
106
+ Customize watched paths and extensions:
40
107
 
41
108
  ```bash
42
- npx tsnite path/to/file.ts
109
+ npx tsnite watch --include src --exclude dist,coverage,uploads --ext ts,tsx,js,jsx,json src/index.ts
43
110
  ```
44
111
 
45
- This will execute the specified TypeScript file, allowing you to quickly test and run scripts during development.
112
+ Options:
113
+
114
+ - `--include <paths>` comma-separated paths to watch
115
+ - `--exclude <paths>` comma-separated paths to ignore
116
+ - `--ext <extensions>` comma-separated file extensions to watch
117
+ - `--source-root <path>` base path used by the watcher
118
+
119
+ Defaults:
120
+
121
+ - include: `.`
122
+ - exclude: `node_modules,.git,dist,build,coverage`
123
+ - ext: `ts,tsx,js,jsx,json`
124
+ - source root: `.`
46
125
 
47
- ## 💡 Tips & Best Practices
126
+ ## Example
48
127
 
49
- - **Use with `package.json` scripts** – integrate `tsnite` into `npm` or `yarn` scripts for a smoother workflow.
50
- Example:
51
- ```json
52
- {
53
- "scripts": {
54
- //...
55
- "dev": "tsnite watch --include src --exclude uploads --ext js,ts,json src/index.ts"
128
+ `tsconfig.json`
129
+
130
+ ```json
131
+ {
132
+ "compilerOptions": {
133
+ "target": "es2024",
134
+ "module": "es2022",
135
+ "moduleResolution": "bundler",
136
+ "baseUrl": ".",
137
+ "paths": {
138
+ "#/*": ["src/*"]
56
139
  }
57
140
  }
58
- ```
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
+ ```
59
157
 
60
- ## 📚 More information
158
+ ## Notes
61
159
 
62
- Check the official npm page for details, examples, and updates:
63
- [https://www.npmjs.com/package/tsnite](https://www.npmjs.com/package/tsnite)
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
64
163
 
65
- #
164
+ ## Links
66
165
 
67
- Enjoy developing TypeScript at full throttle!
166
+ - npm: <https://www.npmjs.com/package/tsnite>
167
+ - repository: <https://github.com/luas10c/tsnite>
package/dist/cache.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mkdir, rm, stat } from 'node:fs/promises';
3
- import path from 'node:path';
3
+ import { join, resolve } from 'node:path';
4
4
  export const resolveCache = new Map();
5
5
  const statCache = new Map();
6
6
  function getTranspileCacheDir() {
7
- return path.join(process.cwd(), 'node_modules', '.cache', 'tsnite');
7
+ return join(import.meta.dirname, '..', 'node_modules', '.cache', 'tsnite');
8
8
  }
9
9
  function getTSConfigPath() {
10
- return path.join(process.cwd(), 'tsconfig.json');
10
+ return join(import.meta.dirname, '..', 'tsconfig.json');
11
11
  }
12
12
  function hash(value) {
13
13
  return createHash('sha1').update(value).digest('hex');
@@ -34,7 +34,7 @@ export function invalidateStatCache(filePath) {
34
34
  statCache.delete(filePath);
35
35
  }
36
36
  export function getTranspileCacheFile(filePath) {
37
- return path.join(getTranspileCacheDir(), `${hash(filePath)}.json`);
37
+ return join(getTranspileCacheDir(), `${hash(filePath)}.json`);
38
38
  }
39
39
  export async function ensureTranspileCacheDir() {
40
40
  await mkdir(getTranspileCacheDir(), { recursive: true });
@@ -43,7 +43,7 @@ export async function clearTranspileCache() {
43
43
  await rm(getTranspileCacheDir(), { recursive: true, force: true });
44
44
  }
45
45
  export function isTSConfigPath(filePath) {
46
- return path.resolve(filePath) === getTSConfigPath();
46
+ return resolve(filePath) === getTSConfigPath();
47
47
  }
48
48
  export async function invalidateFileCaches(filePath) {
49
49
  invalidateStatCache(filePath);
package/dist/cli.js CHANGED
@@ -38,7 +38,8 @@ process.on('SIGTERM', function () {
38
38
  cleanup('SIGTERM');
39
39
  });
40
40
  function spawn(entry, nodeArgs) {
41
- const child = fork(join(process.cwd(), entry), {
41
+ const entryPath = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
42
+ const child = fork(entryPath, {
42
43
  stdio: 'inherit',
43
44
  execArgv: [
44
45
  '--enable-source-maps',
@@ -130,6 +131,7 @@ function createWatchConfig(options) {
130
131
  };
131
132
  }
132
133
  async function handler(entry, options, nodeArgs, isWatch) {
134
+ const runtimeEntry = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
133
135
  async function restart(reason) {
134
136
  process.stdout.write('\x1Bc');
135
137
  if (reason) {
@@ -154,14 +156,14 @@ async function handler(entry, options, nodeArgs, isWatch) {
154
156
  }
155
157
  }
156
158
  clearResolveCache();
157
- spawn(entry, nodeArgs);
159
+ spawn(runtimeEntry, nodeArgs);
158
160
  }
159
161
  const restartDebounced = debounce(restart, WATCH_DEBOUNCE_MS);
160
162
  process.stdout.write('\x1Bc');
161
163
  if (isWatch) {
162
164
  console.log(yellow('Watching for changes...'));
163
165
  }
164
- spawn(entry, nodeArgs);
166
+ spawn(runtimeEntry, nodeArgs);
165
167
  if (!isWatch)
166
168
  return;
167
169
  const { ignored, paths } = createWatchConfig(options);
package/dist/loader.js CHANGED
@@ -4,7 +4,7 @@ import { readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import { extname, join, dirname } from 'node:path';
5
5
  import { createHash } from 'node:crypto';
6
6
  import { parse } from './parse.js';
7
- import { clearTranspileCache, ensureTranspileCacheDir, existsWithCache, getTranspileCacheFile, resolveCache } from './cache.js';
7
+ import { ensureTranspileCacheDir, existsWithCache, getTranspileCacheFile, resolveCache } from './cache.js';
8
8
  const tsconfigCache = { paths: null, baseUrl: null };
9
9
  const transpileCache = new Map();
10
10
  const MAX_TRANSPILE_CACHE_ENTRIES = 256;
@@ -16,6 +16,57 @@ function isTypeScriptSpecifier(specifier) {
16
16
  const extension = extname(specifier);
17
17
  return extension === '' || hasTypeScriptExtension(extension);
18
18
  }
19
+ function getTypeScriptTryFiles(basePath) {
20
+ return [
21
+ basePath,
22
+ ...TS_EXTENSIONS.map((extension) => basePath + extension),
23
+ ...TS_EXTENSIONS.map((extension) => join(basePath, 'index' + extension))
24
+ ];
25
+ }
26
+ async function resolveTypeScriptFile(basePath) {
27
+ for (const file of getTypeScriptTryFiles(basePath)) {
28
+ if (await existsWithCache(file)) {
29
+ return file;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ function matchPathPattern(specifier, pattern) {
35
+ const starIndex = pattern.indexOf('*');
36
+ if (starIndex === -1) {
37
+ return specifier === pattern ? [] : null;
38
+ }
39
+ const prefix = pattern.slice(0, starIndex);
40
+ const suffix = pattern.slice(starIndex + 1);
41
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
42
+ return null;
43
+ }
44
+ return [specifier.slice(prefix.length, specifier.length - suffix.length)];
45
+ }
46
+ function applyPathMapping(target, matches) {
47
+ let mapped = target;
48
+ for (const match of matches) {
49
+ mapped = mapped.replace('*', match);
50
+ }
51
+ return mapped;
52
+ }
53
+ async function resolveTsConfigPath(specifier, paths, baseUrl) {
54
+ if (!paths || !baseUrl || !isTypeScriptSpecifier(specifier)) {
55
+ return null;
56
+ }
57
+ for (const [pattern, targets] of Object.entries(paths)) {
58
+ const matches = matchPathPattern(specifier, pattern);
59
+ if (!matches)
60
+ continue;
61
+ for (const target of targets) {
62
+ const mappedTarget = applyPathMapping(target, matches);
63
+ const resolved = await resolveTypeScriptFile(join(baseUrl, mappedTarget));
64
+ if (resolved)
65
+ return resolved;
66
+ }
67
+ }
68
+ return null;
69
+ }
19
70
  function hash(value) {
20
71
  return createHash('sha1').update(value).digest('hex');
21
72
  }
@@ -76,24 +127,33 @@ async function loadTSConfig() {
76
127
  return tsconfigCache;
77
128
  }
78
129
  try {
79
- const data = await readFile(join(import.meta.dirname, '..', 'tsconfig.json'), 'utf-8');
130
+ const data = await readFile(join(process.cwd(), 'tsconfig.json'), 'utf-8');
80
131
  const { compilerOptions } = parse(data);
81
132
  const paths = compilerOptions?.paths ?? null;
82
133
  const baseUrl = compilerOptions?.baseUrl;
83
134
  tsconfigCache.paths = paths || null;
84
135
  tsconfigCache.baseUrl =
85
- baseUrl ?
86
- join(import.meta.dirname, '..', baseUrl)
87
- : join(import.meta.dirname, '..');
136
+ baseUrl ? join(process.cwd(), baseUrl) : process.cwd();
88
137
  return tsconfigCache;
89
138
  }
90
139
  catch {
91
140
  tsconfigCache.paths = null;
92
- tsconfigCache.baseUrl = join(import.meta.dirname, '..');
141
+ tsconfigCache.baseUrl = process.cwd();
93
142
  return tsconfigCache;
94
143
  }
95
144
  }
96
145
  export async function resolve(specifier, ctx, next) {
146
+ const { paths, baseUrl } = await loadTSConfig();
147
+ const resolvedTsConfigPath = await resolveTsConfigPath(specifier, paths, baseUrl);
148
+ if (resolvedTsConfigPath) {
149
+ const url = pathToFileURL(resolvedTsConfigPath).href;
150
+ resolveCache.set(`${ctx.parentURL}::${specifier}`, url);
151
+ return {
152
+ url,
153
+ format: 'module',
154
+ shortCircuit: true
155
+ };
156
+ }
97
157
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
98
158
  return next(specifier, ctx);
99
159
  }
@@ -153,7 +213,10 @@ export async function load(url, ctx, next) {
153
213
  const { paths, baseUrl } = await loadTSConfig();
154
214
  const filename = fileURLToPath(url);
155
215
  const fileStats = await stat(filename);
156
- const configHash = hash(JSON.stringify({ baseUrl: baseUrl || process.cwd(), paths: paths ?? {} }));
216
+ const configHash = hash(JSON.stringify({
217
+ baseUrl: baseUrl || process.cwd(),
218
+ paths: paths ?? {}
219
+ }));
157
220
  const cachedCode = await readCachedTranspile(filename, fileStats.mtimeMs, fileStats.size, configHash);
158
221
  if (cachedCode !== null) {
159
222
  return { format: 'module', source: cachedCode, shortCircuit: true };
@@ -198,12 +261,3 @@ export async function load(url, ctx, next) {
198
261
  });
199
262
  return { format: 'module', source: code, shortCircuit: true };
200
263
  }
201
- export async function resetLoaderState(options) {
202
- tsconfigCache.paths = null;
203
- tsconfigCache.baseUrl = null;
204
- transpileCache.clear();
205
- resolveCache.clear();
206
- if (!options?.preserveTranspileCache) {
207
- await clearTranspileCache();
208
- }
209
- }
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.2",
4
+ "version": "0.1.5",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "esm",