@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.
- package/CHANGELOG.md +3 -3
- package/app-template/CHANGELOG.md +61 -64
- package/app-template/docs/advanced-usage.md +101 -0
- package/app-template/next.config.mjs +4 -4
- package/app-template/package.json +29 -29
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +2 -7
- package/app-template/src/settings.js +1 -0
- package/app-template/tsconfig.json +6 -10
- package/codemods/migrate-segments/index.js +593 -0
- package/package.json +1 -1
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/not-found.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/product/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts +0 -0
- /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.
|
|
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.
|
|
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.
|
|
69
|
-
- @akinon/pz-similar-products@1.
|
|
70
|
-
- @akinon/pz-masterpass-rest@1.
|
|
71
|
-
- @akinon/pz-
|
|
72
|
-
- @akinon/pz-
|
|
73
|
-
- @akinon/pz-
|
|
74
|
-
- @akinon/pz-
|
|
75
|
-
- @akinon/pz-basket-gift-pack@1.
|
|
76
|
-
- @akinon/pz-bkm@1.
|
|
77
|
-
- @akinon/pz-checkout-gift-pack@1.
|
|
78
|
-
- @akinon/pz-click-collect@1.
|
|
79
|
-
- @akinon/pz-credit-payment@1.
|
|
80
|
-
- @akinon/pz-cybersource-uc@1.
|
|
81
|
-
- @akinon/pz-flow-payment@1.
|
|
82
|
-
- @akinon/pz-google-pay@1.
|
|
83
|
-
- @akinon/pz-gpay@1.
|
|
84
|
-
- @akinon/pz-haso@1.
|
|
85
|
-
- @akinon/pz-hepsipay@1.
|
|
86
|
-
- @akinon/pz-masterpass@1.
|
|
87
|
-
- @akinon/pz-multi-basket@1.
|
|
88
|
-
- @akinon/pz-one-click-checkout@1.
|
|
89
|
-
- @akinon/pz-otp@1.
|
|
90
|
-
- @akinon/pz-pay-on-delivery@1.
|
|
91
|
-
- @akinon/pz-saved-card@1.
|
|
92
|
-
- @akinon/pz-tabby-extension@1.
|
|
93
|
-
- @akinon/pz-tamara-extension@1.
|
|
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: '/:
|
|
12
|
-
destination: '/:
|
|
11
|
+
source: '/:pz/sitemap.xml',
|
|
12
|
+
destination: '/:pz/xml-sitemap'
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
source: '/:
|
|
16
|
-
destination: '/:
|
|
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.
|
|
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.
|
|
28
|
-
"@akinon/pz-akifast": "1.
|
|
29
|
-
"@akinon/pz-apple-pay": "1.
|
|
30
|
-
"@akinon/pz-b2b": "1.
|
|
31
|
-
"@akinon/pz-basket-gift-pack": "1.
|
|
32
|
-
"@akinon/pz-bkm": "1.
|
|
33
|
-
"@akinon/pz-checkout-gift-pack": "1.
|
|
34
|
-
"@akinon/pz-click-collect": "1.
|
|
35
|
-
"@akinon/pz-credit-payment": "1.
|
|
36
|
-
"@akinon/pz-cybersource-uc": "1.
|
|
37
|
-
"@akinon/pz-flow-payment": "1.
|
|
38
|
-
"@akinon/pz-google-pay": "1.
|
|
39
|
-
"@akinon/pz-gpay": "1.
|
|
40
|
-
"@akinon/pz-haso": "1.
|
|
41
|
-
"@akinon/pz-hepsipay": "1.
|
|
42
|
-
"@akinon/pz-masterpass": "1.
|
|
43
|
-
"@akinon/pz-masterpass-rest": "1.
|
|
44
|
-
"@akinon/pz-multi-basket": "1.
|
|
45
|
-
"@akinon/pz-one-click-checkout": "1.
|
|
46
|
-
"@akinon/pz-otp": "1.
|
|
47
|
-
"@akinon/pz-pay-on-delivery": "1.
|
|
48
|
-
"@akinon/pz-saved-card": "1.
|
|
49
|
-
"@akinon/pz-similar-products": "1.
|
|
50
|
-
"@akinon/pz-tabby-extension": "1.
|
|
51
|
-
"@akinon/pz-tamara-extension": "1.
|
|
52
|
-
"@akinon/pz-virtual-try-on": "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.
|
|
76
|
-
"@semantic-release/changelog": "6.0.
|
|
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} {
|
|
50
|
+
<PzRoot translations={translations} locale={locale.value}>
|
|
56
51
|
<ClientRoot>
|
|
57
52
|
<div className="overflow-x-hidden">
|
|
58
53
|
<MobileAppToggler>
|
|
@@ -5,16 +5,12 @@
|
|
|
5
5
|
"baseUrl": "./src",
|
|
6
6
|
"paths": {
|
|
7
7
|
"@theme/*": ["./*"],
|
|
8
|
-
"@root/*": ["./app/[
|
|
9
|
-
"@product/*": ["./app/[
|
|
10
|
-
"@group-product/*": [
|
|
11
|
-
|
|
12
|
-
],
|
|
13
|
-
"@
|
|
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
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx
RENAMED
|
File without changes
|
/package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts
RENAMED
|
File without changes
|
|
File without changes
|