@elliots/typical 0.1.9 → 0.2.0-beta.1

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.
Files changed (55) hide show
  1. package/README.md +232 -96
  2. package/dist/src/cli.js +14 -60
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/cli.typical.ts +136 -0
  5. package/dist/src/config.d.ts +56 -0
  6. package/dist/src/config.js +124 -32
  7. package/dist/src/config.js.map +1 -1
  8. package/dist/src/config.typical.ts +287 -0
  9. package/dist/src/esm-loader-register.js.map +1 -1
  10. package/dist/src/esm-loader.d.ts +1 -0
  11. package/dist/src/esm-loader.js +31 -8
  12. package/dist/src/esm-loader.js.map +1 -1
  13. package/dist/src/file-filter.d.ts +1 -1
  14. package/dist/src/file-filter.js.map +1 -1
  15. package/dist/src/index.d.ts +4 -1
  16. package/dist/src/index.js +2 -1
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/program-manager.d.ts +27 -0
  19. package/dist/src/program-manager.js +121 -0
  20. package/dist/src/program-manager.js.map +1 -0
  21. package/dist/src/regex-hoister.d.ts +1 -1
  22. package/dist/src/regex-hoister.js +13 -19
  23. package/dist/src/regex-hoister.js.map +1 -1
  24. package/dist/src/setup.d.ts +1 -1
  25. package/dist/src/setup.js +3 -3
  26. package/dist/src/setup.js.map +1 -1
  27. package/dist/src/source-map.d.ts +78 -0
  28. package/dist/src/source-map.js +133 -0
  29. package/dist/src/source-map.js.map +1 -0
  30. package/dist/src/source-map.typical.ts +216 -0
  31. package/dist/src/timing.d.ts +19 -0
  32. package/dist/src/timing.js +65 -0
  33. package/dist/src/timing.js.map +1 -0
  34. package/dist/src/transformer.d.ts +28 -126
  35. package/dist/src/transformer.js +44 -1477
  36. package/dist/src/transformer.js.map +1 -1
  37. package/dist/src/transformer.typical.ts +2552 -0
  38. package/dist/src/tsc-plugin.d.ts +8 -1
  39. package/dist/src/tsc-plugin.js +11 -7
  40. package/dist/src/tsc-plugin.js.map +1 -1
  41. package/package.json +54 -44
  42. package/src/cli.ts +45 -98
  43. package/src/config.ts +200 -57
  44. package/src/esm-loader-register.ts +2 -2
  45. package/src/esm-loader.ts +46 -19
  46. package/src/index.ts +5 -2
  47. package/src/patch-fs.cjs +14 -14
  48. package/src/timing.ts +74 -0
  49. package/src/transformer.ts +52 -1969
  50. package/bin/ttsc +0 -12
  51. package/src/file-filter.ts +0 -49
  52. package/src/patch-tsconfig.cjs +0 -52
  53. package/src/regex-hoister.ts +0 -203
  54. package/src/setup.ts +0 -39
  55. package/src/tsc-plugin.ts +0 -12
package/README.md CHANGED
@@ -1,89 +1,129 @@
1
1
  # Typical
2
2
 
3
- Typical adds runtime validation to typescript, making TypeScript type-safe at runtime *with no changes to your code*.
3
+ Typical makes TypeScript type-safe at runtime _with no changes to your code_.
4
4
 
5
- It can be used as a TSC plugin, ESM loader for Node.js, or with bundlers like Vite, Webpack, and Rollup via unplugin.
5
+ It transforms your code to inject runtime validation based on your existing type annotations. With source maps, so errors point to the right lines in your original code.
6
6
 
7
7
  ## Why?
8
8
 
9
- For some use cases it can mean you don't need to use zod, yup, ajv, or other runtime validation libraries, as your types are already validated automatically.
9
+ - Less need for zod, yup, ajv, or other runtime validation libraries - your types are already validated automatically. If you can express it in TypeScript, Typical can validate it at runtime.
10
+ - Protects against data leaks via `JSON.stringify` by ensuring only properties defined in your types are included
11
+ - Catches type mismatches at runtime that TypeScript can't catch at compile time (API responses, JSON parsing, un-typed/badly-typed libraries, vibe-coding coworkers etc.)
10
12
 
