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 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 SRC_DIR = path.join(__dirname, "..", "src");
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
- php8: true,
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 loadLogData(): Promise<Record<string, any>> {
35
+ async function loadConfig(): Promise<PrismaPhpConfig> {
24
36
  try {
25
- const content = await fs.readFile(LOG_FILE, "utf-8");
26
- return JSON.parse(content);
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
- async function saveLogData(logData: Record<string, any>) {
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
- const classesFound: {
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
- } else if (node && typeof node === "object") {
53
- if (node.kind === "class" && node.name?.name) {
54
- const className = node.name.name;
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
- if (node.implements && Array.isArray(node.implements)) {
60
- implementsIPHPX = node.implements.some(
61
- (iface: any) => iface.name === IPHPX_INTERFACE
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
- if (node.extends && node.extends.name === PHPX_BASE_CLASS) {
66
- extendsPHPX = true;
67
- }
134
+ if (node.kind === "class" && node.name?.name) {
135
+ const className = node.name.name as string;
68
136
 
69
- classesFound.push({ name: className, implementsIPHPX, extendsPHPX });
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
- for (const key in node) {
73
- if (node[key]) {
74
- traverse(node[key]);
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
- traverse(ast);
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
- filePath: string,
102
- logData: Record<string, any>
177
+ absFilePath: string,
178
+ logData: LogMap,
179
+ scanRoots: string[],
180
+ projectRoot: string
103
181
  ) {
104
- const classes = await analyzePhpFile(filePath);
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 = await guessFullClassName(filePath, cls.name);
108
- const relativePath = path
109
- .relative(SRC_DIR, filePath)
110
- .replace(/\\/g, "\\");
190
+ const classFullName = cls.fqcn;
191
+
192
+ const legacyRelPath = relativeFromSrc(absFilePath);
193
+
111
194
  logData[classFullName] = {
112
- filePath: relativePath,
195
+ filePath: normalizeForWinBackslashes(legacyRelPath),
196
+ baseDir: baseDirRelToProject,
113
197
  };
114
198
  }
115
199
  }
116
200
  }
117
201
 
118
- export async function updateAllClassLogs() {
119
- const logData = await loadLogData();
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
- return files;
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 phpFiles = await getAllPhpFiles(SRC_DIR);
239
+ const logData: LogMap = {};
137
240
 
138
- // Clear the log to rebuild it fresh each time (optional)
139
- for (const file of phpFiles) {
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 with all PHP files.");
246
+ // console.log("class-log.json updated from configured componentScanDirs.");
145
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "4.0.0-alpha.54",
3
+ "version": "4.0.0-alpha.55",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",