@akinon/projectzero 1.125.0-rc.1 → 1.126.0-rc.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/app-template/CHANGELOG.md +61 -64
  3. package/app-template/docs/advanced-usage.md +101 -0
  4. package/app-template/next.config.mjs +4 -4
  5. package/app-template/package.json +29 -29
  6. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +2 -7
  7. package/app-template/src/settings.js +1 -0
  8. package/app-template/tsconfig.json +6 -10
  9. package/codemods/migrate-segments/index.js +593 -0
  10. package/package.json +1 -1
  11. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +0 -0
  12. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
  13. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
  14. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
  15. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
  16. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
  17. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
  18. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
  19. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
  20. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +0 -0
  21. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
  22. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +0 -0
  23. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
  24. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +0 -0
  25. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
  26. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +0 -0
  27. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +0 -0
  28. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +0 -0
  29. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
  30. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
  31. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +0 -0
  32. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +0 -0
  33. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
  34. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
  35. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/page.tsx +0 -0
  36. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
  37. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
  38. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
  39. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
  40. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/page.tsx +0 -0
  41. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +0 -0
  42. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
  43. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/page.tsx +0 -0
  44. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
  45. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +0 -0
  46. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
  47. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +0 -0
  48. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/not-found.tsx +0 -0
  49. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
  50. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
  51. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +0 -0
  52. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
  53. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/product/[pk]/page.tsx +0 -0
  54. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
  55. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/page.tsx +0 -0
  56. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
  57. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +0 -0
  58. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
  59. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +0 -0
  60. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +0 -0
  61. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts +0 -0
  62. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @akinon/projectzero
2
2
 
3
- ## 1.125.0-rc.1
4
-
5
- ## 1.125.0-rc.0
3
+ ## 1.126.0-rc.0
6
4
 
7
5
  ### Minor Changes
8
6
 
@@ -10,6 +8,8 @@
10
8
  - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
11
9
  - e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
12
10
 
11
+ ## 1.125.0
12
+
13
13
  ## 1.124.0
14
14
 
15
15
  ## 1.123.0
@@ -1,42 +1,6 @@
1
1
  # projectzeronext
2
2
 
3
- ## 1.125.0-rc.1
4
-
5
- ### Minor Changes
6
-
7
- - c0228aff: ZERO-4159: Upgrade the package versions
8
-
9
- ### Patch Changes
10
-
11
- - Updated dependencies [c0228aff]
12
- - @akinon/pz-b2b@1.125.0-rc.1
13
- - @akinon/next@1.125.0-rc.1
14
- - @akinon/pz-akifast@1.125.0-rc.1
15
- - @akinon/pz-apple-pay@1.125.0-rc.1
16
- - @akinon/pz-basket-gift-pack@1.125.0-rc.1
17
- - @akinon/pz-bkm@1.125.0-rc.1
18
- - @akinon/pz-checkout-gift-pack@1.125.0-rc.1
19
- - @akinon/pz-click-collect@1.125.0-rc.1
20
- - @akinon/pz-credit-payment@1.125.0-rc.1
21
- - @akinon/pz-cybersource-uc@1.125.0-rc.1
22
- - @akinon/pz-flow-payment@1.125.0-rc.1
23
- - @akinon/pz-google-pay@1.125.0-rc.1
24
- - @akinon/pz-gpay@1.125.0-rc.1
25
- - @akinon/pz-haso@1.125.0-rc.1
26
- - @akinon/pz-hepsipay@1.125.0-rc.1
27
- - @akinon/pz-masterpass@1.125.0-rc.1
28
- - @akinon/pz-masterpass-rest@1.125.0-rc.1
29
- - @akinon/pz-multi-basket@1.125.0-rc.1
30
- - @akinon/pz-one-click-checkout@1.125.0-rc.1
31
- - @akinon/pz-otp@1.125.0-rc.1
32
- - @akinon/pz-pay-on-delivery@1.125.0-rc.1
33
- - @akinon/pz-saved-card@1.125.0-rc.1
34
- - @akinon/pz-similar-products@1.125.0-rc.1
35
- - @akinon/pz-tabby-extension@1.125.0-rc.1
36
- - @akinon/pz-tamara-extension@1.125.0-rc.1
37
- - @akinon/pz-virtual-try-on@1.125.0-rc.1
38
-
39
- ## 1.125.0-rc.0
3
+ ## 1.126.0-rc.0
40
4
 
