vite-plugin-ferry 0.1.5 → 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/dist/generators/enums.d.ts +10 -5
- package/dist/generators/enums.d.ts.map +1 -1
- package/dist/generators/enums.js +120 -65
- package/dist/generators/resources.d.ts +7 -2
- package/dist/generators/resources.d.ts.map +1 -1
- package/dist/generators/resources.js +111 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/utils/file.d.ts +4 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +15 -2
- package/dist/utils/php-parser.d.ts +10 -1
- package/dist/utils/php-parser.d.ts.map +1 -1
- package/dist/utils/php-parser.js +30 -6
- package/dist/utils/source-map.d.ts +33 -0
- package/dist/utils/source-map.d.ts.map +1 -0
- package/dist/utils/source-map.js +105 -0
- package/dist/watchers/enums.js +2 -2
- package/dist/watchers/resources.js +2 -2
- package/package.json +1 -1
|
@@ -4,20 +4,25 @@ export type EnumGeneratorOptions = {
|
|
|
4
4
|
outputDir: string;
|
|
5
5
|
packageName: string;
|
|
6
6
|
prettyPrint?: boolean;
|
|
7
|
+
cwd: string;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
|
-
* Generate TypeScript type
|
|
10
|
+
* Generate TypeScript type declaration for a single enum.
|
|
10
11
|
*/
|
|
11
|
-
export declare function
|
|
12
|
+
export declare function generateSingleEnumTypeScript(enumDef: EnumDefinition, phpFile?: string): string;
|
|
12
13
|
/**
|
|
13
|
-
* Generate
|
|
14
|
+
* Generate source map for a single enum.
|
|
14
15
|
*/
|
|
15
|
-
export declare function
|
|
16
|
+
export declare function generateEnumSourceMap(enumDef: EnumDefinition, generatedFile: string, phpFile: string, outputDir: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Generate runtime JavaScript for a single enum.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateSingleEnumRuntime(enumDef: EnumDefinition, prettyPrint?: boolean): string;
|
|
16
21
|
/**
|
|
17
22
|
* Collect all enum definitions from the enums directory.
|
|
18
23
|
* This is a plugin-level function that handles file I/O.
|
|
19
24
|
*/
|
|
20
|
-
export declare function collectEnums(enumsDir: string): Record<string, EnumDefinition>;
|
|
25
|
+
export declare function collectEnums(enumsDir: string, cwd: string): Record<string, EnumDefinition>;
|
|
21
26
|
/**
|
|
22
27
|
* Generate enum files (TypeScript declarations and runtime JavaScript).
|
|
23
28
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/generators/enums.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/generators/enums.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAiB/E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAmC9F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,CAsCR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,UAAO,GAAG,MAAM,CAsB7F;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4B1F;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAmDjE"}
|
package/dist/generators/enums.js
CHANGED
|
@@ -1,73 +1,109 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { getPhpFiles, readFileSafe, writeFileEnsureDir } from '../utils/file.js';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { getPhpFiles, readFileSafe, writeFileEnsureDir, cleanOutputDir } from '../utils/file.js';
|
|
5
5
|
import { parseEnumContent } from '../utils/php-parser.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createEnum, createConstObject, createObjectLiteral, createDeclareConstWithType, createTypeLiteral, createStringLiteral, createNumericLiteral, printNode, } from '../utils/ts-generator.js';
|
|
7
|
+
import { generateSourceMap, createSourceMapComment, } from '../utils/source-map.js';
|
|
7
8
|
/**
|
|
8
|
-
* Generate TypeScript type
|
|
9
|
+
* Generate TypeScript type declaration for a single enum.
|
|
9
10
|
*/
|
|
10
|
-
export function
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
11
|
+
export function generateSingleEnumTypeScript(enumDef, phpFile) {
|
|
12
|
+
const hasLabels = enumDef.cases.some((c) => c.label);
|
|
13
|
+
let node;
|
|
14
|
+
if (hasLabels) {
|
|
15
|
+
const properties = enumDef.cases.map((c) => ({
|
|
16
|
+
name: c.key,
|
|
17
|
+
type: createTypeLiteral([
|
|
18
|
+
{ name: 'value', type: ts.factory.createLiteralTypeNode(createStringLiteral(String(c.value))) },
|
|
19
|
+
{
|
|
20
|
+
name: 'label',
|
|
21
|
+
type: ts.factory.createLiteralTypeNode(createStringLiteral(c.label || String(c.value))),
|
|
22
|
+
},
|
|
23
|
+
]),
|
|
24
|
+
}));
|
|
25
|
+
node = createDeclareConstWithType(enumDef.name, createTypeLiteral(properties));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
node = createEnum(enumDef.name, enumDef.cases.map((c) => ({ key: c.key, value: c.value })));
|
|
29
|
+
}
|
|
30
|
+
const lines = [];
|
|
31
|
+
// Add JSDoc with source reference
|
|
32
|
+
if (phpFile) {
|
|
33
|
+
lines.push(`/** @see ${phpFile} */`);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
lines.push(printNode(node));
|
|
36
|
+
// Add source map comment
|
|
37
|
+
if (phpFile) {
|
|
38
|
+
lines.push(createSourceMapComment(`${enumDef.name}.d.ts.map`));
|
|
39
|
+
}
|
|
40
|
+
return lines.join('\n') + '\n';
|
|
35
41
|
}
|
|
36
42
|
/**
|
|
37
|
-
* Generate
|
|
43
|
+
* Generate source map for a single enum.
|
|
38
44
|
*/
|
|
39
|
-
export function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{ key: 'value', value: createStringLiteral(String(c.value)) },
|
|
49
|
-
{ key: 'label', value: createStringLiteral(c.label || String(c.value)) },
|
|
50
|
-
], prettyPrint);
|
|
51
|
-
}
|
|
52
|
-
else if (typeof c.value === 'number') {
|
|
53
|
-
value = createNumericLiteral(c.value);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
value = createStringLiteral(String(c.value));
|
|
57
|
-
}
|
|
58
|
-
return { key: c.key, value };
|
|
45
|
+
export function generateEnumSourceMap(enumDef, generatedFile, phpFile, outputDir) {
|
|
46
|
+
const mappings = [];
|
|
47
|
+
// Map enum declaration to its source location
|
|
48
|
+
if (enumDef.loc) {
|
|
49
|
+
mappings.push({
|
|
50
|
+
generatedLine: 2, // Line after JSDoc comment
|
|
51
|
+
generatedColumn: 0,
|
|
52
|
+
sourceLine: enumDef.loc.line,
|
|
53
|
+
sourceColumn: enumDef.loc.column || 0,
|
|
59
54
|
});
|
|
60
|
-
nodes.push(createConstObject(enumDef.name, properties));
|
|
61
55
|
}
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
// Map each enum case to its source location
|
|
57
|
+
const hasLabels = enumDef.cases.some((c) => c.label);
|
|
58
|
+
let currentLine = 3; // Start after "export enum Name {" or "export declare const Name: {"
|
|
59
|
+
for (const enumCase of enumDef.cases) {
|
|
60
|
+
if (enumCase.loc) {
|
|
61
|
+
mappings.push({
|
|
62
|
+
generatedLine: currentLine,
|
|
63
|
+
generatedColumn: 4, // Indented
|
|
64
|
+
sourceLine: enumCase.loc.line,
|
|
65
|
+
sourceColumn: enumCase.loc.column || 0,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// For labeled enums, each case takes multiple lines
|
|
69
|
+
currentLine += hasLabels ? 4 : 1;
|
|
70
|
+
}
|
|
71
|
+
// Calculate relative path from output dir to PHP file
|
|
72
|
+
const relativeSource = relative(outputDir, join(process.cwd(), phpFile)).replace(/\\/g, '/');
|
|
73
|
+
return generateSourceMap({
|
|
74
|
+
file: generatedFile,
|
|
75
|
+
sources: [relativeSource],
|
|
76
|
+
mappings,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Generate runtime JavaScript for a single enum.
|
|
81
|
+
*/
|
|
82
|
+
export function generateSingleEnumRuntime(enumDef, prettyPrint = true) {
|
|
83
|
+
const hasLabels = enumDef.cases.some((c) => c.label);
|
|
84
|
+
const properties = enumDef.cases.map((c) => {
|
|
85
|
+
let value;
|
|
86
|
+
if (hasLabels) {
|
|
87
|
+
value = createObjectLiteral([
|
|
88
|
+
{ key: 'value', value: createStringLiteral(String(c.value)) },
|
|
89
|
+
{ key: 'label', value: createStringLiteral(c.label || String(c.value)) },
|
|
90
|
+
], prettyPrint);
|
|
91
|
+
}
|
|
92
|
+
else if (typeof c.value === 'number') {
|
|
93
|
+
value = createNumericLiteral(c.value);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
value = createStringLiteral(String(c.value));
|
|
97
|
+
}
|
|
98
|
+
return { key: c.key, value };
|
|
99
|
+
});
|
|
100
|
+
return printNode(createConstObject(enumDef.name, properties)) + '\n';
|
|
65
101
|
}
|
|
66
102
|
/**
|
|
67
103
|
* Collect all enum definitions from the enums directory.
|
|
68
104
|
* This is a plugin-level function that handles file I/O.
|
|
69
105
|
*/
|
|
70
|
-
export function collectEnums(enumsDir) {
|
|
106
|
+
export function collectEnums(enumsDir, cwd) {
|
|
71
107
|
const enums = {};
|
|
72
108
|
if (!existsSync(enumsDir)) {
|
|
73
109
|
return enums;
|
|
@@ -79,7 +115,9 @@ export function collectEnums(enumsDir) {
|
|
|
79
115
|
const content = readFileSafe(enumPath);
|
|
80
116
|
if (!content)
|
|
81
117
|
continue;
|
|
82
|
-
|
|
118
|
+
// Calculate relative path from project root for source mapping
|
|
119
|
+
const relativePhpPath = relative(cwd, enumPath);
|
|
120
|
+
const def = parseEnumContent(content, relativePhpPath);
|
|
83
121
|
if (def) {
|
|
84
122
|
enums[def.name] = def;
|
|
85
123
|
}
|
|
@@ -95,17 +133,34 @@ export function collectEnums(enumsDir) {
|
|
|
95
133
|
* Generate enum files (TypeScript declarations and runtime JavaScript).
|
|
96
134
|
*/
|
|
97
135
|
export function generateEnums(options) {
|
|
98
|
-
const { enumsDir, outputDir, packageName, prettyPrint = true } = options;
|
|
136
|
+
const { enumsDir, outputDir, packageName, prettyPrint = true, cwd } = options;
|
|
137
|
+
// Clean existing generated files
|
|
138
|
+
cleanOutputDir(outputDir);
|
|
99
139
|
// Collect all enums
|
|
100
|
-
const enums = collectEnums(enumsDir);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
140
|
+
const enums = collectEnums(enumsDir, cwd);
|
|
141
|
+
const enumNames = Object.keys(enums);
|
|
142
|
+
// Generate individual files for each enum
|
|
143
|
+
for (const enumName of enumNames) {
|
|
144
|
+
const enumDef = enums[enumName];
|
|
145
|
+
const phpFile = enumDef.loc?.file;
|
|
146
|
+
// Generate {EnumName}.d.ts with JSDoc and source map comment
|
|
147
|
+
const dtsContent = generateSingleEnumTypeScript(enumDef, phpFile);
|
|
148
|
+
writeFileEnsureDir(join(outputDir, `${enumName}.d.ts`), dtsContent);
|
|
149
|
+
// Generate {EnumName}.d.ts.map
|
|
150
|
+
if (phpFile) {
|
|
151
|
+
const sourceMap = generateEnumSourceMap(enumDef, `${enumName}.d.ts`, phpFile, outputDir);
|
|
152
|
+
writeFileEnsureDir(join(outputDir, `${enumName}.d.ts.map`), sourceMap);
|
|
153
|
+
}
|
|
154
|
+
// Generate {EnumName}.js
|
|
155
|
+
const jsContent = generateSingleEnumRuntime(enumDef, prettyPrint);
|
|
156
|
+
writeFileEnsureDir(join(outputDir, `${enumName}.js`), jsContent);
|
|
157
|
+
}
|
|
158
|
+
// Generate barrel index.d.ts
|
|
159
|
+
const indexDts = enumNames.map((n) => `export { ${n} } from './${n}.js';`).join('\n') + '\n';
|
|
160
|
+
writeFileEnsureDir(join(outputDir, 'index.d.ts'), indexDts);
|
|
161
|
+
// Generate barrel index.js
|
|
162
|
+
const indexJs = enumNames.map((n) => `export { ${n} } from './${n}.js';`).join('\n') + '\n';
|
|
163
|
+
writeFileEnsureDir(join(outputDir, 'index.js'), indexJs);
|
|
109
164
|
// Generate package.json
|
|
110
165
|
const pkgJson = JSON.stringify({
|
|
111
166
|
name: packageName,
|
|
@@ -6,12 +6,17 @@ export type ResourceGeneratorOptions = {
|
|
|
6
6
|
outputDir: string;
|
|
7
7
|
packageName: string;
|
|
8
8
|
prettyPrint?: boolean;
|
|
9
|
+
cwd: string;
|
|
9
10
|
};
|
|
10
11
|
export type FieldInfo = ResourceFieldInfo;
|
|
11
12
|
/**
|
|
12
|
-
* Generate TypeScript type
|
|
13
|
+
* Generate TypeScript type declaration for a single resource.
|
|
13
14
|
*/
|
|
14
|
-
export declare function
|
|
15
|
+
export declare function generateSingleResourceTypeScript(className: string, fields: Record<string, ResourceFieldInfo>, isFallback: boolean, referencedEnums: Set<string>, phpFile?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate source map for a single resource.
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateResourceSourceMap(className: string, fields: Record<string, ResourceFieldInfo>, generatedFile: string, phpFile: string, outputDir: string, hasEnumImports: boolean): string;
|
|
15
20
|
/**
|
|
16
21
|
* Generate runtime JavaScript for resources.
|
|
17
22
|
* Resources are type-only, so this just exports an empty object.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/generators/resources.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/generators/resources.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAehC,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAGF,MAAM,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAE1C;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACzC,UAAU,EAAE,OAAO,EACnB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAuDR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,OAAO,GACtB,MAAM,CAiCR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAmHzE"}
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { join, parse } from 'node:path';
|
|
4
|
-
import { getPhpFiles, readFileSafe, writeFileEnsureDir } from '../utils/file.js';
|
|
3
|
+
import { join, parse, relative } from 'node:path';
|
|
4
|
+
import { getPhpFiles, readFileSafe, writeFileEnsureDir, cleanOutputDir } from '../utils/file.js';
|
|
5
5
|
import { extractDocblockArrayShape, parseResourceFieldsAst, } from '../utils/php-parser.js';
|
|
6
6
|
import { mapDocTypeToTs } from '../utils/type-mapper.js';
|
|
7
7
|
import { printNode, createTypeAlias, createImportType, parseTypeString, createTypeLiteral, } from '../utils/ts-generator.js';
|
|
8
|
+
import { generateSourceMap, createSourceMapComment, } from '../utils/source-map.js';
|
|
8
9
|
/**
|
|
9
|
-
* Generate TypeScript type
|
|
10
|
+
* Generate TypeScript type declaration for a single resource.
|
|
10
11
|
*/
|
|
11
|
-
export function
|
|
12
|
+
export function generateSingleResourceTypeScript(className, fields, isFallback, referencedEnums, phpFile) {
|
|
12
13
|
const nodes = [];
|
|
14
|
+
// Find which enums this resource actually uses
|
|
15
|
+
const usedEnums = new Set();
|
|
16
|
+
for (const info of Object.values(fields)) {
|
|
17
|
+
const type = info.type || '';
|
|
18
|
+
for (const enumName of referencedEnums) {
|
|
19
|
+
if (type.includes(enumName)) {
|
|
20
|
+
usedEnums.add(enumName);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
// Import referenced enums from @app/enums
|
|
14
|
-
if (
|
|
15
|
-
const enumImports = Array.from(
|
|
25
|
+
if (usedEnums.size > 0) {
|
|
26
|
+
const enumImports = Array.from(usedEnums).sort();
|
|
16
27
|
nodes.push(createImportType(enumImports, '@app/enums'));
|
|
17
28
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
nodes.push(createTypeAlias(className, recordType));
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
29
|
+
if (isFallback) {
|
|
30
|
+
// Fallback type: Record<string, any>
|
|
31
|
+
const recordType = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
|
|
32
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
33
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
34
|
+
]);
|
|
35
|
+
nodes.push(createTypeAlias(className, recordType));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
30
38
|
// Create type literal with all fields
|
|
31
39
|
const properties = Object.keys(fields).map((key) => {
|
|
32
40
|
const info = fields[key];
|
|
@@ -38,9 +46,49 @@ export function generateResourceTypeScript(resources, fallbacks, referencedEnums
|
|
|
38
46
|
});
|
|
39
47
|
nodes.push(createTypeAlias(className, createTypeLiteral(properties)));
|
|
40
48
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
const lines = [];
|
|
50
|
+
// Add JSDoc with source reference
|
|
51
|
+
if (phpFile) {
|
|
52
|
+
lines.push(`/** @see ${phpFile} */`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(nodes.map(printNode).join('\n\n'));
|
|
55
|
+
// Add source map comment
|
|
56
|
+
if (phpFile) {
|
|
57
|
+
lines.push(createSourceMapComment(`${className}.d.ts.map`));
|
|
58
|
+
}
|
|
59
|
+
return lines.join('\n') + '\n';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate source map for a single resource.
|
|
63
|
+
*/
|
|
64
|
+
export function generateResourceSourceMap(className, fields, generatedFile, phpFile, outputDir, hasEnumImports) {
|
|
65
|
+
const mappings = [];
|
|
66
|
+
// Calculate base line (after JSDoc comment and optional import)
|
|
67
|
+
let currentLine = 2; // Start after JSDoc comment
|
|
68
|
+
if (hasEnumImports) {
|
|
69
|
+
currentLine += 2; // Import statement + blank line
|
|
70
|
+
}
|
|
71
|
+
// Map each field to its source location
|
|
72
|
+
const fieldKeys = Object.keys(fields);
|
|
73
|
+
for (let i = 0; i < fieldKeys.length; i++) {
|
|
74
|
+
const key = fieldKeys[i];
|
|
75
|
+
const info = fields[key];
|
|
76
|
+
if (info.loc) {
|
|
77
|
+
mappings.push({
|
|
78
|
+
generatedLine: currentLine + 1 + i, // +1 for the "export type X = {" line
|
|
79
|
+
generatedColumn: 4, // Indented
|
|
80
|
+
sourceLine: info.loc.line,
|
|
81
|
+
sourceColumn: info.loc.column || 0,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Calculate relative path from output dir to PHP file
|
|
86
|
+
const relativeSource = relative(outputDir, join(process.cwd(), phpFile)).replace(/\\/g, '/');
|
|
87
|
+
return generateSourceMap({
|
|
88
|
+
file: generatedFile,
|
|
89
|
+
sources: [relativeSource],
|
|
90
|
+
mappings,
|
|
91
|
+
});
|
|
44
92
|
}
|
|
45
93
|
/**
|
|
46
94
|
* Generate runtime JavaScript for resources.
|
|
@@ -53,9 +101,12 @@ export function generateResourceRuntime() {
|
|
|
53
101
|
* Generate resource type files (TypeScript declarations and runtime JavaScript).
|
|
54
102
|
*/
|
|
55
103
|
export function generateResources(options) {
|
|
56
|
-
const { resourcesDir, enumsDir, modelsDir, outputDir, packageName } = options;
|
|
104
|
+
const { resourcesDir, enumsDir, modelsDir, outputDir, packageName, cwd } = options;
|
|
105
|
+
// Clean existing generated files
|
|
106
|
+
cleanOutputDir(outputDir);
|
|
57
107
|
const collectedEnums = {};
|
|
58
108
|
const resources = {};
|
|
109
|
+
const resourcePhpFiles = {};
|
|
59
110
|
const fallbacks = [];
|
|
60
111
|
if (!existsSync(resourcesDir)) {
|
|
61
112
|
console.warn(`Resources directory not found: ${resourcesDir}`);
|
|
@@ -67,6 +118,8 @@ export function generateResources(options) {
|
|
|
67
118
|
const filePath = join(resourcesDir, file);
|
|
68
119
|
const content = readFileSafe(filePath) || '';
|
|
69
120
|
const className = parse(file).name;
|
|
121
|
+
// Calculate relative path from project root for source mapping
|
|
122
|
+
const relativePhpPath = relative(cwd, filePath);
|
|
70
123
|
const docShape = extractDocblockArrayShape(content);
|
|
71
124
|
const mappedDocShape = docShape ? mapDocTypeToTsForShape(docShape) : null;
|
|
72
125
|
const fields = parseResourceFieldsAst(content, {
|
|
@@ -75,6 +128,7 @@ export function generateResources(options) {
|
|
|
75
128
|
enumsDir,
|
|
76
129
|
docShape: mappedDocShape,
|
|
77
130
|
collectedEnums,
|
|
131
|
+
filePath: relativePhpPath,
|
|
78
132
|
});
|
|
79
133
|
if (!fields) {
|
|
80
134
|
fallbacks.push(className);
|
|
@@ -83,6 +137,7 @@ export function generateResources(options) {
|
|
|
83
137
|
else {
|
|
84
138
|
resources[className] = fields;
|
|
85
139
|
}
|
|
140
|
+
resourcePhpFiles[className] = relativePhpPath;
|
|
86
141
|
}
|
|
87
142
|
catch (e) {
|
|
88
143
|
console.warn(`Failed to parse resource file: ${file}`, e);
|
|
@@ -90,14 +145,40 @@ export function generateResources(options) {
|
|
|
90
145
|
}
|
|
91
146
|
// Track which enums are actually referenced
|
|
92
147
|
const referencedEnums = new Set(Object.keys(collectedEnums));
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
148
|
+
const resourceNames = Object.keys(resources);
|
|
149
|
+
// Generate individual files for each resource
|
|
150
|
+
for (const className of resourceNames) {
|
|
151
|
+
const fields = resources[className];
|
|
152
|
+
const isFallback = fallbacks.includes(className);
|
|
153
|
+
const phpFile = resourcePhpFiles[className];
|
|
154
|
+
// Check if this resource has enum imports
|
|
155
|
+
const usedEnums = new Set();
|
|
156
|
+
for (const info of Object.values(fields)) {
|
|
157
|
+
const type = info.type || '';
|
|
158
|
+
for (const enumName of referencedEnums) {
|
|
159
|
+
if (type.includes(enumName)) {
|
|
160
|
+
usedEnums.add(enumName);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const hasEnumImports = usedEnums.size > 0;
|
|
165
|
+
// Generate {ResourceName}.d.ts with JSDoc and source map comment
|
|
166
|
+
const dtsContent = generateSingleResourceTypeScript(className, fields, isFallback, referencedEnums, phpFile);
|
|
167
|
+
writeFileEnsureDir(join(outputDir, `${className}.d.ts`), dtsContent);
|
|
168
|
+
// Generate {ResourceName}.d.ts.map
|
|
169
|
+
if (phpFile && !isFallback) {
|
|
170
|
+
const sourceMap = generateResourceSourceMap(className, fields, `${className}.d.ts`, phpFile, outputDir, hasEnumImports);
|
|
171
|
+
writeFileEnsureDir(join(outputDir, `${className}.d.ts.map`), sourceMap);
|
|
172
|
+
}
|
|
173
|
+
// Generate {ResourceName}.js (empty export for type-only)
|
|
174
|
+
writeFileEnsureDir(join(outputDir, `${className}.js`), 'export {};\n');
|
|
175
|
+
}
|
|
176
|
+
// Generate barrel index.d.ts
|
|
177
|
+
const indexDts = resourceNames.map((n) => `export type { ${n} } from './${n}.js';`).join('\n') + '\n';
|
|
178
|
+
writeFileEnsureDir(join(outputDir, 'index.d.ts'), indexDts);
|
|
179
|
+
// Generate barrel index.js
|
|
180
|
+
const indexJs = resourceNames.map((n) => `export * from './${n}.js';`).join('\n') + '\n';
|
|
181
|
+
writeFileEnsureDir(join(outputDir, 'index.js'), indexJs);
|
|
101
182
|
// Generate package.json
|
|
102
183
|
const pkgJson = JSON.stringify({
|
|
103
184
|
name: packageName,
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,OAAO,GAAE,0BAA+B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,OAAO,GAAE,0BAA+B,GAAG,MAAM,CA6F9E"}
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ export default function ferry(options = {}) {
|
|
|
34
34
|
outputDir: enumsOutputDir,
|
|
35
35
|
packageName: `${namespace}/enums`,
|
|
36
36
|
prettyPrint,
|
|
37
|
+
cwd,
|
|
37
38
|
});
|
|
38
39
|
// Generate @app/resources package
|
|
39
40
|
generateResources({
|
|
@@ -43,6 +44,7 @@ export default function ferry(options = {}) {
|
|
|
43
44
|
outputDir: resourcesOutputDir,
|
|
44
45
|
packageName: `${namespace}/resources`,
|
|
45
46
|
prettyPrint,
|
|
47
|
+
cwd,
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
return {
|
|
@@ -78,6 +80,7 @@ export default function ferry(options = {}) {
|
|
|
78
80
|
enumsDir,
|
|
79
81
|
outputDir: enumsOutputDir,
|
|
80
82
|
packageName: `${namespace}/enums`,
|
|
83
|
+
cwd,
|
|
81
84
|
server,
|
|
82
85
|
});
|
|
83
86
|
// Set up resource watcher
|
|
@@ -87,6 +90,7 @@ export default function ferry(options = {}) {
|
|
|
87
90
|
modelsDir,
|
|
88
91
|
outputDir: resourcesOutputDir,
|
|
89
92
|
packageName: `${namespace}/resources`,
|
|
93
|
+
cwd,
|
|
90
94
|
server,
|
|
91
95
|
});
|
|
92
96
|
},
|
package/dist/utils/file.d.ts
CHANGED
|
@@ -10,4 +10,8 @@ export declare function writeFileEnsureDir(filePath: string, content: string): v
|
|
|
10
10
|
* Get all PHP files from a directory.
|
|
11
11
|
*/
|
|
12
12
|
export declare function getPhpFiles(dir: string): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Clean generated files from output directory, keeping package.json.
|
|
15
|
+
*/
|
|
16
|
+
export declare function cleanOutputDir(outputDir: string): void;
|
|
13
17
|
//# sourceMappingURL=file.d.ts.map
|
package/dist/utils/file.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI1E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAKjD"}
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI1E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAKjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAStD"}
|
package/dist/utils/file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
3
|
/**
|
|
4
4
|
* Safely read a file, returning null if it doesn't exist or can't be read.
|
|
5
5
|
*/
|
|
@@ -28,3 +28,16 @@ export function getPhpFiles(dir) {
|
|
|
28
28
|
}
|
|
29
29
|
return readdirSync(dir).filter((f) => f.endsWith('.php'));
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Clean generated files from output directory, keeping package.json.
|
|
33
|
+
*/
|
|
34
|
+
export function cleanOutputDir(outputDir) {
|
|
35
|
+
if (!existsSync(outputDir))
|
|
36
|
+
return;
|
|
37
|
+
const files = readdirSync(outputDir);
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.endsWith('.d.ts') || file.endsWith('.js') || file.endsWith('.d.ts.map')) {
|
|
40
|
+
unlinkSync(join(outputDir, file));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
export type SourceLocation = {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
column?: number;
|
|
5
|
+
};
|
|
1
6
|
export type EnumCase = {
|
|
2
7
|
key: string;
|
|
3
8
|
value: string | number;
|
|
4
9
|
label?: string;
|
|
10
|
+
loc?: SourceLocation;
|
|
5
11
|
};
|
|
6
12
|
export type EnumDefinition = {
|
|
7
13
|
name: string;
|
|
8
14
|
backing: string | null;
|
|
9
15
|
cases: EnumCase[];
|
|
16
|
+
loc?: SourceLocation;
|
|
10
17
|
};
|
|
11
18
|
/**
|
|
12
19
|
* Parse PHP enum content and extract its definition.
|
|
13
20
|
* This is a pure function that takes PHP source code as input.
|
|
14
21
|
*/
|
|
15
|
-
export declare function parseEnumContent(phpContent: string): EnumDefinition | null;
|
|
22
|
+
export declare function parseEnumContent(phpContent: string, filePath?: string): EnumDefinition | null;
|
|
16
23
|
/**
|
|
17
24
|
* Parse model casts from PHP model content.
|
|
18
25
|
* This is a pure function that takes PHP source code as input.
|
|
@@ -26,6 +33,7 @@ export declare function extractDocblockArrayShape(phpContent: string): Record<st
|
|
|
26
33
|
export type ResourceFieldInfo = {
|
|
27
34
|
type: string;
|
|
28
35
|
optional: boolean;
|
|
36
|
+
loc?: SourceLocation;
|
|
29
37
|
};
|
|
30
38
|
export type ResourceArrayEntry = {
|
|
31
39
|
key: string;
|
|
@@ -39,6 +47,7 @@ export type ParseResourceOptions = {
|
|
|
39
47
|
docShape?: Record<string, string> | null;
|
|
40
48
|
collectedEnums?: Record<string, EnumDefinition>;
|
|
41
49
|
resourceClass?: string;
|
|
50
|
+
filePath?: string;
|
|
42
51
|
};
|
|
43
52
|
/**
|
|
44
53
|
* Parse resource fields from PHP content using AST.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-parser.d.ts","sourceRoot":"","sources":["../../src/utils/php-parser.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"php-parser.d.ts","sourceRoot":"","sources":["../../src/utils/php-parser.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB,CAAC;AA6FF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAgG7F;AA4CD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwC1E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAwE3F;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAgTF;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAM,GACxD,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,IAAI,CAqC1C"}
|
package/dist/utils/php-parser.js
CHANGED
|
@@ -13,7 +13,7 @@ const parser = new PhpParser({
|
|
|
13
13
|
php8: true,
|
|
14
14
|
},
|
|
15
15
|
ast: {
|
|
16
|
-
withPositions:
|
|
16
|
+
withPositions: true,
|
|
17
17
|
},
|
|
18
18
|
});
|
|
19
19
|
/**
|
|
@@ -108,7 +108,7 @@ function getStringValue(node) {
|
|
|
108
108
|
* Parse PHP enum content and extract its definition.
|
|
109
109
|
* This is a pure function that takes PHP source code as input.
|
|
110
110
|
*/
|
|
111
|
-
export function parseEnumContent(phpContent) {
|
|
111
|
+
export function parseEnumContent(phpContent, filePath) {
|
|
112
112
|
const ast = parsePhp(phpContent);
|
|
113
113
|
if (!ast)
|
|
114
114
|
return null;
|
|
@@ -118,6 +118,10 @@ export function parseEnumContent(phpContent) {
|
|
|
118
118
|
return null;
|
|
119
119
|
const name = typeof enumNode.name === 'string' ? enumNode.name : enumNode.name.name;
|
|
120
120
|
const backing = enumNode.valueType ? enumNode.valueType.name.toLowerCase() : null;
|
|
121
|
+
// Capture enum location
|
|
122
|
+
const enumLoc = filePath && enumNode.loc?.start
|
|
123
|
+
? { file: filePath, line: enumNode.loc.start.line, column: enumNode.loc.start.column }
|
|
124
|
+
: undefined;
|
|
121
125
|
// Extract enum cases
|
|
122
126
|
const cases = [];
|
|
123
127
|
const enumCases = findAllNodesByKind(enumNode, 'enumcase');
|
|
@@ -147,7 +151,11 @@ export function parseEnumContent(phpContent) {
|
|
|
147
151
|
else {
|
|
148
152
|
value = key;
|
|
149
153
|
}
|
|
150
|
-
|
|
154
|
+
// Capture case location
|
|
155
|
+
const caseLoc = filePath && enumCase.loc?.start
|
|
156
|
+
? { file: filePath, line: enumCase.loc.start.line, column: enumCase.loc.start.column }
|
|
157
|
+
: undefined;
|
|
158
|
+
cases.push({ key, value, loc: caseLoc });
|
|
151
159
|
}
|
|
152
160
|
// Parse label() method if it exists
|
|
153
161
|
const methods = findAllNodesByKind(enumNode, 'method');
|
|
@@ -186,7 +194,7 @@ export function parseEnumContent(phpContent) {
|
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
|
-
return { name, backing, cases };
|
|
197
|
+
return { name, backing, cases, loc: enumLoc };
|
|
190
198
|
}
|
|
191
199
|
/**
|
|
192
200
|
* Extract key-value pairs from a PHP array node.
|
|
@@ -474,13 +482,20 @@ function inferTypeFromAstNode(node, key, options = {}) {
|
|
|
474
482
|
if (resource === 'Collection') {
|
|
475
483
|
return { type: 'any[]', optional };
|
|
476
484
|
}
|
|
477
|
-
// Resource::collection
|
|
478
|
-
if (method === 'collection'
|
|
485
|
+
// Resource::collection returns Resource[] (array of resources)
|
|
486
|
+
if (method === 'collection') {
|
|
479
487
|
if (resourceExists(resource, resourcesDir)) {
|
|
480
488
|
return { type: `${resource}[]`, optional };
|
|
481
489
|
}
|
|
482
490
|
return { type: 'any[]', optional };
|
|
483
491
|
}
|
|
492
|
+
// Resource::make returns a single Resource
|
|
493
|
+
if (method === 'make') {
|
|
494
|
+
if (resourceExists(resource, resourcesDir)) {
|
|
495
|
+
return { type: resource, optional };
|
|
496
|
+
}
|
|
497
|
+
return { type: 'any', optional };
|
|
498
|
+
}
|
|
484
499
|
}
|
|
485
500
|
// Check if it's a whenLoaded call without a wrapper resource
|
|
486
501
|
if (call.what.kind === 'propertylookup') {
|
|
@@ -572,6 +587,7 @@ function inferTypeFromAstNode(node, key, options = {}) {
|
|
|
572
587
|
*/
|
|
573
588
|
function parseArrayEntries(items, options = {}) {
|
|
574
589
|
const result = {};
|
|
590
|
+
const { filePath } = options;
|
|
575
591
|
for (const item of items) {
|
|
576
592
|
if (item.kind !== 'entry')
|
|
577
593
|
continue;
|
|
@@ -582,6 +598,14 @@ function parseArrayEntries(items, options = {}) {
|
|
|
582
598
|
if (!key)
|
|
583
599
|
continue;
|
|
584
600
|
const fieldInfo = inferTypeFromAstNode(entry.value, key, options);
|
|
601
|
+
// Capture field location from the entry key
|
|
602
|
+
if (filePath && entry.key.loc?.start) {
|
|
603
|
+
fieldInfo.loc = {
|
|
604
|
+
file: filePath,
|
|
605
|
+
line: entry.key.loc.start.line,
|
|
606
|
+
column: entry.key.loc.start.column,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
585
609
|
result[key] = { key, fieldInfo };
|
|
586
610
|
// Handle nested arrays
|
|
587
611
|
if (entry.value.kind === 'array') {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source map generation utilities.
|
|
3
|
+
* Implements the Source Map v3 format with VLQ encoding.
|
|
4
|
+
*/
|
|
5
|
+
export type SourceMapping = {
|
|
6
|
+
generatedLine: number;
|
|
7
|
+
generatedColumn: number;
|
|
8
|
+
sourceLine: number;
|
|
9
|
+
sourceColumn: number;
|
|
10
|
+
name?: string;
|
|
11
|
+
};
|
|
12
|
+
export type SourceMapOptions = {
|
|
13
|
+
file: string;
|
|
14
|
+
sourceRoot?: string;
|
|
15
|
+
sources: string[];
|
|
16
|
+
sourcesContent?: (string | null)[];
|
|
17
|
+
names?: string[];
|
|
18
|
+
mappings: SourceMapping[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generate a source map JSON string.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateSourceMap(options: SourceMapOptions): string;
|
|
24
|
+
/**
|
|
25
|
+
* Create the sourceMappingURL comment to append to generated files.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSourceMapComment(mapFileName: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Calculate relative path from one file to another.
|
|
30
|
+
* Used to compute the source path relative to the generated .d.ts.map file.
|
|
31
|
+
*/
|
|
32
|
+
export declare function relativePath(from: string, to: string): string;
|
|
33
|
+
//# sourceMappingURL=source-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-map.d.ts","sourceRoot":"","sources":["../../src/utils/source-map.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAqFF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAcnE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAmB7D"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source map generation utilities.
|
|
3
|
+
* Implements the Source Map v3 format with VLQ encoding.
|
|
4
|
+
*/
|
|
5
|
+
const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
6
|
+
/**
|
|
7
|
+
* Encode a single number as VLQ (Variable Length Quantity).
|
|
8
|
+
*/
|
|
9
|
+
function encodeVLQ(value) {
|
|
10
|
+
let result = '';
|
|
11
|
+
let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
|
|
12
|
+
do {
|
|
13
|
+
let digit = vlq & 0x1f;
|
|
14
|
+
vlq >>>= 5;
|
|
15
|
+
if (vlq > 0) {
|
|
16
|
+
digit |= 0x20; // continuation bit
|
|
17
|
+
}
|
|
18
|
+
result += BASE64_CHARS[digit];
|
|
19
|
+
} while (vlq > 0);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encode a source map segment.
|
|
24
|
+
* Each segment contains: generatedColumn, sourceIndex, sourceLine, sourceColumn, [nameIndex]
|
|
25
|
+
*/
|
|
26
|
+
function encodeSegment(generatedColumn, sourceIndex, sourceLine, sourceColumn, prevState) {
|
|
27
|
+
const result = encodeVLQ(generatedColumn - prevState.genCol) +
|
|
28
|
+
encodeVLQ(sourceIndex - prevState.srcIdx) +
|
|
29
|
+
encodeVLQ(sourceLine - prevState.srcLine) +
|
|
30
|
+
encodeVLQ(sourceColumn - prevState.srcCol);
|
|
31
|
+
prevState.genCol = generatedColumn;
|
|
32
|
+
prevState.srcIdx = sourceIndex;
|
|
33
|
+
prevState.srcLine = sourceLine;
|
|
34
|
+
prevState.srcCol = sourceColumn;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generate VLQ-encoded mappings string from mapping entries.
|
|
39
|
+
*/
|
|
40
|
+
function encodeMappings(mappings) {
|
|
41
|
+
if (mappings.length === 0)
|
|
42
|
+
return '';
|
|
43
|
+
// Sort by generated line, then column
|
|
44
|
+
const sorted = [...mappings].sort((a, b) => {
|
|
45
|
+
if (a.generatedLine !== b.generatedLine)
|
|
46
|
+
return a.generatedLine - b.generatedLine;
|
|
47
|
+
return a.generatedColumn - b.generatedColumn;
|
|
48
|
+
});
|
|
49
|
+
const lines = [];
|
|
50
|
+
const state = { genCol: 0, srcIdx: 0, srcLine: 0, srcCol: 0 };
|
|
51
|
+
for (const mapping of sorted) {
|
|
52
|
+
// Ensure we have enough lines
|
|
53
|
+
while (lines.length < mapping.generatedLine) {
|
|
54
|
+
lines.push([]);
|
|
55
|
+
state.genCol = 0; // Reset column at start of each line
|
|
56
|
+
}
|
|
57
|
+
const lineIndex = mapping.generatedLine - 1;
|
|
58
|
+
const segment = encodeSegment(mapping.generatedColumn, 0, // sourceIndex - always 0 since we have one source file per map
|
|
59
|
+
mapping.sourceLine - 1, // 0-indexed in source maps
|
|
60
|
+
mapping.sourceColumn, state);
|
|
61
|
+
lines[lineIndex].push(segment);
|
|
62
|
+
}
|
|
63
|
+
return lines.map((segments) => segments.join(',')).join(';');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate a source map JSON string.
|
|
67
|
+
*/
|
|
68
|
+
export function generateSourceMap(options) {
|
|
69
|
+
const { file, sourceRoot = '', sources, sourcesContent, names = [], mappings } = options;
|
|
70
|
+
const map = {
|
|
71
|
+
version: 3,
|
|
72
|
+
file,
|
|
73
|
+
sourceRoot,
|
|
74
|
+
sources,
|
|
75
|
+
...(sourcesContent ? { sourcesContent } : {}),
|
|
76
|
+
names,
|
|
77
|
+
mappings: encodeMappings(mappings),
|
|
78
|
+
};
|
|
79
|
+
return JSON.stringify(map);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create the sourceMappingURL comment to append to generated files.
|
|
83
|
+
*/
|
|
84
|
+
export function createSourceMapComment(mapFileName) {
|
|
85
|
+
return `//# sourceMappingURL=${mapFileName}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Calculate relative path from one file to another.
|
|
89
|
+
* Used to compute the source path relative to the generated .d.ts.map file.
|
|
90
|
+
*/
|
|
91
|
+
export function relativePath(from, to) {
|
|
92
|
+
const fromParts = from.split('/').slice(0, -1); // Remove filename
|
|
93
|
+
const toParts = to.split('/');
|
|
94
|
+
// Find common prefix
|
|
95
|
+
let commonLength = 0;
|
|
96
|
+
while (commonLength < fromParts.length &&
|
|
97
|
+
commonLength < toParts.length &&
|
|
98
|
+
fromParts[commonLength] === toParts[commonLength]) {
|
|
99
|
+
commonLength++;
|
|
100
|
+
}
|
|
101
|
+
// Build relative path
|
|
102
|
+
const ups = fromParts.length - commonLength;
|
|
103
|
+
const remaining = toParts.slice(commonLength);
|
|
104
|
+
return [...Array(ups).fill('..'), ...remaining].join('/');
|
|
105
|
+
}
|
package/dist/watchers/enums.js
CHANGED
|
@@ -5,7 +5,7 @@ import { logError, logFileChange, logRegeneration } from '../utils/banner.js';
|
|
|
5
5
|
* Set up a watcher for enum files.
|
|
6
6
|
*/
|
|
7
7
|
export function setupEnumWatcher(options) {
|
|
8
|
-
const { enumsDir, outputDir, packageName, server } = options;
|
|
8
|
+
const { enumsDir, outputDir, packageName, cwd, server } = options;
|
|
9
9
|
const enumPattern = join(enumsDir, '*.php');
|
|
10
10
|
const generatedJsPath = join(outputDir, 'index.js');
|
|
11
11
|
// Watch PHP enum files
|
|
@@ -17,7 +17,7 @@ export function setupEnumWatcher(options) {
|
|
|
17
17
|
try {
|
|
18
18
|
logFileChange('enums', basename(filePath));
|
|
19
19
|
// Regenerate enum files
|
|
20
|
-
generateEnums({ enumsDir, outputDir, packageName });
|
|
20
|
+
generateEnums({ enumsDir, outputDir, packageName, cwd });
|
|
21
21
|
// Tell Vite the generated file changed (triggers normal HMR)
|
|
22
22
|
server.watcher.emit('change', generatedJsPath);
|
|
23
23
|
logRegeneration('enums');
|
|
@@ -5,7 +5,7 @@ import { logError, logFileChange, logRegeneration } from '../utils/banner.js';
|
|
|
5
5
|
* Set up a watcher for resource and model files.
|
|
6
6
|
*/
|
|
7
7
|
export function setupResourceWatcher(options) {
|
|
8
|
-
const { resourcesDir, enumsDir, modelsDir, outputDir, packageName, server } = options;
|
|
8
|
+
const { resourcesDir, enumsDir, modelsDir, outputDir, packageName, cwd, server } = options;
|
|
9
9
|
const resourcePattern = join(resourcesDir, '*.php');
|
|
10
10
|
const modelPattern = join(modelsDir, '*.php');
|
|
11
11
|
const generatedDtsPath = join(outputDir, 'index.d.ts');
|
|
@@ -21,7 +21,7 @@ export function setupResourceWatcher(options) {
|
|
|
21
21
|
const fileType = isModel ? 'model' : 'resource';
|
|
22
22
|
logFileChange(fileType, basename(filePath));
|
|
23
23
|
// Regenerate resource types
|
|
24
|
-
generateResources({ resourcesDir, enumsDir, modelsDir, outputDir, packageName });
|
|
24
|
+
generateResources({ resourcesDir, enumsDir, modelsDir, outputDir, packageName, cwd });
|
|
25
25
|
// Tell Vite the generated type file changed
|
|
26
26
|
// TypeScript will pick up changes automatically
|
|
27
27
|
server.watcher.emit('change', generatedDtsPath);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"prettier": "@aniftyco/prettier",
|
|
4
4
|
"description": "Ferries Laravel types to your TypeScript frontend",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.2.0",
|
|
7
7
|
"repository": "https://github.com/aniftyco/vite-plugin-ferry",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|