11
- It protects you from leaking data via JSON.stringify by making sure only the properties defined in your types are included in the output.
13
+ ## Features
12
14
 
13
- Why not.
15
+ - Validation of function parameters and return types
16
+ - Safe `JSON.parse` with type validation
17
+ - Safe `JSON.stringify` that only includes defined properties
18
+ - Validation of type casts (`as Type`)
19
+ - Configurable include/exclude patterns
14
20
 
15
- ## Features
21
+ ## Example
16
22
 
17
- - Automatic validation of function parameters
18
- - ✅ Automatic validation of return types
19
- - ✅ Replace `JSON.stringify` with a custom stringifier (very fast!)
20
- - ✅ Replace `JSON.parse` with a custom parser and validator (very fast!)
21
- - ✅ Configurable include/exclude patterns
22
- - ✅ Optionally reuse validation logic for identical types to optimize performance (enabled by default)
23
- - ✅ TSC plugin
24
- - ✅ ESM loader for runtime transformation with `node --import @elliots/typical/esm` (or `node --loader @elliots/typical/esm-loader` for older Node versions)
25
- - ✅ tsx wrapper (ttsx) for easy use like `npx ttsx script.ts`
26
- - ✅ Unplugin for Vite, Webpack, Rollup, esbuild, and more
23
+ This code runs without errors in normal TypeScript, but Typical catches the invalid data:
27
24
 
28
- ## Installation
25
+ ```ts
26
+ interface User {
27
+ name: string;
28
+ email: `${string}@${string}`;
29
+ }
29
30
 
30
- ```bash
31
- npm add typical
31
+ // This will throw - email doesn't match the template literal type
32
+ const user = JSON.parse('{"name":"Alice","email":"not-an-email"}') as User;
32
33
  ```
33
34
 
34
- ## Configuration
35
+ ---
36
+
37
+ ## Usage Options
38
+
39
+ Choose the integration that fits your workflow:
35
40
 
36
- Optional: Create a `typical.json` file in your project root.
41
+ | Method | Best For | Package |
42
+ | --------------------------------------------------------- | ------------------------------- | ----------------------------- |
43
+ | [ESM Loader](#nodejs-esm-loader) | Node.js scripts, development | `@elliots/typical` |
44
+ | [ttsx](#ttsx-tsx-wrapper) | Quick scripts with tsx | `@elliots/typical` + `tsx` |
45
+ | [Bun Plugin](#bun) | Bun projects | `@elliots/bun-plugin-typical` |
46
+ | [Vite/Webpack/etc](#bundlers-vite-webpack-rollup-esbuild) | Frontend apps, bundled projects | `@elliots/unplugin-typical` |
47
+ | [tsc Plugin](#typescript-compiler-tsc) | Pure TypeScript compilation | `@elliots/typical-tsc-plugin` |
37
48
 
38
- If not provided, these default settings will be used.
49
+ ---
50
+
51
+ ## Node.js (ESM Loader)
52
+
53
+ The simplest way to run TypeScript with Typical validation.
54
+
55
+ ```bash
56
+ npm add @elliots/typical
57
+ ```
58
+
59
+ ```bash
60
+ node --import @elliots/typical/esm src/index.ts
61
+ ```
62
+
63
+ Add to `package.json` scripts:
39
64
 
40
65
  ```json
41
66
  {
42
- "include": ["**/*.ts", "**/*.tsx"],
43
- "exclude": ["node_modules/**", "**/*.d.ts", "dist/**", "build/**"],
44
- "reusableValidators": true,
45
- "validateFunctions": true,
46
- "validateCasts": false,
47
- "hoistRegex": true,
48
- "ignoreDOMTypes": true,
49
- "ignoreTypes": []
67
+ "scripts": {
68
+ "start": "node --import @elliots/typical/esm src/index.ts"
69
+ }
50
70
  }