41
5
  ### Minor Changes
42
6
 
@@ -45,6 +9,7 @@
45
9
  - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
46
10
  - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
47
11
  - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
12
+ - c0228aff: ZERO-4159: Upgrade the package versions
48
13
  - b05c3543: ZERO-2570: Category filters routes to absolute url
49
14
  - e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
50
15
  - c3244a31: ZERO-4013: Update runtime version to node:25-alpine in project configuration
@@ -55,7 +20,6 @@
55
20
  - Updated dependencies [b55acb76]
56
21
  - Updated dependencies [760258c1]
57
22
  - Updated dependencies [143be2b9]
58
- - Updated dependencies [cd68a97a]
59
23
  - Updated dependencies [9f8cd3bc]
60
24
  - Updated dependencies [bfafa3f4]
61
25
  - Updated dependencies [d99a6a7d]
@@ -63,34 +27,67 @@
63
27
  - Updated dependencies [4de5303c]
64
28
  - Updated dependencies [63a72000]
65
29
  - Updated dependencies [95b139dc]
30
+ - Updated dependencies [c0228aff]
66
31
  - Updated dependencies [3909d322]
67
32
  - Updated dependencies [e18836b2]
68
- - @akinon/next@1.125.0-rc.0
69
- - @akinon/pz-similar-products@1.125.0-rc.0
70
- - @akinon/pz-masterpass-rest@1.125.0-rc.0
71
- - @akinon/pz-virtual-try-on@1.125.0-rc.0
72
- - @akinon/pz-akifast@1.125.0-rc.0
73
- - @akinon/pz-apple-pay@1.125.0-rc.0
74
- - @akinon/pz-b2b@1.125.0-rc.0
75
- - @akinon/pz-basket-gift-pack@1.125.0-rc.0
76
- - @akinon/pz-bkm@1.125.0-rc.0
77
- - @akinon/pz-checkout-gift-pack@1.125.0-rc.0
78
- - @akinon/pz-click-collect@1.125.0-rc.0
79
- - @akinon/pz-credit-payment@1.125.0-rc.0
80
- - @akinon/pz-cybersource-uc@1.125.0-rc.0
81
- - @akinon/pz-flow-payment@1.125.0-rc.0
82
- - @akinon/pz-google-pay@1.125.0-rc.0
83
- - @akinon/pz-gpay@1.125.0-rc.0
84
- - @akinon/pz-haso@1.125.0-rc.0
85
- - @akinon/pz-hepsipay@1.125.0-rc.0
86
- - @akinon/pz-masterpass@1.125.0-rc.0
87
- - @akinon/pz-multi-basket@1.125.0-rc.0
88
- - @akinon/pz-one-click-checkout@1.125.0-rc.0
89
- - @akinon/pz-otp@1.125.0-rc.0
90
- - @akinon/pz-pay-on-delivery@1.125.0-rc.0
91
- - @akinon/pz-saved-card@1.125.0-rc.0
92
- - @akinon/pz-tabby-extension@1.125.0-rc.0
93
- - @akinon/pz-tamara-extension@1.125.0-rc.0
33
+ - @akinon/next@1.126.0-rc.0
34
+ - @akinon/pz-similar-products@1.126.0-rc.0
35
+ - @akinon/pz-masterpass-rest@1.126.0-rc.0
36
+ - @akinon/pz-b2b@1.126.0-rc.0
37
+ - @akinon/pz-virtual-try-on@1.126.0-rc.0
38
+ - @akinon/pz-akifast@1.126.0-rc.0
39
+ - @akinon/pz-apple-pay@1.126.0-rc.0
40
+ - @akinon/pz-basket-gift-pack@1.126.0-rc.0
41
+ - @akinon/pz-bkm@1.126.0-rc.0
42
+ - @akinon/pz-checkout-gift-pack@1.126.0-rc.0
43
+ - @akinon/pz-click-collect@1.126.0-rc.0
44
+ - @akinon/pz-credit-payment@1.126.0-rc.0
45
+ - @akinon/pz-cybersource-uc@1.126.0-rc.0
46
+ - @akinon/pz-flow-payment@1.126.0-rc.0
47
+ - @akinon/pz-google-pay@1.126.0-rc.0
48
+ - @akinon/pz-gpay@1.126.0-rc.0
49
+ - @akinon/pz-haso@1.126.0-rc.0
50
+ - @akinon/pz-hepsipay@1.126.0-rc.0
51
+ - @akinon/pz-masterpass@1.126.0-rc.0
52
+ - @akinon/pz-multi-basket@1.126.0-rc.0
53
+ - @akinon/pz-one-click-checkout@1.126.0-rc.0
54
+ - @akinon/pz-otp@1.126.0-rc.0
55
+ - @akinon/pz-pay-on-delivery@1.126.0-rc.0
56
+ - @akinon/pz-saved-card@1.126.0-rc.0
57
+ - @akinon/pz-tabby-extension@1.126.0-rc.0
58
+ - @akinon/pz-tamara-extension@1.126.0-rc.0
59
+
60
+ ## 1.125.0
61
+
62
+ ### Patch Changes
63
+
64
+ - Updated dependencies [cd68a97a]
65
+ - @akinon/next@1.125.0
66
+ - @akinon/pz-virtual-try-on@1.125.0
67
+ - @akinon/pz-akifast@1.125.0
68
+ - @akinon/pz-apple-pay@1.125.0
69
+ - @akinon/pz-b2b@1.125.0
70
+ - @akinon/pz-basket-gift-pack@1.125.0
71
+ - @akinon/pz-bkm@1.125.0
72
+ - @akinon/pz-checkout-gift-pack@1.125.0
73
+ - @akinon/pz-click-collect@1.125.0
74
+ - @akinon/pz-credit-payment@1.125.0
75
+ - @akinon/pz-cybersource-uc@1.125.0
76
+ - @akinon/pz-flow-payment@1.125.0
77
+ - @akinon/pz-google-pay@1.125.0
78
+ - @akinon/pz-gpay@1.125.0
79
+ - @akinon/pz-haso@1.125.0
80
+ - @akinon/pz-hepsipay@1.125.0
81
+ - @akinon/pz-masterpass@1.125.0
82
+ - @akinon/pz-masterpass-rest@1.125.0
83
+ - @akinon/pz-multi-basket@1.125.0
84
+ - @akinon/pz-one-click-checkout@1.125.0
85
+ - @akinon/pz-otp@1.125.0
86
+ - @akinon/pz-pay-on-delivery@1.125.0
87
+ - @akinon/pz-saved-card@1.125.0
88
+ - @akinon/pz-similar-products@1.125.0
89
+ - @akinon/pz-tabby-extension@1.125.0
90
+ - @akinon/pz-tamara-extension@1.125.0
94
91
 
