create-prisma-php-app 5.0.0-alpha.7 → 5.1.0-alpha.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/.github/copilot-instructions.md +142 -0
- package/dist/AGENTS.md +548 -194
- package/dist/bootstrap.php +227 -34
- package/dist/index.js +2 -2
- package/dist/prisma-php.js +2 -2
- package/dist/public/js/pp-reactive-v2.js +1 -1
- package/dist/settings/auto-swagger-docs.ts +10 -10
- package/dist/settings/bs-config.ts +3 -26
- package/dist/settings/build.ts +2 -32
- package/dist/settings/component-map.ts +476 -0
- package/dist/settings/files-list.ts +2 -2
- package/dist/settings/project-name.ts +3 -6
- package/dist/settings/restart-mcp.ts +2 -2
- package/dist/settings/restart-websocket.ts +2 -2
- package/dist/settings/swagger-config.ts +3 -3
- package/dist/settings/utils.ts +3 -3
- package/dist/src/Lib/Auth/Auth.php +53 -12
- package/dist/src/Lib/Middleware/AuthMiddleware.php +2 -2
- package/dist/src/Lib/Middleware/CorsMiddleware.php +72 -0
- package/package.json +1 -1
- package/dist/README.md +0 -213
- package/dist/settings/bs-config.json +0 -6
- package/dist/settings/class-imports.ts +0 -165
- package/dist/settings/class-log.ts +0 -244
- package/dist/settings/component-import-checker.ts +0 -90
- package/dist/settings/files-list.json +0 -1
- package/dist/settings/prisma-schema-config.json +0 -16
|
@@ -457,7 +457,7 @@ function generateEndpoints(modelName: string, fields: any[]): void {
|
|
|
457
457
|
const baseDir = `src/app/${kebabCasedModelName}`;
|
|
458
458
|
const idField = fields.find((field) => field.isId);
|
|
459
459
|
const fieldsToCreateAndUpdate = fields.filter(
|
|
460
|
-
(field) => shouldSkipField(field) === false
|
|
460
|
+
(field) => shouldSkipField(field) === false,
|
|
461
461
|
);
|
|
462
462
|
const idFieldName = idField.name;
|
|
463
463
|
const baseDirPath = resolve(__dirname, `../${baseDir}`);
|
|
@@ -476,7 +476,7 @@ function generateEndpoints(modelName: string, fields: any[]): void {
|
|
|
476
476
|
writeFileSync(
|
|
477
477
|
resolve(__dirname, `../${listRoutePath}`),
|
|
478
478
|
listRouteContent,
|
|
479
|
-
"utf-8"
|
|
479
|
+
"utf-8",
|
|
480
480
|
);
|
|
481
481
|
|
|
482
482
|
const idDir = `${baseDir}/[id]`;
|
|
@@ -508,7 +508,7 @@ echo json_encode($${camelCaseModelName});`;
|
|
|
508
508
|
writeFileSync(
|
|
509
509
|
resolve(__dirname, `../${idRoutePath}`),
|
|
510
510
|
idRouteContent,
|
|
511
|
-
"utf-8"
|
|
511
|
+
"utf-8",
|
|
512
512
|
);
|
|
513
513
|
|
|
514
514
|
const createDir = `${baseDir}/create`;
|
|
@@ -564,7 +564,7 @@ echo json_encode($new${modelName});`;
|
|
|
564
564
|
writeFileSync(
|
|
565
565
|
resolve(__dirname, `../${createRoutePath}`),
|
|
566
566
|
createRouteContent,
|
|
567
|
-
"utf-8"
|
|
567
|
+
"utf-8",
|
|
568
568
|
);
|
|
569
569
|
|
|
570
570
|
const updateDir = `${baseDir}/update/[id]`;
|
|
@@ -624,7 +624,7 @@ echo json_encode($updated${modelName});`;
|
|
|
624
624
|
writeFileSync(
|
|
625
625
|
resolve(__dirname, `../${updateRoutePath}`),
|
|
626
626
|
updateRouteContent,
|
|
627
|
-
"utf-8"
|
|
627
|
+
"utf-8",
|
|
628
628
|
);
|
|
629
629
|
|
|
630
630
|
const deleteDir = `${baseDir}/delete/[id]`;
|
|
@@ -655,7 +655,7 @@ echo json_encode($deleted${modelName});`;
|
|
|
655
655
|
writeFileSync(
|
|
656
656
|
resolve(__dirname, `../${deleteRoutePath}`),
|
|
657
657
|
deleteRouteContent,
|
|
658
|
-
"utf-8"
|
|
658
|
+
"utf-8",
|
|
659
659
|
);
|
|
660
660
|
}
|
|
661
661
|
|
|
@@ -675,7 +675,7 @@ async function promptUserForGenerationOptions() {
|
|
|
675
675
|
writeFileSync(
|
|
676
676
|
resolve(__dirname, "./prisma-schema-config.json"),
|
|
677
677
|
JSON.stringify(prismaSchemaConfigJson, null, 2),
|
|
678
|
-
"utf-8"
|
|
678
|
+
"utf-8",
|
|
679
679
|
);
|
|
680
680
|
|
|
681
681
|
await swaggerConfig();
|
|
@@ -704,7 +704,7 @@ async function promptUserForGenerationOptions() {
|
|
|
704
704
|
writeFileSync(
|
|
705
705
|
resolve(__dirname, "./prisma-schema-config.json"),
|
|
706
706
|
JSON.stringify(prismaSchemaConfigJson, null, 2),
|
|
707
|
-
"utf-8"
|
|
707
|
+
"utf-8",
|
|
708
708
|
);
|
|
709
709
|
}
|
|
710
710
|
|
|
@@ -752,8 +752,8 @@ function generateAndSaveSwaggerDocsForModel(model: any): void {
|
|
|
752
752
|
writeFileSync(outputFilePath, swaggerAnnotation, "utf-8");
|
|
753
753
|
console.log(
|
|
754
754
|
`Swagger annotations for model "${model.name}" generated at: ${chalk.blue(
|
|
755
|
-
whereToSave
|
|
756
|
-
)}
|
|
755
|
+
whereToSave,
|
|
756
|
+
)}`,
|
|
757
757
|
);
|
|
758
758
|
|
|
759
759
|
if (prismaSchemaConfigJson.generateEndpoints) {
|
|
@@ -9,20 +9,14 @@ import prismaPhpConfigJson from "../prisma-php.json";
|
|
|
9
9
|
import { generateFileListJson } from "./files-list.js";
|
|
10
10
|
import { join, dirname, relative } from "path";
|
|
11
11
|
import { getFileMeta, PUBLIC_DIR, SRC_DIR } from "./utils.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
analyzeImportsInFile,
|
|
15
|
-
getAllPhpFiles,
|
|
16
|
-
updateComponentImports,
|
|
17
|
-
} from "./class-imports";
|
|
18
|
-
import { checkComponentImports } from "./component-import-checker";
|
|
12
|
+
import { updateComponentMap } from "./component-map";
|
|
19
13
|
import { DebouncedWorker, createSrcWatcher, DEFAULT_AWF } from "./utils.js";
|
|
20
14
|
import chalk from "chalk";
|
|
21
15
|
|
|
22
16
|
const { __dirname } = getFileMeta();
|
|
23
17
|
const bs: BrowserSyncInstance = browserSync.create();
|
|
24
18
|
|
|
25
|
-
const PUBLIC_IGNORE_DIRS = [""];
|
|
19
|
+
const PUBLIC_IGNORE_DIRS = ["uploads"];
|
|
26
20
|
|
|
27
21
|
function getExternalIP(): string | null {
|
|
28
22
|
const nets = networkInterfaces();
|
|
@@ -39,24 +33,7 @@ function getExternalIP(): string | null {
|
|
|
39
33
|
const pipeline = new DebouncedWorker(
|
|
40
34
|
async () => {
|
|
41
35
|
await generateFileListJson();
|
|
42
|
-
await
|
|
43
|
-
await updateComponentImports();
|
|
44
|
-
|
|
45
|
-
const phpFiles = await getAllPhpFiles(SRC_DIR);
|
|
46
|
-
for (const file of phpFiles) {
|
|
47
|
-
const rawFileImports = await analyzeImportsInFile(file);
|
|
48
|
-
const fileImports: Record<
|
|
49
|
-
string,
|
|
50
|
-
{ className: string; filePath: string; importer?: string }[]
|
|
51
|
-
> = {};
|
|
52
|
-
for (const key in rawFileImports) {
|
|
53
|
-
const v = rawFileImports[key];
|
|
54
|
-
fileImports[key] = Array.isArray(v)
|
|
55
|
-
? v
|
|
56
|
-
: [{ className: key, filePath: v }];
|
|
57
|
-
}
|
|
58
|
-
await checkComponentImports(file, fileImports);
|
|
59
|
-
}
|
|
36
|
+
await updateComponentMap();
|
|
60
37
|
|
|
61
38
|
if (bs.active) {
|
|
62
39
|
bs.reload();
|
package/dist/settings/build.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
1
|
import { generateFileListJson } from "./files-list.js";
|
|
3
|
-
import { updateAllClassLogs } from "./class-log.js";
|
|
4
2
|
import {
|
|
5
3
|
deleteFilesIfExist,
|
|
6
4
|
filesToDelete,
|
|
7
5
|
deleteDirectoriesIfExist,
|
|
8
6
|
dirsToDelete,
|
|
9
7
|
} from "./project-name.js";
|
|
10
|
-
import {
|
|
11
|
-
analyzeImportsInFile,
|
|
12
|
-
getAllPhpFiles,
|
|
13
|
-
SRC_DIR,
|
|
14
|
-
updateComponentImports,
|
|
15
|
-
} from "./class-imports";
|
|
16
|
-
import { checkComponentImports } from "./component-import-checker";
|
|
8
|
+
import { updateComponentMap } from "./component-map.js";
|
|
17
9
|
|
|
18
10
|
(async () => {
|
|
19
11
|
console.log("📦 Generating files for production...");
|
|
@@ -21,29 +13,7 @@ import { checkComponentImports } from "./component-import-checker";
|
|
|
21
13
|
await deleteFilesIfExist(filesToDelete);
|
|
22
14
|
await deleteDirectoriesIfExist(dirsToDelete);
|
|
23
15
|
await generateFileListJson();
|
|
24
|
-
await
|
|
25
|
-
await updateComponentImports();
|
|
26
|
-
|
|
27
|
-
const phpFiles = await getAllPhpFiles(join(SRC_DIR, "app"));
|
|
28
|
-
for (const file of phpFiles) {
|
|
29
|
-
const rawFileImports = await analyzeImportsInFile(file);
|
|
30
|
-
|
|
31
|
-
const fileImports: Record<
|
|
32
|
-
string,
|
|
33
|
-
{ className: string; filePath: string; importer?: string }[]
|
|
34
|
-
> = {};
|
|
35
|
-
|
|
36
|
-
for (const key in rawFileImports) {
|
|
37
|
-
const val = rawFileImports[key];
|
|
38
|
-
if (typeof val === "string") {
|
|
39
|
-
fileImports[key] = [{ className: key, filePath: val }];
|
|
40
|
-
} else {
|
|
41
|
-
fileImports[key] = val;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
await checkComponentImports(file, fileImports);
|
|
46
|
-
}
|
|
16
|
+
await updateComponentMap();
|
|
47
17
|
|
|
48
18
|
console.log("✅ Generating files for production completed.");
|
|
49
19
|
})();
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Engine } from "php-parser";
|
|
4
|
+
import { getFileMeta } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
const { __dirname } = getFileMeta();
|
|
7
|
+
|
|
8
|
+
const parser = new Engine({
|
|
9
|
+
parser: {
|
|
10
|
+
php8: true,
|
|
11
|
+
suppressErrors: true,
|
|
12
|
+
},
|
|
13
|
+
ast: {
|
|
14
|
+
withPositions: false,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const PROJECT_ROOT = path.join(__dirname, "..");
|
|
19
|
+
const CONFIG_FILE = path.join(PROJECT_ROOT, "prisma-php.json");
|
|
20
|
+
const COMPONENT_MAP_FILE = path.join(
|
|
21
|
+
PROJECT_ROOT,
|
|
22
|
+
"settings/component-map.json",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
26
|
+
|
|
27
|
+
const PHPX_BASE_CLASS = "PHPX";
|
|
28
|
+
|
|
29
|
+
type JsonValue =
|
|
30
|
+
| string
|
|
31
|
+
| number
|
|
32
|
+
| boolean
|
|
33
|
+
| null
|
|
34
|
+
| JsonValue[]
|
|
35
|
+
| { [key: string]: JsonValue };
|
|
36
|
+
|
|
37
|
+
export interface ComponentMapProp {
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
hasDefault: boolean;
|
|
41
|
+
defaultValue: JsonValue | string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ComponentMapEntry {
|
|
45
|
+
componentName: string;
|
|
46
|
+
tagName: string;
|
|
47
|
+
filePath: string;
|
|
48
|
+
relativePath: string;
|
|
49
|
+
importRoute: string;
|
|
50
|
+
extendsPHPX: boolean;
|
|
51
|
+
acceptsArbitraryProps: boolean;
|
|
52
|
+
props: ComponentMapProp[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface PrismaPhpConfig {
|
|
56
|
+
projectRootPath?: string;
|
|
57
|
+
excludeFiles?: string[];
|
|
58
|
+
componentScanDirs?: string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type DiscoveredComponent = ComponentMapEntry;
|
|
62
|
+
|
|
63
|
+
async function loadConfig(): Promise<PrismaPhpConfig> {
|
|
64
|
+
try {
|
|
65
|
+
const raw = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
66
|
+
return JSON.parse(raw) as PrismaPhpConfig;
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveProjectRoot(config: PrismaPhpConfig): string {
|
|
73
|
+
if (config.projectRootPath && path.isAbsolute(config.projectRootPath)) {
|
|
74
|
+
return config.projectRootPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return PROJECT_ROOT;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveScanRoots(
|
|
81
|
+
config: PrismaPhpConfig,
|
|
82
|
+
projectRoot: string,
|
|
83
|
+
): string[] {
|
|
84
|
+
const scanDirs =
|
|
85
|
+
Array.isArray(config.componentScanDirs) &&
|
|
86
|
+
config.componentScanDirs.length > 0
|
|
87
|
+
? config.componentScanDirs
|
|
88
|
+
: ["src"];
|
|
89
|
+
|
|
90
|
+
return scanDirs.map((dirPath) =>
|
|
91
|
+
path.isAbsolute(dirPath) ? dirPath : path.join(projectRoot, dirPath),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizePathKey(filePath: string): string {
|
|
96
|
+
return path.normalize(filePath).toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function toAbsoluteComponentPath(filePath: string): string {
|
|
100
|
+
return path.normalize(filePath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toProjectRelativePath(filePath: string, projectRoot: string): string {
|
|
104
|
+
return path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function saveJsonFile(filePath: string, value: unknown): Promise<void> {
|
|
108
|
+
await fs.writeFile(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function componentNameToTagName(componentName: string): string {
|
|
112
|
+
const kebabName = componentName
|
|
113
|
+
.replace(/([a-z\d])([A-Z])/g, "$1-$2")
|
|
114
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
115
|
+
.replace(/[_.\s]+/g, "-")
|
|
116
|
+
.toLowerCase();
|
|
117
|
+
|
|
118
|
+
return `x-${kebabName}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getAllPhpFiles(dir: string): Promise<string[]> {
|
|
122
|
+
const files: string[] = [];
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
126
|
+
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
const fullPath = path.join(dir, entry.name);
|
|
129
|
+
|
|
130
|
+
if (entry.isDirectory()) {
|
|
131
|
+
files.push(...(await getAllPhpFiles(fullPath)));
|
|
132
|
+
} else if (entry.isFile() && fullPath.toLowerCase().endsWith(".php")) {
|
|
133
|
+
files.push(fullPath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
return files;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function nameToString(node: any): string {
|
|
144
|
+
if (!node) {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof node === "string") {
|
|
149
|
+
return node;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (typeof node.raw === "string" && node.raw.trim() !== "") {
|
|
153
|
+
return node.raw;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (typeof node.name === "string") {
|
|
157
|
+
return node.name;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (Array.isArray(node.name)) {
|
|
161
|
+
return node.name
|
|
162
|
+
.map((part: any) =>
|
|
163
|
+
typeof part === "string" ? part : (part?.name ?? ""),
|
|
164
|
+
)
|
|
165
|
+
.filter(Boolean)
|
|
166
|
+
.join("\\");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (Array.isArray(node.items)) {
|
|
170
|
+
return node.items.map(nameToString).filter(Boolean).join("|");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return String(node.name ?? "");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function typeToString(typeNode: any, nullable = false): string {
|
|
177
|
+
if (!typeNode) {
|
|
178
|
+
return "mixed";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let rawType = "";
|
|
182
|
+
|
|
183
|
+
if (typeof typeNode.raw === "string" && typeNode.raw.trim() !== "") {
|
|
184
|
+
rawType = typeNode.raw.replace(/\s+/g, "");
|
|
185
|
+
} else if (Array.isArray(typeNode.types)) {
|
|
186
|
+
rawType = typeNode.types.map((item: any) => typeToString(item)).join("|");
|
|
187
|
+
} else {
|
|
188
|
+
rawType = nameToString(typeNode).replace(/\s+/g, "");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
nullable &&
|
|
193
|
+
rawType !== "mixed" &&
|
|
194
|
+
!rawType.startsWith("?") &&
|
|
195
|
+
!rawType.split("|").includes("null")
|
|
196
|
+
) {
|
|
197
|
+
return `?${rawType}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return rawType || "mixed";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function astValueToJson(valueNode: any): JsonValue | string {
|
|
204
|
+
if (!valueNode || typeof valueNode !== "object") {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
switch (valueNode.kind) {
|
|
209
|
+
case "string":
|
|
210
|
+
return valueNode.value ?? "";
|
|
211
|
+
case "number":
|
|
212
|
+
return Number(valueNode.value ?? valueNode.raw ?? 0);
|
|
213
|
+
case "boolean":
|
|
214
|
+
return Boolean(valueNode.value);
|
|
215
|
+
case "nullkeyword":
|
|
216
|
+
return null;
|
|
217
|
+
case "array": {
|
|
218
|
+
const items = Array.isArray(valueNode.items) ? valueNode.items : [];
|
|
219
|
+
const hasNamedKeys = items.some((item: any) => item?.key);
|
|
220
|
+
|
|
221
|
+
if (!hasNamedKeys) {
|
|
222
|
+
return items.map((item: any) => astValueToJson(item?.value ?? item));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const mapped: Record<string, JsonValue | string> = {};
|
|
226
|
+
|
|
227
|
+
for (const item of items) {
|
|
228
|
+
const key = item?.key ? String(astValueToJson(item.key)) : "";
|
|
229
|
+
mapped[key] = astValueToJson(item?.value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return mapped;
|
|
233
|
+
}
|
|
234
|
+
case "unary": {
|
|
235
|
+
const inner = astValueToJson(valueNode.what);
|
|
236
|
+
|
|
237
|
+
if (typeof inner === "number") {
|
|
238
|
+
return valueNode.type === "-" ? -inner : inner;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return typeof valueNode.raw === "string" ? valueNode.raw : String(inner);
|
|
242
|
+
}
|
|
243
|
+
case "constref": {
|
|
244
|
+
const constantName = nameToString(valueNode.name).toLowerCase();
|
|
245
|
+
|
|
246
|
+
if (constantName === "true") {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (constantName === "false") {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (constantName === "null") {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return nameToString(valueNode.name);
|
|
259
|
+
}
|
|
260
|
+
default:
|
|
261
|
+
if (typeof valueNode.raw === "string" && valueNode.raw.trim() !== "") {
|
|
262
|
+
return valueNode.raw;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return nameToString(valueNode);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function extractComponentProps(classNode: any): ComponentMapProp[] {
|
|
270
|
+
const props: ComponentMapProp[] = [];
|
|
271
|
+
|
|
272
|
+
for (const statement of classNode.body ?? []) {
|
|
273
|
+
if (
|
|
274
|
+
!statement ||
|
|
275
|
+
statement.kind !== "propertystatement" ||
|
|
276
|
+
statement.visibility !== "public" ||
|
|
277
|
+
statement.isStatic
|
|
278
|
+
) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (const property of statement.properties ?? []) {
|
|
283
|
+
props.push({
|
|
284
|
+
name: nameToString(property.name),
|
|
285
|
+
type: typeToString(property.type, Boolean(property.nullable)),
|
|
286
|
+
hasDefault: property.value !== undefined && property.value !== null,
|
|
287
|
+
defaultValue:
|
|
288
|
+
property.value !== undefined && property.value !== null
|
|
289
|
+
? astValueToJson(property.value)
|
|
290
|
+
: null,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return props;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function classExtendsPHPX(classNode: any): boolean {
|
|
299
|
+
if (!classNode?.extends) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const name = nameToString(classNode.extends);
|
|
304
|
+
return name.split("\\").pop() === PHPX_BASE_CLASS;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function analyzeComponentsInFile(
|
|
308
|
+
filePath: string,
|
|
309
|
+
projectRoot: string,
|
|
310
|
+
): Promise<DiscoveredComponent[]> {
|
|
311
|
+
const code = await fs.readFile(filePath, "utf-8");
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const ast = parser.parseCode(code, filePath);
|
|
315
|
+
const components: DiscoveredComponent[] = [];
|
|
316
|
+
|
|
317
|
+
function traverse(node: any, currentNamespace = "") {
|
|
318
|
+
if (Array.isArray(node)) {
|
|
319
|
+
node.forEach((childNode) => traverse(childNode, currentNamespace));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!node || typeof node !== "object") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (node.kind === "namespace") {
|
|
328
|
+
const nextNamespace = nameToString(node.name).replace(/^\\+/, "");
|
|
329
|
+
|
|
330
|
+
for (const [key, value] of Object.entries(node)) {
|
|
331
|
+
if (key === "kind" || key === "name") {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
traverse(value, nextNamespace || currentNamespace);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (node.kind === "class" && node.name?.name) {
|
|
342
|
+
const extendsPHPX = classExtendsPHPX(node);
|
|
343
|
+
|
|
344
|
+
if (extendsPHPX) {
|
|
345
|
+
const componentName = node.name.name as string;
|
|
346
|
+
const importRoute =
|
|
347
|
+
(currentNamespace ? `${currentNamespace}\\` : "") + componentName;
|
|
348
|
+
|
|
349
|
+
components.push({
|
|
350
|
+
componentName,
|
|
351
|
+
tagName: componentNameToTagName(componentName),
|
|
352
|
+
filePath: toAbsoluteComponentPath(filePath),
|
|
353
|
+
relativePath: toProjectRelativePath(filePath, projectRoot),
|
|
354
|
+
importRoute,
|
|
355
|
+
extendsPHPX,
|
|
356
|
+
acceptsArbitraryProps: extendsPHPX,
|
|
357
|
+
props: extractComponentProps(node),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const [key, value] of Object.entries(node)) {
|
|
363
|
+
if (key === "kind" || key === "name") {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
traverse(value, currentNamespace);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
traverse(ast);
|
|
372
|
+
return components;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error(`Error parsing component file: ${filePath}`, error);
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function sortComponentMap(
|
|
380
|
+
componentMap: DiscoveredComponent[],
|
|
381
|
+
): DiscoveredComponent[] {
|
|
382
|
+
return [...componentMap].sort((left, right) => {
|
|
383
|
+
const nameComparison = left.componentName.localeCompare(
|
|
384
|
+
right.componentName,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (nameComparison !== 0) {
|
|
388
|
+
return nameComparison;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return left.relativePath.localeCompare(right.relativePath);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function buildRuntimeComponentLookup(
|
|
396
|
+
componentMap: ComponentMapEntry[],
|
|
397
|
+
): Record<
|
|
398
|
+
string,
|
|
399
|
+
Array<{
|
|
400
|
+
tagName: string;
|
|
401
|
+
componentName: string;
|
|
402
|
+
className: string;
|
|
403
|
+
filePath: string;
|
|
404
|
+
}>
|
|
405
|
+
> {
|
|
406
|
+
const lookup: Record<
|
|
407
|
+
string,
|
|
408
|
+
Array<{
|
|
409
|
+
tagName: string;
|
|
410
|
+
componentName: string;
|
|
411
|
+
className: string;
|
|
412
|
+
filePath: string;
|
|
413
|
+
}>
|
|
414
|
+
> = {};
|
|
415
|
+
|
|
416
|
+
for (const entry of componentMap) {
|
|
417
|
+
const tagName = entry.tagName.toLowerCase();
|
|
418
|
+
|
|
419
|
+
if (!lookup[tagName]) {
|
|
420
|
+
lookup[tagName] = [];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
lookup[tagName].push({
|
|
424
|
+
tagName,
|
|
425
|
+
componentName: entry.componentName,
|
|
426
|
+
className: entry.importRoute,
|
|
427
|
+
filePath: entry.filePath,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return Object.fromEntries(
|
|
432
|
+
Object.entries(lookup).sort(([left], [right]) => left.localeCompare(right)),
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export async function updateComponentMap(): Promise<ComponentMapEntry[]> {
|
|
437
|
+
const config = await loadConfig();
|
|
438
|
+
const projectRoot = resolveProjectRoot(config);
|
|
439
|
+
const scanRoots = resolveScanRoots(config, projectRoot);
|
|
440
|
+
const excludedFiles = new Set(
|
|
441
|
+
(config.excludeFiles ?? []).map((filePath) =>
|
|
442
|
+
normalizePathKey(
|
|
443
|
+
path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath),
|
|
444
|
+
),
|
|
445
|
+
),
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const phpFiles: string[] = [];
|
|
449
|
+
|
|
450
|
+
for (const scanRoot of scanRoots) {
|
|
451
|
+
const discoveredFiles = await getAllPhpFiles(scanRoot);
|
|
452
|
+
|
|
453
|
+
for (const filePath of discoveredFiles) {
|
|
454
|
+
if (!excludedFiles.has(normalizePathKey(filePath))) {
|
|
455
|
+
phpFiles.push(filePath);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const uniquePhpFiles = [
|
|
461
|
+
...new Set(phpFiles.map((filePath) => path.normalize(filePath))),
|
|
462
|
+
];
|
|
463
|
+
const discoveredComponents: DiscoveredComponent[] = [];
|
|
464
|
+
|
|
465
|
+
for (const filePath of uniquePhpFiles) {
|
|
466
|
+
discoveredComponents.push(
|
|
467
|
+
...(await analyzeComponentsInFile(filePath, projectRoot)),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const sortedComponents = sortComponentMap(discoveredComponents);
|
|
472
|
+
const componentMap: ComponentMapEntry[] = [...sortedComponents];
|
|
473
|
+
await saveJsonFile(COMPONENT_MAP_FILE, componentMap);
|
|
474
|
+
|
|
475
|
+
return componentMap;
|
|
476
|
+
}
|
|
@@ -23,7 +23,7 @@ const getAllFiles = (dirPath: string): string[] => {
|
|
|
23
23
|
} else {
|
|
24
24
|
const relativePath = `.${sep}${relative(
|
|
25
25
|
join(__dirname, ".."),
|
|
26
|
-
fullPath
|
|
26
|
+
fullPath,
|
|
27
27
|
)}`;
|
|
28
28
|
files.push(relativePath.replace(/\\/g, "/").replace(/^\.\.\//, ""));
|
|
29
29
|
}
|
|
@@ -41,7 +41,7 @@ export const generateFileListJson = async (): Promise<void> => {
|
|
|
41
41
|
if (allFiles.length > 0) {
|
|
42
42
|
writeFileSync(jsonFilePath, JSON.stringify(allFiles, null, 2));
|
|
43
43
|
console.log(
|
|
44
|
-
`File list generated: ${appFiles.length} app files, ${publicFiles.length} public files
|
|
44
|
+
`File list generated: ${appFiles.length} app files, ${publicFiles.length} public files`,
|
|
45
45
|
);
|
|
46
46
|
} else {
|
|
47
47
|
console.error("No files found to save in the JSON file.");
|
|
@@ -3,8 +3,7 @@ import { join, basename, dirname, normalize, sep } from "path";
|
|
|
3
3
|
import prismaPhpConfigJson from "../prisma-php.json";
|
|
4
4
|
import { getFileMeta } from "./utils.js";
|
|
5
5
|
import { promises as fsPromises } from "fs";
|
|
6
|
-
import {
|
|
7
|
-
import { updateComponentImports } from "./class-imports";
|
|
6
|
+
import { updateComponentMap } from "./component-map";
|
|
8
7
|
import { generateFileListJson } from "./files-list";
|
|
9
8
|
|
|
10
9
|
const { __dirname } = getFileMeta();
|
|
@@ -152,8 +151,7 @@ export async function deleteDirectoriesIfExist(
|
|
|
152
151
|
|
|
153
152
|
export const filesToDelete = [
|
|
154
153
|
join(__dirname, "request-data.json"),
|
|
155
|
-
join(__dirname, "
|
|
156
|
-
join(__dirname, "class-imports.json"),
|
|
154
|
+
join(__dirname, "component-map.json"),
|
|
157
155
|
];
|
|
158
156
|
|
|
159
157
|
export const dirsToDelete = [
|
|
@@ -164,5 +162,4 @@ export const dirsToDelete = [
|
|
|
164
162
|
await deleteFilesIfExist(filesToDelete);
|
|
165
163
|
await deleteDirectoriesIfExist(dirsToDelete);
|
|
166
164
|
await generateFileListJson();
|
|
167
|
-
await
|
|
168
|
-
await updateComponentImports();
|
|
165
|
+
await updateComponentMap();
|