@schemashift/core 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 +129 -0
- package/dist/index.cjs +206 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +176 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @schemashift/core
|
|
2
|
+
|
|
3
|
+
Core functionality for SchemaShift schema migrations. Provides schema analysis, detection, and the transform engine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @schemashift/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### SchemaAnalyzer
|
|
14
|
+
|
|
15
|
+
Analyzes TypeScript/JavaScript files to detect schema definitions.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SchemaAnalyzer } from '@schemashift/core';
|
|
19
|
+
|
|
20
|
+
const analyzer = new SchemaAnalyzer('./tsconfig.json');
|
|
21
|
+
analyzer.addSourceFiles(['src/**/*.ts']);
|
|
22
|
+
|
|
23
|
+
const result = analyzer.analyze();
|
|
24
|
+
console.log(`Found ${result.schemas.length} schemas in ${result.filesWithSchemas} files`);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Methods
|
|
28
|
+
|
|
29
|
+
- `addSourceFiles(patterns: string[])` - Add files to analyze by glob patterns
|
|
30
|
+
- `analyze(): AnalysisResult` - Run analysis and return results
|
|
31
|
+
- `getProject(): Project` - Get the underlying ts-morph Project
|
|
32
|
+
|
|
33
|
+
#### AnalysisResult
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface AnalysisResult {
|
|
37
|
+
schemas: SchemaInfo[];
|
|
38
|
+
imports: Map<string, SchemaLibrary>;
|
|
39
|
+
totalFiles: number;
|
|
40
|
+
filesWithSchemas: number;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### detectSchemaLibrary
|
|
45
|
+
|
|
46
|
+
Detects which schema library a module import refers to.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { detectSchemaLibrary } from '@schemashift/core';
|
|
50
|
+
|
|
51
|
+
detectSchemaLibrary('zod'); // 'zod'
|
|
52
|
+
detectSchemaLibrary('yup'); // 'yup'
|
|
53
|
+
detectSchemaLibrary('joi'); // 'joi'
|
|
54
|
+
detectSchemaLibrary('io-ts'); // 'io-ts'
|
|
55
|
+
detectSchemaLibrary('valibot'); // 'valibot'
|
|
56
|
+
detectSchemaLibrary('lodash'); // 'unknown'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### TransformEngine
|
|
60
|
+
|
|
61
|
+
Manages transformation handlers and executes migrations.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { TransformEngine } from '@schemashift/core';
|
|
65
|
+
import { createYupToZodHandler } from '@schemashift/yup-zod';
|
|
66
|
+
|
|
67
|
+
const engine = new TransformEngine();
|
|
68
|
+
engine.registerHandler('yup', 'zod', createYupToZodHandler());
|
|
69
|
+
|
|
70
|
+
const sourceFile = project.getSourceFileOrThrow('schema.ts');
|
|
71
|
+
const result = engine.transform(sourceFile, 'yup', 'zod');
|
|
72
|
+
|
|
73
|
+
if (result.success) {
|
|
74
|
+
console.log(result.transformedCode);
|
|
75
|
+
} else {
|
|
76
|
+
console.error(result.errors);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### Methods
|
|
81
|
+
|
|
82
|
+
- `registerHandler(from, to, handler)` - Register a transformation handler
|
|
83
|
+
- `getHandler(from, to)` - Get a registered handler
|
|
84
|
+
- `transform(sourceFile, from, to)` - Transform a source file
|
|
85
|
+
|
|
86
|
+
### Types
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
type SchemaLibrary =
|
|
90
|
+
| 'zod'
|
|
91
|
+
| 'zod-v3'
|
|
92
|
+
| 'yup'
|
|
93
|
+
| 'joi'
|
|
94
|
+
| 'io-ts'
|
|
95
|
+
| 'valibot'
|
|
96
|
+
| 'v4'
|
|
97
|
+
| 'unknown';
|
|
98
|
+
|
|
99
|
+
interface SchemaInfo {
|
|
100
|
+
name: string;
|
|
101
|
+
filePath: string;
|
|
102
|
+
library: SchemaLibrary;
|
|
103
|
+
lineNumber: number;
|
|
104
|
+
code: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface TransformResult {
|
|
108
|
+
success: boolean;
|
|
109
|
+
filePath: string;
|
|
110
|
+
originalCode: string;
|
|
111
|
+
transformedCode?: string;
|
|
112
|
+
errors: TransformError[];
|
|
113
|
+
warnings: string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface TransformError {
|
|
117
|
+
message: string;
|
|
118
|
+
lineNumber?: number;
|
|
119
|
+
code?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface TransformHandler {
|
|
123
|
+
transform(sourceFile: SourceFile): TransformResult;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SchemaAnalyzer: () => SchemaAnalyzer,
|
|
24
|
+
TransformEngine: () => TransformEngine,
|
|
25
|
+
detectSchemaLibrary: () => detectSchemaLibrary,
|
|
26
|
+
loadConfig: () => loadConfig
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/analyzer.ts
|
|
31
|
+
var import_ts_morph = require("ts-morph");
|
|
32
|
+
|
|
33
|
+
// src/detect.ts
|
|
34
|
+
var LIBRARY_PATTERNS = {
|
|
35
|
+
zod: [/^zod$/, /^zod\//],
|
|
36
|
+
"zod-v3": [/^zod$/],
|
|
37
|
+
// Detected same as zod, version determined by package.json
|
|
38
|
+
yup: [/^yup$/],
|
|
39
|
+
joi: [/^joi$/, /^@hapi\/joi$/],
|
|
40
|
+
"io-ts": [/^io-ts$/, /^io-ts\//],
|
|
41
|
+
valibot: [/^valibot$/],
|
|
42
|
+
v4: [],
|
|
43
|
+
// Target version, not detectable from imports
|
|
44
|
+
unknown: []
|
|
45
|
+
};
|
|
46
|
+
function detectSchemaLibrary(moduleSpecifier) {
|
|
47
|
+
for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {
|
|
48
|
+
if (library === "unknown") continue;
|
|
49
|
+
if (patterns.some((pattern) => pattern.test(moduleSpecifier))) {
|
|
50
|
+
return library;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/analyzer.ts
|
|
57
|
+
var SchemaAnalyzer = class {
|
|
58
|
+
project;
|
|
59
|
+
constructor(tsconfigPath) {
|
|
60
|
+
this.project = new import_ts_morph.Project({
|
|
61
|
+
tsConfigFilePath: tsconfigPath,
|
|
62
|
+
skipAddingFilesFromTsConfig: !tsconfigPath
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
addSourceFiles(patterns) {
|
|
66
|
+
this.project.addSourceFilesAtPaths(patterns);
|
|
67
|
+
}
|
|
68
|
+
analyze() {
|
|
69
|
+
const schemas = [];
|
|
70
|
+
const imports = /* @__PURE__ */ new Map();
|
|
71
|
+
let filesWithSchemas = 0;
|
|
72
|
+
for (const sourceFile of this.project.getSourceFiles()) {
|
|
73
|
+
const fileSchemas = this.analyzeFile(sourceFile);
|
|
74
|
+
if (fileSchemas.length > 0) {
|
|
75
|
+
filesWithSchemas++;
|
|
76
|
+
schemas.push(...fileSchemas);
|
|
77
|
+
}
|
|
78
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
79
|
+
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
80
|
+
const lib = detectSchemaLibrary(moduleSpecifier);
|
|
81
|
+
if (lib !== "unknown") {
|
|
82
|
+
imports.set(sourceFile.getFilePath(), lib);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
schemas,
|
|
88
|
+
imports,
|
|
89
|
+
totalFiles: this.project.getSourceFiles().length,
|
|
90
|
+
filesWithSchemas
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
analyzeFile(sourceFile) {
|
|
94
|
+
const schemas = [];
|
|
95
|
+
const filePath = sourceFile.getFilePath();
|
|
96
|
+
const library = this.detectFileLibrary(sourceFile);
|
|
97
|
+
if (library === "unknown") return schemas;
|
|
98
|
+
sourceFile.getVariableDeclarations().forEach((varDecl) => {
|
|
99
|
+
const initializer = varDecl.getInitializer();
|
|
100
|
+
if (initializer && this.isSchemaExpression(initializer, library)) {
|
|
101
|
+
schemas.push({
|
|
102
|
+
name: varDecl.getName(),
|
|
103
|
+
filePath,
|
|
104
|
+
library,
|
|
105
|
+
lineNumber: varDecl.getStartLineNumber(),
|
|
106
|
+
code: varDecl.getText()
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return schemas;
|
|
111
|
+
}
|
|
112
|
+
detectFileLibrary(sourceFile) {
|
|
113
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
114
|
+
const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());
|
|
115
|
+
if (lib !== "unknown") return lib;
|
|
116
|
+
}
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
isSchemaExpression(node, library) {
|
|
120
|
+
const text = node.getText();
|
|
121
|
+
switch (library) {
|
|
122
|
+
case "zod":
|
|
123
|
+
return text.includes("z.") || text.includes("zod.");
|
|
124
|
+
case "yup":
|
|
125
|
+
return text.includes("yup.") || text.includes("Yup.");
|
|
126
|
+
case "joi":
|
|
127
|
+
return text.includes("Joi.") || text.includes("joi.");
|
|
128
|
+
case "io-ts":
|
|
129
|
+
return text.includes("t.") && node.getSourceFile().getText().includes("from 'io-ts'");
|
|
130
|
+
case "valibot":
|
|
131
|
+
return text.includes("v.") || text.includes("valibot.");
|
|
132
|
+
default:
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
getProject() {
|
|
137
|
+
return this.project;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/config.ts
|
|
142
|
+
var import_cosmiconfig = require("cosmiconfig");
|
|
143
|
+
async function loadConfig(configPath) {
|
|
144
|
+
const explorer = (0, import_cosmiconfig.cosmiconfig)("schemashift", {
|
|
145
|
+
searchPlaces: [
|
|
146
|
+
".schemashiftrc",
|
|
147
|
+
".schemashiftrc.json",
|
|
148
|
+
".schemashiftrc.yaml",
|
|
149
|
+
".schemashiftrc.yml",
|
|
150
|
+
".schemashiftrc.js",
|
|
151
|
+
".schemashiftrc.cjs",
|
|
152
|
+
"schemashift.config.js",
|
|
153
|
+
"schemashift.config.cjs",
|
|
154
|
+
"package.json"
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
const result = configPath ? await explorer.load(configPath) : await explorer.search();
|
|
158
|
+
return {
|
|
159
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
160
|
+
exclude: ["**/node_modules/**", "**/dist/**", "**/*.d.ts"],
|
|
161
|
+
git: { enabled: false },
|
|
162
|
+
backup: { enabled: true, dir: ".schemashift-backup" },
|
|
163
|
+
...result?.config
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/transform.ts
|
|
168
|
+
var TransformEngine = class {
|
|
169
|
+
handlers = /* @__PURE__ */ new Map();
|
|
170
|
+
registerHandler(from, to, handler) {
|
|
171
|
+
this.handlers.set(`${from}->${to}`, handler);
|
|
172
|
+
}
|
|
173
|
+
getHandler(from, to) {
|
|
174
|
+
return this.handlers.get(`${from}->${to}`);
|
|
175
|
+
}
|
|
176
|
+
hasHandler(from, to) {
|
|
177
|
+
return this.handlers.has(`${from}->${to}`);
|
|
178
|
+
}
|
|
179
|
+
getSupportedPaths() {
|
|
180
|
+
return Array.from(this.handlers.keys()).map((key) => {
|
|
181
|
+
const [from, to] = key.split("->");
|
|
182
|
+
return { from, to };
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
transform(sourceFile, from, to, options) {
|
|
186
|
+
const handler = this.getHandler(from, to);
|
|
187
|
+
if (!handler) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
filePath: sourceFile.getFilePath(),
|
|
191
|
+
originalCode: sourceFile.getFullText(),
|
|
192
|
+
errors: [{ message: `No handler for ${from}->${to}` }],
|
|
193
|
+
warnings: []
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return handler.transform(sourceFile, options);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
200
|
+
0 && (module.exports = {
|
|
201
|
+
SchemaAnalyzer,
|
|
202
|
+
TransformEngine,
|
|
203
|
+
detectSchemaLibrary,
|
|
204
|
+
loadConfig
|
|
205
|
+
});
|
|
206
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/analyzer.ts","../src/detect.ts","../src/config.ts","../src/transform.ts"],"sourcesContent":["export { type AnalysisResult, SchemaAnalyzer } from './analyzer.js';\nexport {\n type CustomRule,\n loadConfig,\n type SchemaShiftConfig,\n} from './config.js';\nexport { detectSchemaLibrary } from './detect.js';\nexport { TransformEngine, type TransformHandler } from './transform.js';\nexport * from './types.js';\n","import { type Node, Project, type SourceFile, type VariableDeclaration } from 'ts-morph';\nimport { detectSchemaLibrary } from './detect.js';\nimport type { SchemaInfo, SchemaLibrary } from './types.js';\n\nexport interface AnalysisResult {\n schemas: SchemaInfo[];\n imports: Map<string, SchemaLibrary>;\n totalFiles: number;\n filesWithSchemas: number;\n}\n\nexport class SchemaAnalyzer {\n private project: Project;\n\n constructor(tsconfigPath?: string) {\n this.project = new Project({\n tsConfigFilePath: tsconfigPath,\n skipAddingFilesFromTsConfig: !tsconfigPath,\n });\n }\n\n addSourceFiles(patterns: string[]): void {\n this.project.addSourceFilesAtPaths(patterns);\n }\n\n analyze(): AnalysisResult {\n const schemas: SchemaInfo[] = [];\n const imports = new Map<string, SchemaLibrary>();\n let filesWithSchemas = 0;\n\n for (const sourceFile of this.project.getSourceFiles()) {\n const fileSchemas = this.analyzeFile(sourceFile);\n if (fileSchemas.length > 0) {\n filesWithSchemas++;\n schemas.push(...fileSchemas);\n }\n\n for (const imp of sourceFile.getImportDeclarations()) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n const lib = detectSchemaLibrary(moduleSpecifier);\n if (lib !== 'unknown') {\n imports.set(sourceFile.getFilePath(), lib);\n }\n }\n }\n\n return {\n schemas,\n imports,\n totalFiles: this.project.getSourceFiles().length,\n filesWithSchemas,\n };\n }\n\n private analyzeFile(sourceFile: SourceFile): SchemaInfo[] {\n const schemas: SchemaInfo[] = [];\n const filePath = sourceFile.getFilePath();\n const library = this.detectFileLibrary(sourceFile);\n\n if (library === 'unknown') return schemas;\n\n sourceFile.getVariableDeclarations().forEach((varDecl: VariableDeclaration) => {\n const initializer = varDecl.getInitializer();\n if (initializer && this.isSchemaExpression(initializer, library)) {\n schemas.push({\n name: varDecl.getName(),\n filePath,\n library,\n lineNumber: varDecl.getStartLineNumber(),\n code: varDecl.getText(),\n });\n }\n });\n\n return schemas;\n }\n\n private detectFileLibrary(sourceFile: SourceFile): SchemaLibrary {\n for (const imp of sourceFile.getImportDeclarations()) {\n const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());\n if (lib !== 'unknown') return lib;\n }\n return 'unknown';\n }\n\n private isSchemaExpression(node: Node, library: SchemaLibrary): boolean {\n const text = node.getText();\n switch (library) {\n case 'zod':\n return text.includes('z.') || text.includes('zod.');\n case 'yup':\n return text.includes('yup.') || text.includes('Yup.');\n case 'joi':\n return text.includes('Joi.') || text.includes('joi.');\n case 'io-ts':\n return text.includes('t.') && node.getSourceFile().getText().includes(\"from 'io-ts'\");\n case 'valibot':\n return text.includes('v.') || text.includes('valibot.');\n default:\n return false;\n }\n }\n\n getProject(): Project {\n return this.project;\n }\n}\n","import type { SchemaLibrary } from './types.js';\n\nconst LIBRARY_PATTERNS: Record<SchemaLibrary, RegExp[]> = {\n zod: [/^zod$/, /^zod\\//],\n 'zod-v3': [/^zod$/], // Detected same as zod, version determined by package.json\n yup: [/^yup$/],\n joi: [/^joi$/, /^@hapi\\/joi$/],\n 'io-ts': [/^io-ts$/, /^io-ts\\//],\n valibot: [/^valibot$/],\n v4: [], // Target version, not detectable from imports\n unknown: [],\n};\n\nexport function detectSchemaLibrary(moduleSpecifier: string): SchemaLibrary {\n for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {\n if (library === 'unknown') continue;\n if (patterns.some((pattern) => pattern.test(moduleSpecifier))) {\n return library as SchemaLibrary;\n }\n }\n return 'unknown';\n}\n","import { cosmiconfig } from 'cosmiconfig';\n\nexport interface SchemaShiftConfig {\n // File patterns\n include: string[];\n exclude: string[];\n\n // Migration defaults\n defaultFrom?: string;\n defaultTo?: string;\n\n // Output\n outputDir?: string;\n reportFormat?: 'json' | 'html' | 'console';\n\n // Git integration\n git?: {\n enabled: boolean;\n createBranch?: boolean;\n branchPrefix?: string;\n autoCommit?: boolean;\n commitMessage?: string;\n };\n\n // Backup/Rollback\n backup?: {\n enabled: boolean;\n dir?: string;\n };\n\n // Custom rules\n customRules?: CustomRule[];\n\n // CI mode\n ci?: boolean;\n}\n\nexport interface CustomRule {\n name: string;\n description?: string;\n match: {\n library: string;\n method: string;\n pattern?: string;\n };\n transform: {\n method: string;\n args?: string[];\n warning?: string;\n };\n}\n\nexport async function loadConfig(configPath?: string): Promise<SchemaShiftConfig> {\n const explorer = cosmiconfig('schemashift', {\n searchPlaces: [\n '.schemashiftrc',\n '.schemashiftrc.json',\n '.schemashiftrc.yaml',\n '.schemashiftrc.yml',\n '.schemashiftrc.js',\n '.schemashiftrc.cjs',\n 'schemashift.config.js',\n 'schemashift.config.cjs',\n 'package.json',\n ],\n });\n\n const result = configPath ? await explorer.load(configPath) : await explorer.search();\n\n return {\n include: ['**/*.ts', '**/*.tsx'],\n exclude: ['**/node_modules/**', '**/dist/**', '**/*.d.ts'],\n git: { enabled: false },\n backup: { enabled: true, dir: '.schemashift-backup' },\n ...result?.config,\n };\n}\n","import type { SourceFile } from 'ts-morph';\nimport type { SchemaLibrary, TransformOptions, TransformResult } from './types.js';\n\nexport interface TransformHandler {\n transform(sourceFile: SourceFile, options: TransformOptions): TransformResult;\n}\n\nexport class TransformEngine {\n private handlers = new Map<string, TransformHandler>();\n\n registerHandler(from: SchemaLibrary, to: SchemaLibrary, handler: TransformHandler): void {\n this.handlers.set(`${from}->${to}`, handler);\n }\n\n getHandler(from: SchemaLibrary, to: SchemaLibrary): TransformHandler | undefined {\n return this.handlers.get(`${from}->${to}`);\n }\n\n hasHandler(from: SchemaLibrary, to: SchemaLibrary): boolean {\n return this.handlers.has(`${from}->${to}`);\n }\n\n getSupportedPaths(): Array<{ from: SchemaLibrary; to: SchemaLibrary }> {\n return Array.from(this.handlers.keys()).map((key) => {\n const [from, to] = key.split('->') as [SchemaLibrary, SchemaLibrary];\n return { from, to };\n });\n }\n\n transform(\n sourceFile: SourceFile,\n from: SchemaLibrary,\n to: SchemaLibrary,\n options: TransformOptions,\n ): TransformResult {\n const handler = this.getHandler(from, to);\n if (!handler) {\n return {\n success: false,\n filePath: sourceFile.getFilePath(),\n originalCode: sourceFile.getFullText(),\n errors: [{ message: `No handler for ${from}->${to}` }],\n warnings: [],\n };\n }\n return handler.transform(sourceFile, options);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Project, SourceFile } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
type SchemaLibrary = 'zod' | 'zod-v3' | 'yup' | 'joi' | 'io-ts' | 'valibot' | 'v4' | 'unknown';
|
|
4
|
+
interface SchemaInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
library: SchemaLibrary;
|
|
8
|
+
lineNumber: number;
|
|
9
|
+
code: string;
|
|
10
|
+
}
|
|
11
|
+
interface TransformResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
filePath: string;
|
|
14
|
+
originalCode: string;
|
|
15
|
+
transformedCode?: string;
|
|
16
|
+
errors: TransformError[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
interface TransformError {
|
|
20
|
+
message: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
column?: number;
|
|
23
|
+
suggestion?: string;
|
|
24
|
+
}
|
|
25
|
+
interface TransformOptions {
|
|
26
|
+
from: SchemaLibrary;
|
|
27
|
+
to: SchemaLibrary;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
preserveComments?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface AnalysisResult {
|
|
33
|
+
schemas: SchemaInfo[];
|
|
34
|
+
imports: Map<string, SchemaLibrary>;
|
|
35
|
+
totalFiles: number;
|
|
36
|
+
filesWithSchemas: number;
|
|
37
|
+
}
|
|
38
|
+
declare class SchemaAnalyzer {
|
|
39
|
+
private project;
|
|
40
|
+
constructor(tsconfigPath?: string);
|
|
41
|
+
addSourceFiles(patterns: string[]): void;
|
|
42
|
+
analyze(): AnalysisResult;
|
|
43
|
+
private analyzeFile;
|
|
44
|
+
private detectFileLibrary;
|
|
45
|
+
private isSchemaExpression;
|
|
46
|
+
getProject(): Project;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SchemaShiftConfig {
|
|
50
|
+
include: string[];
|
|
51
|
+
exclude: string[];
|
|
52
|
+
defaultFrom?: string;
|
|
53
|
+
defaultTo?: string;
|
|
54
|
+
outputDir?: string;
|
|
55
|
+
reportFormat?: 'json' | 'html' | 'console';
|
|
56
|
+
git?: {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
createBranch?: boolean;
|
|
59
|
+
branchPrefix?: string;
|
|
60
|
+
autoCommit?: boolean;
|
|
61
|
+
commitMessage?: string;
|
|
62
|
+
};
|
|
63
|
+
backup?: {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
dir?: string;
|
|
66
|
+
};
|
|
67
|
+
customRules?: CustomRule[];
|
|
68
|
+
ci?: boolean;
|
|
69
|
+
}
|
|
70
|
+
interface CustomRule {
|
|
71
|
+
name: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
match: {
|
|
74
|
+
library: string;
|
|
75
|
+
method: string;
|
|
76
|
+
pattern?: string;
|
|
77
|
+
};
|
|
78
|
+
transform: {
|
|
79
|
+
method: string;
|
|
80
|
+
args?: string[];
|
|
81
|
+
warning?: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
declare function loadConfig(configPath?: string): Promise<SchemaShiftConfig>;
|
|
85
|
+
|
|
86
|
+
declare function detectSchemaLibrary(moduleSpecifier: string): SchemaLibrary;
|
|
87
|
+
|
|
88
|
+
interface TransformHandler {
|
|
89
|
+
transform(sourceFile: SourceFile, options: TransformOptions): TransformResult;
|
|
90
|
+
}
|
|
91
|
+
declare class TransformEngine {
|
|
92
|
+
private handlers;
|
|
93
|
+
registerHandler(from: SchemaLibrary, to: SchemaLibrary, handler: TransformHandler): void;
|
|
94
|
+
getHandler(from: SchemaLibrary, to: SchemaLibrary): TransformHandler | undefined;
|
|
95
|
+
hasHandler(from: SchemaLibrary, to: SchemaLibrary): boolean;
|
|
96
|
+
getSupportedPaths(): Array<{
|
|
97
|
+
from: SchemaLibrary;
|
|
98
|
+
to: SchemaLibrary;
|
|
99
|
+
}>;
|
|
100
|
+
transform(sourceFile: SourceFile, from: SchemaLibrary, to: SchemaLibrary, options: TransformOptions): TransformResult;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { type AnalysisResult, type CustomRule, SchemaAnalyzer, type SchemaInfo, type SchemaLibrary, type SchemaShiftConfig, TransformEngine, type TransformError, type TransformHandler, type TransformOptions, type TransformResult, detectSchemaLibrary, loadConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Project, SourceFile } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
type SchemaLibrary = 'zod' | 'zod-v3' | 'yup' | 'joi' | 'io-ts' | 'valibot' | 'v4' | 'unknown';
|
|
4
|
+
interface SchemaInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
library: SchemaLibrary;
|
|
8
|
+
lineNumber: number;
|
|
9
|
+
code: string;
|
|
10
|
+
}
|
|
11
|
+
interface TransformResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
filePath: string;
|
|
14
|
+
originalCode: string;
|
|
15
|
+
transformedCode?: string;
|
|
16
|
+
errors: TransformError[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
interface TransformError {
|
|
20
|
+
message: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
column?: number;
|
|
23
|
+
suggestion?: string;
|
|
24
|
+
}
|
|
25
|
+
interface TransformOptions {
|
|
26
|
+
from: SchemaLibrary;
|
|
27
|
+
to: SchemaLibrary;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
preserveComments?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface AnalysisResult {
|
|
33
|
+
schemas: SchemaInfo[];
|
|
34
|
+
imports: Map<string, SchemaLibrary>;
|
|
35
|
+
totalFiles: number;
|
|
36
|
+
filesWithSchemas: number;
|
|
37
|
+
}
|
|
38
|
+
declare class SchemaAnalyzer {
|
|
39
|
+
private project;
|
|
40
|
+
constructor(tsconfigPath?: string);
|
|
41
|
+
addSourceFiles(patterns: string[]): void;
|
|
42
|
+
analyze(): AnalysisResult;
|
|
43
|
+
private analyzeFile;
|
|
44
|
+
private detectFileLibrary;
|
|
45
|
+
private isSchemaExpression;
|
|
46
|
+
getProject(): Project;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SchemaShiftConfig {
|
|
50
|
+
include: string[];
|
|
51
|
+
exclude: string[];
|
|
52
|
+
defaultFrom?: string;
|
|
53
|
+
defaultTo?: string;
|
|
54
|
+
outputDir?: string;
|
|
55
|
+
reportFormat?: 'json' | 'html' | 'console';
|
|
56
|
+
git?: {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
createBranch?: boolean;
|
|
59
|
+
branchPrefix?: string;
|
|
60
|
+
autoCommit?: boolean;
|
|
61
|
+
commitMessage?: string;
|
|
62
|
+
};
|
|
63
|
+
backup?: {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
dir?: string;
|
|
66
|
+
};
|
|
67
|
+
customRules?: CustomRule[];
|
|
68
|
+
ci?: boolean;
|
|
69
|
+
}
|
|
70
|
+
interface CustomRule {
|
|
71
|
+
name: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
match: {
|
|
74
|
+
library: string;
|
|
75
|
+
method: string;
|
|
76
|
+
pattern?: string;
|
|
77
|
+
};
|
|
78
|
+
transform: {
|
|
79
|
+
method: string;
|
|
80
|
+
args?: string[];
|
|
81
|
+
warning?: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
declare function loadConfig(configPath?: string): Promise<SchemaShiftConfig>;
|
|
85
|
+
|
|
86
|
+
declare function detectSchemaLibrary(moduleSpecifier: string): SchemaLibrary;
|
|
87
|
+
|
|
88
|
+
interface TransformHandler {
|
|
89
|
+
transform(sourceFile: SourceFile, options: TransformOptions): TransformResult;
|
|
90
|
+
}
|
|
91
|
+
declare class TransformEngine {
|
|
92
|
+
private handlers;
|
|
93
|
+
registerHandler(from: SchemaLibrary, to: SchemaLibrary, handler: TransformHandler): void;
|
|
94
|
+
getHandler(from: SchemaLibrary, to: SchemaLibrary): TransformHandler | undefined;
|
|
95
|
+
hasHandler(from: SchemaLibrary, to: SchemaLibrary): boolean;
|
|
96
|
+
getSupportedPaths(): Array<{
|
|
97
|
+
from: SchemaLibrary;
|
|
98
|
+
to: SchemaLibrary;
|
|
99
|
+
}>;
|
|
100
|
+
transform(sourceFile: SourceFile, from: SchemaLibrary, to: SchemaLibrary, options: TransformOptions): TransformResult;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { type AnalysisResult, type CustomRule, SchemaAnalyzer, type SchemaInfo, type SchemaLibrary, type SchemaShiftConfig, TransformEngine, type TransformError, type TransformHandler, type TransformOptions, type TransformResult, detectSchemaLibrary, loadConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// src/analyzer.ts
|
|
2
|
+
import { Project } from "ts-morph";
|
|
3
|
+
|
|
4
|
+
// src/detect.ts
|
|
5
|
+
var LIBRARY_PATTERNS = {
|
|
6
|
+
zod: [/^zod$/, /^zod\//],
|
|
7
|
+
"zod-v3": [/^zod$/],
|
|
8
|
+
// Detected same as zod, version determined by package.json
|
|
9
|
+
yup: [/^yup$/],
|
|
10
|
+
joi: [/^joi$/, /^@hapi\/joi$/],
|
|
11
|
+
"io-ts": [/^io-ts$/, /^io-ts\//],
|
|
12
|
+
valibot: [/^valibot$/],
|
|
13
|
+
v4: [],
|
|
14
|
+
// Target version, not detectable from imports
|
|
15
|
+
unknown: []
|
|
16
|
+
};
|
|
17
|
+
function detectSchemaLibrary(moduleSpecifier) {
|
|
18
|
+
for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {
|
|
19
|
+
if (library === "unknown") continue;
|
|
20
|
+
if (patterns.some((pattern) => pattern.test(moduleSpecifier))) {
|
|
21
|
+
return library;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/analyzer.ts
|
|
28
|
+
var SchemaAnalyzer = class {
|
|
29
|
+
project;
|
|
30
|
+
constructor(tsconfigPath) {
|
|
31
|
+
this.project = new Project({
|
|
32
|
+
tsConfigFilePath: tsconfigPath,
|
|
33
|
+
skipAddingFilesFromTsConfig: !tsconfigPath
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
addSourceFiles(patterns) {
|
|
37
|
+
this.project.addSourceFilesAtPaths(patterns);
|
|
38
|
+
}
|
|
39
|
+
analyze() {
|
|
40
|
+
const schemas = [];
|
|
41
|
+
const imports = /* @__PURE__ */ new Map();
|
|
42
|
+
let filesWithSchemas = 0;
|
|
43
|
+
for (const sourceFile of this.project.getSourceFiles()) {
|
|
44
|
+
const fileSchemas = this.analyzeFile(sourceFile);
|
|
45
|
+
if (fileSchemas.length > 0) {
|
|
46
|
+
filesWithSchemas++;
|
|
47
|
+
schemas.push(...fileSchemas);
|
|
48
|
+
}
|
|
49
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
50
|
+
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
51
|
+
const lib = detectSchemaLibrary(moduleSpecifier);
|
|
52
|
+
if (lib !== "unknown") {
|
|
53
|
+
imports.set(sourceFile.getFilePath(), lib);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
schemas,
|
|
59
|
+
imports,
|
|
60
|
+
totalFiles: this.project.getSourceFiles().length,
|
|
61
|
+
filesWithSchemas
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
analyzeFile(sourceFile) {
|
|
65
|
+
const schemas = [];
|
|
66
|
+
const filePath = sourceFile.getFilePath();
|
|
67
|
+
const library = this.detectFileLibrary(sourceFile);
|
|
68
|
+
if (library === "unknown") return schemas;
|
|
69
|
+
sourceFile.getVariableDeclarations().forEach((varDecl) => {
|
|
70
|
+
const initializer = varDecl.getInitializer();
|
|
71
|
+
if (initializer && this.isSchemaExpression(initializer, library)) {
|
|
72
|
+
schemas.push({
|
|
73
|
+
name: varDecl.getName(),
|
|
74
|
+
filePath,
|
|
75
|
+
library,
|
|
76
|
+
lineNumber: varDecl.getStartLineNumber(),
|
|
77
|
+
code: varDecl.getText()
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return schemas;
|
|
82
|
+
}
|
|
83
|
+
detectFileLibrary(sourceFile) {
|
|
84
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
85
|
+
const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());
|
|
86
|
+
if (lib !== "unknown") return lib;
|
|
87
|
+
}
|
|
88
|
+
return "unknown";
|
|
89
|
+
}
|
|
90
|
+
isSchemaExpression(node, library) {
|
|
91
|
+
const text = node.getText();
|
|
92
|
+
switch (library) {
|
|
93
|
+
case "zod":
|
|
94
|
+
return text.includes("z.") || text.includes("zod.");
|
|
95
|
+
case "yup":
|
|
96
|
+
return text.includes("yup.") || text.includes("Yup.");
|
|
97
|
+
case "joi":
|
|
98
|
+
return text.includes("Joi.") || text.includes("joi.");
|
|
99
|
+
case "io-ts":
|
|
100
|
+
return text.includes("t.") && node.getSourceFile().getText().includes("from 'io-ts'");
|
|
101
|
+
case "valibot":
|
|
102
|
+
return text.includes("v.") || text.includes("valibot.");
|
|
103
|
+
default:
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
getProject() {
|
|
108
|
+
return this.project;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/config.ts
|
|
113
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
114
|
+
async function loadConfig(configPath) {
|
|
115
|
+
const explorer = cosmiconfig("schemashift", {
|
|
116
|
+
searchPlaces: [
|
|
117
|
+
".schemashiftrc",
|
|
118
|
+
".schemashiftrc.json",
|
|
119
|
+
".schemashiftrc.yaml",
|
|
120
|
+
".schemashiftrc.yml",
|
|
121
|
+
".schemashiftrc.js",
|
|
122
|
+
".schemashiftrc.cjs",
|
|
123
|
+
"schemashift.config.js",
|
|
124
|
+
"schemashift.config.cjs",
|
|
125
|
+
"package.json"
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
const result = configPath ? await explorer.load(configPath) : await explorer.search();
|
|
129
|
+
return {
|
|
130
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
131
|
+
exclude: ["**/node_modules/**", "**/dist/**", "**/*.d.ts"],
|
|
132
|
+
git: { enabled: false },
|
|
133
|
+
backup: { enabled: true, dir: ".schemashift-backup" },
|
|
134
|
+
...result?.config
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/transform.ts
|
|
139
|
+
var TransformEngine = class {
|
|
140
|
+
handlers = /* @__PURE__ */ new Map();
|
|
141
|
+
registerHandler(from, to, handler) {
|
|
142
|
+
this.handlers.set(`${from}->${to}`, handler);
|
|
143
|
+
}
|
|
144
|
+
getHandler(from, to) {
|
|
145
|
+
return this.handlers.get(`${from}->${to}`);
|
|
146
|
+
}
|
|
147
|
+
hasHandler(from, to) {
|
|
148
|
+
return this.handlers.has(`${from}->${to}`);
|
|
149
|
+
}
|
|
150
|
+
getSupportedPaths() {
|
|
151
|
+
return Array.from(this.handlers.keys()).map((key) => {
|
|
152
|
+
const [from, to] = key.split("->");
|
|
153
|
+
return { from, to };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
transform(sourceFile, from, to, options) {
|
|
157
|
+
const handler = this.getHandler(from, to);
|
|
158
|
+
if (!handler) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
filePath: sourceFile.getFilePath(),
|
|
162
|
+
originalCode: sourceFile.getFullText(),
|
|
163
|
+
errors: [{ message: `No handler for ${from}->${to}` }],
|
|
164
|
+
warnings: []
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return handler.transform(sourceFile, options);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
export {
|
|
171
|
+
SchemaAnalyzer,
|
|
172
|
+
TransformEngine,
|
|
173
|
+
detectSchemaLibrary,
|
|
174
|
+
loadConfig
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts","../src/detect.ts","../src/config.ts","../src/transform.ts"],"sourcesContent":["import { type Node, Project, type SourceFile, type VariableDeclaration } from 'ts-morph';\nimport { detectSchemaLibrary } from './detect.js';\nimport type { SchemaInfo, SchemaLibrary } from './types.js';\n\nexport interface AnalysisResult {\n schemas: SchemaInfo[];\n imports: Map<string, SchemaLibrary>;\n totalFiles: number;\n filesWithSchemas: number;\n}\n\nexport class SchemaAnalyzer {\n private project: Project;\n\n constructor(tsconfigPath?: string) {\n this.project = new Project({\n tsConfigFilePath: tsconfigPath,\n skipAddingFilesFromTsConfig: !tsconfigPath,\n });\n }\n\n addSourceFiles(patterns: string[]): void {\n this.project.addSourceFilesAtPaths(patterns);\n }\n\n analyze(): AnalysisResult {\n const schemas: SchemaInfo[] = [];\n const imports = new Map<string, SchemaLibrary>();\n let filesWithSchemas = 0;\n\n for (const sourceFile of this.project.getSourceFiles()) {\n const fileSchemas = this.analyzeFile(sourceFile);\n if (fileSchemas.length > 0) {\n filesWithSchemas++;\n schemas.push(...fileSchemas);\n }\n\n for (const imp of sourceFile.getImportDeclarations()) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n const lib = detectSchemaLibrary(moduleSpecifier);\n if (lib !== 'unknown') {\n imports.set(sourceFile.getFilePath(), lib);\n }\n }\n }\n\n return {\n schemas,\n imports,\n totalFiles: this.project.getSourceFiles().length,\n filesWithSchemas,\n };\n }\n\n private analyzeFile(sourceFile: SourceFile): SchemaInfo[] {\n const schemas: SchemaInfo[] = [];\n const filePath = sourceFile.getFilePath();\n const library = this.detectFileLibrary(sourceFile);\n\n if (library === 'unknown') return schemas;\n\n sourceFile.getVariableDeclarations().forEach((varDecl: VariableDeclaration) => {\n const initializer = varDecl.getInitializer();\n if (initializer && this.isSchemaExpression(initializer, library)) {\n schemas.push({\n name: varDecl.getName(),\n filePath,\n library,\n lineNumber: varDecl.getStartLineNumber(),\n code: varDecl.getText(),\n });\n }\n });\n\n return schemas;\n }\n\n private detectFileLibrary(sourceFile: SourceFile): SchemaLibrary {\n for (const imp of sourceFile.getImportDeclarations()) {\n const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());\n if (lib !== 'unknown') return lib;\n }\n return 'unknown';\n }\n\n private isSchemaExpression(node: Node, library: SchemaLibrary): boolean {\n const text = node.getText();\n switch (library) {\n case 'zod':\n return text.includes('z.') || text.includes('zod.');\n case 'yup':\n return text.includes('yup.') || text.includes('Yup.');\n case 'joi':\n return text.includes('Joi.') || text.includes('joi.');\n case 'io-ts':\n return text.includes('t.') && node.getSourceFile().getText().includes(\"from 'io-ts'\");\n case 'valibot':\n return text.includes('v.') || text.includes('valibot.');\n default:\n return false;\n }\n }\n\n getProject(): Project {\n return this.project;\n }\n}\n","import type { SchemaLibrary } from './types.js';\n\nconst LIBRARY_PATTERNS: Record<SchemaLibrary, RegExp[]> = {\n zod: [/^zod$/, /^zod\\//],\n 'zod-v3': [/^zod$/], // Detected same as zod, version determined by package.json\n yup: [/^yup$/],\n joi: [/^joi$/, /^@hapi\\/joi$/],\n 'io-ts': [/^io-ts$/, /^io-ts\\//],\n valibot: [/^valibot$/],\n v4: [], // Target version, not detectable from imports\n unknown: [],\n};\n\nexport function detectSchemaLibrary(moduleSpecifier: string): SchemaLibrary {\n for (const [library, patterns] of Object.entries(LIBRARY_PATTERNS)) {\n if (library === 'unknown') continue;\n if (patterns.some((pattern) => pattern.test(moduleSpecifier))) {\n return library as SchemaLibrary;\n }\n }\n return 'unknown';\n}\n","import { cosmiconfig } from 'cosmiconfig';\n\nexport interface SchemaShiftConfig {\n // File patterns\n include: string[];\n exclude: string[];\n\n // Migration defaults\n defaultFrom?: string;\n defaultTo?: string;\n\n // Output\n outputDir?: string;\n reportFormat?: 'json' | 'html' | 'console';\n\n // Git integration\n git?: {\n enabled: boolean;\n createBranch?: boolean;\n branchPrefix?: string;\n autoCommit?: boolean;\n commitMessage?: string;\n };\n\n // Backup/Rollback\n backup?: {\n enabled: boolean;\n dir?: string;\n };\n\n // Custom rules\n customRules?: CustomRule[];\n\n // CI mode\n ci?: boolean;\n}\n\nexport interface CustomRule {\n name: string;\n description?: string;\n match: {\n library: string;\n method: string;\n pattern?: string;\n };\n transform: {\n method: string;\n args?: string[];\n warning?: string;\n };\n}\n\nexport async function loadConfig(configPath?: string): Promise<SchemaShiftConfig> {\n const explorer = cosmiconfig('schemashift', {\n searchPlaces: [\n '.schemashiftrc',\n '.schemashiftrc.json',\n '.schemashiftrc.yaml',\n '.schemashiftrc.yml',\n '.schemashiftrc.js',\n '.schemashiftrc.cjs',\n 'schemashift.config.js',\n 'schemashift.config.cjs',\n 'package.json',\n ],\n });\n\n const result = configPath ? await explorer.load(configPath) : await explorer.search();\n\n return {\n include: ['**/*.ts', '**/*.tsx'],\n exclude: ['**/node_modules/**', '**/dist/**', '**/*.d.ts'],\n git: { enabled: false },\n backup: { enabled: true, dir: '.schemashift-backup' },\n ...result?.config,\n };\n}\n","import type { SourceFile } from 'ts-morph';\nimport type { SchemaLibrary, TransformOptions, TransformResult } from './types.js';\n\nexport interface TransformHandler {\n transform(sourceFile: SourceFile, options: TransformOptions): TransformResult;\n}\n\nexport class TransformEngine {\n private handlers = new Map<string, TransformHandler>();\n\n registerHandler(from: SchemaLibrary, to: SchemaLibrary, handler: TransformHandler): void {\n this.handlers.set(`${from}->${to}`, handler);\n }\n\n getHandler(from: SchemaLibrary, to: SchemaLibrary): TransformHandler | undefined {\n return this.handlers.get(`${from}->${to}`);\n }\n\n hasHandler(from: SchemaLibrary, to: SchemaLibrary): boolean {\n return this.handlers.has(`${from}->${to}`);\n }\n\n getSupportedPaths(): Array<{ from: SchemaLibrary; to: SchemaLibrary }> {\n return Array.from(this.handlers.keys()).map((key) => {\n const [from, to] = key.split('->') as [SchemaLibrary, SchemaLibrary];\n return { from, to };\n });\n }\n\n transform(\n sourceFile: SourceFile,\n from: SchemaLibrary,\n to: SchemaLibrary,\n options: TransformOptions,\n ): TransformResult {\n const handler = this.getHandler(from, to);\n if (!handler) {\n return {\n success: false,\n filePath: sourceFile.getFilePath(),\n originalCode: sourceFile.getFullText(),\n errors: [{ message: `No handler for ${from}->${to}` }],\n warnings: [],\n };\n }\n return handler.transform(sourceFile, options);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@schemashift/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"ts-morph": "27.0.2",
|
|
30
|
+
"cosmiconfig": "9.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|