51
71
  ```
52
72
 
53
- ### Configuration Options
73
+ ---
74
+
75
+ ## ttsx (tsx wrapper)
76
+
77
+ A convenience wrapper that combines [tsx](https://github.com/privatenumber/tsx) with Typical.
78
+
79
+ ```bash
80
+ npm add @elliots/typical tsx
81
+ ```
82
+
83
+ ```bash
84
+ npx ttsx script.ts
85
+ ```
86
+
87
+ Or install globally:
54
88
 
55
- | Option | Default | Description |
56
- |--------|---------|-------------|
57
- | `include` | `["**/*.ts", "**/*.tsx"]` | Glob patterns for files to transform |
58
- | `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Glob patterns for files to skip |
59
- | `reusableValidators` | `true` | Create shared validators for identical types (smaller output, allows reuse) |
60
- | `validateFunctions` | `true` | Validate function parameters and return types at runtime |
61
- | `validateCasts` | `false` | Validate type assertions (`as Type`) at runtime |
62
- | `hoistRegex` | `true` | Hoist regex patterns to top-level constants (improves performance) |
63
- | `ignoreDOMTypes` | `true` | Skip validation for DOM types (Document, Element, etc.) |
64
- | `ignoreTypes` | `[]` | Type patterns to skip validation for (supports wildcards, e.g., `["React.*"]`) |
89
+ ```bash
90
+ npm add -g @elliots/typical tsx
91
+ ttsx script.ts
92
+ ```
65
93
 
66
- ## Usage
94
+ > **Note:** `tsx` must be installed separately. The `ttsx` command is a thin wrapper that runs `tsx` with the Typical ESM loader.
67
95
 
68
- See ./samples/esm and ./samples/tsc and ./samples/ttsx
96
+ ---
69
97
 
70
- Quickest way to try it out is to use ttsx:
98
+ ## Bun
71
99
 
72
100
  ```bash
73
- npm add @elliots/typical
74
- npx ttsx your-script.ts
101
+ bun add @elliots/bun-plugin-typical
102
+ ```
103
+
104
+ Create `bunfig.toml`:
105
+
106
+ ```toml
107
+ preload = ["./preload.ts"]
108
+ ```
109
+
110
+ Create `preload.ts`:
111
+
112
+ ```ts
113
+ import { typicalPlugin } from "@elliots/bun-plugin-typical";
114
+
115
+ Bun.plugin(typicalPlugin());
75
116
  ```
76
117
 
77
- or globally:
118
+ Then run:
78
119
 
79
120
  ```bash
80
- npm add -g @elliots/typical
81
- ttsx your-script.ts
121
+ bun run src/index.ts
82
122
  ```
83
123
 
84
- ## Vite / Webpack / Rollup (unplugin)
124
+ ---
85
125
 
86
- Install the unplugin:
126
+ ## Bundlers (Vite, Webpack, Rollup, esbuild)
87
127
 
88
128
  ```bash
89
129
  npm add @elliots/unplugin-typical
@@ -91,91 +131,187 @@ npm add @elliots/unplugin-typical
91
131
 
92
132
  ### Vite
93
133
 
94
- ```typescript
134
+ ```ts
95
135
  // vite.config.ts
96
- import Typical from '@elliots/unplugin-typical/vite'
136
+ import Typical from "@elliots/unplugin-typical/vite";
97
137
 
98
138
  export default defineConfig({
99
- plugins: [
100
- Typical(),
101
- ],
102
- })
139
+ plugins: [Typical()],
140
+ });
103
141
  ```
104
142
 
105
143
  ### Webpack
106
144
 
107
- ```typescript
145
+ ```js
108
146
  // webpack.config.js
109
- const Typical = require('@elliots/unplugin-typical/webpack').default
147
+ const Typical = require("@elliots/unplugin-typical/webpack").default;
110
148
 
111
149
  module.exports = {
112
- plugins: [
113
- Typical(),
114
- ],
115
- }
150
+ plugins: [Typical()],
151
+ };
116
152
  ```
117
153
 
118
154
  ### Rollup
119
155
 
120
- ```typescript
156
+ ```js
121
157
  // rollup.config.js
122
- import Typical from '@elliots/unplugin-typical/rollup'
158
+ import Typical from "@elliots/unplugin-typical/rollup";
123
159
 
124
160
  export default {
125
- plugins: [
126
- Typical(),
127
- ],
128
- }
161
+ plugins: [Typical()],
162
+ };
129
163
  ```
130
164
 
131
165
  ### esbuild
132
166
 
