@akinon/projectzero 2.0.0-beta.12 → 2.0.0-beta.13

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.
Files changed (176) hide show
  1. package/CHANGELOG.md +100 -23
  2. package/app-template/.env.example +1 -0
  3. package/app-template/.github/instructions/account.instructions.md +749 -0
  4. package/app-template/.github/instructions/checkout.instructions.md +678 -0
  5. package/app-template/.github/instructions/default.instructions.md +279 -0
  6. package/app-template/.github/instructions/edge-cases.instructions.md +73 -0
  7. package/app-template/.github/instructions/routing.instructions.md +603 -0
  8. package/app-template/.github/instructions/settings.instructions.md +338 -0
  9. package/app-template/.gitignore +3 -0
  10. package/app-template/AGENTS.md +7 -0
  11. package/app-template/CHANGELOG.md +1348 -310
  12. package/app-template/Procfile +1 -1
  13. package/app-template/akinon.json +0 -3
  14. package/app-template/build.sh +10 -0
  15. package/app-template/docs/advanced-usage.md +101 -0
  16. package/app-template/docs/sentry-usage.md +35 -0
  17. package/app-template/next-env.d.ts +1 -0
  18. package/app-template/{next.config.ts → next.config.mjs} +6 -6
  19. package/app-template/package.json +58 -51
  20. package/app-template/postcss.config.mjs +1 -4
  21. package/app-template/public/locales/en/checkout.json +6 -0
  22. package/app-template/public/locales/en/common.json +50 -1
  23. package/app-template/public/locales/en/product.json +62 -1
  24. package/app-template/public/locales/tr/checkout.json +6 -0
  25. package/app-template/public/locales/tr/common.json +50 -1
  26. package/app-template/public/locales/tr/product.json +63 -0
  27. package/app-template/public/masterpass-javascript-sdk-web.min.js +1 -0
  28. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +9 -9
  29. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +2 -2
  30. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +6 -6
  31. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +6 -6
  32. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +1 -1
  33. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +2 -2
  34. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +2 -2
  35. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +1 -1
  36. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +2 -2
  37. package/app-template/src/app/[pz]/category/[pk]/page.tsx +27 -0
  38. package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +23 -0
  39. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +2 -3
  40. package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +93 -0
  41. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +2 -4
  42. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +3 -10
  43. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +2 -4
  44. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/not-found.tsx +5 -7
  45. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +6 -4
  46. package/app-template/src/app/[pz]/product/[pk]/page.tsx +102 -0
  47. package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +35 -0
  48. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +3 -4
  49. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -3
  50. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +6 -12
  51. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts +2 -2
  52. package/app-template/src/app/api/auth/[...nextauth]/route.ts +3 -0
  53. package/app-template/src/app/api/form/[...id]/route.ts +1 -7
  54. package/app-template/src/app/api/image-proxy/route.ts +1 -0
  55. package/app-template/src/app/api/product-categories/route.ts +1 -0
  56. package/app-template/src/app/api/similar-product-list/route.ts +1 -0
  57. package/app-template/src/app/api/similar-products/route.ts +1 -0
  58. package/app-template/src/app/api/virtual-try-on/limited-categories/route.ts +1 -0
  59. package/app-template/src/app/api/virtual-try-on/route.ts +1 -0
  60. package/app-template/src/assets/globals.scss +4 -133
  61. package/app-template/src/auth.ts +3 -0
  62. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  63. package/app-template/src/components/__tests__/link.test.tsx +2 -0
  64. package/app-template/src/components/accordion.tsx +23 -20
  65. package/app-template/src/components/button.tsx +1 -1
  66. package/app-template/src/components/carousel-core.tsx +4 -11
  67. package/app-template/src/components/checkbox.tsx +1 -1
  68. package/app-template/src/components/currency-select.tsx +1 -0
  69. package/app-template/src/components/file-input.tsx +27 -7
  70. package/app-template/src/components/generate-form-fields.tsx +49 -10
  71. package/app-template/src/components/input.tsx +11 -5
  72. package/app-template/src/components/modal.tsx +32 -16
  73. package/app-template/src/components/pagination.tsx +1 -0
  74. package/app-template/src/components/price.tsx +1 -1
  75. package/app-template/src/components/pwa-tags.tsx +1 -0
  76. package/app-template/src/components/select.tsx +39 -27
  77. package/app-template/src/components/shimmer.tsx +1 -1
  78. package/app-template/src/components/types/index.ts +25 -1
  79. package/app-template/src/hooks/use-fav-button.tsx +4 -8
  80. package/app-template/src/hooks/use-product-cart.ts +77 -0
  81. package/app-template/src/hooks/use-stock-alert.ts +74 -0
  82. package/app-template/src/plugins.js +12 -2
  83. package/app-template/src/redux/middlewares/category.ts +5 -4
  84. package/app-template/src/redux/store.ts +21 -1
  85. package/app-template/src/routes/index.ts +2 -1
  86. package/app-template/src/settings.js +3 -1
  87. package/app-template/src/types/index.ts +74 -3
  88. package/app-template/src/types/next-auth.d.ts +2 -2
  89. package/app-template/src/utils/variant-validation.ts +41 -0
  90. package/app-template/src/views/account/address-form.tsx +8 -4
  91. package/app-template/src/views/account/contact-form.tsx +2 -2
  92. package/app-template/src/views/account/content-header.tsx +4 -3
  93. package/app-template/src/views/account/faq/faq-tabs.tsx +8 -2
  94. package/app-template/src/views/account/order.tsx +1 -1
  95. package/app-template/src/views/account/orders/order-cancellation-item.tsx +1 -1
  96. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
  97. package/app-template/src/views/basket/basket-item.tsx +6 -1
  98. package/app-template/src/views/basket/summary.tsx +16 -0
  99. package/app-template/src/views/breadcrumb.tsx +2 -2
  100. package/app-template/src/views/category/category-info.tsx +2 -1
  101. package/app-template/src/views/category/filters/index.tsx +1 -1
  102. package/app-template/src/views/checkout/auth.tsx +1 -1
  103. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  104. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  105. package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +121 -0
  106. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  107. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  108. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  109. package/app-template/src/views/checkout/summary.tsx +12 -2
  110. package/app-template/src/views/find-in-store/index.tsx +2 -2
  111. package/app-template/src/views/header/action-menu.tsx +2 -6
  112. package/app-template/src/views/header/band.tsx +2 -2
  113. package/app-template/src/views/header/index.tsx +1 -1
  114. package/app-template/src/views/header/mini-basket.tsx +2 -2
  115. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  116. package/app-template/src/views/header/navbar.tsx +1 -1
  117. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  118. package/app-template/src/views/header/search/index.tsx +13 -3
  119. package/app-template/src/views/header/search/results.tsx +1 -1
  120. package/app-template/src/views/header/user-menu.tsx +1 -3
  121. package/app-template/src/views/login/index.tsx +14 -13
  122. package/app-template/src/views/otp-login/index.tsx +11 -6
  123. package/app-template/src/views/product/layout.tsx +15 -1
  124. package/app-template/src/views/product/product-actions.tsx +165 -0
  125. package/app-template/src/views/product/product-info.tsx +69 -261
  126. package/app-template/src/views/product/product-share.tsx +56 -0
  127. package/app-template/src/views/product/product-variants.tsx +26 -0
  128. package/app-template/src/views/product/slider.tsx +22 -1
  129. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  130. package/app-template/src/views/register/index.tsx +17 -21
  131. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  132. package/app-template/src/widgets/footer-info.tsx +1 -1
  133. package/app-template/src/widgets/footer-menu.tsx +7 -3
  134. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  135. package/app-template/src/widgets/home-stories-eng.tsx +43 -35
  136. package/app-template/tailwind.config.js +129 -1
  137. package/app-template/tsconfig.json +29 -11
  138. package/codemods/migrate-segments/index.js +591 -0
  139. package/commands/plugins.ts +62 -14
  140. package/dist/commands/plugins.js +62 -14
  141. package/package.json +1 -1
  142. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
  143. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
  144. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
  145. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
  146. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
  147. package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
  148. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
  149. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
  150. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
  151. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
  152. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
  153. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
  154. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
  155. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
  156. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
  157. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
  158. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
  159. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
  160. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
  161. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
  162. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
  163. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
  164. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
  165. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
  166. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
  167. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
  168. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
  169. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
  170. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
  171. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
  172. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
  173. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
  174. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
  175. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
  176. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