95
92
  ## 1.124.0
96
93
 
@@ -42,6 +42,107 @@ const middleware: NextMiddleware = (
42
42
  };
43
43
  ```
44
44
 
45
+ ## Custom Segments
46
+
47
+ The `[pz]` segment encodes `locale`, `currency`, and `url` by default. You can add custom segments via `pzSegments` in `settings.js` to encode additional values into the URL.
48
+
49
+ ### Configuration
50
+
51
+ Define custom segments in `settings.js` with a `resolve` function that computes the segment value at request time:
52
+
53
+ ```js
54
+ /** @type {import('@akinon/next/types').Settings} */
55
+ module.exports = {
56
+ // ...
57
+ usePzSegment: true,
58
+ pzSegments: {
59
+ segments: [
60
+ {
61
+ name: 'segment',
62
+ resolve: (context) => context.req.cookies.get('pz-segment')?.value ?? 'default'
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ ```
68
+
69
+ Default segments (`locale`, `currency`, `url`) are always included automatically. You only need to define your custom ones.
70
+
71
+ ### Resolve Context
72
+
73
+ The `resolve` function receives a context object with the following properties:
74
+
75
+ | Property | Type | Description |
76
+ |------------|------------------|--------------------------------------|
77
+ | `req` | `PzNextRequest` | The incoming request object (cookies, headers, middlewareParams) |
78
+ | `event` | `NextFetchEvent` | The Next.js fetch event (waitUntil, etc.) |
79
+ | `url` | `NextURL` | Cloned URL object of the current request |
80
+ | `locale` | `string` | Resolved locale value |
81
+ | `currency` | `string` | Resolved currency value |
82
+ | `pathname` | `string` | Pathname without locale prefix |
83
+
84
+ ### Reading Segment Values
85
+
86
+ In server components, use `parsePzParams` to read all segment values (both built-in and custom):
87
+
88
+ ```tsx
89
+ import { parsePzParams } from '@akinon/next/utils'
90
+ import settings from 'settings'
91
+
92
+ export default function Page({ params }) {
93
+ const { locale, currency, url, segment } = parsePzParams(params, settings)
94
+ // ...
95
+ }
96
+ ```
97
+
98
+ In client components, use the `usePzParams` hook:
99
+
100
+ ```tsx
101
+ 'use client'
102
+ import { usePzParams } from '@akinon/next/hooks/use-pz-params'
103
+
104
+ export default function MyComponent() {
105
+ const { locale, currency, url, segment } = usePzParams()
106
+ // ...
107
+ }
108
+ ```
109
+
110
+ ### Example: Cookie-based Segment
111
+
112
+ A segment that reads a value from a cookie and falls back to a default:
113
+
114
+ ```js
115
+ pzSegments: {
116
+ segments: [
117
+ {
118
+ name: 'segment',
119
+ resolve: (context) => context.req.cookies.get('pz-segment')?.value ?? 'default'
120
+ }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ This encodes the cookie value into the `[pz]` URL parameter. The resulting URL structure becomes:
126
+
127
+ ```
128
+ /tr--TL--<encoded-url>--default/page-path
129
+ ```
130
+
131
+ ### Migration from Legacy Structure
132
+
133
+ To migrate a project using the legacy `[commerce]/[locale]/[currency]` directory structure to the new `[pz]` segment:
134
+
135
+ ```bash
136
+ npx projectzero codemod --codemod=migrate-segments
137
+ ```
138
+
139
+ This codemod will:
140
+ - Detect all dynamic segments and merge them into `[pz]/`
141
+ - Update `settings.js` with `usePzSegment: true`
142
+ - Clean up middleware rewrite blocks
143
+ - Update path references in `tsconfig.json`, `next.config.js`, and source files
144
+ - Replace `params.locale` / `params.currency` / `params.url` usages with `parsePzParams`
145
+
45
146
  ## Enabling Browser Back/Forward Cache (bfcache)
46
147
 
47
148
  By default, dynamic pages may include `Cache-Control: no-store` headers, which prevents browsers from using the back/forward cache (bfcache). To enable bfcache support, set the `BF_CACHE` environment variable to `true`:
@@ -8,12 +8,12 @@ const nextConfig = {
8
8
  rewrites: async () => {
9
9
  return [
10
10
  {
11
- source: '/:commerce/:locale/:currency/sitemap.xml',
12
- destination: '/:commerce/:locale/:currency/xml-sitemap'
11
+ source: '/:pz/sitemap.xml',
12
+ destination: '/:pz/xml-sitemap'
13
13
  },
14
14
  {
15
- source: '/:commerce/:locale/:currency/sitemap/:node',
16
- destination: '/:commerce/:locale/:currency/xml-sitemap/:node'
15
+ source: '/:pz/sitemap/:node',
16
+ destination: '/:pz/xml-sitemap/:node'
17
17
  }
18
18
  ];
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectzeronext",
3
- "version": "1.125.0-rc.1",
3
+ "version": "1.126.0-rc.0",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -24,32 +24,32 @@
24
24
  "test:middleware": "jest middleware-matcher.test.ts --bail"
25
25
  },
26
26
  "dependencies": {
27
- "@akinon/next": "1.125.0-rc.1",
28
- "@akinon/pz-akifast": "1.125.0-rc.1",
29
- "@akinon/pz-apple-pay": "1.125.0-rc.1",
30
- "@akinon/pz-b2b": "1.125.0-rc.1",
31
- "@akinon/pz-basket-gift-pack": "1.125.0-rc.1",
32
- "@akinon/pz-bkm": "1.125.0-rc.1",
33
- "@akinon/pz-checkout-gift-pack": "1.125.0-rc.1",
34
- "@akinon/pz-click-collect": "1.125.0-rc.1",
35
- "@akinon/pz-credit-payment": "1.125.0-rc.1",
36
- "@akinon/pz-cybersource-uc": "1.125.0-rc.1",
37
- "@akinon/pz-flow-payment": "1.125.0-rc.1",
38
- "@akinon/pz-google-pay": "1.125.0-rc.1",
39
- "@akinon/pz-gpay": "1.125.0-rc.1",
40
- "@akinon/pz-haso": "1.125.0-rc.1",
41
- "@akinon/pz-hepsipay": "1.125.0-rc.1",
42
- "@akinon/pz-masterpass": "1.125.0-rc.1",
43
- "@akinon/pz-masterpass-rest": "1.125.0-rc.1",
44
- "@akinon/pz-multi-basket": "1.125.0-rc.1",
45
- "@akinon/pz-one-click-checkout": "1.125.0-rc.1",
46
- "@akinon/pz-otp": "1.125.0-rc.1",
47
- "@akinon/pz-pay-on-delivery": "1.125.0-rc.1",
48
- "@akinon/pz-saved-card": "1.125.0-rc.1",
49
- "@akinon/pz-similar-products": "1.125.0-rc.1",
50
- "@akinon/pz-tabby-extension": "1.125.0-rc.1",
51
- "@akinon/pz-tamara-extension": "1.125.0-rc.1",
52
- "@akinon/pz-virtual-try-on": "1.125.0-rc.1",
27
+ "@akinon/next": "1.126.0-rc.0",
28
+ "@akinon/pz-akifast": "1.126.0-rc.0",
29
+ "@akinon/pz-apple-pay": "1.126.0-rc.0",
30
+ "@akinon/pz-b2b": "1.126.0-rc.0",
31
+ "@akinon/pz-basket-gift-pack": "1.126.0-rc.0",
32
+ "@akinon/pz-bkm": "1.126.0-rc.0",
33
+ "@akinon/pz-checkout-gift-pack": "1.126.0-rc.0",
34
+ "@akinon/pz-click-collect": "1.126.0-rc.0",
35
+ "@akinon/pz-credit-payment": "1.126.0-rc.0",
36
+ "@akinon/pz-cybersource-uc": "1.126.0-rc.0",
37
+ "@akinon/pz-flow-payment": "1.126.0-rc.0",
38
+ "@akinon/pz-google-pay": "1.126.0-rc.0",
39
+ "@akinon/pz-gpay": "1.126.0-rc.0",
40
+ "@akinon/pz-haso": "1.126.0-rc.0",
41
+ "@akinon/pz-hepsipay": "1.126.0-rc.0",
42
+ "@akinon/pz-masterpass": "1.126.0-rc.0",
43
+ "@akinon/pz-masterpass-rest": "1.126.0-rc.0",
44
+ "@akinon/pz-multi-basket": "1.126.0-rc.0",
45
+ "@akinon/pz-one-click-checkout": "1.126.0-rc.0",
46
+ "@akinon/pz-otp": "1.126.0-rc.0",
47
+ "@akinon/pz-pay-on-delivery": "1.126.0-rc.0",
48
+ "@akinon/pz-saved-card": "1.126.0-rc.0",
49
+ "@akinon/pz-similar-products": "1.126.0-rc.0",
50
+ "@akinon/pz-tabby-extension": "1.126.0-rc.0",
51
+ "@akinon/pz-tamara-extension": "1.126.0-rc.0",
52
+ "@akinon/pz-virtual-try-on": "1.126.0-rc.0",
53
53
  "@hookform/resolvers": "2.9.0",
54
54
  "@next/third-parties": "14.1.0",
55
55
  "@react-google-maps/api": "2.17.1",
@@ -72,8 +72,8 @@
72
72
  "yup": "0.32.11"
73
73
  },
74
74
  "devDependencies": {
75
- "@akinon/eslint-plugin-projectzero": "1.125.0-rc.1",
76
- "@semantic-release/changelog": "6.0.3",
75
+ "@akinon/eslint-plugin-projectzero": "1.126.0-rc.0",
76
+ "@semantic-release/changelog": "6.0.2",
77
77
  "@semantic-release/exec": "6.0.3",
78
78
  "@semantic-release/git": "10.0.1",
79
79
  "@tailwindcss/typography": "0.5.15",
@@ -42,17 +42,12 @@ export async function generateMetadata() {
42
42
  return result;
43
43
  }
44
44
 
45
- async function RootLayout({
46
- params,
47
- locale,
48
- translations,
49
- children
50
- }: RootLayoutProps) {
45
+ async function RootLayout({ locale, translations, children }: RootLayoutProps) {
51
46
  return (
52
47
  <html lang={locale.isoCode} {...(locale.rtl ? { dir: 'rtl' } : {})}>
53
48
  <head />
54
49
  <body className="overflow-x-hidden">
55
- <PzRoot translations={translations} {...params}>
50
+ <PzRoot translations={translations} locale={locale.value}>
56
51
  <ClientRoot>
57
52
  <div className="overflow-x-hidden">
58
53
  <MobileAppToggler>
@@ -63,5 +63,6 @@ module.exports = {
63
63
  defaultExpirationTime: 900 // 15 min
64
64
  },
65
65
  customNotFoundEnabled: false,
66
+ usePzSegment: true,
66
67
  commerceRedirectionIgnoreList: ['/users/reset']
67
68
  };
@@ -5,16 +5,12 @@
5
5
  "baseUrl": "./src",
6
6
  "paths": {
7
7
  "@theme/*": ["./*"],
8
- "@root/*": ["./app/[commerce]/[locale]/[currency]/*"],
9
- "@product/*": ["./app/[commerce]/[locale]/[currency]/product/*"],
10
- "@group-product/*": [
11
- "./app/[commerce]/[locale]/[currency]/group-product/*"
12
- ],
13
- "@category/*": ["./app/[commerce]/[locale]/[currency]/category/*"],
14
- "@special-page/*": [
15
- "./app/[commerce]/[locale]/[currency]/special-page/*"
16
- ],
17
- "@flat-page/*": ["./app/[commerce]/[locale]/[currency]/flat-page/*"]
8
+ "@root/*": ["./app/[pz]/*"],
9
+ "@product/*": ["./app/[pz]/product/*"],
10
+ "@group-product/*": ["./app/[pz]/group-product/*"],
11
+ "@category/*": ["./app/[pz]/category/*"],
12
+ "@special-page/*": ["./app/[pz]/special-page/*"],
13
+ "@flat-page/*": ["./app/[pz]/flat-page/*"]
18
14
  },
19
15
  "allowSyntheticDefaultImports": true,
20
16
  "composite": false,
@@ -0,0 +1,593 @@
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
381
+ if (
382
+ !updated.includes("from 'settings'") &&
383
+ !updated.includes('from "settings"')
384
+ ) {
385
+ const lastImportIdx = updated.lastIndexOf('\nimport ');
386
+ if (lastImportIdx !== -1) {
387
+ const lineEnd = updated.indexOf('\n', lastImportIdx + 1);
388
+ updated =
389
+ updated.slice(0, lineEnd + 1) +
390
+ "import settings from 'settings';\n" +
391
+ updated.slice(lineEnd + 1);
392
+ }
393
+ }
394
+
395
+ fs.writeFileSync(fullPath, updated);
396
+ updatedFiles.push(path.relative(workingDir, fullPath));
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ walk(path.join(workingDir, 'src'));
403
+ return updatedFiles;
404
+ }
405
+
406
+ function generatePzSegmentsConfig(segments, resolveHints) {
407
+ const lines = segments.map((name) => {
408
+ const hint = resolveHints[name];
409
+ if (hint && hint.code) {
410
+ return ` { name: '${name}', resolve: ${hint.code} }`;
411
+ }
412
+ if (hint && hint.source === 'unknown') {
413
+ return ` { name: '${name}', resolve: ({ req }) => '' /* TODO: return ${name} value */ }`;
414
+ }
415
+ return ` { name: '${name}' }`;
416
+ });
417
+ return `pzSegments: {\n segments: [\n${lines.join(',\n')}\n ]\n }`;
418
+ }
419
+
420
+ const transform = () => {
421
+ const workingDir = path.resolve(process.cwd());
422
+ const appDir = path.join(workingDir, 'src', 'app');
423
+ const settingsPath = path.join(workingDir, 'src', 'settings.js');
424
+
425
+ if (!fs.existsSync(appDir)) {
426
+ console.error(red('Error: src/app/ directory not found.'));
427
+ process.exit(1);
428
+ }
429
+
430
+ const pzDir = path.join(appDir, '[pz]');
431
+ if (fs.existsSync(pzDir)) {
432
+ console.log(
433
+ yellow(
434
+ '[pz] directory already exists. Migration may have already been done.'
435
+ )
436
+ );
437
+ process.exit(0);
438
+ }
439
+
440
+ const commerceDir = path.join(appDir, '[commerce]');
441
+ if (!fs.existsSync(commerceDir)) {
442
+ console.error(red('Error: [commerce] directory not found in src/app/.'));
443
+ process.exit(1);
444
+ }
445
+
446
+ // Detect all segments by walking down the directory tree
447
+ const { segments, levels, contentDir } = detectSegments(commerceDir);
448
+
449
+ const customSegments = segments.filter(
450
+ (s) => !BUILTIN_SEGMENTS.includes(s)
451
+ );
452
+ const segmentPath = ['[commerce]', ...segments.map((s) => `[${s}]`)].join(
453
+ '/'
454
+ );
455
+
456
+ // Count entries across all levels that will be merged
457
+ let totalEntries = countEntries(contentDir);
458
+ for (const { dir, segmentChildName } of levels) {
459
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
460
+ if (entry.name === segmentChildName) continue;
461
+ totalEntries++;
462
+ if (entry.isDirectory()) {
463
+ totalEntries += countEntries(path.join(dir, entry.name));
464
+ }
465
+ }
466
+ }
467
+
468
+ console.log(bold('\nPZ Segment Migration'));
469
+ console.log('====================\n');
470
+ console.log(`Detected: ${yellow(segmentPath + '/')}`);
471
+ console.log(`Segments: ${segments.join(', ')}`);
472
+ if (customSegments.length > 0) {
473
+ console.log(
474
+ `Custom: ${yellow(customSegments.join(', '))} (will be added to pzSegments)`
475
+ );
476
+ }
477
+ if (levels.some(({ dir, segmentChildName }) =>
478
+ fs.readdirSync(dir).some((f) => f !== segmentChildName)
479
+ )) {
480
+ console.log(`Note: Intermediate levels have content, will be merged`);
481
+ }
482
+ console.log(`Target: ${yellow('[pz]/')}`);
483
+ console.log(`Entries: ${totalEntries}\n`);
484
+
485
+ // Merge all levels into [pz]/
486
+ console.log('Merging content into [pz]/...');
487
+ mergeContentIntoPz(levels, contentDir, pzDir);
488
+
489
+ // Remove legacy [commerce]/ tree
490
+ console.log('Removing legacy directories...');
491
+ fs.rmSync(commerceDir, { recursive: true, force: true });
492
+
493
+ // Update settings.js
494
+ if (fs.existsSync(settingsPath)) {
495
+ let content = fs.readFileSync(settingsPath, 'utf-8');
496
+
497
+ // Add usePzSegment: true
498
+ if (!content.includes('usePzSegment')) {
499
+ content = content.replace(/(\n};\s*$)/, ',\n usePzSegment: true$1');
500
+
501
+ if (!content.includes('usePzSegment')) {
502
+ content = content.replace(/(};\s*$)/, ' usePzSegment: true,\n$1');
503
+ }
504
+ }
505
+
506
+ // Add pzSegments config if custom segments were found
507
+ if (customSegments.length > 0 && !content.includes('pzSegments')) {
508
+ const resolveHints = analyzeMiddleware(workingDir, customSegments);
509
+ const orderedSegments = segments.filter((s) => s !== 'commerce');
510
+ const pzSegmentsBlock = generatePzSegmentsConfig(
511
+ orderedSegments,
512
+ resolveHints
513
+ );
514
+
515
+ content = content.replace(
516
+ /(\n};\s*$)/,
517
+ `,\n ${pzSegmentsBlock}$1`
518
+ );
519
+
520
+ if (!content.includes('pzSegments')) {
521
+ content = content.replace(
522
+ /(};\s*$)/,
523
+ ` ${pzSegmentsBlock},\n$1`
524
+ );
525
+ }
526
+ }
527
+
528
+ fs.writeFileSync(settingsPath, content);
529
+
530
+ console.log('Updated settings.js:');
531
+ console.log(' - usePzSegment: true');
532
+ if (customSegments.length > 0) {
533
+ const resolveHints = analyzeMiddleware(workingDir, customSegments);
534
+ console.log(
535
+ ` - pzSegments with segments: [${segments.filter((s) => s !== 'commerce').join(', ')}]`
536
+ );
537
+ for (const seg of customSegments) {
538
+ const hint = resolveHints[seg];
539
+ if (hint && hint.source !== 'unknown') {
540
+ console.log(
541
+ ` ${bold(seg)}: resolve from ${hint.source}`
542
+ );
543
+ } else {
544
+ console.log(
545
+ yellow(
546
+ ` ${seg}: resolve source unknown — fill in the resolve function manually`
547
+ )
548
+ );
549
+ }
550
+ }
551
+ }
552
+ } else {
553
+ console.log(
554
+ yellow(
555
+ 'Warning: settings.js not found. Add usePzSegment: true manually.'
556
+ )
557
+ );
558
+ }
559
+
560
+ // Clean up middleware.ts rewrite block since url is now a built-in segment
561
+ if (segments.includes('url') || customSegments.length > 0) {
562
+ const cleaned = cleanupMiddleware(workingDir);
563
+ if (cleaned) {
564
+ console.log('Cleaned middleware.ts: removed rewrite block (now handled by built-in url segment)');
565
+ }
566
+ }
567
+
568
+ // Update path references in tsconfig, imports, etc.
569
+ const updatedFiles = updatePathReferences(workingDir, segments);
570
+ if (updatedFiles.length > 0) {
571
+ console.log(`Updated path references in ${updatedFiles.length} file(s):`);
572
+ updatedFiles.forEach((f) => console.log(` - ${f}`));
573
+ }
574
+
575
+ // Update params.locale/currency/url usages to parsePzParams
576
+ const paramsUpdated = updateParamsUsage(workingDir);
577
+ if (paramsUpdated.length > 0) {
578
+ console.log(
579
+ `Updated params usage in ${paramsUpdated.length} file(s):`
580
+ );
581
+ paramsUpdated.forEach((f) => console.log(` - ${f}`));
582
+ }
583
+
584
+ console.log(green('\nMigration completed successfully!\n'));
585
+ console.log('Next steps:');
586
+ console.log(` 1. Review changes with ${bold('git diff')}`);
587
+ console.log(` 2. Build with ${bold('yarn build')}`);
588
+ console.log('');
589
+ };
590
+
591
+ module.exports = {
592
+ transform
593
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/projectzero",
3
- "version": "1.125.0-rc.1",
3
+ "version": "1.126.0-rc.0",
4
4
  "private": false,
5
5
  "description": "CLI tool to manage your Project Zero Next project",
6
6
  "bin": {