133
- ```typescript
134
- import { build } from 'esbuild'
135
- import Typical from '@elliots/unplugin-typical/esbuild'
167
+ ```ts
168
+ import { build } from "esbuild";
169
+ import Typical from "@elliots/unplugin-typical/esbuild";
136
170
 
137
171
  build({
138
172
  plugins: [Typical()],
139
- })
173
+ });
140
174
  ```
141
175
 
142
- ### Plugin Configuration
176
+ ### Rolldown
143
177
 
144
- Pass options directly to the plugin:
178
+ ```ts
179
+ // rolldown.config.ts
180
+ import Typical from "@elliots/unplugin-typical/rolldown";
145
181
 
146
- ```typescript
147
- Typical({
148
- validateFunctions: true,
149
- validateCasts: false,
150
- // ... other options
151
- })
182
+ export default {
183
+ plugins: [Typical()],
184
+ };
152
185
  ```
153
186
 
154
- Or use a `typical.json` file in your project root (shared with other entry points like TSC plugin and ESM loader).
187
+ ### Farm
155
188
 
156
- ## Example
189
+ ```ts
190
+ // farm.config.ts
191
+ import Typical from "@elliots/unplugin-typical/farm";
157
192
 
158
- This code will run without errors when compiled normally, but will throw an error when using Typical.
193
+ export default {
194
+ plugins: [Typical()],
195
+ };
196
+ ```
159
197
 
198
+ ### Rspack
160
199
 
