create-prisma-php-app 4.0.0-alpha.54 → 4.0.0-alpha.55
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/index.js +7 -0
- package/dist/settings/class-log.ts +172 -70
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -339,6 +339,7 @@ async function main() {
|
|
|
339
339
|
prisma: localSettings.prisma,
|
|
340
340
|
docker: localSettings.docker,
|
|
341
341
|
isUpdate: true,
|
|
342
|
+
componentScanDirs: localSettings.componentScanDirs ?? [],
|
|
342
343
|
excludeFiles: localSettings.excludeFiles ?? [],
|
|
343
344
|
excludeFilePath: excludeFiles ?? [],
|
|
344
345
|
filePath: currentDir,
|
|
@@ -370,6 +371,7 @@ async function main() {
|
|
|
370
371
|
prisma: answer.prisma,
|
|
371
372
|
docker: answer.docker,
|
|
372
373
|
isUpdate: true,
|
|
374
|
+
componentScanDirs: localSettings.componentScanDirs ?? [],
|
|
373
375
|
excludeFiles: localSettings.excludeFiles ?? [],
|
|
374
376
|
excludeFilePath: excludeFiles ?? [],
|
|
375
377
|
filePath: currentDir,
|
|
@@ -479,6 +481,7 @@ async function main() {
|
|
|
479
481
|
updateAnswer = {
|
|
480
482
|
...answer,
|
|
481
483
|
isUpdate: true,
|
|
484
|
+
componentScanDirs: existingConfig.componentScanDirs ?? [],
|
|
482
485
|
excludeFiles: existingConfig.excludeFiles ?? [],
|
|
483
486
|
excludeFilePath: excludeFiles ?? [],
|
|
484
487
|
filePath: projectPath,
|
|
@@ -822,6 +825,10 @@ async function main() {
|
|
|
822
825
|
prisma: answer.prisma,
|
|
823
826
|
docker: answer.docker,
|
|
824
827
|
version: latestVersionOfCreatePrismaPhpApp,
|
|
828
|
+
componentScanDirs: updateAnswer?.componentScanDirs ?? [
|
|
829
|
+
"src",
|
|
830
|
+
"vendor/tsnc/prisma-php/src",
|
|
831
|
+
],
|
|
825
832
|
excludeFiles: updateAnswer?.excludeFiles ?? [],
|
|
826
833
|
};
|
|
827
834
|
fs.writeFileSync(
|
|
@@ -5,80 +5,167 @@ import { getFileMeta } from "./utils.js";
|
|
|
5
5
|
|
|
6
6
|
const { __dirname } = getFileMeta();
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const CONFIG_FILE = path.join(__dirname, "..", "prisma-php.json");
|
|
9
9
|
const LOG_FILE = path.join(__dirname, "class-log.json");
|
|
10
|
+
|
|
11
|
+
const SRC_DIR = path.join(__dirname, "..", "src");
|
|
12
|
+
|
|
10
13
|
const IPHPX_INTERFACE = "IPHPX";
|
|
11
14
|
const PHPX_BASE_CLASS = "PHPX";
|
|
12
15
|
|
|
16
|
+
type LogMap = Record<
|
|
17
|
+
string,
|
|
18
|
+
{
|
|
19
|
+
filePath: string;
|
|
20
|
+
baseDir?: string;
|
|
21
|
+
}
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
interface PrismaPhpConfig {
|
|
25
|
+
projectRootPath?: string;
|
|
26
|
+
excludeFiles?: string[];
|
|
27
|
+
componentScanDirs?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
const parser = new Engine({
|
|
14
|
-
parser: {
|
|
15
|
-
|
|
16
|
-
suppressErrors: true,
|
|
17
|
-
},
|
|
18
|
-
ast: {
|
|
19
|
-
withPositions: false,
|
|
20
|
-
},
|
|
31
|
+
parser: { php8: true, suppressErrors: true },
|
|
32
|
+
ast: { withPositions: false },
|
|
21
33
|
});
|
|
22
34
|
|
|
23
|
-
async function
|
|
35
|
+
async function loadConfig(): Promise<PrismaPhpConfig> {
|
|
24
36
|
try {
|
|
25
|
-
const
|
|
26
|
-
return JSON.parse(
|
|
37
|
+
const raw = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
38
|
+
return JSON.parse(raw) as PrismaPhpConfig;
|
|
27
39
|
} catch {
|
|
28
40
|
return {};
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
|
|
44
|
+
function resolveProjectRoot(cfg: PrismaPhpConfig): string {
|
|
45
|
+
if (cfg.projectRootPath && path.isAbsolute(cfg.projectRootPath)) {
|
|
46
|
+
return cfg.projectRootPath;
|
|
47
|
+
}
|
|
48
|
+
return path.join(__dirname, "..");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveScanRoots(cfg: PrismaPhpConfig, projectRoot: string): string[] {
|
|
52
|
+
const dirs =
|
|
53
|
+
Array.isArray(cfg.componentScanDirs) && cfg.componentScanDirs.length
|
|
54
|
+
? cfg.componentScanDirs
|
|
55
|
+
: ["src"];
|
|
56
|
+
|
|
57
|
+
return dirs.map((d) => (path.isAbsolute(d) ? d : path.join(projectRoot, d)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function relativeFromSrc(absPath: string): string {
|
|
61
|
+
const rel = path.relative(SRC_DIR, absPath);
|
|
62
|
+
return rel.replace(/\\/g, "\\");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeForWinBackslashes(p: string): string {
|
|
66
|
+
return p.replace(/\\/g, "\\");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pickRootForFile(scanRoots: string[], absPath: string): string {
|
|
70
|
+
for (const root of scanRoots) {
|
|
71
|
+
const rel = path.relative(root, absPath);
|
|
72
|
+
if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return scanRoots[0] ?? path.dirname(absPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function saveLogData(logData: LogMap) {
|
|
33
80
|
await fs.writeFile(LOG_FILE, JSON.stringify(logData, null, 2));
|
|
34
81
|
}
|
|
35
82
|
|
|
83
|
+
function nameToString(n: any): string {
|
|
84
|
+
if (!n) return "";
|
|
85
|
+
if (typeof n === "string") return n;
|
|
86
|
+
if (typeof n.name === "string") return n.name;
|
|
87
|
+
if (Array.isArray(n.name)) {
|
|
88
|
+
return n.name
|
|
89
|
+
.map((p: any) => (typeof p === "string" ? p : p?.name ?? ""))
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.join("\\");
|
|
92
|
+
}
|
|
93
|
+
if (n.kind === "name" && typeof n.name === "string") return n.name;
|
|
94
|
+
return String(n.name ?? "");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function namespaceNodeToString(nsNode: any): string {
|
|
98
|
+
if (!nsNode) return "";
|
|
99
|
+
const s = nameToString(nsNode);
|
|
100
|
+
return s.replace(/^\s+|\s+$/g, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
36
103
|
async function analyzePhpFile(filePath: string) {
|
|
37
104
|
const code = await fs.readFile(filePath, "utf-8");
|
|
38
105
|
|
|
39
106
|
try {
|
|
40
|
-
// Parse the PHP file to AST
|
|
41
107
|
const ast = parser.parseCode(code, filePath);
|
|
42
108
|
|
|
43
|
-
|
|
109
|
+
type Found = {
|
|
110
|
+
fqcn: string;
|
|
44
111
|
name: string;
|
|
45
112
|
implementsIPHPX: boolean;
|
|
46
113
|
extendsPHPX: boolean;
|
|
47
|
-
}
|
|
114
|
+
};
|
|
115
|
+
const classesFound: Found[] = [];
|
|
48
116
|
|
|
49
|
-
function traverse(node: any) {
|
|
117
|
+
function traverse(node: any, currentNs: string) {
|
|
50
118
|
if (Array.isArray(node)) {
|
|
51
|
-
node.forEach(traverse);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let implementsIPHPX = false;
|
|
57
|
-
let extendsPHPX = false;
|
|
119
|
+
node.forEach((n) => traverse(n, currentNs));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!node || typeof node !== "object") return;
|
|
58
123
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
124
|
+
if (node.kind === "namespace") {
|
|
125
|
+
const nsName = namespaceNodeToString(node.name ?? node);
|
|
126
|
+
const nextNs = nsName || "";
|
|
127
|
+
for (const key in node) {
|
|
128
|
+
if (key === "kind" || key === "name") continue;
|
|
129
|
+
traverse(node[key], nextNs);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
64
133
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
134
|
+
if (node.kind === "class" && node.name?.name) {
|
|
135
|
+
const className = node.name.name as string;
|
|
68
136
|
|
|
69
|
-
|
|
137
|
+
let implementsIPHPX = false;
|
|
138
|
+
if (Array.isArray(node.implements)) {
|
|
139
|
+
implementsIPHPX = node.implements.some((iface: any) => {
|
|
140
|
+
const nm = nameToString(iface);
|
|
141
|
+
const leaf = nm.split("\\").pop()!;
|
|
142
|
+
return leaf === IPHPX_INTERFACE;
|
|
143
|
+
});
|
|
70
144
|
}
|
|
71
145
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
let extendsPHPX = false;
|
|
147
|
+
if (node.extends) {
|
|
148
|
+
const nm = nameToString(node.extends);
|
|
149
|
+
const leaf = nm.split("\\").pop()!;
|
|
150
|
+
extendsPHPX = leaf === PHPX_BASE_CLASS;
|
|
76
151
|
}
|
|
152
|
+
|
|
153
|
+
const fqcn = (currentNs ? currentNs + "\\" : "") + className;
|
|
154
|
+
classesFound.push({
|
|
155
|
+
fqcn,
|
|
156
|
+
name: className,
|
|
157
|
+
implementsIPHPX,
|
|
158
|
+
extendsPHPX,
|
|
159
|
+
});
|
|
77
160
|
}
|
|
78
|
-
}
|
|
79
161
|
|
|
80
|
-
|
|
162
|
+
for (const key in node) {
|
|
163
|
+
if (key === "name" || key === "kind") continue;
|
|
164
|
+
if (node[key]) traverse(node[key], currentNs);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
81
167
|
|
|
168
|
+
traverse(ast, "");
|
|
82
169
|
return classesFound;
|
|
83
170
|
} catch (error) {
|
|
84
171
|
console.error(`Error parsing file: ${filePath}`, error);
|
|
@@ -86,60 +173,75 @@ async function analyzePhpFile(filePath: string) {
|
|
|
86
173
|
}
|
|
87
174
|
}
|
|
88
175
|
|
|
89
|
-
async function guessFullClassName(filePath: string, className: string) {
|
|
90
|
-
const srcDir = path.join(__dirname, "..", "src");
|
|
91
|
-
const relativeFromSrc = path.relative(srcDir, filePath);
|
|
92
|
-
const withoutExtension = relativeFromSrc.replace(/\.php$/, "");
|
|
93
|
-
const parts = withoutExtension.split(path.sep);
|
|
94
|
-
parts.pop();
|
|
95
|
-
const namespace = parts.join("\\");
|
|
96
|
-
|
|
97
|
-
return `${namespace}\\${className}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
176
|
async function updateClassLogForFile(
|
|
101
|
-
|
|
102
|
-
logData:
|
|
177
|
+
absFilePath: string,
|
|
178
|
+
logData: LogMap,
|
|
179
|
+
scanRoots: string[],
|
|
180
|
+
projectRoot: string
|
|
103
181
|
) {
|
|
104
|
-
const classes = await analyzePhpFile(
|
|
182
|
+
const classes = await analyzePhpFile(absFilePath);
|
|
183
|
+
const matchedRoot = pickRootForFile(scanRoots, absFilePath);
|
|
184
|
+
const baseDirRelToProject = path
|
|
185
|
+
.relative(projectRoot, matchedRoot)
|
|
186
|
+
.replace(/\\/g, "/");
|
|
187
|
+
|
|
105
188
|
for (const cls of classes) {
|
|
106
189
|
if (cls.implementsIPHPX || cls.extendsPHPX) {
|
|
107
|
-
const classFullName =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
190
|
+
const classFullName = cls.fqcn;
|
|
191
|
+
|
|
192
|
+
const legacyRelPath = relativeFromSrc(absFilePath);
|
|
193
|
+
|
|
111
194
|
logData[classFullName] = {
|
|
112
|
-
filePath:
|
|
195
|
+
filePath: normalizeForWinBackslashes(legacyRelPath),
|
|
196
|
+
baseDir: baseDirRelToProject,
|
|
113
197
|
};
|
|
114
198
|
}
|
|
115
199
|
}
|
|
116
200
|
}
|
|
117
201
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
// Get all PHP files in src
|
|
122
|
-
async function getAllPhpFiles(dir: string): Promise<string[]> {
|
|
202
|
+
async function getAllPhpFiles(dir: string): Promise<string[]> {
|
|
203
|
+
const files: string[] = [];
|
|
204
|
+
try {
|
|
123
205
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
124
|
-
const files: string[] = [];
|
|
125
206
|
for (const entry of entries) {
|
|
126
207
|
const fullPath = path.join(dir, entry.name);
|
|
127
208
|
if (entry.isDirectory()) {
|
|
128
209
|
files.push(...(await getAllPhpFiles(fullPath)));
|
|
129
|
-
} else if (entry.isFile() && fullPath.endsWith(".php")) {
|
|
210
|
+
} else if (entry.isFile() && fullPath.toLowerCase().endsWith(".php")) {
|
|
130
211
|
files.push(fullPath);
|
|
131
212
|
}
|
|
132
213
|
}
|
|
133
|
-
|
|
214
|
+
} catch {
|
|
215
|
+
// ignore missing/unreadable dirs to keep resilient
|
|
216
|
+
}
|
|
217
|
+
return files;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function updateAllClassLogs() {
|
|
221
|
+
const cfg = await loadConfig();
|
|
222
|
+
const projectRoot = resolveProjectRoot(cfg);
|
|
223
|
+
const scanRoots = resolveScanRoots(cfg, projectRoot);
|
|
224
|
+
|
|
225
|
+
const excludeAbs = new Set(
|
|
226
|
+
(cfg.excludeFiles ?? []).map((p) =>
|
|
227
|
+
path.isAbsolute(p) ? p : path.join(projectRoot, p)
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const allPhpFiles: string[] = [];
|
|
232
|
+
for (const root of scanRoots) {
|
|
233
|
+
const files = await getAllPhpFiles(root);
|
|
234
|
+
for (const f of files) {
|
|
235
|
+
if (!excludeAbs.has(f)) allPhpFiles.push(f);
|
|
236
|
+
}
|
|
134
237
|
}
|
|
135
238
|
|
|
136
|
-
const
|
|
239
|
+
const logData: LogMap = {};
|
|
137
240
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await updateClassLogForFile(file, logData);
|
|
241
|
+
for (const file of allPhpFiles) {
|
|
242
|
+
await updateClassLogForFile(file, logData, scanRoots, projectRoot);
|
|
141
243
|
}
|
|
142
244
|
|
|
143
245
|
await saveLogData(logData);
|
|
144
|
-
// console.log("class-log.json updated
|
|
246
|
+
// console.log("class-log.json updated from configured componentScanDirs.");
|
|
145
247
|
}
|