@elliots/typical 0.1.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 +82 -0
- package/bin/ttsc +12 -0
- package/bin/ttsx +3 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +89 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +7 -0
- package/dist/src/config.js +26 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/esm-loader-register.d.ts +1 -0
- package/dist/src/esm-loader-register.js +3 -0
- package/dist/src/esm-loader-register.js.map +1 -0
- package/dist/src/esm-loader.d.ts +4 -0
- package/dist/src/esm-loader.js +25 -0
- package/dist/src/esm-loader.js.map +1 -0
- package/dist/src/file-filter.d.ts +13 -0
- package/dist/src/file-filter.js +38 -0
- package/dist/src/file-filter.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/setup.d.ts +2 -0
- package/dist/src/setup.js +14 -0
- package/dist/src/setup.js.map +1 -0
- package/dist/src/transformer.d.ts +29 -0
- package/dist/src/transformer.js +472 -0
- package/dist/src/transformer.js.map +1 -0
- package/dist/src/tsc-plugin.d.ts +3 -0
- package/dist/src/tsc-plugin.js +9 -0
- package/dist/src/tsc-plugin.js.map +1 -0
- package/package.json +71 -0
- package/src/cli.ts +111 -0
- package/src/config.ts +35 -0
- package/src/esm-loader-register.ts +2 -0
- package/src/esm-loader.ts +26 -0
- package/src/file-filter.ts +44 -0
- package/src/index.ts +2 -0
- package/src/patch-fs.cjs +25 -0
- package/src/patch-tsconfig.cjs +52 -0
- package/src/setup.ts +29 -0
- package/src/transformer.ts +831 -0
- package/src/tsc-plugin.ts +12 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { TypicalTransformer } from './transformer.js';
|
|
7
|
+
import * as ts from 'typescript';
|
|
8
|
+
import { loadConfig } from './config.js';
|
|
9
|
+
import { shouldIncludeFile } from './file-filter.js';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('typical')
|
|
15
|
+
.description('Runtime safe TypeScript transformer using typia')
|
|
16
|
+
.version('0.1.0');
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('transform')
|
|
20
|
+
.description('Transform a TypeScript file with runtime validation')
|
|
21
|
+
.argument('<file>', 'TypeScript file to transform')
|
|
22
|
+
.option('-o, --output <file>', 'Output file')
|
|
23
|
+
.option('-c, --config <file>', 'Config file path', 'typical.json')
|
|
24
|
+
.option('-m, --mode <mode>', 'Transformation mode: basic, typia, js', 'basic')
|
|
25
|
+
.action(async (file: string, options: { output?: string; config?: string; mode?: 'basic' | 'typia' | 'js' }) => {
|
|
26
|
+
try {
|
|
27
|
+
const config = loadConfig(options.config);
|
|
28
|
+
const transformer = new TypicalTransformer(config);
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(file)) {
|
|
31
|
+
console.error(`File not found: ${file}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`Transforming ${file}...`);
|
|
36
|
+
const transformedCode = transformer.transform(path.resolve(file), options.mode ?? 'basic');
|
|
37
|
+
|
|
38
|
+
const outputFilename = options.output ? path.resolve(options.output) : options.mode === 'js' ? file + '.js' : file + '.transformed.ts';
|
|
39
|
+
|
|
40
|
+
const outputFile = options.output ? path.resolve(options.output) : file + '.transformed.ts';
|
|
41
|
+
fs.writeFileSync(outputFile, transformedCode);
|
|
42
|
+
|
|
43
|
+
console.log(`Transformed code written to ${outputFile}`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Transformation failed:', error);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// program
|
|
51
|
+
// .command('build')
|
|
52
|
+
// .description('Transform all TypeScript files in the project')
|
|
53
|
+
// .option('-c, --config <file>', 'Config file path')
|
|
54
|
+
// .option('--dry-run', 'Show what would be transformed without making changes')
|
|
55
|
+
// .action(async (options: { config?: string, dryRun?: boolean }) => {
|
|
56
|
+
// try {
|
|
57
|
+
// const transformer = new TypicalTransformer();
|
|
58
|
+
|
|
59
|
+
// const { glob } = await import('glob');
|
|
60
|
+
|
|
61
|
+
// const config = loadConfig(options.config);
|
|
62
|
+
|
|
63
|
+
// if (!config.include || config.include.length === 0) {
|
|
64
|
+
// console.error('No include patterns specified in config');
|
|
65
|
+
// process.exit(1);
|
|
66
|
+
// }
|
|
67
|
+
|
|
68
|
+
// const files: string[] = [];
|
|
69
|
+
|
|
70
|
+
// for (const pattern of config.include) {
|
|
71
|
+
// const matched = await glob(pattern, {
|
|
72
|
+
// ignore: config.exclude,
|
|
73
|
+
// absolute: true
|
|
74
|
+
// });
|
|
75
|
+
// files.push(...matched);
|
|
76
|
+
// }
|
|
77
|
+
|
|
78
|
+
// console.log(`Found ${files.length} files to transform`);
|
|
79
|
+
|
|
80
|
+
// if (options.dryRun) {
|
|
81
|
+
// files.forEach(file => console.log(`Would transform: ${file}`));
|
|
82
|
+
// return;
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// let transformed = 0;
|
|
86
|
+
|
|
87
|
+
// for (const file of files) {
|
|
88
|
+
// // Double-check with our shared filtering logic
|
|
89
|
+
// if (!shouldIncludeFile(file, config)) {
|
|
90
|
+
// console.log(`Skipping ${file} (excluded by filters)`);
|
|
91
|
+
// continue;
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
// try {
|
|
95
|
+
// console.log(`Transforming ${file}...`);
|
|
96
|
+
// const transformedCode = transformer.transformFile(file, ts);
|
|
97
|
+
// fs.writeFileSync(file, transformedCode);
|
|
98
|
+
// transformed++;
|
|
99
|
+
// } catch (error) {
|
|
100
|
+
// console.error(`Failed to transform ${file}:`, error);
|
|
101
|
+
// }
|
|
102
|
+
// }
|
|
103
|
+
|
|
104
|
+
// console.log(`Successfully transformed ${transformed}/${files.length} files`);
|
|
105
|
+
// } catch (error) {
|
|
106
|
+
// console.error('Build failed:', error);
|
|
107
|
+
// process.exit(1);
|
|
108
|
+
// }
|
|
109
|
+
// });
|
|
110
|
+
|
|
111
|
+
program.parse();
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface TypicalConfig {
|
|
2
|
+
include?: string[];
|
|
3
|
+
exclude?: string[];
|
|
4
|
+
reusableValidators?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const defaultConfig: TypicalConfig = {
|
|
8
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
9
|
+
exclude: ["node_modules/**", "**/*.d.ts", "dist/**", "build/**"],
|
|
10
|
+
reusableValidators: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
|
|
16
|
+
export function loadConfig(configPath?: string): TypicalConfig {
|
|
17
|
+
const configFile = configPath || path.join(process.cwd(), 'typical.json');
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(configFile)) {
|
|
20
|
+
try {
|
|
21
|
+
const configContent = fs.readFileSync(configFile, 'utf8');
|
|
22
|
+
const userConfig: Partial<TypicalConfig> = JSON.parse(configContent);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...defaultConfig,
|
|
26
|
+
...userConfig,
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.warn(`Failed to parse config file ${configFile}:`, error);
|
|
30
|
+
return defaultConfig;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return defaultConfig;
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import { TypicalTransformer } from "./transformer.js";
|
|
3
|
+
|
|
4
|
+
const transformer = new TypicalTransformer();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load hook - transforms TypeScript files on the fly
|
|
8
|
+
*/
|
|
9
|
+
export async function load(url: string, context: any, nextLoad: any) {
|
|
10
|
+
if (!url.endsWith(".ts")) {
|
|
11
|
+
return nextLoad(url, context);
|
|
12
|
+
}
|
|
13
|
+
const filePath = fileURLToPath(url);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const transformedCode = transformer.transform(filePath, 'js');
|
|
17
|
+
return {
|
|
18
|
+
format: "module",
|
|
19
|
+
source: transformedCode,
|
|
20
|
+
shortCircuit: true,
|
|
21
|
+
};
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`Error transforming ${filePath}:`, error);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { minimatch } from 'minimatch';
|
|
3
|
+
import { TypicalConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Determines if a file should be transformed based on include/exclude patterns
|
|
7
|
+
*/
|
|
8
|
+
export function shouldTransformFile(fileName: string, config: TypicalConfig): boolean {
|
|
9
|
+
const relativePath = path.relative(process.cwd(), fileName);
|
|
10
|
+
|
|
11
|
+
// Check include patterns
|
|
12
|
+
const isIncluded = config.include?.some(pattern => {
|
|
13
|
+
return minimatch(relativePath, pattern);
|
|
14
|
+
}) ?? true;
|
|
15
|
+
|
|
16
|
+
if (!isIncluded) return false;
|
|
17
|
+
|
|
18
|
+
// Check exclude patterns
|
|
19
|
+
const isExcluded = config.exclude?.some(pattern => {
|
|
20
|
+
return minimatch(relativePath, pattern);
|
|
21
|
+
}) ?? false;
|
|
22
|
+
|
|
23
|
+
return !isExcluded;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks if a file is a TypeScript file that can be transformed
|
|
28
|
+
*/
|
|
29
|
+
export function isTransformableTypeScriptFile(fileName: string): boolean {
|
|
30
|
+
// Only transform TypeScript files
|
|
31
|
+
if (!/\.(ts|tsx)$/.test(fileName)) return false;
|
|
32
|
+
|
|
33
|
+
// Skip declaration files
|
|
34
|
+
if (fileName.endsWith('.d.ts')) return false;
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Combined check for both file type and include/exclude patterns
|
|
41
|
+
*/
|
|
42
|
+
export function shouldIncludeFile(fileName: string, config: TypicalConfig): boolean {
|
|
43
|
+
return isTransformableTypeScriptFile(fileName) && shouldTransformFile(fileName, config);
|
|
44
|
+
}
|
package/src/index.ts
ADDED
package/src/patch-fs.cjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// logs all readFile and readFileSync calls (for debugging)
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const fsp = require("fs/promises");
|
|
5
|
+
|
|
6
|
+
// monkeypatch promises/readFile
|
|
7
|
+
const origFspReadFile = fsp.readFile;
|
|
8
|
+
fsp.readFile = async function (path, ...args) {
|
|
9
|
+
console.log("fsp.readFile", path);
|
|
10
|
+
return origFspReadFile.call(this, path, ...args);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// monkeypatch readFile
|
|
14
|
+
const origFsReadFile = fs.readFile;
|
|
15
|
+
fs.readFile = async function (path, ...args) {
|
|
16
|
+
console.log("fs.readFile", path);
|
|
17
|
+
return origFsReadFile.call(this, path, ...args);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// monkeypatch readFileSync
|
|
21
|
+
const origFsReadFileSync = fs.readFileSync;
|
|
22
|
+
fs.readFileSync = function (path, ...args) {
|
|
23
|
+
console.log("fs.readFileSync", path);
|
|
24
|
+
return origFsReadFileSync.call(this, path, ...args);
|
|
25
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// monkeypatch fs.readFileSync to automatically add typical/tsc-plugin to tsconfig.json (if not present)
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const stripJsonComments = require('strip-json-comments').default;
|
|
5
|
+
|
|
6
|
+
const origFsReadFileSync = fs.readFileSync;
|
|
7
|
+
|
|
8
|
+
fs.readFileSync = function (path, ...args) {
|
|
9
|
+
const result = origFsReadFileSync.call(this, path, ...args);
|
|
10
|
+
|
|
11
|
+
if (typeof path === "string" && path.endsWith("/tsconfig.json")) {
|
|
12
|
+
try {
|
|
13
|
+
|
|
14
|
+
const json = stripJsonComments(result.toString(), { trailingCommas: true });
|
|
15
|
+
|
|
16
|
+
const config = JSON.parse(json);
|
|
17
|
+
|
|
18
|
+
if (!config.compilerOptions) {
|
|
19
|
+
config.compilerOptions = {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!config.compilerOptions.plugins) {
|
|
23
|
+
config.compilerOptions.plugins = [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const hasTypical = config.compilerOptions.plugins.some(
|
|
27
|
+
(plugin) => plugin.transform === "typical/tsc-plugin"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!hasTypical) {
|
|
31
|
+
if (fs.existsSync("./dist/src/tsc-plugin.js")) {
|
|
32
|
+
console.log("DEV MODE: Adding ./dist/src/tsc-plugin.js to tsconfig.json");
|
|
33
|
+
config.compilerOptions.plugins.push({
|
|
34
|
+
transform: "./dist/src/tsc-plugin.js",
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
config.compilerOptions.plugins.push({
|
|
38
|
+
transform: "typical/tsc-plugin",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// console.log("patched config", JSON.stringify(config, null, 2));
|
|
44
|
+
|
|
45
|
+
return JSON.stringify(config, null, 2);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error("ERROR patching tsconfig.json to add typical/tsc-plugin", e);
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
};
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export function setupTsProgram(tsInstance: typeof ts): ts.Program {
|
|
4
|
+
// Find tsconfig.json
|
|
5
|
+
const tsConfigPath = tsInstance.findConfigFile(
|
|
6
|
+
process.cwd(),
|
|
7
|
+
tsInstance.sys.fileExists,
|
|
8
|
+
"tsconfig.json"
|
|
9
|
+
);
|
|
10
|
+
if (!tsConfigPath) {
|
|
11
|
+
throw new Error("Could not find tsconfig.json");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Load and parse tsconfig.json
|
|
15
|
+
const configFile = tsInstance.readConfigFile(tsConfigPath, tsInstance.sys.readFile);
|
|
16
|
+
const parsedConfig = tsInstance.parseJsonConfigFileContent(
|
|
17
|
+
configFile.config,
|
|
18
|
+
tsInstance.sys,
|
|
19
|
+
process.cwd()
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Create the TypeScript program with all project files
|
|
23
|
+
const tsProgram = tsInstance.createProgram(
|
|
24
|
+
parsedConfig.fileNames,
|
|
25
|
+
parsedConfig.options
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return tsProgram;
|
|
29
|
+
}
|