@@ -0,0 +1,591 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const green = (msg) => `\x1b[32m${msg}\x1b[0m`;
5
+ const red = (msg) => `\x1b[31m${msg}\x1b[0m`;
6
+ const yellow = (msg) => `\x1b[33m${msg}\x1b[0m`;
7
+ const bold = (msg) => `\x1b[1m${msg}\x1b[0m`;
8
+
9
+ const BUILTIN_SEGMENTS = ['commerce', 'locale', 'currency', 'url'];
10
+
11
+ function copyDirRecursive(src, dest) {
12
+ fs.mkdirSync(dest, { recursive: true });
13
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
14
+ const srcPath = path.join(src, entry.name);
15
+ const destPath = path.join(dest, entry.name);
16
+ if (entry.isDirectory()) {
17
+ copyDirRecursive(srcPath, destPath);
18
+ } else {
19
+ fs.copyFileSync(srcPath, destPath);
20
+ }
21
+ }
22
+ }
23
+
24
+ function countEntries(dir) {
25
+ let count = 0;
26
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
27
+ count++;
28
+ if (entry.isDirectory()) {
29
+ count += countEntries(path.join(dir, entry.name));
30
+ }
31
+ }
32
+ return count;
33
+ }
34
+
35
+ /**
36
+ * Walks down from [commerce]/ through nested dynamic segments,
37
+ * collecting segment names and the directories at each level.
38
+ *
39
+ * Handles intermediate content (e.g. layout.tsx at [locale]/ level)
40
+ * by continuing to descend as long as a single-bracket dynamic child exists.
41
+ *
42
+ * Example:
43
+ * [commerce]/[locale]/[currency]/[url]/...
44
+ * → segments: ['locale', 'currency', 'url']
45
+ * → levels: [{ dir: '[locale]/', segmentChild: '[currency]' }, ...]
46
+ * → contentDir: the deepest level with route content
47
+ */
48
+ function detectSegments(commerceDir) {
49
+ const segments = [];
50
+ const levels = []; // { dir, segmentChildName } for each intermediate level
51
+ let currentDir = commerceDir;
52
+
53
+ while (true) {
54
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
55
+
56
+ // Find single-bracket dynamic segment dirs (not catch-all [...])
57
+ const dynamicDirs = entries.filter(
58
+ (e) =>
59
+ e.isDirectory() &&
60
+ e.name.startsWith('[') &&
61
+ e.name.endsWith(']') &&
62
+ !e.name.startsWith('[...')
63
+ );
64
+
65
+ if (dynamicDirs.length === 1) {
66
+ const segName = dynamicDirs[0].name.slice(1, -1);
67
+ segments.push(segName);
68
+ levels.push({ dir: currentDir, segmentChildName: dynamicDirs[0].name });
69
+ currentDir = path.join(currentDir, dynamicDirs[0].name);
70
+ } else {
71
+ // No more single dynamic segment children — this is the deepest content level
72
+ break;
73
+ }
74
+ }
75
+
76
+ return { segments, levels, contentDir: currentDir };
77
+ }
78
+
79
+ /**
80
+ * Copies non-segment content from each intermediate level into dest,
81
+ * then copies the deepest content level on top (deeper wins on conflict).
82
+ */
83
+ function mergeContentIntoPz(levels, contentDir, pzDir) {
84
+ // First: copy non-segment content from each intermediate level (shallowest first)
85
+ for (const { dir, segmentChildName } of levels) {
86
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
87
+ for (const entry of entries) {
88
+ // Skip the segment child directory itself
89
+ if (entry.name === segmentChildName) continue;
90
+
91
+ const srcPath = path.join(dir, entry.name);
92
+ const destPath = path.join(pzDir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ copyDirRecursive(srcPath, destPath);
95
+ } else {
96
+ fs.mkdirSync(pzDir, { recursive: true });
97
+ fs.copyFileSync(srcPath, destPath);
98
+ }
99
+ }
100
+ }
101
+
102
+ // Last: copy deepest content level (overwrites conflicts from shallower levels)
103
+ copyDirRecursive(contentDir, pzDir);
104
+ }
105
+
106
+ /**
107
+ * Reads middleware.ts and extracts the expression used to build
108
+ * the URL rewrite for each custom segment.
109
+ *
110
+ * Looks for patterns like:
111
+ * url.pathname = '/' + <expression> + url.pathname
112
+ * url.pathname = `/${<expression>}${url.pathname}`
113
+ *
114
+ * Then maps that expression into a resolve function.
115
+ */
116
+ function analyzeMiddleware(workingDir, customSegments) {
117
+ const hints = {};
118
+
119
+ const middlewarePath = path.join(workingDir, 'src', 'middleware.ts');
120
+ if (!fs.existsSync(middlewarePath)) return hints;
121
+
122
+ const content = fs.readFileSync(middlewarePath, 'utf-8');
123
+
124
+ // Extract the rewrite expression from url.pathname assignment
125
+ // Pattern 1: url.pathname = '/' + <expr> + url.pathname
126
+ const concatMatch = content.match(
127
+ /url\.pathname\s*=\s*['"`]\/['"`]\s*\+\s*(.+?)\s*\+\s*url\.pathname/
128
+ );
129
+ // Pattern 2: url.pathname = `/${<expr>}${url.pathname}`
130
+ const templateMatch = content.match(
131
+ /url\.pathname\s*=\s*`\/\$\{(.+?)\}\$\{url\.pathname\}`/
132
+ );
133
+
134
+ const rewriteExpression = concatMatch?.[1]?.trim() || templateMatch?.[1]?.trim();
135
+
136
+ for (const seg of customSegments) {
137
+ // If we found a rewrite expression in middleware, use it directly
138
+ if (rewriteExpression) {
139
+ hints[seg] = {
140
+ source: 'middleware-rewrite',
141
+ code: `({ req }) => ${rewriteExpression}`
142
+ };
143
+ continue;
144
+ }
145
+
146
+ // Check for cookie pattern: req.cookies.get('pz-{seg}') or similar
147
+ const cookieRegex = new RegExp(
148
+ `cookies\\.get\\(['"\`](?:pz-)?${seg}['"\`]\\)`,
149
+ 'i'
150
+ );
151
+ if (cookieRegex.test(content)) {
152
+ hints[seg] = {
153
+ source: 'cookie',
154
+ code: `({ req }) => req.cookies.get('pz-${seg}')?.value ?? ''`
155
+ };
156
+ continue;
157
+ }
158
+
159
+ // Check for header pattern: req.headers.get('x-{seg}')
160
+ const headerRegex = new RegExp(
161
+ `headers\\.get\\(['"\`](?:x-)?${seg}['"\`]\\)`,
162
+ 'i'
163
+ );
164
+ if (headerRegex.test(content)) {
165
+ hints[seg] = {
166
+ source: 'header',
167
+ code: `({ req }) => req.headers.get('x-${seg}') ?? ''`
168
+ };
169
+ continue;
170
+ }
171
+
172
+ // No hint found
173
+ hints[seg] = {
174
+ source: 'unknown',
175
+ code: null
176
+ };
177
+ }
178
+
179
+ return hints;
180
+ }
181
+
182
+ /**
183
+ * Removes the rewrite block from middleware.ts since resolve now handles it.
184
+ * Strips: url clone, url.pathname assignment, NextResponse.rewrite with pz-override-response.
185
+ * Leaves the middleware function body empty (or with remaining custom logic).
186
+ */
187
+ function cleanupMiddleware(workingDir) {
188
+ const middlewarePath = path.join(workingDir, 'src', 'middleware.ts');
189
+ if (!fs.existsSync(middlewarePath)) return false;
190
+
191
+ let content = fs.readFileSync(middlewarePath, 'utf-8');
192
+
193
+ if (!content.includes('pz-override-response')) return false;
194
+
195
+ // Remove: const url = req.nextUrl.clone();
196
+ content = content.replace(
197
+ /\n?\s*const url = req\.nextUrl\.clone\(\);\s*\n?/,
198
+ '\n'
199
+ );
200
+
201
+ // Remove: url.pathname = ... + url.pathname;
202
+ content = content.replace(
203
+ /\s*url\.pathname\s*=\s*.+?url\.pathname.*;\s*\n?/,
204
+ ''
205
+ );
206
+
207
+ // Replace NextResponse.rewrite(url, { headers: { 'pz-override-response': ... } })
208
+ // with empty return (or no return)
209
+ content = content.replace(
210
+ /\s*return\s+NextResponse\.rewrite\(url,\s*\{[\s\S]*?'pz-override-response'[\s\S]*?\}\s*\);\s*/,
211
+ '\n'
212
+ );
213
+
214
+ // Clean up empty lines left behind
215
+ content = content.replace(/\n{3,}/g, '\n\n');
216
+
217
+ fs.writeFileSync(middlewarePath, content);
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * Replaces old segment path references in project files (tsconfig, imports, etc.)
223
+ * e.g. ./app/[commerce]/[locale]/[currency]/[url]/ → ./app/[pz]/
224
+ */
225
+ function updatePathReferences(workingDir, segments) {
226
+ // Build old path patterns from detected segments in different formats
227
+ // Bracket format: [commerce]/[locale]/[currency]/[url]
228
+ const bracketPath = ['[commerce]', ...segments.map((s) => `[${s}]`)].join(
229
+ '/'
230
+ );
231
+ // Colon format (Next.js rewrites): :commerce/:locale/:currency/:url
232
+ const colonPath = [':commerce', ...segments.map((s) => `:${s}`)].join('/');
233
+
234
+ const replacements = [
235
+ { from: bracketPath, to: '[pz]' },
236
+ { from: colonPath, to: ':pz' }
237
+ ];
238
+
239
+ const extensions = ['.json', '.js', '.mjs', '.ts', '.tsx'];
240
+ let updatedFiles = [];
241
+
242
+ function walkAndReplace(dir) {
243
+ let entries;
244
+ try {
245
+ entries = fs.readdirSync(dir, { withFileTypes: true });
246
+ } catch {
247
+ return;
248
+ }
249
+
250
+ for (const entry of entries) {
251
+ if (entry.name === 'node_modules' || entry.name === '.next') continue;
252
+ // Skip the app directory itself (already handled by directory migration)
253
+ if (entry.name === 'app' && dir === path.join(workingDir, 'src')) continue;
254
+
255
+ const fullPath = path.join(dir, entry.name);
256
+
257
+ if (entry.isDirectory()) {
258
+ walkAndReplace(fullPath);
259
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
260
+ let content;
261
+ try {
262
+ content = fs.readFileSync(fullPath, 'utf-8');
263
+ } catch {
264
+ continue;
265
+ }
266
+
267
+ let changed = false;
268
+ for (const { from, to } of replacements) {
269
+ if (content.includes(from)) {
270
+ content = content.split(from).join(to);
271
+ changed = true;
272
+ }
273
+ }
274
+
275
+ if (changed) {
276
+ fs.writeFileSync(fullPath, content);
277
+ updatedFiles.push(path.relative(workingDir, fullPath));
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ walkAndReplace(workingDir);
284
+ return updatedFiles;
285
+ }
286
+
287
+ function updateParamsUsage(workingDir) {
288
+ const extensions = ['.ts', '.tsx'];
289
+ const updatedFiles = [];
290
+
291
+ // Patterns to replace inline
292
+ const replacements = [
293
+ // decodeURIComponent(params.url) → parsePzParams(params, settings).url
294
+ {
295
+ pattern: /decodeURIComponent\(params\.url\)/g,
296
+ replacement: 'parsePzParams(params, settings).url'
297
+ },
298
+ // params.locale → parsePzParams(params, settings).locale
299
+ {
300
+ pattern: /params\.locale/g,
301
+ replacement: 'parsePzParams(params, settings).locale'
302
+ },
303
+ // params.currency → parsePzParams(params, settings).currency
304
+ {
305
+ pattern: /params\.currency/g,
306
+ replacement: 'parsePzParams(params, settings).currency'
307
+ },
308
+ // params.url (standalone, not already wrapped) → parsePzParams(params, settings).url
309
+ {
310
+ pattern: /params\.url/g,
311
+ replacement: 'parsePzParams(params, settings).url'
312
+ }
313
+ ];
314
+
315
+ function walk(dir) {
316
+ let entries;
317
+ try {
318
+ entries = fs.readdirSync(dir, { withFileTypes: true });
319
+ } catch {
320
+ return;
321
+ }
322
+
323
+ for (const entry of entries) {
324
+ if (entry.name === 'node_modules' || entry.name === '.next') continue;
325
+ const fullPath = path.join(dir, entry.name);
326
+
327
+ if (entry.isDirectory()) {
328
+ walk(fullPath);
329
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
330
+ let content;
331
+ try {
332
+ content = fs.readFileSync(fullPath, 'utf-8');
333
+ } catch {
334
+ continue;
335
+ }
336
+
337
+ // Check if file has any params.locale/currency/url usage
338
+ if (!/params\.(locale|currency|url)/.test(content)) continue;
339
+
340
+ let updated = content;
341
+ for (const { pattern, replacement } of replacements) {
342
+ updated = updated.replace(pattern, replacement);
343
+ }
344
+
345
+ if (updated !== content) {
346
+ // Add import if not already present
347
+ if (!updated.includes('parsePzParams')) {
348
+ // This shouldn't happen since we just added it, but safety check
349
+ }
350
+ if (
351
+ !updated.includes("from '@akinon/next/utils'") &&
352
+ !updated.includes("from '@akinon/next/utils/pz-segments'")
353
+ ) {
354
+ // Add import at the top after last import
355
+ const lastImportIdx = updated.lastIndexOf('\nimport ');
356
+ if (lastImportIdx !== -1) {
357
+ const lineEnd = updated.indexOf('\n', lastImportIdx + 1);
358
+ updated =
359
+ updated.slice(0, lineEnd + 1) +
360
+ "import { parsePzParams } from '@akinon/next/utils';\n" +
361
+ updated.slice(lineEnd + 1);
362
+ } else {
363
+ updated =
364
+ "import { parsePzParams } from '@akinon/next/utils';\n" +
365
+ updated;
366
+ }
367
+ } else {
368
+ // File already imports from @akinon/next/utils, add parsePzParams to existing import
369
+ const importRegex =
370
+ /import\s*\{([^}]*)\}\s*from\s*['"]@akinon\/next\/utils['"]/;
371
+ const match = updated.match(importRegex);
372
+ if (match && !match[1].includes('parsePzParams')) {
373
+ updated = updated.replace(
374
+ importRegex,
375
+ `import {${match[1]}, parsePzParams } from '@akinon/next/utils'`
376
+ );
377
+ }
378
+ }
379
+
380
+ // Ensure settings import exists (check for any settings import: 'settings', '@theme/settings', './settings', etc.)
381
+ const hasSettingsImport = /import\s+settings\s+from\s+['"]/.test(updated);
382
+ if (!hasSettingsImport) {
383
+ const lastImportIdx = updated.lastIndexOf('\nimport ');
384
+ if (lastImportIdx !== -1) {
385
+ const lineEnd = updated.indexOf('\n', lastImportIdx + 1);
386
+ updated =
387
+ updated.slice(0, lineEnd + 1) +
388
+ "import settings from 'settings';\n" +
389
+ updated.slice(lineEnd + 1);
390
+ }
391
+ }
392
+
393
+ fs.writeFileSync(fullPath, updated);
394
+ updatedFiles.push(path.relative(workingDir, fullPath));
395
+ }
396
+ }
397
+ }
398
+ }
399
+
400
+ walk(path.join(workingDir, 'src'));
401
+ return updatedFiles;
402
+ }
403
+
404
+ function generatePzSegmentsConfig(segments, resolveHints) {
405
+ const lines = segments.map((name) => {
406
+ const hint = resolveHints[name];
407
+ if (hint && hint.code) {
408
+ return ` { name: '${name}', resolve: ${hint.code} }`;
409
+ }
410
+ if (hint && hint.source === 'unknown') {
411
+ return ` { name: '${name}', resolve: ({ req }) => '' /* TODO: return ${name} value */ }`;
412
+ }
413
+ return ` { name: '${name}' }`;
414
+ });
415
+ return `pzSegments: {\n segments: [\n${lines.join(',\n')}\n ]\n }`;
416
+ }
417
+
418
+ const transform = () => {
419
+ const workingDir = path.resolve(process.cwd());
420
+ const appDir = path.join(workingDir, 'src', 'app');
421
+ const settingsPath = path.join(workingDir, 'src', 'settings.js');
422
+
423
+ if (!fs.existsSync(appDir)) {
424
+ console.error(red('Error: src/app/ directory not found.'));
425
+ process.exit(1);
426
+ }
427
+
428
+ const pzDir = path.join(appDir, '[pz]');
429
+ if (fs.existsSync(pzDir)) {
430
+ console.log(
431
+ yellow(
432
+ '[pz] directory already exists. Migration may have already been done.'
433
+ )
434
+ );
435
+ process.exit(0);
436
+ }
437
+
438
+ const commerceDir = path.join(appDir, '[commerce]');
439
+ if (!fs.existsSync(commerceDir)) {
440
+ console.error(red('Error: [commerce] directory not found in src/app/.'));
441
+ process.exit(1);
442
+ }
443
+
444
+ // Detect all segments by walking down the directory tree
445
+ const { segments, levels, contentDir } = detectSegments(commerceDir);
446
+
447
+ const customSegments = segments.filter(
448
+ (s) => !BUILTIN_SEGMENTS.includes(s)
449
+ );
450
+ const segmentPath = ['[commerce]', ...segments.map((s) => `[${s}]`)].join(
451
+ '/'
452
+ );
453
+
454
+ // Count entries across all levels that will be merged
455
+ let totalEntries = countEntries(contentDir);
456
+ for (const { dir, segmentChildName } of levels) {
457
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
458
+ if (entry.name === segmentChildName) continue;
459
+ totalEntries++;
460
+ if (entry.isDirectory()) {
461
+ totalEntries += countEntries(path.join(dir, entry.name));
462
+ }
463
+ }
464
+ }
465
+
466
+ console.log(bold('\nPZ Segment Migration'));
467
+ console.log('====================\n');
468
+ console.log(`Detected: ${yellow(segmentPath + '/')}`);
469
+ console.log(`Segments: ${segments.join(', ')}`);
470
+ if (customSegments.length > 0) {
471
+ console.log(
472
+ `Custom: ${yellow(customSegments.join(', '))} (will be added to pzSegments)`
473
+ );
474
+ }
475
+ if (levels.some(({ dir, segmentChildName }) =>
476
+ fs.readdirSync(dir).some((f) => f !== segmentChildName)
477
+ )) {
478
+ console.log(`Note: Intermediate levels have content, will be merged`);
479
+ }
480
+ console.log(`Target: ${yellow('[pz]/')}`);
481
+ console.log(`Entries: ${totalEntries}\n`);
482
+
483
+ // Merge all levels into [pz]/
484
+ console.log('Merging content into [pz]/...');
485
+ mergeContentIntoPz(levels, contentDir, pzDir);
486
+
487
+ // Remove legacy [commerce]/ tree
488
+ console.log('Removing legacy directories...');
489
+ fs.rmSync(commerceDir, { recursive: true, force: true });
490
+
491
+ // Update settings.js
492
+ if (fs.existsSync(settingsPath)) {
493
+ let content = fs.readFileSync(settingsPath, 'utf-8');
494
+
495
+ // Add usePzSegment: true
496
+ if (!content.includes('usePzSegment')) {
497
+ content = content.replace(/(\n};\s*$)/, ',\n usePzSegment: true$1');
498
+
499
+ if (!content.includes('usePzSegment')) {
500
+ content = content.replace(/(};\s*$)/, ' usePzSegment: true,\n$1');
501
+ }
502
+ }
503
+
504
+ // Add pzSegments config if custom segments were found
505
+ if (customSegments.length > 0 && !content.includes('pzSegments')) {
506
+ const resolveHints = analyzeMiddleware(workingDir, customSegments);
507
+ const orderedSegments = segments.filter((s) => s !== 'commerce');
508
+ const pzSegmentsBlock = generatePzSegmentsConfig(
509
+ orderedSegments,
510
+ resolveHints
511
+ );
512
+
513
+ content = content.replace(
514
+ /(\n};\s*$)/,
515
+ `,\n ${pzSegmentsBlock}$1`
516
+ );
517
+
518
+ if (!content.includes('pzSegments')) {
519
+ content = content.replace(
520
+ /(};\s*$)/,
521
+ ` ${pzSegmentsBlock},\n$1`
522
+ );
523
+ }
524
+ }
525
+
526
+ fs.writeFileSync(settingsPath, content);
527
+
528
+ console.log('Updated settings.js:');
529
+ console.log(' - usePzSegment: true');
530
+ if (customSegments.length > 0) {
531
+ const resolveHints = analyzeMiddleware(workingDir, customSegments);
532
+ console.log(
533
+ ` - pzSegments with segments: [${segments.filter((s) => s !== 'commerce').join(', ')}]`
534
+ );
535
+ for (const seg of customSegments) {
536
+ const hint = resolveHints[seg];
537
+ if (hint && hint.source !== 'unknown') {
538
+ console.log(
539
+ ` ${bold(seg)}: resolve from ${hint.source}`
540
+ );
541
+ } else {
542
+ console.log(
543
+ yellow(
544
+ ` ${seg}: resolve source unknown — fill in the resolve function manually`
545
+ )
546
+ );
547
+ }
548
+ }
549
+ }
550
+ } else {
551
+ console.log(
552
+ yellow(
553
+ 'Warning: settings.js not found. Add usePzSegment: true manually.'
554
+ )
555
+ );
556
+ }
557
+
558
+ // Clean up middleware.ts rewrite block since url is now a built-in segment
559
+ if (segments.includes('url') || customSegments.length > 0) {
560
+ const cleaned = cleanupMiddleware(workingDir);
561
+ if (cleaned) {
562
+ console.log('Cleaned middleware.ts: removed rewrite block (now handled by built-in url segment)');
563
+ }
564
+ }
565
+
566
+ // Update path references in tsconfig, imports, etc.
567
+ const updatedFiles = updatePathReferences(workingDir, segments);
568
+ if (updatedFiles.length > 0) {
569
+ console.log(`Updated path references in ${updatedFiles.length} file(s):`);
570
+ updatedFiles.forEach((f) => console.log(` - ${f}`));
571
+ }
572
+
573
+ // Update params.locale/currency/url usages to parsePzParams
574
+ const paramsUpdated = updateParamsUsage(workingDir);
575
+ if (paramsUpdated.length > 0) {
576
+ console.log(
577
+ `Updated params usage in ${paramsUpdated.length} file(s):`
578
+ );
579
+ paramsUpdated.forEach((f) => console.log(` - ${f}`));
580
+ }
581
+
582
+ console.log(green('\nMigration completed successfully!\n'));
583
+ console.log('Next steps:');
584
+ console.log(` 1. Review changes with ${bold('git diff')}`);
585
+ console.log(` 2. Build with ${bold('yarn build')}`);
586
+ console.log('');
587
+ };
588
+
589
+ module.exports = {
590
+ transform
591
+ };
@@ -69,45 +69,65 @@ export default async () => {
69
69
  }
70
70
 
71
71
  const definedPlugins = [
72
+ {
73
+ name: 'Akifast',
74
+ value: 'pz-akifast'
75
+ },
76
+ {
77
+ name: 'Apple Pay',
78
+ value: 'pz-apple-pay'
79
+ },
80
+ {
81
+ name: 'B2B',
82
+ value: 'pz-b2b'
83
+ },
72
84
  {
73
85
  name: 'Basket Gift Pack',
74
86
  value: 'pz-basket-gift-pack'
75
87
  },
76
88
  {
77
- name: 'Click & Collect',
78
- value: 'pz-click-collect'
89
+ name: 'BKM Express',
90
+ value: 'pz-bkm'
79
91
  },
80
92
  {
81
93
  name: 'Checkout Gift Pack',
82
94
  value: 'pz-checkout-gift-pack'
83
95
  },
84
96
  {
85
- name: 'One Click Checkout',
86
- value: 'pz-one-click-checkout'
97
+ name: 'Click & Collect',
98
+ value: 'pz-click-collect'
99
+ },
100
+ {
101
+ name: 'Credit Payment',
102
+ value: 'pz-credit-payment'
87
103
  },
88
104
  {
89
105
  name: 'Garanti Pay',
90
106
  value: 'pz-gpay'
91
107
  },
92
108
  {
93
- name: 'Pay On Delivery',
94
- value: 'pz-pay-on-delivery'
109
+ name: 'Masterpass',
110
+ value: 'pz-masterpass'
95
111
  },
96
112
  {
97
- name: 'Otp',
98
- value: 'pz-otp'
113
+ name: 'Multi Basket',
114
+ value: 'pz-multi-basket'
99
115
  },
100
116
  {
101
- name: 'BKM Express',
102
- value: 'pz-bkm'
117
+ name: 'One Click Checkout',
118
+ value: 'pz-one-click-checkout'
103
119
  },
104
120
  {
105
- name: 'Credit Payment',
106
- value: 'pz-credit-payment'
121
+ name: 'Otp',
122
+ value: 'pz-otp'
107
123
  },
108
124
  {
109
- name: 'Multi Basket',
110
- value: 'pz-multi-basket'
125
+ name: 'Pay On Delivery',
126
+ value: 'pz-pay-on-delivery'
127
+ },
128
+ {
129
+ name: 'Saved Card',
130
+ value: 'pz-saved-card'
111
131
  },
112
132
  {
113
133
  name: 'Tabby Payment Extension',
@@ -116,6 +136,34 @@ export default async () => {
116
136
  {
117
137
  name: 'Tamara Payment Extension',
118
138
  value: 'pz-tamara-extension'
139
+ },
140
+ {
141
+ name: 'Hepsipay',
142
+ value: 'pz-hepsipay'
143
+ },
144
+ {
145
+ name: 'Flow Payment',
146
+ value: 'pz-flow-payment'
147
+ },
148
+ {
149
+ name: 'Virtual Try-On',
150
+ value: 'pz-virtual-try-on'
151
+ },
152
+ {
153
+ name: 'Masterpass Rest',
154
+ value: 'pz-masterpass-rest'
155
+ },
156
+ {
157
+ name: 'Similar Products',
158
+ value: 'pz-similar-products'
159
+ },
160
+ {
161
+ name: 'Haso Payment Gateway',
162
+ value: 'pz-haso'
163
+ },
164
+ {
165
+ name: 'Google Pay',
166
+ value: 'pz-google-pay'
119
167
  }
120
168
  ];
121
169