@kernlang/core 2.0.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/LICENSE +661 -0
- package/dist/codegen-core.d.ts +30 -0
- package/dist/codegen-core.js +751 -0
- package/dist/codegen-core.js.map +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/decompiler.d.ts +2 -0
- package/dist/decompiler.js +44 -0
- package/dist/decompiler.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.js +380 -0
- package/dist/parser.js.map +1 -0
- package/dist/scanner.d.ts +40 -0
- package/dist/scanner.js +380 -0
- package/dist/scanner.js.map +1 -0
- package/dist/spec.d.ts +17 -0
- package/dist/spec.js +101 -0
- package/dist/spec.js.map +1 -0
- package/dist/styles-react.d.ts +3 -0
- package/dist/styles-react.js +20 -0
- package/dist/styles-react.js.map +1 -0
- package/dist/styles-tailwind.d.ts +8 -0
- package/dist/styles-tailwind.js +197 -0
- package/dist/styles-tailwind.js.map +1 -0
- package/dist/template-catalog.d.ts +26 -0
- package/dist/template-catalog.js +228 -0
- package/dist/template-catalog.js.map +1 -0
- package/dist/template-engine.d.ts +33 -0
- package/dist/template-engine.js +196 -0
- package/dist/template-engine.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +62 -0
- package/dist/utils.js.map +1 -0
- package/dist/version-adapters.d.ts +76 -0
- package/dist/version-adapters.js +172 -0
- package/dist/version-adapters.js.map +1 -0
- package/dist/version-detect.d.ts +30 -0
- package/dist/version-detect.js +63 -0
- package/dist/version-detect.js.map +1 -0
- package/package.json +20 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kern Template Engine — dynamic pattern support
|
|
3
|
+
*
|
|
4
|
+
* Users define reusable code patterns in .kern files with typed slots.
|
|
5
|
+
* Templates are registered at config/CLI time, then expanded during codegen.
|
|
6
|
+
*/
|
|
7
|
+
import { generateCoreNode } from './codegen-core.js';
|
|
8
|
+
// ── Registry ────────────────────────────────────────────────────────────
|
|
9
|
+
const _registry = new Map();
|
|
10
|
+
const MAX_EXPANSION_DEPTH = 10;
|
|
11
|
+
// ── Errors ──────────────────────────────────────────────────────────────
|
|
12
|
+
export class KernTemplateError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'KernTemplateError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// ── Slot Validation ─────────────────────────────────────────────────────
|
|
19
|
+
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20
|
+
function validateSlotValue(name, value, slotType) {
|
|
21
|
+
switch (slotType) {
|
|
22
|
+
case 'identifier':
|
|
23
|
+
if (!IDENTIFIER_RE.test(value)) {
|
|
24
|
+
throw new KernTemplateError(`Slot '${name}' requires a valid identifier, got '${value}'`);
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'type':
|
|
28
|
+
case 'expr':
|
|
29
|
+
if (!value || value.trim().length === 0) {
|
|
30
|
+
throw new KernTemplateError(`Slot '${name}' (${slotType}) must be non-empty`);
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
case 'block':
|
|
34
|
+
// blocks are always valid (can be empty)
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ── Registration ────────────────────────────────────────────────────────
|
|
39
|
+
function p(node) {
|
|
40
|
+
return node.props || {};
|
|
41
|
+
}
|
|
42
|
+
function kids(node, type) {
|
|
43
|
+
const c = node.children || [];
|
|
44
|
+
return type ? c.filter(n => n.type === type) : c;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register a template from a parsed 'template' IRNode.
|
|
48
|
+
* Extracts slot definitions, import declarations, and body from children.
|
|
49
|
+
*/
|
|
50
|
+
export function registerTemplate(node, sourceFile) {
|
|
51
|
+
const props = p(node);
|
|
52
|
+
const name = props.name;
|
|
53
|
+
if (!name) {
|
|
54
|
+
throw new KernTemplateError('Template must have a name= prop');
|
|
55
|
+
}
|
|
56
|
+
// Parse slots from children
|
|
57
|
+
const slots = [];
|
|
58
|
+
for (const child of kids(node, 'slot')) {
|
|
59
|
+
const cp = p(child);
|
|
60
|
+
const slotName = cp.name;
|
|
61
|
+
if (!slotName) {
|
|
62
|
+
throw new KernTemplateError(`Template '${name}': slot must have a name= prop`);
|
|
63
|
+
}
|
|
64
|
+
const slotType = cp.type || 'expr';
|
|
65
|
+
const optional = cp.optional === 'true' || cp.optional === true;
|
|
66
|
+
const defaultValue = cp.default;
|
|
67
|
+
slots.push({ name: slotName, slotType, optional, defaultValue });
|
|
68
|
+
}
|
|
69
|
+
// Parse imports from children
|
|
70
|
+
const imports = [];
|
|
71
|
+
for (const child of kids(node, 'import')) {
|
|
72
|
+
const cp = p(child);
|
|
73
|
+
const from = cp.from;
|
|
74
|
+
const names = cp.names;
|
|
75
|
+
if (from && names) {
|
|
76
|
+
imports.push({ from, names });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Extract body from body child
|
|
80
|
+
const bodyNode = kids(node, 'body')[0];
|
|
81
|
+
if (!bodyNode) {
|
|
82
|
+
throw new KernTemplateError(`Template '${name}': must have a body <<< >>> block`);
|
|
83
|
+
}
|
|
84
|
+
const body = p(bodyNode).code || '';
|
|
85
|
+
_registry.set(name, { name, slots, imports, body, sourceFile });
|
|
86
|
+
}
|
|
87
|
+
/** Check if a node type matches a registered template. */
|
|
88
|
+
export function isTemplateNode(type) {
|
|
89
|
+
return _registry.has(type);
|
|
90
|
+
}
|
|
91
|
+
/** Clear all registered templates (for test isolation). */
|
|
92
|
+
export function clearTemplates() {
|
|
93
|
+
_registry.clear();
|
|
94
|
+
}
|
|
95
|
+
/** Get a registered template definition by name. */
|
|
96
|
+
export function getTemplate(name) {
|
|
97
|
+
return _registry.get(name);
|
|
98
|
+
}
|
|
99
|
+
/** Get count of registered templates. */
|
|
100
|
+
export function templateCount() {
|
|
101
|
+
return _registry.size;
|
|
102
|
+
}
|
|
103
|
+
// ── Expansion ───────────────────────────────────────────────────────────
|
|
104
|
+
/** Strip common leading whitespace from multiline body text. */
|
|
105
|
+
function dedentBody(code) {
|
|
106
|
+
const lines = code.split('\n');
|
|
107
|
+
const nonEmpty = lines.filter(l => l.trim().length > 0);
|
|
108
|
+
if (nonEmpty.length === 0)
|
|
109
|
+
return code;
|
|
110
|
+
const min = Math.min(...nonEmpty.map(l => l.match(/^(\s*)/)?.[1].length ?? 0));
|
|
111
|
+
return lines.map(l => l.slice(min)).join('\n');
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Expand a template instance node into TypeScript lines.
|
|
115
|
+
*
|
|
116
|
+
* 1. Look up template definition
|
|
117
|
+
* 2. Validate required slots against node.props
|
|
118
|
+
* 3. Replace {{slotName}} placeholders in body
|
|
119
|
+
* 4. Handle {{CHILDREN}}: iterate child nodes through codegen
|
|
120
|
+
* 5. Prepend import lines
|
|
121
|
+
*/
|
|
122
|
+
export function expandTemplateNode(node, _depth = 0) {
|
|
123
|
+
if (_depth > MAX_EXPANSION_DEPTH) {
|
|
124
|
+
throw new KernTemplateError(`Template expansion depth exceeded ${MAX_EXPANSION_DEPTH} — possible recursion in template '${node.type}'`);
|
|
125
|
+
}
|
|
126
|
+
const template = _registry.get(node.type);
|
|
127
|
+
if (!template) {
|
|
128
|
+
throw new KernTemplateError(`No template registered for type '${node.type}'`);
|
|
129
|
+
}
|
|
130
|
+
const nodeProps = p(node);
|
|
131
|
+
const slotValues = new Map();
|
|
132
|
+
// Resolve slot values from node props
|
|
133
|
+
for (const slot of template.slots) {
|
|
134
|
+
const rawValue = nodeProps[slot.name];
|
|
135
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
136
|
+
const value = String(rawValue);
|
|
137
|
+
validateSlotValue(slot.name, value, slot.slotType);
|
|
138
|
+
slotValues.set(slot.name, value);
|
|
139
|
+
}
|
|
140
|
+
else if (slot.optional) {
|
|
141
|
+
slotValues.set(slot.name, slot.defaultValue ?? '');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new KernTemplateError(`Template '${template.name}': required slot '${slot.name}' not provided`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Build CHILDREN output
|
|
148
|
+
const childrenLines = [];
|
|
149
|
+
const nodeChildren = node.children || [];
|
|
150
|
+
for (const child of nodeChildren) {
|
|
151
|
+
// handler <<< >>> blocks become inline code
|
|
152
|
+
if (child.type === 'handler') {
|
|
153
|
+
const code = p(child).code || '';
|
|
154
|
+
const dedented = dedentBody(code);
|
|
155
|
+
for (const line of dedented.split('\n')) {
|
|
156
|
+
childrenLines.push(line);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Other children go through codegen (supports nested templates)
|
|
161
|
+
const expanded = isTemplateNode(child.type)
|
|
162
|
+
? expandTemplateNode(child, _depth + 1)
|
|
163
|
+
: generateCoreNode(child);
|
|
164
|
+
childrenLines.push(...expanded);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Interpolate body
|
|
168
|
+
let body = dedentBody(template.body);
|
|
169
|
+
// Replace {{CHILDREN}} with children output
|
|
170
|
+
if (body.includes('{{CHILDREN}}')) {
|
|
171
|
+
// Preserve indentation: find indent before {{CHILDREN}} and apply to each child line
|
|
172
|
+
body = body.replace(/^([ \t]*)(\{\{CHILDREN\}\})/gm, (_match, indent) => {
|
|
173
|
+
if (childrenLines.length === 0)
|
|
174
|
+
return '';
|
|
175
|
+
return childrenLines.map(l => indent + l).join('\n');
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Replace {{slotName}} placeholders
|
|
179
|
+
for (const [name, value] of slotValues) {
|
|
180
|
+
const re = new RegExp(`\\{\\{${name}\\}\\}`, 'g');
|
|
181
|
+
body = body.replace(re, value);
|
|
182
|
+
}
|
|
183
|
+
const lines = body.split('\n');
|
|
184
|
+
// Prepend imports
|
|
185
|
+
if (template.imports.length > 0) {
|
|
186
|
+
const importLines = [];
|
|
187
|
+
for (const imp of template.imports) {
|
|
188
|
+
const nameList = imp.names.split(',').map(s => s.trim()).join(', ');
|
|
189
|
+
importLines.push(`import { ${nameList} } from '${imp.from}';`);
|
|
190
|
+
}
|
|
191
|
+
importLines.push('');
|
|
192
|
+
lines.unshift(...importLines);
|
|
193
|
+
}
|
|
194
|
+
return lines;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=template-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-engine.js","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kern IR Type System
|
|
3
|
+
*
|
|
4
|
+
* This is the contract. Each forge engine must implement:
|
|
5
|
+
* 1. A parser that reads IR → IRNode tree
|
|
6
|
+
* 2. A transpiler that converts IRNode tree → React Native TypeScript
|
|
7
|
+
* 3. A decompiler that converts IRNode tree → human-readable TypeScript
|
|
8
|
+
* 4. Source map generation for debugging
|
|
9
|
+
*/
|
|
10
|
+
/** Base node in the IR tree */
|
|
11
|
+
export interface IRNode {
|
|
12
|
+
/** Node type identifier */
|
|
13
|
+
type: string;
|
|
14
|
+
/** Source location for source maps */
|
|
15
|
+
loc?: IRSourceLocation;
|
|
16
|
+
/** Child nodes */
|
|
17
|
+
children?: IRNode[];
|
|
18
|
+
/** Node-specific properties */
|
|
19
|
+
props?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Source location tracking */
|
|
22
|
+
export interface IRSourceLocation {
|
|
23
|
+
line: number;
|
|
24
|
+
col: number;
|
|
25
|
+
endLine?: number;
|
|
26
|
+
endCol?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Source map entry */
|
|
29
|
+
export interface SourceMapEntry {
|
|
30
|
+
/** Position in IR source */
|
|
31
|
+
irLine: number;
|
|
32
|
+
irCol: number;
|
|
33
|
+
/** Position in generated output */
|
|
34
|
+
outLine: number;
|
|
35
|
+
outCol: number;
|
|
36
|
+
}
|
|
37
|
+
/** Generated output artifact (for multi-file targets like Next.js, Express) */
|
|
38
|
+
export interface GeneratedArtifact {
|
|
39
|
+
/** Relative output path */
|
|
40
|
+
path: string;
|
|
41
|
+
/** Generated code */
|
|
42
|
+
content: string;
|
|
43
|
+
/** Artifact type */
|
|
44
|
+
type: 'page' | 'layout' | 'route' | 'middleware' | 'component' | 'config' | 'entry' | 'command' | 'hook' | 'types' | 'barrel' | 'theme' | 'template';
|
|
45
|
+
}
|
|
46
|
+
/** Result of transpilation */
|
|
47
|
+
export interface TranspileResult {
|
|
48
|
+
/** Generated React Native TypeScript code */
|
|
49
|
+
code: string;
|
|
50
|
+
/** Source map entries */
|
|
51
|
+
sourceMap: SourceMapEntry[];
|
|
52
|
+
/** Token count of the IR input */
|
|
53
|
+
irTokenCount: number;
|
|
54
|
+
/** Token count of the generated TypeScript output */
|
|
55
|
+
tsTokenCount: number;
|
|
56
|
+
/** Token reduction percentage */
|
|
57
|
+
tokenReduction: number;
|
|
58
|
+
/** Multi-file output artifacts */
|
|
59
|
+
artifacts?: GeneratedArtifact[];
|
|
60
|
+
}
|
|
61
|
+
/** Result of decompilation (IR → human-readable) */
|
|
62
|
+
export interface DecompileResult {
|
|
63
|
+
/** Human-readable TypeScript representation */
|
|
64
|
+
code: string;
|
|
65
|
+
}
|
|
66
|
+
/** The main engine interface each implementation must satisfy */
|
|
67
|
+
export type TemplateSlotType = 'identifier' | 'type' | 'expr' | 'block';
|
|
68
|
+
export interface TemplateSlot {
|
|
69
|
+
name: string;
|
|
70
|
+
slotType: TemplateSlotType;
|
|
71
|
+
optional: boolean;
|
|
72
|
+
defaultValue?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface TemplateImport {
|
|
75
|
+
from: string;
|
|
76
|
+
names: string;
|
|
77
|
+
}
|
|
78
|
+
export interface TemplateDefinition {
|
|
79
|
+
name: string;
|
|
80
|
+
slots: TemplateSlot[];
|
|
81
|
+
imports: TemplateImport[];
|
|
82
|
+
body: string;
|
|
83
|
+
sourceFile?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface KernEngine {
|
|
86
|
+
/** Parse IR source text into an IR node tree */
|
|
87
|
+
parse(source: string): IRNode;
|
|
88
|
+
/** Transpile IR node tree to React Native TypeScript */
|
|
89
|
+
transpile(root: IRNode): TranspileResult;
|
|
90
|
+
/** Decompile IR node tree to human-readable TypeScript */
|
|
91
|
+
decompile(root: IRNode): DecompileResult;
|
|
92
|
+
/** Get the IR representation of a component (for token comparison) */
|
|
93
|
+
serialize(root: IRNode): string;
|
|
94
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kern IR Type System
|
|
3
|
+
*
|
|
4
|
+
* This is the contract. Each forge engine must implement:
|
|
5
|
+
* 1. A parser that reads IR → IRNode tree
|
|
6
|
+
* 2. A transpiler that converts IRNode tree → React Native TypeScript
|
|
7
|
+
* 3. A decompiler that converts IRNode tree → human-readable TypeScript
|
|
8
|
+
* 4. Source map generation for debugging
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IRNode } from './types.js';
|
|
2
|
+
export declare function countTokens(text: string): number;
|
|
3
|
+
export declare function serializeIR(node: IRNode, indent?: string): string;
|
|
4
|
+
export declare function camelKey(text: string): string;
|
|
5
|
+
/** Escape text content for JSX — prevents XSS in rendered HTML */
|
|
6
|
+
export declare function escapeJsxText(s: string): string;
|
|
7
|
+
/** Escape attribute values for JSX — prevents XSS in attributes */
|
|
8
|
+
export declare function escapeJsxAttr(s: string): string;
|
|
9
|
+
/** Escape strings for JS string literals (single-quoted) */
|
|
10
|
+
export declare function escapeJsString(s: string): string;
|
|
11
|
+
/** @deprecated Use escapeJsxText, escapeJsxAttr, or escapeJsString */
|
|
12
|
+
export declare function escapeJsx(s: string): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function countTokens(text) {
|
|
2
|
+
return text.split(/[\s{}()\[\];,.<>:='"]+/).filter(Boolean).length;
|
|
3
|
+
}
|
|
4
|
+
export function serializeIR(node, indent = '') {
|
|
5
|
+
let line = `${indent}${node.type}`;
|
|
6
|
+
const props = node.props || {};
|
|
7
|
+
for (const [k, v] of Object.entries(props)) {
|
|
8
|
+
if (k === 'styles' || k === 'pseudoStyles' || k === 'themeRefs')
|
|
9
|
+
continue;
|
|
10
|
+
line += ` ${k}=${typeof v === 'string' && v.includes(' ') ? `"${v}"` : v}`;
|
|
11
|
+
}
|
|
12
|
+
if (props.styles) {
|
|
13
|
+
const pairs = Object.entries(props.styles)
|
|
14
|
+
.map(([k, v]) => `${k}:${v}`).join(',');
|
|
15
|
+
line += ` {${pairs}}`;
|
|
16
|
+
}
|
|
17
|
+
if (props.themeRefs) {
|
|
18
|
+
for (const ref of props.themeRefs) {
|
|
19
|
+
line += ` $${ref}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let result = line + '\n';
|
|
23
|
+
if (node.children) {
|
|
24
|
+
for (const child of node.children) {
|
|
25
|
+
result += serializeIR(child, indent + ' ');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
export function camelKey(text) {
|
|
31
|
+
return text.toLowerCase().replace(/[^a-z0-9]+(.)/g, (_, c) => c.toUpperCase()).replace(/[^a-zA-Z0-9]/g, '');
|
|
32
|
+
}
|
|
33
|
+
/** Escape text content for JSX — prevents XSS in rendered HTML */
|
|
34
|
+
export function escapeJsxText(s) {
|
|
35
|
+
return s
|
|
36
|
+
.replace(/&/g, '&')
|
|
37
|
+
.replace(/</g, '<')
|
|
38
|
+
.replace(/>/g, '>')
|
|
39
|
+
.replace(/\{/g, '{')
|
|
40
|
+
.replace(/\}/g, '}');
|
|
41
|
+
}
|
|
42
|
+
/** Escape attribute values for JSX — prevents XSS in attributes */
|
|
43
|
+
export function escapeJsxAttr(s) {
|
|
44
|
+
return s
|
|
45
|
+
.replace(/&/g, '&')
|
|
46
|
+
.replace(/"/g, '"')
|
|
47
|
+
.replace(/</g, '<')
|
|
48
|
+
.replace(/>/g, '>');
|
|
49
|
+
}
|
|
50
|
+
/** Escape strings for JS string literals (single-quoted) */
|
|
51
|
+
export function escapeJsString(s) {
|
|
52
|
+
return s
|
|
53
|
+
.replace(/\\/g, '\\\\')
|
|
54
|
+
.replace(/'/g, "\\'")
|
|
55
|
+
.replace(/\n/g, '\\n')
|
|
56
|
+
.replace(/\r/g, '\\r');
|
|
57
|
+
}
|
|
58
|
+
/** @deprecated Use escapeJsxText, escapeJsxAttr, or escapeJsString */
|
|
59
|
+
export function escapeJsx(s) {
|
|
60
|
+
return escapeJsxText(s);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Adapters for Tailwind CSS and Next.js
|
|
3
|
+
*
|
|
4
|
+
* Two-layer architecture:
|
|
5
|
+
* (a) Token rules: class name transforms (e.g., bg-opacity-50 -> removed in TW v4)
|
|
6
|
+
* (b) Output-mode rules: structural differences (import patterns, metadata API)
|
|
7
|
+
*
|
|
8
|
+
* Stored as TypeScript modules so they can hold predicates and transforms.
|
|
9
|
+
* Shared across all targets that use Tailwind (React, Vue, Svelte).
|
|
10
|
+
*/
|
|
11
|
+
import type { FrameworkVersions } from './config.js';
|
|
12
|
+
/**
|
|
13
|
+
* A token rule transforms individual Tailwind class names.
|
|
14
|
+
* If the predicate matches, the transform function is applied.
|
|
15
|
+
*/
|
|
16
|
+
export interface TailwindTokenRule {
|
|
17
|
+
/** Human-readable name for debugging */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Returns true if this rule applies to the given class */
|
|
20
|
+
predicate: (cls: string) => boolean;
|
|
21
|
+
/** Transform the class name. Return empty string to remove. */
|
|
22
|
+
transform: (cls: string) => string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Output-mode rules for Tailwind structural differences.
|
|
26
|
+
*/
|
|
27
|
+
export interface TailwindOutputRules {
|
|
28
|
+
/** Whether to use @import instead of @tailwind directives */
|
|
29
|
+
useAtImport: boolean;
|
|
30
|
+
/** Whether opacity utilities use slash syntax (bg-black/50 vs bg-opacity-50) */
|
|
31
|
+
useSlashOpacity: boolean;
|
|
32
|
+
/** Whether to use the new color-mix() approach for arbitrary opacity */
|
|
33
|
+
useColorMix: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Combined Tailwind compatibility profile.
|
|
37
|
+
*/
|
|
38
|
+
export interface TailwindVersionProfile {
|
|
39
|
+
major: 3 | 4;
|
|
40
|
+
tokenRules: TailwindTokenRule[];
|
|
41
|
+
outputRules: TailwindOutputRules;
|
|
42
|
+
}
|
|
43
|
+
export declare function buildTailwindProfile(versions: FrameworkVersions): TailwindVersionProfile;
|
|
44
|
+
/**
|
|
45
|
+
* Apply token rules to a list of Tailwind classes.
|
|
46
|
+
* Returns the transformed class string.
|
|
47
|
+
*/
|
|
48
|
+
export declare function applyTailwindTokenRules(classes: string, profile: TailwindVersionProfile): string;
|
|
49
|
+
/**
|
|
50
|
+
* Output-mode rules for Next.js structural differences.
|
|
51
|
+
*/
|
|
52
|
+
export interface NextjsOutputRules {
|
|
53
|
+
/** Metadata export style: 'typed' = Metadata type, 'satisfies' = satisfies Metadata */
|
|
54
|
+
metadataStyle: 'typed' | 'satisfies';
|
|
55
|
+
/** Whether to use the next/navigation module (v13+) vs next/router */
|
|
56
|
+
useAppRouter: boolean;
|
|
57
|
+
/** Image component import path */
|
|
58
|
+
imageImport: 'next/image' | 'next/legacy/image';
|
|
59
|
+
/** Whether async server components are supported */
|
|
60
|
+
asyncServerComponents: boolean;
|
|
61
|
+
/** Whether to use the new Link behavior (no nested <a>) */
|
|
62
|
+
linkNoAnchor: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Combined Next.js compatibility profile.
|
|
66
|
+
*/
|
|
67
|
+
export interface NextjsVersionProfile {
|
|
68
|
+
major: 13 | 14 | 15;
|
|
69
|
+
outputRules: NextjsOutputRules;
|
|
70
|
+
}
|
|
71
|
+
export declare function buildNextjsProfile(versions: FrameworkVersions): NextjsVersionProfile;
|
|
72
|
+
export interface VersionProfile {
|
|
73
|
+
tailwind: TailwindVersionProfile;
|
|
74
|
+
nextjs: NextjsVersionProfile;
|
|
75
|
+
}
|
|
76
|
+
export declare function buildVersionProfile(versions: FrameworkVersions): VersionProfile;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Adapters for Tailwind CSS and Next.js
|
|
3
|
+
*
|
|
4
|
+
* Two-layer architecture:
|
|
5
|
+
* (a) Token rules: class name transforms (e.g., bg-opacity-50 -> removed in TW v4)
|
|
6
|
+
* (b) Output-mode rules: structural differences (import patterns, metadata API)
|
|
7
|
+
*
|
|
8
|
+
* Stored as TypeScript modules so they can hold predicates and transforms.
|
|
9
|
+
* Shared across all targets that use Tailwind (React, Vue, Svelte).
|
|
10
|
+
*/
|
|
11
|
+
import { resolveTailwindMajor, resolveNextjsMajor } from './version-detect.js';
|
|
12
|
+
// ── Token rules for Tailwind v3 -> v4 migration ────────────────────────
|
|
13
|
+
const TW_V4_TOKEN_RULES = [
|
|
14
|
+
{
|
|
15
|
+
name: 'blur-filter-rename',
|
|
16
|
+
predicate: (cls) => cls === 'filter' || cls === 'backdrop-filter',
|
|
17
|
+
transform: () => '',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'decoration-slice-rename',
|
|
21
|
+
predicate: (cls) => cls === 'decoration-slice' || cls === 'decoration-clone',
|
|
22
|
+
transform: (cls) => cls.replace('decoration-', 'box-decoration-'),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'flex-shrink-grow-rename',
|
|
26
|
+
predicate: (cls) => /^(flex-shrink|flex-grow)(-\d+)?$/.test(cls),
|
|
27
|
+
transform: (cls) => cls.replace('flex-shrink', 'shrink').replace('flex-grow', 'grow'),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'overflow-ellipsis-rename',
|
|
31
|
+
predicate: (cls) => cls === 'overflow-ellipsis',
|
|
32
|
+
transform: () => 'text-ellipsis',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
// ── Build Tailwind profile ──────────────────────────────────────────────
|
|
36
|
+
export function buildTailwindProfile(versions) {
|
|
37
|
+
const major = resolveTailwindMajor(versions);
|
|
38
|
+
if (major >= 4) {
|
|
39
|
+
return {
|
|
40
|
+
major: 4,
|
|
41
|
+
tokenRules: TW_V4_TOKEN_RULES,
|
|
42
|
+
outputRules: {
|
|
43
|
+
useAtImport: true,
|
|
44
|
+
useSlashOpacity: true,
|
|
45
|
+
useColorMix: true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
major: 3,
|
|
51
|
+
tokenRules: [],
|
|
52
|
+
outputRules: {
|
|
53
|
+
useAtImport: false,
|
|
54
|
+
useSlashOpacity: false,
|
|
55
|
+
useColorMix: false,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Apply token rules to a list of Tailwind classes.
|
|
61
|
+
* Returns the transformed class string.
|
|
62
|
+
*/
|
|
63
|
+
export function applyTailwindTokenRules(classes, profile) {
|
|
64
|
+
if (profile.tokenRules.length === 0 && !profile.outputRules.useSlashOpacity)
|
|
65
|
+
return classes;
|
|
66
|
+
const parts = classes.split(/\s+/).filter(Boolean);
|
|
67
|
+
const result = [];
|
|
68
|
+
for (const cls of parts) {
|
|
69
|
+
let transformed = cls;
|
|
70
|
+
for (const rule of profile.tokenRules) {
|
|
71
|
+
if (rule.predicate(cls)) {
|
|
72
|
+
transformed = rule.transform(cls);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (transformed) {
|
|
77
|
+
result.push(transformed);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Post-processing: merge opacity utilities into slash syntax
|
|
81
|
+
// e.g., bg-red-500 bg-opacity-50 → bg-red-500/50
|
|
82
|
+
if (profile.outputRules.useSlashOpacity) {
|
|
83
|
+
return mergeOpacityUtilities(result).join(' ');
|
|
84
|
+
}
|
|
85
|
+
return result.join(' ');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Merge `{prefix}-opacity-{N}` classes into the preceding color class
|
|
89
|
+
* using slash syntax: `bg-red-500 bg-opacity-50` → `bg-red-500/50`.
|
|
90
|
+
* Supports bg, text, border, ring prefixes.
|
|
91
|
+
*/
|
|
92
|
+
function mergeOpacityUtilities(classes) {
|
|
93
|
+
const opacityPattern = /^(bg|text|border|ring)-opacity-(\d+)$/;
|
|
94
|
+
// First pass: collect opacity values by prefix
|
|
95
|
+
const opacityMap = new Map();
|
|
96
|
+
const nonOpacity = [];
|
|
97
|
+
for (const cls of classes) {
|
|
98
|
+
const match = cls.match(opacityPattern);
|
|
99
|
+
if (match) {
|
|
100
|
+
opacityMap.set(match[1], match[2]);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
nonOpacity.push(cls);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (opacityMap.size === 0)
|
|
107
|
+
return classes;
|
|
108
|
+
// Second pass: merge opacity into color classes
|
|
109
|
+
const merged = [];
|
|
110
|
+
for (const cls of nonOpacity) {
|
|
111
|
+
let didMerge = false;
|
|
112
|
+
for (const [prefix, opacity] of opacityMap) {
|
|
113
|
+
// Match color classes like bg-red-500, text-zinc-300, border-gray-200, ring-blue-500
|
|
114
|
+
if (cls.startsWith(`${prefix}-`) && !cls.includes('/')) {
|
|
115
|
+
merged.push(`${cls}/${opacity}`);
|
|
116
|
+
opacityMap.delete(prefix);
|
|
117
|
+
didMerge = true;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!didMerge) {
|
|
122
|
+
merged.push(cls);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return merged;
|
|
126
|
+
}
|
|
127
|
+
export function buildNextjsProfile(versions) {
|
|
128
|
+
const major = resolveNextjsMajor(versions);
|
|
129
|
+
switch (major) {
|
|
130
|
+
case 15:
|
|
131
|
+
return {
|
|
132
|
+
major: 15,
|
|
133
|
+
outputRules: {
|
|
134
|
+
metadataStyle: 'satisfies',
|
|
135
|
+
useAppRouter: true,
|
|
136
|
+
imageImport: 'next/image',
|
|
137
|
+
asyncServerComponents: true,
|
|
138
|
+
linkNoAnchor: true,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
case 14:
|
|
142
|
+
return {
|
|
143
|
+
major: 14,
|
|
144
|
+
outputRules: {
|
|
145
|
+
metadataStyle: 'typed',
|
|
146
|
+
useAppRouter: true,
|
|
147
|
+
imageImport: 'next/image',
|
|
148
|
+
asyncServerComponents: true,
|
|
149
|
+
linkNoAnchor: true,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
case 13:
|
|
153
|
+
default:
|
|
154
|
+
return {
|
|
155
|
+
major: 13,
|
|
156
|
+
outputRules: {
|
|
157
|
+
metadataStyle: 'typed',
|
|
158
|
+
useAppRouter: true,
|
|
159
|
+
imageImport: 'next/image',
|
|
160
|
+
asyncServerComponents: false,
|
|
161
|
+
linkNoAnchor: true,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function buildVersionProfile(versions) {
|
|
167
|
+
return {
|
|
168
|
+
tailwind: buildTailwindProfile(versions),
|
|
169
|
+
nextjs: buildNextjsProfile(versions),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=version-adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-adapters.js","sourceRoot":"","sources":["../src/version-adapters.ts"],"names":[],"mappings":"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"}
|