create-prisma-php-app 2.1.3 → 2.1.4

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.
@@ -20,17 +20,12 @@ export const SRC_DIR = path.join(PROJECT_ROOT, "src");
20
20
  const IMPORTS_FILE = path.join(PROJECT_ROOT, "settings/class-imports.json");
21
21
  const CLASS_LOG_FILE = path.join(PROJECT_ROOT, "settings/class-log.json");
22
22
 
23
- async function loadImportsData(): Promise<Record<string, string>> {
24
- try {
25
- const content = await fs.readFile(IMPORTS_FILE, "utf-8");
26
- return JSON.parse(content);
27
- } catch {
28
- return {};
29
- }
30
- }
31
23
 
32
24
  async function saveImportsData(
33
- data: Record<string, { className: string; filePath: string }>
25
+ data: Record<
26
+ string,
27
+ Array<{ className: string; filePath: string; importer: string }>
28
+ >
34
29
  ) {
35
30
  await fs.writeFile(IMPORTS_FILE, JSON.stringify(data, null, 2), "utf-8");
36
31
  }
@@ -86,16 +81,14 @@ export async function analyzeImportsInFile(
86
81
  } else {
87
82
  // Handle grouped `use` statements
88
83
  if (node.kind === "usegroup" && node.name) {
89
- baseNamespace = node.name.name || node.name; // Set base namespace
84
+ baseNamespace = node.name.name || node.name;
90
85
  for (const useItem of node.items || []) {
91
86
  if (useItem.kind === "useitem" && useItem.name) {
92
- const subNamespace = useItem.name.name || useItem.name; // Sub-namespace
93
- const fqn = combineNamespaces(baseNamespace, subNamespace); // Fully Qualified Namespace
94
- const alias = useItem.alias ? useItem.alias.name : subNamespace; // Alias or default to subNamespace
87
+ const subNamespace = useItem.name.name || useItem.name;
88
+ const fqn = combineNamespaces(baseNamespace, subNamespace);
89
+ const alias = useItem.alias ? useItem.alias.name : subNamespace;
95
90
  if (!imports[alias]) {
96
- // Prevent overwriting
97
- imports[alias] = fqn; // Map alias to FQN
98
- // console.log(`🚀 Adding import: ${alias} -> ${fqn}`);
91
+ imports[alias] = fqn;
99
92
  }
100
93
  }
101
94
  }
@@ -103,12 +96,11 @@ export async function analyzeImportsInFile(
103
96
 
104
97
  // Handle non-grouped `use` statements
105
98
  if (node.kind === "useitem" && node.name) {
106
- const fqn = node.name.name || node.name; // Fully Qualified Namespace
99
+ const fqn = node.name.name || node.name;
107
100
  const alias = node.alias
108
101
  ? node.alias.name
109
102
  : path.basename(fqn.replace(/\\/g, "/"));
110
103
  if (!imports[alias]) {
111
- // Prevent overwriting
112
104
  imports[alias] = fqn;
113
105
  }
114
106
  }
@@ -129,38 +121,56 @@ export async function analyzeImportsInFile(
129
121
  }
130
122
 
131
123
  export async function updateComponentImports() {
132
- // Load existing imports (if any)
133
- const allImports = await loadImportsData();
134
-
135
124
  // Analyze all PHP files for use statements
136
125
  const phpFiles = await getAllPhpFiles(SRC_DIR);
126
+ // Build a mapping: alias -> array of { fqn, importer }
127
+ const allImports: Record<
128
+ string,
129
+ Array<{ fqn: string; importer: string }>
130
+ > = {};
131
+
137
132
  for (const file of phpFiles) {
138
133
  const fileImports = await analyzeImportsInFile(file);
139
- // Merge fileImports into allImports
140
- Object.assign(allImports, fileImports);
134
+ for (const [alias, fqn] of Object.entries(fileImports)) {
135
+ if (allImports[alias]) {
136
+ // Check both fqn and importer to avoid duplicates
137
+ if (
138
+ !allImports[alias].some(
139
+ (entry) => entry.fqn === fqn && entry.importer === file
140
+ )
141
+ ) {
142
+ allImports[alias].push({ fqn, importer: file });
143
+ }
144
+ } else {
145
+ allImports[alias] = [{ fqn, importer: file }];
146
+ }
147
+ }
141
148
  }
142
149
 
143
- // Now filter using class-log.json
150
+ // Load the class log to filter valid imports
144
151
  const classLog = await loadClassLogData();
145
-
146
152
  const filteredImports: Record<
147
153
  string,
148
- { className: string; filePath: string }
154
+ Array<{ className: string; filePath: string; importer: string }>
149
155
  > = {};
150
- for (const [alias, fqn] of Object.entries(allImports)) {
151
- if (classLog[fqn]) {
152
- // console.log(`Including: ${alias} -> ${fqn}`);
153
- filteredImports[alias] = {
154
- className: fqn,
155
- filePath: classLog[fqn].filePath,
156
- };
157
- } else {
158
- // console.log(`Excluding: ${alias} -> ${fqn}`);
156
+
157
+ for (const [alias, entries] of Object.entries(allImports)) {
158
+ for (const entry of entries) {
159
+ if (classLog[entry.fqn]) {
160
+ const importEntry = {
161
+ className: entry.fqn,
162
+ filePath: classLog[entry.fqn].filePath,
163
+ importer: entry.importer,
164
+ };
165
+ if (filteredImports[alias]) {
166
+ filteredImports[alias].push(importEntry);
167
+ } else {
168
+ filteredImports[alias] = [importEntry];
169
+ }
170
+ }
159
171
  }
160
172
  }
161
173
 
162
174
  await saveImportsData(filteredImports);
163
- // console.log(
164
- // "component_imports.json updated with IPHPX/PHPX components only."
165
- // );
175
+ // console.log("component_imports.json updated with importer file path included.");
166
176
  }
@@ -26,12 +26,49 @@ function findComponentsInFile(code: string): string[] {
26
26
 
27
27
  export async function checkComponentImports(
28
28
  filePath: string,
29
- fileImports: Record<string, string>
29
+ fileImports: Record<
30
+ string,
31
+ | Array<{ className: string; filePath: string; importer?: string }>
32
+ | { className: string; filePath: string; importer?: string }
33
+ >
30
34
  ) {
31
35
  const code = await fs.readFile(filePath, "utf-8");
32
36
  const usedComponents = findComponentsInFile(code);
37
+ // Normalize the current file path: replace backslashes, trim, remove trailing slash, and lower-case.
38
+ const normalizedFilePath = filePath
39
+ .replace(/\\/g, "/")
40
+ .trim()
41
+ .replace(/\/+$/, "")
42
+ .toLowerCase();
43
+
33
44
  usedComponents.forEach((component) => {
34
- if (!fileImports[component]) {
45
+ const rawMapping = fileImports[component];
46
+ // Normalize rawMapping to an array
47
+ let mappings: Array<{
48
+ className: string;
49
+ filePath: string;
50
+ importer?: string;
51
+ }> = [];
52
+ if (Array.isArray(rawMapping)) {
53
+ mappings = rawMapping;
54
+ } else if (rawMapping) {
55
+ mappings = [rawMapping];
56
+ }
57
+
58
+ // Check if any mapping's importer matches the current file.
59
+ const found = mappings.some((mapping) => {
60
+ const normalizedImporter = (mapping.importer || "")
61
+ .replace(/\\/g, "/")
62
+ .trim()
63
+ .replace(/\/+$/, "")
64
+ .toLowerCase();
65
+ // Either exact match or the current file path ends with the importer.
66
+ return (
67
+ normalizedFilePath === normalizedImporter ||
68
+ normalizedFilePath.endsWith(normalizedImporter)
69
+ );
70
+ });
71
+ if (!found) {
35
72
  console.warn(
36
73
  chalk.yellow("Warning: ") +
37
74
  chalk.white("Component ") +
@@ -35,7 +35,7 @@ class Auth
35
35
  */
36
36
  private function __construct()
37
37
  {
38
- $this->secretKey = $_ENV['AUTH_SECRET'];
38
+ $this->secretKey = $_ENV['AUTH_SECRET'] ?? 'CD24eEv4qbsC5LOzqeaWbcr58mBMSvA4Mkii8GjRiHkt';
39
39
  self::$cookieName = self::getCookieName();
40
40
  }
41
41
 
@@ -16,7 +16,7 @@ class PHPX implements IPHPX
16
16
  /**
17
17
  * @var mixed The children elements or content to be rendered within the component.
18
18
  */
19
- protected mixed $children;
19
+ public mixed $children;
20
20
 
21
21
  /**
22
22
  * @var string The CSS class for custom styling.
@@ -11,6 +11,7 @@ use DOMElement;
11
11
  use DOMComment;
12
12
  use DOMNode;
13
13
  use RuntimeException;
14
+ use Bootstrap;
14
15
 
15
16
  class TemplateCompiler
16
17
  {
@@ -40,6 +41,7 @@ class TemplateCompiler
40
41
  self::initializeClassMappings();
41
42
  }
42
43
 
44
+
43
45
  $templateContent = self::preprocessBindings($templateContent);
44
46
 
45
47
  $dom = self::convertToXml($templateContent);
@@ -71,7 +73,6 @@ class TemplateCompiler
71
73
  $expression = $matches[3];
72
74
  $afterBinding = $matches[4];
73
75
 
74
- // Escape the binding expression for attributes.
75
76
  $escapedExpression = htmlspecialchars($expression, ENT_QUOTES, 'UTF-8');
76
77
  $placeholder = '%%ATTR_BIND_' . count($attributePlaceholders) . '%%';
77
78
 
@@ -81,17 +82,14 @@ class TemplateCompiler
81
82
  $templateContent
82
83
  );
83
84
 
84
- // Process text {{ ... }} bindings, but skip ones inside attribute values.
85
85
  $textBindingPattern = '/(?:"[^"]*"|\'[^\']*\')(*SKIP)(*FAIL)|{{\s*(.+?)\s*}}/u';
86
86
  $templateContent = preg_replace_callback(
87
87
  $textBindingPattern,
88
88
  function ($matches) {
89
89
  $expr = $matches[1];
90
- // If the expression is a simple word/dot path, use a simple binding.
91
90
  if (preg_match('/^[\w.]+$/u', $expr)) {
92
91
  return "<span pp-bind=\"{$expr}\"></span>";
93
92
  } else {
94
- // Otherwise, encode the expression and use an expression binding.
95
93
  $encodedExpr = htmlspecialchars($expr, ENT_QUOTES, 'UTF-8');
96
94
  return "<span pp-bind-expr=\"{$encodedExpr}\"></span>";
97
95
  }
@@ -219,56 +217,97 @@ class TemplateCompiler
219
217
  if ($node instanceof DOMElement) {
220
218
  $componentName = $node->nodeName;
221
219
  $attributes = [];
222
-
223
220
  foreach ($node->attributes as $attr) {
224
221
  $attributes[$attr->name] = $attr->value;
225
222
  }
226
223
 
227
- $childOutput = [];
228
- foreach ($node->childNodes as $child) {
229
- $childOutput[] = self::processNode($child);
230
- }
231
- $innerContent = implode('', $childOutput);
224
+ if (isset(self::$classMappings[$componentName])) {
225
+ $componentInstance = self::initializeComponentInstance($componentName, $attributes);
232
226
 
233
- $attributes['children'] = $innerContent;
227
+ $childOutput = [];
228
+ foreach ($node->childNodes as $child) {
229
+ $childOutput[] = self::processNode($child);
230
+ }
231
+ $componentInstance->children = implode('', $childOutput);
234
232
 
235
- return self::processComponent($componentName, $attributes);
233
+ $renderedContent = $componentInstance->render();
234
+ if (self::hasComponentTag($renderedContent)) {
235
+ return self::compile($renderedContent);
236
+ }
237
+ return $renderedContent;
238
+ } else {
239
+ $childOutput = [];
240
+ foreach ($node->childNodes as $child) {
241
+ $childOutput[] = self::processNode($child);
242
+ }
243
+ $attributes['children'] = implode('', $childOutput);
244
+ return self::renderAsHtml($componentName, $attributes);
245
+ }
236
246
  } elseif ($node instanceof DOMComment) {
237
247
  return "<!--{$node->textContent}-->";
238
248
  }
239
-
240
249
  return $node->textContent;
241
250
  }
242
251
 
243
- protected static function initializeClassMappings(): void
252
+ protected static function initializeComponentInstance(string $componentName, array $attributes)
244
253
  {
245
- foreach (PrismaPHPSettings::$classLogFiles as $tagName => $fullyQualifiedClassName) {
246
- self::$classMappings[$tagName] = $fullyQualifiedClassName;
247
- }
248
- }
254
+ $importerFile = Bootstrap::$contentToInclude;
255
+ $normalizedImporterFile = str_replace('\\', '/', $importerFile);
249
256
 
250
- protected static function processComponent(string $componentName, array $attributes): string
251
- {
252
- if (isset(self::$classMappings[$componentName])) {
253
- $className = self::$classMappings[$componentName]['className'];
254
- $filePath = self::$classMappings[$componentName]['filePath'];
257
+ $srcPathNormalized = str_replace('\\', '/', SRC_PATH);
258
+ $relativeImporterFile = str_replace($srcPathNormalized . '/', '', $normalizedImporterFile);
255
259
 
256
- require_once str_replace('\\', '/', SRC_PATH . '/' . $filePath);
260
+ if (!isset(self::$classMappings[$componentName])) {
261
+ throw new RuntimeException("Component {$componentName} is not registered.");
262
+ }
257
263
 
258
- if (!class_exists($className)) {
259
- throw new RuntimeException("Class $className does not exist.");
264
+ $mappings = self::$classMappings[$componentName];
265
+ $selectedMapping = null;
266
+
267
+ if (is_array($mappings)) {
268
+ if (isset($mappings[0]) && is_array($mappings[0])) {
269
+ foreach ($mappings as $entry) {
270
+ $entryImporter = isset($entry['importer']) ? str_replace('\\', '/', $entry['importer']) : '';
271
+ $relativeEntryImporter = str_replace($srcPathNormalized . '/', '', $entryImporter);
272
+ if ($relativeEntryImporter === $relativeImporterFile) {
273
+ $selectedMapping = $entry;
274
+ break;
275
+ }
276
+ }
277
+ if ($selectedMapping === null) {
278
+ $selectedMapping = $mappings[0];
279
+ }
280
+ } else {
281
+ $selectedMapping = $mappings;
260
282
  }
283
+ }
261
284
 
262
- $componentInstance = new $className($attributes);
263
- $renderedContent = $componentInstance->render();
285
+ if (!isset($selectedMapping['className']) || !isset($selectedMapping['filePath'])) {
286
+ throw new RuntimeException("Invalid component mapping for {$componentName}.");
287
+ }
264
288
 
265
- if (strpos($renderedContent, '<') !== false) {
266
- return self::compile($renderedContent);
267
- }
268
- return $renderedContent;
289
+ $className = $selectedMapping['className'];
290
+ $filePath = $selectedMapping['filePath'];
291
+
292
+ require_once str_replace('\\', '/', SRC_PATH . '/' . $filePath);
293
+
294
+ if (!class_exists($className)) {
295
+ throw new RuntimeException("Class {$className} does not exist.");
296
+ }
297
+
298
+ return new $className($attributes);
299
+ }
300
+
301
+ protected static function initializeClassMappings(): void
302
+ {
303
+ foreach (PrismaPHPSettings::$classLogFiles as $tagName => $fullyQualifiedClassName) {
304
+ self::$classMappings[$tagName] = $fullyQualifiedClassName;
269
305
  }
306
+ }
270
307
 
271
- return self::renderAsHtml($componentName, $attributes);
308
+ protected static function hasComponentTag(string $templateContent): bool
309
+ {
310
+ return preg_match('/<\/*[A-Z][\w-]*/u', $templateContent) === 1;
272
311
  }
273
312
 
274
313
  protected static function renderAsHtml(string $tagName, array $attributes): string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",