161
200
  ```ts
162
- interface User {
163
- name: string;
164
- email: `${string}@${string}`;
165
- }
166
- const u = JSON.parse('{"name":"Alice","email":"oops-not-an-email"}') as User;
201
+ // rspack.config.ts
202
+ import Typical from "@elliots/unplugin-typical/rspack";
203
+
204
+ export default {
205
+ plugins: [Typical()],
206
+ };
207
+ ```
208
+
209
+ ---
210
+
211
+ ## TypeScript Compiler (tsc)
212
+
213
+ For projects that compile with `tsc` directly using [ts-patch](https://github.com/nonara/ts-patch).
214
+
215
+ ```bash
216
+ npm add @elliots/typical-tsc-plugin ts-patch
217
+ ```
218
+
219
+ ### Option 1: ttsc (auto-injects plugin)
220
+
221
+ The `ttsc` command automatically injects the plugin - no config needed:
222
+
223
+ ```bash
224
+ npx ttsc
225
+ ```
226
+
227
+ Add to `package.json`:
228
+
229
+ ```json
230
+ {
231
+ "scripts": {
232
+ "build": "ttsc"
233
+ }
234
+ }
235
+ ```
236
+
237
+ ### Option 2: Manual tsconfig.json
238
+
239
+ Add to your `tsconfig.json`:
240
+
241
+ ```json
242
+ {
243
+ "compilerOptions": {
244
+ "plugins": [
245
+ {
246
+ "transform": "@elliots/typical-tsc-plugin",
247
+ "transformProgram": true
248
+ }
249
+ ]
250
+ }
251
+ }
167
252
  ```
168
253
 
169
- ## How it works
254
+ Then run ts-patch's tsc:
170
255
 
171
- Typical uses the TypeScript Compiler API to parse and transform your TypeScript code. It analyzes function signatures, return types, and JSON operations to inject appropriate typia validation calls.
256
+ ```bash
257
+ npx ts-patch install
258
+ npx tsc
259
+ ```
172
260
 
173
- But basically you shouldn't need to care about how it works internally, it makes typescript strongly typed*. You can still use `any` and `unknown` if you want to opt out of type safety.
261
+ Or add a prepare script:
174
262
 
175
- * sort of. probably. something like it anyway.
263
+ ```json
264
+ {
265
+ "scripts": {
266
+ "prepare": "ts-patch install -s",
267
+ "build": "tsc"
268
+ }
269
+ }
270
+ ```
176
271
 
177
- ## Credits
178
- The actual validation work is done by [typia](https://github.com/samchon/typia). This package just generates the necessary code to call typia's functions based on your TypeScript types.
272
+ ---
273
+
274
+ ## Configuration
275
+
276
+ Create a `typical.json` file in your project root (optional):
277
+
278
+ ```json
279
+ {
280
+ "include": ["**/*.ts", "**/*.tsx"],
281
+ "exclude": ["node_modules/**", "**/*.d.ts"],
282
+ "validateFunctions": true,
283
+ "validateCasts": false
284
+ }
285
+ ```
286
+
287
+ ### Options
288
+
289
+ | Option | Default | Description |
290
+ | ------------------- | --------------------------------------------------------- | --------------------------------------------- |
291
+ | `include` | `["**/*.ts", "**/*.tsx"]` | Files to transform |
292
+ | `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Files to skip |
293
+ | `validateFunctions` | `true` | Validate function parameters and return types |
294
+ | `validateCasts` | `false` | Validate type assertions (`as Type`) |
295
+
296
+ ---
297
+
298
+ ## How It Works
299
+
300
+ Typical uses a Go-based compiler that leverages the TypeScript type checker to analyze your code. It generates runtime validators that check values against their declared types.
301
+
302
+ Types that can't be validated at runtime (like generic type parameters `T`) are skipped. You can still use `any` and `unknown` to opt out of validation.
303
+
304
+ ## Debugging
305
+
306
+ Set `DEBUG=1` for verbose logging:
307
+
308
+ ```bash
309
+ DEBUG=1 npm run build
310
+ ```
179
311
 
180
- > NOTE: The whole package was all mostly LLM. Feel free to improve it without care for the author's feelings.
312
+ ## Limitations
181
313
 
314
+ - Generic type parameters (`T`) cannot be validated - no runtime type information
315
+ - Type-only imports of classes aren't checked (can't do instanceof on type-only imports)
316
+ - Validation of functions is just not done. Need to think about that one.
317
+ - Some complex types may not be fully supported yet. If you find any that fail, please open an issue!
package/dist/src/cli.js CHANGED
@@ -3,87 +3,41 @@ import { Command } from 'commander';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
5
  import { TypicalTransformer } from './transformer.js';
6
- import { loadConfig } from './config.js';
6
+ import { loadConfig, validateConfig } from './config.js';
7
7
  const program = new Command();
8
- program
9
- .name('typical')
10
- .description('Runtime safe TypeScript transformer using typia')
11
- .version('0.1.0');
8
+ program.name('typical').description('Runtime safe TypeScript transformer').version('0.1.0');
12
9
  program
13
10
  .command('transform')
14
11
  .description('Transform a TypeScript file with runtime validation')
15
12
  .argument('<file>', 'TypeScript file to transform')
16
13
  .option('-o, --output <file>', 'Output file')
17
14
  .option('-c, --config <file>', 'Config file path', 'typical.json')
18
- .option('-m, --mode <mode>', 'Transformation mode: basic, typia, js', 'basic')
15
+ .option('-p, --project <file>', 'TypeScript config file path', 'tsconfig.json')
19
16
  .action(async (file, options) => {
17
+ let transformer = null;
20
18
  try {
21
- const config = loadConfig(options.config);
22
- const transformer = new TypicalTransformer(config);
19
+ const config = validateConfig(loadConfig(options.config));
20
+ transformer = new TypicalTransformer(config, options.project);
23
21
  if (!fs.existsSync(file)) {
24
22
  console.error(`File not found: ${file}`);
25
23
  process.exit(1);
26
24
  }
27
25
  console.log(`Transforming ${file}...`);
28
- const transformedCode = transformer.transform(path.resolve(file), options.mode ?? 'basic');
29
- const outputFilename = options.output ? path.resolve(options.output) : options.mode === 'js' ? file + '.js' : file + '.transformed.ts';
26
+ const result = await transformer.transform(path.resolve(file), 'ts');
27
+ // Determine output file path
30
28
  const outputFile = options.output ? path.resolve(options.output) : file + '.transformed.ts';
31
- fs.writeFileSync(outputFile, transformedCode);
29
+ fs.writeFileSync(outputFile, result.code);
32
30
  console.log(`Transformed code written to ${outputFile}`);
33
31
  }
34
32
  catch (error) {
35
33
  console.error('Transformation failed:', error);
36
34
  process.exit(1);
37
35
  }
36
+ finally {
37
+ if (transformer) {
38
+ await transformer.close();
39
+ }
40
+ }
38
41
  });
39
- // program
40
- // .command('build')
41
- // .description('Transform all TypeScript files in the project')
42
- // .option('-c, --config <file>', 'Config file path')
43
- // .option('--dry-run', 'Show what would be transformed without making changes')
44
- // .action(async (options: { config?: string, dryRun?: boolean }) => {
45
- // try {
46
- // const transformer = new TypicalTransformer();
47
- // const { glob } = await import('glob');
48
- // const config = loadConfig(options.config);
49
- // if (!config.include || config.include.length === 0) {
50
- // console.error('No include patterns specified in config');
51
- // process.exit(1);
52
- // }
53
- // const files: string[] = [];
54
- // for (const pattern of config.include) {
55
- // const matched = await glob(pattern, {
56
- // ignore: config.exclude,
57
- // absolute: true
58
- // });
59
- // files.push(...matched);
60
- // }
61
- // console.log(`Found ${files.length} files to transform`);
62
- // if (options.dryRun) {
63
- // files.forEach(file => console.log(`Would transform: ${file}`));
64
- // return;
65
- // }
66
- // let transformed = 0;
67
- // for (const file of files) {
68
- // // Double-check with our shared filtering logic
69
- // if (!shouldIncludeFile(file, config)) {
70
- // console.log(`Skipping ${file} (excluded by filters)`);
71
- // continue;
72
- // }
73
- // try {
74
- // console.log(`Transforming ${file}...`);
75
- // const transformedCode = transformer.transformFile(file, ts);
76
- // fs.writeFileSync(file, transformedCode);
77
- // transformed++;
78
- // } catch (error) {
79
- // console.error(`Failed to transform ${file}:`, error);
80
- // }
81
- // }
82
- // console.log(`Successfully transformed ${transformed}/${files.length} files`);
83
- // } catch (error) {
84
- // console.error('Build failed:', error);
85
- // process.exit(1);
86
- // }
87
- // });
88
42
  program.parse();
89
43
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3F,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qDAAqD,CAAC;KAClE,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,CAAC;KAClD,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,cAAc,CAAC;KACjE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,EAAE,eAAe,CAAC;KAC9E,MAAM,CACL,KAAK,EACH,IAAY,EACZ,OAIC,EACD,EAAE;IACF,IAAI,WAAW,GAA8B,IAAI,CAAA;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QACzD,WAAW,GAAG,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;QAE7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAA;YACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAA;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QAEpE,6BAA6B;QAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAE3F,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,CAAC,GAAG,CAAC,+BAA+B,UAAU,EAAE,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAA;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,WAAW,CAAC,KAAK,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;AACH,CAAC,CACF,CAAA;AAEH,OAAO,CAAC,KAAK,EAAE,CAAA"}
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ import typia from "typia";
3
+ //@L:3
4
+ import { Command } from 'commander';
5
+ //@L:4
6
+ import * as fs from 'fs';
7
+ //@L:5
8
+ import * as path from 'path';
9
+ //@L:6
10
+ import { TypicalTransformer } from './transformer.js';
11
+ //@L:7
12
+ import * as ts from 'typescript';
13
+ //@L:8
14
+ import { loadConfig, validateConfig } from './config.js';
15
+ //@L:9
16
+ import { shouldIncludeFile } from './file-filter.js';
17
+ //@L:10
18
+ import { inlineSourceMapComment, externalSourceMapComment } from './source-map.js';
19
+ //@L:12
20
+ const program = new Command();
21
+ //@L:14
22
+ program
23
+ .name('typical')
24
+ .description('Runtime safe TypeScript transformer using typia')
25
+ .version('0.1.0');
26
+ //@L:19
27
+ program
28
+ .command('transform')
29
+ .description('Transform a TypeScript file with runtime validation')
30
+ .argument('<file>', 'TypeScript file to transform')
31
+ .option('-o, --output <file>', 'Output file')
32
+ .option('-c, --config <file>', 'Config file path', 'typical.json')
33
+ .option('-m, --mode <mode>', 'Transformation mode: basic, typia, js', 'basic')
34
+ .option('--source-map', 'Generate external source map file')
35
+ .option('--inline-source-map', 'Include inline source map in output')
36
+ .option('--no-source-map', 'Disable source map generation')
37
+ .action(async (file: string, options: {
38
+ output?: string;
39
+ config?: string;
40
+ mode?: 'basic' | 'typia' | 'js';
41
+ sourceMap?: boolean;
42
+ inlineSourceMap?: boolean;
43
+ }) => {
44
+ try {
45
+ const config = validateConfig(loadConfig(options.config));
46
+ const transformer = new TypicalTransformer(config);
47
+ if (!fs.existsSync(file)) {
48
+ console.error(`File not found: ${file}`);
49
+ process.exit(1);
50
+ }
51
+ // Determine source map behavior
52
+ const generateSourceMap = options.inlineSourceMap || options.sourceMap !== false;
53
+ console.log(`Transforming ${file}...`);
54
+ const result = transformer.transform(path.resolve(file), options.mode ?? 'basic', {
55
+ sourceMap: generateSourceMap,
56
+ });
57
+ // Determine output file path
58
+ const outputFile = options.output
59
+ ? path.resolve(options.output)
60
+ : options.mode === 'js'
61
+ ? file.replace(/\.tsx?$/, '.js')
62
+ : file + '.transformed.ts';
63
+ let outputCode = result.code;
64
+ // Handle source maps
65
+ if (result.map) {
66
+ if (options.inlineSourceMap) {
67
+ // Inline source map as data URL
68
+ outputCode += '\n' + inlineSourceMapComment(result.map);
69
+ }
70
+ else if (options.sourceMap !== false) {
71
+ // Write external source map file
72
+ const mapFile = outputFile + '.map';
73
+ fs.writeFileSync(mapFile, typia.json.stringify(result.map, null, 2));
74
+ outputCode += '\n' + externalSourceMapComment(path.basename(mapFile));
75
+ console.log(`Source map written to ${mapFile}`);
76
+ }
77
+ }
78
+ fs.writeFileSync(outputFile, outputCode);
79
+ console.log(`Transformed code written to ${outputFile}`);
80
+ }
81
+ catch (error) {
82
+ console.error('Transformation failed:', error);
83
+ process.exit(1);
84
+ }
85
+ });
86
+ // program
87
+ // .command('build')
88
+ // .description('Transform all TypeScript files in the project')
89
+ // .option('-c, --config <file>', 'Config file path')
90
+ // .option('--dry-run', 'Show what would be transformed without making changes')
91
+ // .action(async (options: { config?: string, dryRun?: boolean }) => {
92
+ // try {
93
+ // const transformer = new TypicalTransformer();
94
+ // const { glob } = await import('glob');
95
+ // const config = loadConfig(options.config);
96
+ // if (!config.include || config.include.length === 0) {
97
+ // console.error('No include patterns specified in config');
98
+ // process.exit(1);
99
+ // }
100
+ // const files: string[] = [];
101
+ // for (const pattern of config.include) {
102
+ // const matched = await glob(pattern, {
103
+ // ignore: config.exclude,
104
+ // absolute: true
105
+ // });
106
+ // files.push(...matched);
107
+ // }
108
+ // console.log(`Found ${files.length} files to transform`);
109
+ // if (options.dryRun) {
110
+ // files.forEach(file => console.log(`Would transform: ${file}`));
111
+ // return;
112
+ // }
113
+ // let transformed = 0;
114
+ // for (const file of files) {
115
+ // // Double-check with our shared filtering logic
116
+ // if (!shouldIncludeFile(file, config)) {
117
+ // console.log(`Skipping ${file} (excluded by filters)`);
118
+ // continue;
119
+ // }
120
+ // try {
121
+ // console.log(`Transforming ${file}...`);
122
+ // const transformedCode = transformer.transformFile(file, ts);
123
+ // fs.writeFileSync(file, transformedCode);
124
+ // transformed++;
125
+ // } catch (error) {
126
+ // console.error(`Failed to transform ${file}:`, error);
127
+ // }
128
+ // }
129
+ // console.log(`Successfully transformed ${transformed}/${files.length} files`);
130
+ // } catch (error) {
131
+ // console.error('Build failed:', error);
132
+ // process.exit(1);
133
+ // }
134
+ // });
135
+ //@L:145
136
+ program.parse();