@akinon/projectzero 2.0.10-rc.0 → 2.0.10
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 -4
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +32 -70
- package/app-template/README.md +1 -25
- package/app-template/next.config.mjs +1 -4
- package/app-template/package.json +41 -41
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +0 -1
- package/app-template/src/proxy.ts +1 -2
- package/app-template/src/views/header/search/index.tsx +5 -13
- package/app-template/src/views/product/slider.tsx +38 -85
- package/codemods/migrate-eslint/index.js +270 -0
- package/codemods/upgrade-to-2/index.js +114 -52
- package/commands/plugins.ts +0 -4
- package/dist/commands/plugins.js +0 -4
- package/package.json +1 -1
- package/app-template/src/app/global-error.tsx +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# @akinon/projectzero
|
|
2
2
|
|
|
3
|
-
## 2.0.10
|
|
3
|
+
## 2.0.10
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
|
|
7
|
+
- 31f07bd7: ZERO-4459: Move migrate-eslint codemod into @akinon/projectzero and integrate into upgrade-to-2
|
|
8
|
+
- 6760888a: ZERO-4461: upgrade-to-2 codemod targets @akinon/\* latest (stable) instead of beta dist-tag
|
|
10
9
|
|
|
11
10
|
## 2.0.9
|
|
12
11
|
|
|
@@ -7,7 +7,6 @@ NEXT_PUBLIC_URL=http://localhost:3000
|
|
|
7
7
|
SERVICE_BACKEND_URL=https://02fde10fee4440269e695aa10707dfaf.lb.akinoncloud.com
|
|
8
8
|
SITEMAP_S3_BUCKET_NAME=0fb534
|
|
9
9
|
NEXT_PUBLIC_VIRTUAL_TRY_ON_API_URL=https://d2a26507c7094f359aba349b96a96881.lb.akinoncloud.com
|
|
10
|
-
NEXT_PUBLIC_ENABLE_IMAGE_SEARCH=true
|
|
11
10
|
|
|
12
11
|
# LOG_LEVEL_DEV=debug # For more details, please refer to the Logging documentation.
|
|
13
12
|
|
|
@@ -1,75 +1,37 @@
|
|
|
1
1
|
# projectzeronext
|
|
2
2
|
|
|
3
|
-
## 2.0.10
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
- Updated dependencies [591e345e]
|
|
36
|
-
- Updated dependencies [0df6bc1f]
|
|
37
|
-
- Updated dependencies [4de5303c5]
|
|
38
|
-
- Updated dependencies [63a72000]
|
|
39
|
-
- Updated dependencies [95b139dc1]
|
|
40
|
-
- Updated dependencies [1d00f2d0]
|
|
41
|
-
- Updated dependencies [4ac7b2a1]
|
|
42
|
-
- Updated dependencies [4998a963]
|
|
43
|
-
- Updated dependencies [c0228aff]
|
|
44
|
-
- Updated dependencies [3909d322]
|
|
45
|
-
- Updated dependencies [e18836b2]
|
|
46
|
-
- @akinon/pz-flow-payment@2.0.10-rc.0
|
|
47
|
-
- @akinon/next@2.0.10-rc.0
|
|
48
|
-
- @akinon/pz-checkout-gift-pack@2.0.10-rc.0
|
|
49
|
-
- @akinon/pz-one-click-checkout@2.0.10-rc.0
|
|
50
|
-
- @akinon/pz-basket-gift-pack@2.0.10-rc.0
|
|
51
|
-
- @akinon/pz-masterpass-rest@2.0.10-rc.0
|
|
52
|
-
- @akinon/pz-pay-on-delivery@2.0.10-rc.0
|
|
53
|
-
- @akinon/pz-credit-payment@2.0.10-rc.0
|
|
54
|
-
- @akinon/pz-click-collect@2.0.10-rc.0
|
|
55
|
-
- @akinon/pz-multi-basket@2.0.10-rc.0
|
|
56
|
-
- @akinon/pz-masterpass@2.0.10-rc.0
|
|
57
|
-
- @akinon/pz-gpay@2.0.10-rc.0
|
|
58
|
-
- @akinon/pz-b2b@2.0.10-rc.0
|
|
59
|
-
- @akinon/pz-bkm@2.0.10-rc.0
|
|
60
|
-
- @akinon/pz-otp@2.0.10-rc.0
|
|
61
|
-
- @akinon/pz-similar-products@2.0.10-rc.0
|
|
62
|
-
- @akinon/pz-virtual-try-on@2.0.10-rc.0
|
|
63
|
-
- @akinon/pz-theme@2.0.10-rc.0
|
|
64
|
-
- @akinon/pz-akifast@2.0.10-rc.0
|
|
65
|
-
- @akinon/pz-apple-pay@2.0.10-rc.0
|
|
66
|
-
- @akinon/pz-cybersource-uc@2.0.10-rc.0
|
|
67
|
-
- @akinon/pz-google-pay@2.0.10-rc.0
|
|
68
|
-
- @akinon/pz-haso@2.0.10-rc.0
|
|
69
|
-
- @akinon/pz-hepsipay@2.0.10-rc.0
|
|
70
|
-
- @akinon/pz-saved-card@2.0.10-rc.0
|
|
71
|
-
- @akinon/pz-tabby-extension@2.0.10-rc.0
|
|
72
|
-
- @akinon/pz-tamara-extension@2.0.10-rc.0
|
|
3
|
+
## 2.0.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [31f07bd7]
|
|
8
|
+
- @akinon/next@2.0.10
|
|
9
|
+
- @akinon/pz-theme@2.0.10
|
|
10
|
+
- @akinon/pz-virtual-try-on@2.0.10
|
|
11
|
+
- @akinon/pz-akifast@2.0.10
|
|
12
|
+
- @akinon/pz-apple-pay@2.0.10
|
|
13
|
+
- @akinon/pz-b2b@2.0.10
|
|
14
|
+
- @akinon/pz-basket-gift-pack@2.0.10
|
|
15
|
+
- @akinon/pz-bkm@2.0.10
|
|
16
|
+
- @akinon/pz-checkout-gift-pack@2.0.10
|
|
17
|
+
- @akinon/pz-click-collect@2.0.10
|
|
18
|
+
- @akinon/pz-credit-payment@2.0.10
|
|
19
|
+
- @akinon/pz-cybersource-uc@2.0.10
|
|
20
|
+
- @akinon/pz-flow-payment@2.0.10
|
|
21
|
+
- @akinon/pz-google-pay@2.0.10
|
|
22
|
+
- @akinon/pz-gpay@2.0.10
|
|
23
|
+
- @akinon/pz-haso@2.0.10
|
|
24
|
+
- @akinon/pz-hepsipay@2.0.10
|
|
25
|
+
- @akinon/pz-masterpass@2.0.10
|
|
26
|
+
- @akinon/pz-masterpass-rest@2.0.10
|
|
27
|
+
- @akinon/pz-multi-basket@2.0.10
|
|
28
|
+
- @akinon/pz-one-click-checkout@2.0.10
|
|
29
|
+
- @akinon/pz-otp@2.0.10
|
|
30
|
+
- @akinon/pz-pay-on-delivery@2.0.10
|
|
31
|
+
- @akinon/pz-saved-card@2.0.10
|
|
32
|
+
- @akinon/pz-similar-products@2.0.10
|
|
33
|
+
- @akinon/pz-tabby-extension@2.0.10
|
|
34
|
+
- @akinon/pz-tamara-extension@2.0.10
|
|
73
35
|
|
|
74
36
|
## 2.0.9
|
|
75
37
|
|
package/app-template/README.md
CHANGED
|
@@ -12,30 +12,6 @@ Run `cp .env.example .env && yarn` command at project root and all dependencies
|
|
|
12
12
|
|
|
13
13
|
- `SITEMAP_S3_BUCKET_NAME`: S3 bucket name for XML sitemaps (e.g., "0fb534"). This is required for the sitemap route to function correctly.
|
|
14
14
|
|
|
15
|
-
### Optional Environment Variables
|
|
16
|
-
|
|
17
|
-
- `NEXT_PUBLIC_ENABLE_IMAGE_SEARCH`: Set to `true` to enable image search feature for all users. If not set or `false`, the feature will only be visible to users who have enabled it via localStorage.
|
|
18
|
-
|
|
19
|
-
## Image Search Feature
|
|
20
|
-
|
|
21
|
-
The image search functionality allows users to search for similar products by uploading images or cropping existing product images.
|
|
22
|
-
|
|
23
|
-
### Feature Visibility Control
|
|
24
|
-
|
|
25
|
-
The image search feature can be controlled in two ways:
|
|
26
|
-
|
|
27
|
-
1. **Environment Variable (Global)**: Set `NEXT_PUBLIC_ENABLE_IMAGE_SEARCH=true` to enable for all users
|
|
28
|
-
2. **localStorage (Individual Users)**: Users can enable it locally using browser developer tools
|
|
29
|
-
|
|
30
|
-
````
|
|
31
|
-
|
|
32
|
-
### Feature Locations
|
|
33
|
-
|
|
34
|
-
When enabled, the image search feature appears in:
|
|
35
|
-
|
|
36
|
-
- **Header Search**: Camera icon next to the search input
|
|
37
|
-
- **Product Detail Page**: "View Similar Styles" button on product images
|
|
38
|
-
|
|
39
15
|
### Build
|
|
40
16
|
|
|
41
17
|
To build the app, run the following command:
|
|
@@ -43,7 +19,7 @@ To build the app, run the following command:
|
|
|
43
19
|
```bash
|
|
44
20
|
cd projectzeronext # Project root
|
|
45
21
|
yarn build
|
|
46
|
-
|
|
22
|
+
```
|
|
47
23
|
|
|
48
24
|
### Develop
|
|
49
25
|
|
|
@@ -48,12 +48,9 @@ const withSerwist = withSerwistInit({
|
|
|
48
48
|
const sentryConfig = {
|
|
49
49
|
silent: true,
|
|
50
50
|
dryRun: !process.env.SENTRY_DSN,
|
|
51
|
-
org: 'akinon'
|
|
51
|
+
org: 'akinon'
|
|
52
52
|
// project: 'enter_your_project_name_here',
|
|
53
53
|
// authToken: 'enter_your_auth_token_here'
|
|
54
|
-
sourcemaps: {
|
|
55
|
-
deleteSourcemapsAfterUpload: true
|
|
56
|
-
}
|
|
57
54
|
};
|
|
58
55
|
|
|
59
56
|
const enhancedConfig = withPzConfig(nextConfig);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectzeronext",
|
|
3
|
-
"version": "2.0.10
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"private": true,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -24,33 +24,33 @@
|
|
|
24
24
|
"test:middleware": "jest middleware-matcher.test.ts --bail"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@akinon/next": "2.0.10
|
|
28
|
-
"@akinon/pz-akifast": "2.0.10
|
|
29
|
-
"@akinon/pz-apple-pay": "2.0.10
|
|
30
|
-
"@akinon/pz-b2b": "2.0.10
|
|
31
|
-
"@akinon/pz-basket-gift-pack": "2.0.10
|
|
32
|
-
"@akinon/pz-bkm": "2.0.10
|
|
33
|
-
"@akinon/pz-checkout-gift-pack": "2.0.10
|
|
34
|
-
"@akinon/pz-click-collect": "2.0.10
|
|
35
|
-
"@akinon/pz-credit-payment": "2.0.10
|
|
36
|
-
"@akinon/pz-cybersource-uc": "2.0.10
|
|
37
|
-
"@akinon/pz-flow-payment": "2.0.10
|
|
38
|
-
"@akinon/pz-google-pay": "2.0.10
|
|
39
|
-
"@akinon/pz-gpay": "2.0.10
|
|
40
|
-
"@akinon/pz-haso": "2.0.10
|
|
41
|
-
"@akinon/pz-hepsipay": "2.0.10
|
|
42
|
-
"@akinon/pz-masterpass": "2.0.10
|
|
43
|
-
"@akinon/pz-masterpass-rest": "2.0.10
|
|
44
|
-
"@akinon/pz-multi-basket": "2.0.10
|
|
45
|
-
"@akinon/pz-one-click-checkout": "2.0.10
|
|
46
|
-
"@akinon/pz-otp": "2.0.10
|
|
47
|
-
"@akinon/pz-pay-on-delivery": "2.0.10
|
|
48
|
-
"@akinon/pz-saved-card": "2.0.10
|
|
49
|
-
"@akinon/pz-similar-products": "2.0.10
|
|
50
|
-
"@akinon/pz-tabby-extension": "2.0.10
|
|
51
|
-
"@akinon/pz-tamara-extension": "2.0.10
|
|
52
|
-
"@akinon/pz-theme": "2.0.10
|
|
53
|
-
"@akinon/pz-virtual-try-on": "2.0.10
|
|
27
|
+
"@akinon/next": "2.0.10",
|
|
28
|
+
"@akinon/pz-akifast": "2.0.10",
|
|
29
|
+
"@akinon/pz-apple-pay": "2.0.10",
|
|
30
|
+
"@akinon/pz-b2b": "2.0.10",
|
|
31
|
+
"@akinon/pz-basket-gift-pack": "2.0.10",
|
|
32
|
+
"@akinon/pz-bkm": "2.0.10",
|
|
33
|
+
"@akinon/pz-checkout-gift-pack": "2.0.10",
|
|
34
|
+
"@akinon/pz-click-collect": "2.0.10",
|
|
35
|
+
"@akinon/pz-credit-payment": "2.0.10",
|
|
36
|
+
"@akinon/pz-cybersource-uc": "2.0.10",
|
|
37
|
+
"@akinon/pz-flow-payment": "2.0.10",
|
|
38
|
+
"@akinon/pz-google-pay": "2.0.10",
|
|
39
|
+
"@akinon/pz-gpay": "2.0.10",
|
|
40
|
+
"@akinon/pz-haso": "2.0.10",
|
|
41
|
+
"@akinon/pz-hepsipay": "2.0.10",
|
|
42
|
+
"@akinon/pz-masterpass": "2.0.10",
|
|
43
|
+
"@akinon/pz-masterpass-rest": "2.0.10",
|
|
44
|
+
"@akinon/pz-multi-basket": "2.0.10",
|
|
45
|
+
"@akinon/pz-one-click-checkout": "2.0.10",
|
|
46
|
+
"@akinon/pz-otp": "2.0.10",
|
|
47
|
+
"@akinon/pz-pay-on-delivery": "2.0.10",
|
|
48
|
+
"@akinon/pz-saved-card": "2.0.10",
|
|
49
|
+
"@akinon/pz-similar-products": "2.0.10",
|
|
50
|
+
"@akinon/pz-tabby-extension": "2.0.10",
|
|
51
|
+
"@akinon/pz-tamara-extension": "2.0.10",
|
|
52
|
+
"@akinon/pz-theme": "2.0.10",
|
|
53
|
+
"@akinon/pz-virtual-try-on": "2.0.10",
|
|
54
54
|
"@hookform/resolvers": "2.9.0",
|
|
55
55
|
"@next/third-parties": "16.2.6",
|
|
56
56
|
"@react-google-maps/api": "2.17.1",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"next": "16.2.6",
|
|
60
60
|
"@serwist/next": "9.5.11",
|
|
61
61
|
"next-auth": "5.0.0-beta.30",
|
|
62
|
-
"pino": "8.
|
|
62
|
+
"pino": "8.11.0",
|
|
63
63
|
"serwist": "9.5.11",
|
|
64
64
|
"postcss": "8.4.49",
|
|
65
65
|
"react": "19.2.5",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"react-google-recaptcha": "2.1.0",
|
|
68
68
|
"react-hook-form": "7.71.2",
|
|
69
69
|
"react-intersection-observer": "9.4.0",
|
|
70
|
-
"react-multi-carousel": "2.8.
|
|
71
|
-
"react-string-replace": "1.1.
|
|
72
|
-
"start-server-and-test": "2.0.
|
|
73
|
-
"tailwind-merge": "1.
|
|
70
|
+
"react-multi-carousel": "2.8.4",
|
|
71
|
+
"react-string-replace": "1.1.0",
|
|
72
|
+
"start-server-and-test": "2.0.3",
|
|
73
|
+
"tailwind-merge": "1.8.0",
|
|
74
74
|
"yup": "0.32.11"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@akinon/eslint-plugin-projectzero": "2.0.10
|
|
77
|
+
"@akinon/eslint-plugin-projectzero": "2.0.10",
|
|
78
78
|
"@semantic-release/changelog": "6.0.2",
|
|
79
79
|
"@semantic-release/exec": "6.0.3",
|
|
80
80
|
"@semantic-release/git": "10.0.1",
|
|
@@ -89,25 +89,25 @@
|
|
|
89
89
|
"@types/react": "19.2.14",
|
|
90
90
|
"@types/react-dom": "19.2.3",
|
|
91
91
|
"client-only": "0.0.1",
|
|
92
|
-
"clsx": "1.
|
|
92
|
+
"clsx": "1.1.1",
|
|
93
93
|
"currency-symbol-map": "5.1.0",
|
|
94
94
|
"eslint": "9.39.4",
|
|
95
95
|
"eslint-config-next": "16.2.4",
|
|
96
96
|
"eslint-config-prettier": "10.1.1",
|
|
97
97
|
"husky": "8.0.0",
|
|
98
98
|
"jest": "29.7.0",
|
|
99
|
-
"jest-css-modules-transform": "4.
|
|
99
|
+
"jest-css-modules-transform": "4.3.0",
|
|
100
100
|
"jest-environment-jsdom": "29.7.0",
|
|
101
|
-
"lint-staged": "13.
|
|
102
|
-
"prettier": "2.
|
|
101
|
+
"lint-staged": "13.1.0",
|
|
102
|
+
"prettier": "2.6.2",
|
|
103
103
|
"react-number-format": "5.3.4",
|
|
104
|
-
"sass": "1.
|
|
104
|
+
"sass": "1.49.9",
|
|
105
105
|
"semantic-release": "19.0.5",
|
|
106
106
|
"server-only": "0.0.1",
|
|
107
|
-
"stylelint": "14.
|
|
107
|
+
"stylelint": "14.6.0",
|
|
108
108
|
"stylelint-config-sass-guidelines": "9.0.1",
|
|
109
109
|
"stylelint-config-standard": "25.0.0",
|
|
110
|
-
"stylelint-scss": "4.
|
|
110
|
+
"stylelint-scss": "4.2.0",
|
|
111
111
|
"stylelint-selector-bem-pattern": "2.1.1",
|
|
112
112
|
"tailwindcss": "4.2.0",
|
|
113
113
|
"ts-jest": "29.2.6",
|
|
@@ -14,8 +14,7 @@ export const config = {
|
|
|
14
14
|
'/((?!api|_next|[\\w-\\/*]+\\.\\w+).*)',
|
|
15
15
|
'/(.*sitemap\\.xml)',
|
|
16
16
|
'/(.+\\.)(html|htm|aspx|asp|php)',
|
|
17
|
-
'/(.*orders\\/checkout-with-token.*)'
|
|
18
|
-
'/(.*orders\\/post-checkout.*)'
|
|
17
|
+
'/(.*orders\\/checkout-with-token.*)'
|
|
19
18
|
]
|
|
20
19
|
};
|
|
21
20
|
|
|
@@ -4,7 +4,8 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
5
5
|
import { closeSearch } from '@akinon/next/redux/reducers/header';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import { Icon } from '@theme/components';
|
|
8
9
|
import Results from './results';
|
|
9
10
|
import { ROUTES } from '@theme/routes';
|
|
10
11
|
import { useLocalization, useRouter } from '@akinon/next/hooks';
|
|
@@ -41,14 +42,6 @@ export default function Search() {
|
|
|
41
42
|
};
|
|
42
43
|
}, [isSearchOpen, dispatch]);
|
|
43
44
|
|
|
44
|
-
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
45
|
-
setSearchText(e.target.value);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const handleCloseSearch = () => {
|
|
49
|
-
dispatch(closeSearch());
|
|
50
|
-
};
|
|
51
|
-
|
|
52
45
|
return (
|
|
53
46
|
<>
|
|
54
47
|
<div
|
|
@@ -74,9 +67,9 @@ export default function Search() {
|
|
|
74
67
|
{t('common.search.results_for')}
|
|
75
68
|
</span>
|
|
76
69
|
<div className="flex items-center">
|
|
77
|
-
<
|
|
70
|
+
<input
|
|
78
71
|
value={searchText}
|
|
79
|
-
onChange={
|
|
72
|
+
onChange={(e) => setSearchText(e.target.value)}
|
|
80
73
|
onKeyDown={(e) => {
|
|
81
74
|
if (e.key === 'Enter' && searchText.trim() !== '') {
|
|
82
75
|
router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
|
|
@@ -98,12 +91,11 @@ export default function Search() {
|
|
|
98
91
|
<Icon
|
|
99
92
|
name="close"
|
|
100
93
|
size={14}
|
|
101
|
-
onClick={
|
|
94
|
+
onClick={() => dispatch(closeSearch())}
|
|
102
95
|
className="cursor-pointer"
|
|
103
96
|
/>
|
|
104
97
|
</div>
|
|
105
98
|
</div>
|
|
106
|
-
|
|
107
99
|
<Results searchText={searchText} />
|
|
108
100
|
</div>
|
|
109
101
|
</div>
|
|
@@ -36,100 +36,53 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
36
36
|
carouselRef.current?.next();
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const handleThumbnailClick = (index
|
|
39
|
+
const handleThumbnailClick = (index) => {
|
|
40
40
|
setActiveIndex(index);
|
|
41
41
|
carouselRef.current?.goToSlide(index);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
|
|
46
|
-
<div className="lg:
|
|
47
|
-
<div className="
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
disabled={activeIndex === 0}
|
|
56
|
-
>
|
|
57
|
-
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
58
|
-
</button>
|
|
59
|
-
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
60
|
-
{product?.productimage_set?.map((item, index) => (
|
|
61
|
-
<Image
|
|
62
|
-
key={index}
|
|
63
|
-
src={item.image}
|
|
64
|
-
alt={`Thumbnail ${index}`}
|
|
65
|
-
width={80}
|
|
66
|
-
height={128}
|
|
67
|
-
aspectRatio={80 / 128}
|
|
68
|
-
className={twMerge('cursor-pointer', [
|
|
69
|
-
activeIndex === index && 'border-2 border-primary'
|
|
70
|
-
])}
|
|
71
|
-
onClick={() => handleThumbnailClick(index)}
|
|
72
|
-
/>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
<button
|
|
76
|
-
onClick={goToNext}
|
|
77
|
-
className={twMerge(
|
|
78
|
-
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
79
|
-
[
|
|
80
|
-
activeIndex === product.productimage_set.length - 1 &&
|
|
81
|
-
'cursor-not-allowed opacity-45'
|
|
82
|
-
]
|
|
83
|
-
)}
|
|
84
|
-
disabled={activeIndex === product.productimage_set.length - 1}
|
|
85
|
-
>
|
|
86
|
-
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
87
|
-
</button>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div className="relative lg:col-span-5">
|
|
92
|
-
<FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
|
|
93
|
-
|
|
94
|
-
<PluginModule
|
|
95
|
-
component={Component.ProductImageSearchFeature}
|
|
96
|
-
props={{
|
|
97
|
-
product,
|
|
98
|
-
activeIndex,
|
|
99
|
-
showResetButton: true
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
|
|
103
|
-
<CarouselCore
|
|
104
|
-
responsive={{
|
|
105
|
-
all: {
|
|
106
|
-
breakpoint: { max: 5000, min: 0 },
|
|
107
|
-
items: 1
|
|
108
|
-
}
|
|
109
|
-
}}
|
|
110
|
-
arrows={false}
|
|
111
|
-
swipeable={true}
|
|
112
|
-
ref={carouselRef}
|
|
113
|
-
afterChange={(previousSlide, { currentSlide }) => {
|
|
114
|
-
setActiveIndex(currentSlide);
|
|
115
|
-
}}
|
|
116
|
-
containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
|
|
45
|
+
<div className="lg:grid lg:grid-cols-6">
|
|
46
|
+
<div className="lg:col-span-1">
|
|
47
|
+
<div className="flex flex-col items-center justify-center md:mr-[6px]">
|
|
48
|
+
<button
|
|
49
|
+
onClick={goToPrev}
|
|
50
|
+
className={twMerge(
|
|
51
|
+
'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
52
|
+
[activeIndex === 0 && 'cursor-not-allowed opacity-45']
|
|
53
|
+
)}
|
|
54
|
+
disabled={activeIndex === 0}
|
|
117
55
|
>
|
|
118
|
-
{
|
|
56
|
+
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
57
|
+
</button>
|
|
58
|
+
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
59
|
+
{product?.productimage_set?.map((item, index) => (
|
|
119
60
|
<Image
|
|
120
|
-
key={
|
|
61
|
+
key={index}
|
|
121
62
|
src={item.image}
|
|
122
|
-
alt={
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
fill
|
|
63
|
+
alt={`Thumbnail ${index}`}
|
|
64
|
+
width={80}
|
|
65
|
+
height={128}
|
|
66
|
+
className={twMerge('cursor-pointer', [
|
|
67
|
+
activeIndex === index && 'border-2 border-primary'
|
|
68
|
+
])}
|
|
69
|
+
onClick={() => handleThumbnailClick(index)}
|
|
130
70
|
/>
|
|
131
71
|
))}
|
|
132
|
-
</
|
|
72
|
+
</div>
|
|
73
|
+
<button
|
|
74
|
+
onClick={goToNext}
|
|
75
|
+
className={twMerge(
|
|
76
|
+
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
77
|
+
[
|
|
78
|
+
activeIndex === product.productimage_set.length - 1 &&
|
|
79
|
+
'cursor-not-allowed opacity-45'
|
|
80
|
+
]
|
|
81
|
+
)}
|
|
82
|
+
disabled={activeIndex === product.productimage_set.length - 1}
|
|
83
|
+
>
|
|
84
|
+
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
85
|
+
</button>
|
|
133
86
|
</div>
|
|
134
87
|
</div>
|
|
135
88
|
|
|
@@ -187,6 +140,6 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
187
140
|
))}
|
|
188
141
|
</CarouselCore>
|
|
189
142
|
</div>
|
|
190
|
-
|
|
143
|
+
</div>
|
|
191
144
|
);
|
|
192
145
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint flat config migration codemod for Project Zero brands.
|
|
3
|
+
*
|
|
4
|
+
* Converts a brand project from legacy `.eslintrc.js` to ESLint v9 flat config
|
|
5
|
+
* (`eslint.config.mjs`), consuming the shared base from
|
|
6
|
+
* `@akinon/next/eslint.config.base.mjs`.
|
|
7
|
+
*
|
|
8
|
+
* Idempotent: re-running on an already migrated project is a no-op for
|
|
9
|
+
* already-applied steps.
|
|
10
|
+
*
|
|
11
|
+
* Steps:
|
|
12
|
+
* 1. Read .eslintrc.js (if present) to detect brand-specific settings
|
|
13
|
+
* (settings.next.rootDir, custom rules).
|
|
14
|
+
* 2. Read .eslintignore (if present) to collect ignore patterns.
|
|
15
|
+
* 3. Write eslint.config.mjs (skipped if already exists).
|
|
16
|
+
* 4. Delete .eslintrc.js and .eslintignore.
|
|
17
|
+
* 5. Rewrite .lintstagedrc.js to use `eslint --fix` instead of `next lint`.
|
|
18
|
+
* 6. Patch package.json:
|
|
19
|
+
* - scripts.lint: "next lint" -> "eslint ."
|
|
20
|
+
* - eslint: ^8.x -> ^9.39.4
|
|
21
|
+
* - eslint-config-prettier: ^8.x -> ^10.1.1
|
|
22
|
+
* - remove @typescript-eslint/eslint-plugin and @typescript-eslint/parser
|
|
23
|
+
* (now provided by eslint-config-next/typescript).
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* cd <brand-root>
|
|
27
|
+
* npx @akinon/projectzero codemod --codemod=migrate-eslint
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
|
|
35
|
+
const ESLINT_TARGET = '9.39.4';
|
|
36
|
+
const ESLINT_CONFIG_PRETTIER_TARGET = '10.1.1';
|
|
37
|
+
const TYPESCRIPT_ESLINT_DROP = [
|
|
38
|
+
'@typescript-eslint/eslint-plugin',
|
|
39
|
+
'@typescript-eslint/parser'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function log(msg) {
|
|
43
|
+
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
44
|
+
console.log(`[${ts}] [migrate-eslint] ${msg}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildFlatConfig({ rootDir, ignores, customRules }) {
|
|
48
|
+
const lines = [
|
|
49
|
+
"import { defineConfig, globalIgnores } from 'eslint/config';",
|
|
50
|
+
"import baseConfig from '@akinon/next/eslint.config.base.mjs';",
|
|
51
|
+
'',
|
|
52
|
+
'export default defineConfig([',
|
|
53
|
+
' ...baseConfig'
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
if (rootDir) {
|
|
57
|
+
lines[lines.length - 1] += ',';
|
|
58
|
+
lines.push(
|
|
59
|
+
' {',
|
|
60
|
+
' settings: {',
|
|
61
|
+
' next: {',
|
|
62
|
+
` rootDir: ${JSON.stringify(rootDir)}`,
|
|
63
|
+
' }',
|
|
64
|
+
' }',
|
|
65
|
+
' }'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (customRules) {
|
|
70
|
+
lines[lines.length - 1] += ',';
|
|
71
|
+
const indented = JSON.stringify(customRules, null, 2)
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((l, i) => (i === 0 ? ' rules: ' + l : ' ' + l))
|
|
74
|
+
.join('\n');
|
|
75
|
+
lines.push(' {', indented, ' }');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (ignores && ignores.length) {
|
|
79
|
+
lines[lines.length - 1] += ',';
|
|
80
|
+
lines.push(` globalIgnores(${JSON.stringify(ignores)})`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push(']);', '');
|
|
84
|
+
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const LINT_STAGED_TEMPLATE = `const path = require('path');
|
|
89
|
+
|
|
90
|
+
const buildEslintCommand = (filenames) =>
|
|
91
|
+
\`eslint --fix \${filenames
|
|
92
|
+
.map((f) => \`"\${path.relative(process.cwd(), f)}"\`)
|
|
93
|
+
.join(' ')}\`;
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
'**/*.(ts|tsx)': () => 'yarn tsc --noEmit',
|
|
97
|
+
'*.{js,jsx,ts,tsx}': [buildEslintCommand, 'prettier --write'],
|
|
98
|
+
'*.{css,scss}': [
|
|
99
|
+
'stylelint --fix --allow-empty-input',
|
|
100
|
+
'prettier --write',
|
|
101
|
+
'stylelint --allow-empty-input'
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
function loadLegacyConfig(eslintrcPath) {
|
|
107
|
+
try {
|
|
108
|
+
delete require.cache[require.resolve(eslintrcPath)];
|
|
109
|
+
return require(eslintrcPath);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return { __parseError: err.message };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readIgnoreFile(eslintignorePath) {
|
|
116
|
+
if (!fs.existsSync(eslintignorePath)) return [];
|
|
117
|
+
const raw = fs.readFileSync(eslintignorePath, 'utf8');
|
|
118
|
+
return raw
|
|
119
|
+
.split(/\r?\n/)
|
|
120
|
+
.map((line) => line.trim())
|
|
121
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
122
|
+
.map((pattern) => (pattern.endsWith('/**') ? pattern : pattern + '/**'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function patchPackageJson(pkgPath, { dryRun }) {
|
|
126
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
127
|
+
const pkg = JSON.parse(raw);
|
|
128
|
+
const changes = [];
|
|
129
|
+
|
|
130
|
+
if (pkg.scripts && typeof pkg.scripts.lint === 'string') {
|
|
131
|
+
if (/^next\s+lint\b/.test(pkg.scripts.lint.trim())) {
|
|
132
|
+
pkg.scripts.lint = 'eslint .';
|
|
133
|
+
changes.push('scripts.lint -> "eslint ."');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const bumpDev = (name, target) => {
|
|
138
|
+
if (!pkg.devDependencies || !pkg.devDependencies[name]) return;
|
|
139
|
+
const current = pkg.devDependencies[name].replace(/^\^|^~/, '');
|
|
140
|
+
if (current === target) return;
|
|
141
|
+
pkg.devDependencies[name] = target;
|
|
142
|
+
changes.push(`devDependencies.${name} ${current} -> ${target}`);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
bumpDev('eslint', ESLINT_TARGET);
|
|
146
|
+
bumpDev('eslint-config-prettier', ESLINT_CONFIG_PRETTIER_TARGET);
|
|
147
|
+
|
|
148
|
+
for (const dep of TYPESCRIPT_ESLINT_DROP) {
|
|
149
|
+
if (pkg.devDependencies && pkg.devDependencies[dep]) {
|
|
150
|
+
delete pkg.devDependencies[dep];
|
|
151
|
+
changes.push(`removed devDependencies.${dep}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (changes.length === 0) return { changed: false, changes };
|
|
156
|
+
|
|
157
|
+
if (!dryRun) {
|
|
158
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
159
|
+
}
|
|
160
|
+
return { changed: true, changes };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const transform = () => {
|
|
164
|
+
const root = path.resolve(process.cwd());
|
|
165
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
166
|
+
|
|
167
|
+
const eslintrcPath = path.join(root, '.eslintrc.js');
|
|
168
|
+
const eslintignorePath = path.join(root, '.eslintignore');
|
|
169
|
+
const flatConfigPath = path.join(root, 'eslint.config.mjs');
|
|
170
|
+
const lintStagedPath = path.join(root, '.lintstagedrc.js');
|
|
171
|
+
const packageJsonPath = path.join(root, 'package.json');
|
|
172
|
+
|
|
173
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
174
|
+
log(`No package.json found in ${root}`);
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
log(`starting in ${root}${dryRun ? ' (DRY RUN)' : ''}`);
|
|
180
|
+
const summary = [];
|
|
181
|
+
|
|
182
|
+
const legacyConfig = fs.existsSync(eslintrcPath)
|
|
183
|
+
? loadLegacyConfig(eslintrcPath)
|
|
184
|
+
: null;
|
|
185
|
+
|
|
186
|
+
if (legacyConfig && legacyConfig.__parseError) {
|
|
187
|
+
log(
|
|
188
|
+
`WARN: could not parse .eslintrc.js (${legacyConfig.__parseError}); continuing with default brand template`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const rootDir = legacyConfig?.settings?.next?.rootDir;
|
|
193
|
+
const customRules =
|
|
194
|
+
legacyConfig && legacyConfig.rules && Object.keys(legacyConfig.rules).length
|
|
195
|
+
? legacyConfig.rules
|
|
196
|
+
: null;
|
|
197
|
+
|
|
198
|
+
let ignores = readIgnoreFile(eslintignorePath);
|
|
199
|
+
if (ignores.length === 0) ignores = ['public/**'];
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(flatConfigPath)) {
|
|
202
|
+
const body = buildFlatConfig({ rootDir, ignores, customRules });
|
|
203
|
+
if (dryRun) {
|
|
204
|
+
log(`[DRY] CREATE ${flatConfigPath} (${body.length} bytes)`);
|
|
205
|
+
} else {
|
|
206
|
+
fs.writeFileSync(flatConfigPath, body, 'utf8');
|
|
207
|
+
}
|
|
208
|
+
summary.push('created eslint.config.mjs');
|
|
209
|
+
} else {
|
|
210
|
+
summary.push('eslint.config.mjs already exists (skipped)');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (fs.existsSync(eslintrcPath)) {
|
|
214
|
+
if (dryRun) {
|
|
215
|
+
log(`[DRY] DELETE ${eslintrcPath}`);
|
|
216
|
+
} else {
|
|
217
|
+
fs.unlinkSync(eslintrcPath);
|
|
218
|
+
}
|
|
219
|
+
summary.push('removed .eslintrc.js');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (fs.existsSync(eslintignorePath)) {
|
|
223
|
+
if (dryRun) {
|
|
224
|
+
log(`[DRY] DELETE ${eslintignorePath}`);
|
|
225
|
+
} else {
|
|
226
|
+
fs.unlinkSync(eslintignorePath);
|
|
227
|
+
}
|
|
228
|
+
summary.push('removed .eslintignore');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(lintStagedPath)) {
|
|
232
|
+
const before = fs.readFileSync(lintStagedPath, 'utf8');
|
|
233
|
+
if (before !== LINT_STAGED_TEMPLATE) {
|
|
234
|
+
if (dryRun) {
|
|
235
|
+
log(`[DRY] REWRITE ${lintStagedPath}`);
|
|
236
|
+
} else {
|
|
237
|
+
fs.writeFileSync(lintStagedPath, LINT_STAGED_TEMPLATE, 'utf8');
|
|
238
|
+
}
|
|
239
|
+
summary.push('rewrote .lintstagedrc.js');
|
|
240
|
+
} else {
|
|
241
|
+
summary.push('.lintstagedrc.js already up to date (skipped)');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const pkgResult = patchPackageJson(packageJsonPath, { dryRun });
|
|
246
|
+
if (pkgResult.changed) {
|
|
247
|
+
summary.push(`patched package.json (${pkgResult.changes.join(', ')})`);
|
|
248
|
+
} else {
|
|
249
|
+
summary.push('package.json already up to date (skipped)');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log('\nESLint flat config migration summary:');
|
|
253
|
+
for (const line of summary) console.log(` - ${line}`);
|
|
254
|
+
|
|
255
|
+
if (customRules) {
|
|
256
|
+
console.log(
|
|
257
|
+
`\nNote: ${Object.keys(customRules).length} custom rule(s) from the old ` +
|
|
258
|
+
'.eslintrc.js were carried over into eslint.config.mjs.\nReview if ' +
|
|
259
|
+
'they are still needed (some may already be covered by the base config).'
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
log('done');
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
module.exports = { transform };
|
|
267
|
+
|
|
268
|
+
if (require.main === module) {
|
|
269
|
+
transform();
|
|
270
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* upgrade-to-2: orchestrator codemod that chains every step required to move
|
|
3
|
-
* a Project Zero brand project from pz-next 1.x to 2.0
|
|
3
|
+
* a Project Zero brand project from pz-next 1.x to 2.0 stable.
|
|
4
4
|
*
|
|
5
5
|
* Steps (in order):
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
13
|
-
* 8.
|
|
14
|
-
* 9.
|
|
6
|
+
* 1. Preflight checks (git state, package.json, Node.js project)
|
|
7
|
+
* 2. Create a backup git branch (pre-upgrade-2-YYYYMMDD)
|
|
8
|
+
* 3. Bump package.json dependencies to the 2.0 target matrix
|
|
9
|
+
* 4. yarn clean && yarn install
|
|
10
|
+
* 5. Run @next/codemod upgrade (Next 16 + React 19)
|
|
11
|
+
* 6. Run migrate-auth-v5 codemod (next-auth v4 -> v5)
|
|
12
|
+
* 7. Run sentry-9 codemod (only when @sentry/nextjs is present)
|
|
13
|
+
* 8. Run migrate-eslint codemod (ESLint v9 flat config)
|
|
14
|
+
* 9. yarn build (validation)
|
|
15
|
+
* 10. Summary report
|
|
15
16
|
*
|
|
16
17
|
* Out of scope in this first version:
|
|
17
18
|
* - Tailwind v3 -> v4 migration (run manually with @tailwindcss/upgrade)
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
* --skip-next-codemod Do not run @next/codemod upgrade
|
|
31
32
|
* --skip-auth Do not run migrate-auth-v5
|
|
32
33
|
* --skip-sentry Do not run sentry-9 (auto-skipped if not needed)
|
|
34
|
+
* --skip-eslint Do not run migrate-eslint
|
|
33
35
|
* --skip-install Do not run yarn install
|
|
34
36
|
* --skip-build Do not run yarn build
|
|
35
37
|
* --interactive Pause for confirmation after each step
|
|
@@ -43,6 +45,7 @@ const readline = require('readline');
|
|
|
43
45
|
|
|
44
46
|
const AUTH_CODEMOD = path.resolve(__dirname, '..', 'migrate-auth-v5');
|
|
45
47
|
const SENTRY_CODEMOD = path.resolve(__dirname, '..', 'sentry-9');
|
|
48
|
+
const ESLINT_CODEMOD = path.resolve(__dirname, '..', 'migrate-eslint');
|
|
46
49
|
|
|
47
50
|
const TARGET_MATRIX = {
|
|
48
51
|
dependencies: {
|
|
@@ -50,22 +53,31 @@ const TARGET_MATRIX = {
|
|
|
50
53
|
react: '19.2.5',
|
|
51
54
|
'react-dom': '19.2.5',
|
|
52
55
|
'next-auth': '5.0.0-beta.25',
|
|
53
|
-
'@akinon/next': '
|
|
56
|
+
'@akinon/next': 'latest'
|
|
54
57
|
},
|
|
55
58
|
devDependencies: {
|
|
56
59
|
'@types/react': '19.2.14',
|
|
57
60
|
'@types/react-dom': '19.2.3',
|
|
58
61
|
'eslint-config-next': '16.2.4',
|
|
62
|
+
eslint: '9.39.4',
|
|
63
|
+
'eslint-config-prettier': '10.1.1',
|
|
59
64
|
tailwindcss: '3.4.17',
|
|
60
|
-
'@akinon/projectzero': '
|
|
61
|
-
'@akinon/eslint-plugin-projectzero': '
|
|
62
|
-
'@akinon/tsconfig': '
|
|
65
|
+
'@akinon/projectzero': 'latest',
|
|
66
|
+
'@akinon/eslint-plugin-projectzero': 'latest',
|
|
67
|
+
'@akinon/tsconfig': 'latest'
|
|
63
68
|
},
|
|
64
69
|
conditional: {
|
|
65
70
|
'@sentry/nextjs': '^9.0.0'
|
|
66
71
|
}
|
|
67
72
|
};
|
|
68
73
|
|
|
74
|
+
// Packages removed when migrating to ESLint v9 / flat config; their rules are
|
|
75
|
+
// now provided by `eslint-config-next/typescript`.
|
|
76
|
+
const DEP_DROP = [
|
|
77
|
+
'@typescript-eslint/eslint-plugin',
|
|
78
|
+
'@typescript-eslint/parser'
|
|
79
|
+
];
|
|
80
|
+
|
|
69
81
|
const STEP_COLUMN_WIDTH = 32;
|
|
70
82
|
|
|
71
83
|
function log(msg) {
|
|
@@ -114,6 +126,7 @@ function parseArgs(argv) {
|
|
|
114
126
|
skipNextCodemod: argv.includes('--skip-next-codemod'),
|
|
115
127
|
skipAuth: argv.includes('--skip-auth'),
|
|
116
128
|
skipSentry: argv.includes('--skip-sentry'),
|
|
129
|
+
skipEslint: argv.includes('--skip-eslint'),
|
|
117
130
|
skipInstall: argv.includes('--skip-install'),
|
|
118
131
|
skipBuild: argv.includes('--skip-build'),
|
|
119
132
|
interactive: argv.includes('--interactive'),
|
|
@@ -134,7 +147,7 @@ function writePackageJson(cwd, pkg) {
|
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
function stepPreflight(cwd, flags) {
|
|
137
|
-
logStep('[1/
|
|
150
|
+
logStep('[1/10] Preflight', 'starting');
|
|
138
151
|
|
|
139
152
|
const pkg = readPackageJson(cwd);
|
|
140
153
|
if (!pkg) {
|
|
@@ -166,21 +179,21 @@ function stepPreflight(cwd, flags) {
|
|
|
166
179
|
const currentAkinon = pkg.dependencies?.['@akinon/next'] || 'unknown';
|
|
167
180
|
|
|
168
181
|
log(` Current: next@${currentNext} react@${currentReact} next-auth@${currentAuth} @akinon/next@${currentAkinon}`);
|
|
169
|
-
log(` Target: next@${TARGET_MATRIX.dependencies.next} react@${TARGET_MATRIX.dependencies.react} next-auth@${TARGET_MATRIX.dependencies['next-auth']} @akinon/next@
|
|
182
|
+
log(` Target: next@${TARGET_MATRIX.dependencies.next} react@${TARGET_MATRIX.dependencies.react} next-auth@${TARGET_MATRIX.dependencies['next-auth']} @akinon/next@${TARGET_MATRIX.dependencies['@akinon/next']}`);
|
|
170
183
|
|
|
171
|
-
logStep('[1/
|
|
184
|
+
logStep('[1/10] Preflight', 'ok');
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
function stepBackup(cwd, flags) {
|
|
175
188
|
if (flags.skipBackup) {
|
|
176
|
-
logStep('[2/
|
|
189
|
+
logStep('[2/10] Backup branch', 'skipped');
|
|
177
190
|
return null;
|
|
178
191
|
}
|
|
179
192
|
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
180
193
|
const branchName = `pre-upgrade-2-${today}`;
|
|
181
194
|
|
|
182
195
|
if (flags.dryRun) {
|
|
183
|
-
logStep('[2/
|
|
196
|
+
logStep('[2/10] Backup branch', `[DRY] git branch ${branchName}`);
|
|
184
197
|
return branchName;
|
|
185
198
|
}
|
|
186
199
|
|
|
@@ -190,7 +203,7 @@ function stepBackup(cwd, flags) {
|
|
|
190
203
|
{ cwd, encoding: 'utf-8' }
|
|
191
204
|
);
|
|
192
205
|
if (existing.status === 0) {
|
|
193
|
-
logStep('[2/
|
|
206
|
+
logStep('[2/10] Backup branch', `already exists: ${branchName}`);
|
|
194
207
|
return branchName;
|
|
195
208
|
}
|
|
196
209
|
|
|
@@ -201,7 +214,7 @@ function stepBackup(cwd, flags) {
|
|
|
201
214
|
if (result.status !== 0) {
|
|
202
215
|
throw new Error(`Failed to create backup branch ${branchName}`);
|
|
203
216
|
}
|
|
204
|
-
logStep('[2/
|
|
217
|
+
logStep('[2/10] Backup branch', `created ${branchName}`);
|
|
205
218
|
return branchName;
|
|
206
219
|
}
|
|
207
220
|
|
|
@@ -217,6 +230,21 @@ function bumpDepsSection(section, matrix) {
|
|
|
217
230
|
return { section, changes };
|
|
218
231
|
}
|
|
219
232
|
|
|
233
|
+
function dropDeps(pkg, names) {
|
|
234
|
+
const changes = [];
|
|
235
|
+
for (const name of names) {
|
|
236
|
+
if (pkg.dependencies && pkg.dependencies[name]) {
|
|
237
|
+
delete pkg.dependencies[name];
|
|
238
|
+
changes.push(`removed dependencies.${name}`);
|
|
239
|
+
}
|
|
240
|
+
if (pkg.devDependencies && pkg.devDependencies[name]) {
|
|
241
|
+
delete pkg.devDependencies[name];
|
|
242
|
+
changes.push(`removed devDependencies.${name}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return changes;
|
|
246
|
+
}
|
|
247
|
+
|
|
220
248
|
function pinNextScriptsToWebpack(pkg) {
|
|
221
249
|
// Next 16 defaults build/dev to Turbopack, which surfaces bundle-time
|
|
222
250
|
// errors for Node APIs used on the edge runtime (e.g. CompressionStream
|
|
@@ -244,7 +272,7 @@ function pinNextScriptsToWebpack(pkg) {
|
|
|
244
272
|
|
|
245
273
|
function stepBump(cwd, flags) {
|
|
246
274
|
if (flags.skipBump) {
|
|
247
|
-
logStep('[3/
|
|
275
|
+
logStep('[3/10] Bump deps', 'skipped');
|
|
248
276
|
return { hasSentry: false, changes: [] };
|
|
249
277
|
}
|
|
250
278
|
|
|
@@ -271,20 +299,23 @@ function stepBump(cwd, flags) {
|
|
|
271
299
|
pkg.dependencies = sentryDeps.section;
|
|
272
300
|
}
|
|
273
301
|
|
|
302
|
+
const dropChanges = dropDeps(pkg, DEP_DROP);
|
|
303
|
+
allChanges.push(...dropChanges);
|
|
304
|
+
|
|
274
305
|
const scriptChanges = pinNextScriptsToWebpack(pkg);
|
|
275
306
|
allChanges.push(...scriptChanges);
|
|
276
307
|
|
|
277
308
|
if (allChanges.length === 0) {
|
|
278
|
-
logStep('[3/
|
|
309
|
+
logStep('[3/10] Bump deps', 'already at target, no changes');
|
|
279
310
|
return { hasSentry, changes: [] };
|
|
280
311
|
}
|
|
281
312
|
|
|
282
313
|
if (flags.dryRun) {
|
|
283
|
-
logStep('[3/
|
|
314
|
+
logStep('[3/10] Bump deps', `[DRY] ${allChanges.length} changes`);
|
|
284
315
|
allChanges.forEach((c) => log(` - ${c}`));
|
|
285
316
|
} else {
|
|
286
317
|
writePackageJson(cwd, pkg);
|
|
287
|
-
logStep('[3/
|
|
318
|
+
logStep('[3/10] Bump deps', `${allChanges.length} changes applied`);
|
|
288
319
|
if (flags.verbose) allChanges.forEach((c) => log(` - ${c}`));
|
|
289
320
|
}
|
|
290
321
|
|
|
@@ -293,28 +324,28 @@ function stepBump(cwd, flags) {
|
|
|
293
324
|
|
|
294
325
|
function stepNextCodemod(cwd, flags) {
|
|
295
326
|
if (flags.skipNextCodemod) {
|
|
296
|
-
logStep('[5/
|
|
327
|
+
logStep('[5/10] Next codemod', 'skipped');
|
|
297
328
|
return { ok: true };
|
|
298
329
|
}
|
|
299
330
|
const cmd = flags.dryRun
|
|
300
331
|
? 'npx --yes @next/codemod@latest upgrade latest --verbose'
|
|
301
332
|
: 'npx --yes @next/codemod@latest upgrade latest';
|
|
302
|
-
logStep('[5/
|
|
333
|
+
logStep('[5/10] Next codemod', `running ${cmd}`);
|
|
303
334
|
const result = runCommand(cmd, { cwd, dryRun: false, verbose: flags.verbose });
|
|
304
335
|
if (result.status !== 0) {
|
|
305
|
-
logStep('[5/
|
|
336
|
+
logStep('[5/10] Next codemod', `FAILED (exit ${result.status})`);
|
|
306
337
|
return { ok: false };
|
|
307
338
|
}
|
|
308
|
-
logStep('[5/
|
|
339
|
+
logStep('[5/10] Next codemod', 'ok');
|
|
309
340
|
return { ok: true };
|
|
310
341
|
}
|
|
311
342
|
|
|
312
343
|
function stepAuthCodemod(cwd, flags) {
|
|
313
344
|
if (flags.skipAuth) {
|
|
314
|
-
logStep('[6/
|
|
345
|
+
logStep('[6/10] Auth codemod', 'skipped');
|
|
315
346
|
return { ok: true };
|
|
316
347
|
}
|
|
317
|
-
logStep('[6/
|
|
348
|
+
logStep('[6/10] Auth codemod', 'running migrate-auth-v5');
|
|
318
349
|
try {
|
|
319
350
|
const prevArgv = process.argv;
|
|
320
351
|
process.argv = [
|
|
@@ -334,21 +365,21 @@ function stepAuthCodemod(cwd, flags) {
|
|
|
334
365
|
return result.then(
|
|
335
366
|
() => {
|
|
336
367
|
finish();
|
|
337
|
-
logStep('[6/
|
|
368
|
+
logStep('[6/10] Auth codemod', 'ok');
|
|
338
369
|
return { ok: true };
|
|
339
370
|
},
|
|
340
371
|
(err) => {
|
|
341
372
|
finish();
|
|
342
|
-
logStep('[6/
|
|
373
|
+
logStep('[6/10] Auth codemod', `FAILED: ${err.message}`);
|
|
343
374
|
return { ok: false };
|
|
344
375
|
}
|
|
345
376
|
);
|
|
346
377
|
}
|
|
347
378
|
finish();
|
|
348
|
-
logStep('[6/
|
|
379
|
+
logStep('[6/10] Auth codemod', 'ok');
|
|
349
380
|
return { ok: true };
|
|
350
381
|
} catch (err) {
|
|
351
|
-
logStep('[6/
|
|
382
|
+
logStep('[6/10] Auth codemod', `FAILED: ${err.message}`);
|
|
352
383
|
return { ok: false };
|
|
353
384
|
}
|
|
354
385
|
}
|
|
@@ -356,38 +387,65 @@ function stepAuthCodemod(cwd, flags) {
|
|
|
356
387
|
function stepSentryCodemod(cwd, flags, hasSentry) {
|
|
357
388
|
if (flags.skipSentry || !hasSentry) {
|
|
358
389
|
const reason = flags.skipSentry ? 'skipped' : 'no @sentry/nextjs, skipping';
|
|
359
|
-
logStep('[7/
|
|
390
|
+
logStep('[7/10] Sentry codemod', reason);
|
|
360
391
|
return { ok: true };
|
|
361
392
|
}
|
|
362
393
|
if (flags.dryRun) {
|
|
363
|
-
logStep('[7/
|
|
394
|
+
logStep('[7/10] Sentry codemod', '[DRY] would run sentry-9');
|
|
364
395
|
return { ok: true };
|
|
365
396
|
}
|
|
366
|
-
logStep('[7/
|
|
397
|
+
logStep('[7/10] Sentry codemod', 'running sentry-9');
|
|
367
398
|
try {
|
|
368
399
|
const savedCwd = process.cwd();
|
|
369
400
|
process.chdir(cwd);
|
|
370
401
|
const codemod = require(SENTRY_CODEMOD);
|
|
371
402
|
codemod.transform();
|
|
372
403
|
process.chdir(savedCwd);
|
|
373
|
-
logStep('[7/
|
|
404
|
+
logStep('[7/10] Sentry codemod', 'ok');
|
|
374
405
|
return { ok: true };
|
|
375
406
|
} catch (err) {
|
|
376
|
-
logStep('[7/
|
|
407
|
+
logStep('[7/10] Sentry codemod', `FAILED: ${err.message}`);
|
|
408
|
+
return { ok: false };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function stepEslintCodemod(cwd, flags) {
|
|
413
|
+
if (flags.skipEslint) {
|
|
414
|
+
logStep('[8/10] ESLint codemod', 'skipped');
|
|
415
|
+
return { ok: true };
|
|
416
|
+
}
|
|
417
|
+
logStep('[8/10] ESLint codemod', 'running migrate-eslint');
|
|
418
|
+
try {
|
|
419
|
+
const prevArgv = process.argv;
|
|
420
|
+
process.argv = [
|
|
421
|
+
process.argv[0],
|
|
422
|
+
process.argv[1],
|
|
423
|
+
...(flags.dryRun ? ['--dry-run'] : [])
|
|
424
|
+
];
|
|
425
|
+
const savedCwd = process.cwd();
|
|
426
|
+
process.chdir(cwd);
|
|
427
|
+
const codemod = require(ESLINT_CODEMOD);
|
|
428
|
+
codemod.transform();
|
|
429
|
+
process.chdir(savedCwd);
|
|
430
|
+
process.argv = prevArgv;
|
|
431
|
+
logStep('[8/10] ESLint codemod', 'ok');
|
|
432
|
+
return { ok: true };
|
|
433
|
+
} catch (err) {
|
|
434
|
+
logStep('[8/10] ESLint codemod', `FAILED: ${err.message}`);
|
|
377
435
|
return { ok: false };
|
|
378
436
|
}
|
|
379
437
|
}
|
|
380
438
|
|
|
381
439
|
function stepInstall(cwd, flags) {
|
|
382
440
|
if (flags.skipInstall) {
|
|
383
|
-
logStep('[4/
|
|
441
|
+
logStep('[4/10] yarn install', 'skipped');
|
|
384
442
|
return { ok: true };
|
|
385
443
|
}
|
|
386
444
|
if (flags.dryRun) {
|
|
387
|
-
logStep('[4/
|
|
445
|
+
logStep('[4/10] yarn install', '[DRY] yarn clean && yarn');
|
|
388
446
|
return { ok: true };
|
|
389
447
|
}
|
|
390
|
-
logStep('[4/
|
|
448
|
+
logStep('[4/10] yarn install', 'running yarn clean && yarn');
|
|
391
449
|
const clean = runCommand('yarn clean', {
|
|
392
450
|
cwd,
|
|
393
451
|
dryRun: false,
|
|
@@ -402,33 +460,33 @@ function stepInstall(cwd, flags) {
|
|
|
402
460
|
verbose: flags.verbose
|
|
403
461
|
});
|
|
404
462
|
if (install.status !== 0) {
|
|
405
|
-
logStep('[4/
|
|
463
|
+
logStep('[4/10] yarn install', `FAILED (exit ${install.status})`);
|
|
406
464
|
return { ok: false };
|
|
407
465
|
}
|
|
408
|
-
logStep('[4/
|
|
466
|
+
logStep('[4/10] yarn install', 'ok');
|
|
409
467
|
return { ok: true };
|
|
410
468
|
}
|
|
411
469
|
|
|
412
470
|
function stepBuild(cwd, flags) {
|
|
413
471
|
if (flags.skipBuild) {
|
|
414
|
-
logStep('[
|
|
472
|
+
logStep('[9/10] yarn build', 'skipped');
|
|
415
473
|
return { ok: true };
|
|
416
474
|
}
|
|
417
475
|
if (flags.dryRun) {
|
|
418
|
-
logStep('[
|
|
476
|
+
logStep('[9/10] yarn build', '[DRY] yarn build');
|
|
419
477
|
return { ok: true };
|
|
420
478
|
}
|
|
421
|
-
logStep('[
|
|
479
|
+
logStep('[9/10] yarn build', 'running');
|
|
422
480
|
const result = runCommand('yarn build', {
|
|
423
481
|
cwd,
|
|
424
482
|
dryRun: false,
|
|
425
483
|
verbose: flags.verbose
|
|
426
484
|
});
|
|
427
485
|
if (result.status !== 0) {
|
|
428
|
-
logStep('[
|
|
486
|
+
logStep('[9/10] yarn build', `FAILED (exit ${result.status})`);
|
|
429
487
|
return { ok: false };
|
|
430
488
|
}
|
|
431
|
-
logStep('[
|
|
489
|
+
logStep('[9/10] yarn build', 'ok');
|
|
432
490
|
return { ok: true };
|
|
433
491
|
}
|
|
434
492
|
|
|
@@ -440,7 +498,7 @@ function countTodoMarkers(cwd) {
|
|
|
440
498
|
}
|
|
441
499
|
|
|
442
500
|
function stepReport(cwd, results) {
|
|
443
|
-
logStep('[
|
|
501
|
+
logStep('[10/10] Report', 'summary');
|
|
444
502
|
const pkg = readPackageJson(cwd) || {};
|
|
445
503
|
const todoCount = countTodoMarkers(cwd);
|
|
446
504
|
|
|
@@ -452,6 +510,7 @@ function stepReport(cwd, results) {
|
|
|
452
510
|
console.log(` Next codemod: ${results.nextCodemod ? 'ok' : 'FAILED'}`);
|
|
453
511
|
console.log(` Auth codemod: ${results.authCodemod ? 'ok' : 'FAILED'}`);
|
|
454
512
|
console.log(` Sentry codemod: ${results.sentryCodemod ? 'ok' : results.sentrySkippedReason || 'FAILED'}`);
|
|
513
|
+
console.log(` ESLint codemod: ${results.eslintCodemod ? 'ok' : 'FAILED'}`);
|
|
455
514
|
console.log(` Build: ${results.build ? 'ok' : 'FAILED'}`);
|
|
456
515
|
console.log('');
|
|
457
516
|
console.log(` Versions after:`);
|
|
@@ -498,7 +557,7 @@ async function transform() {
|
|
|
498
557
|
results.preflight = true;
|
|
499
558
|
await maybePause(flags, 'preflight');
|
|
500
559
|
} else {
|
|
501
|
-
logStep('[1/
|
|
560
|
+
logStep('[1/10] Preflight', 'skipped');
|
|
502
561
|
results.preflight = true;
|
|
503
562
|
}
|
|
504
563
|
|
|
@@ -529,6 +588,9 @@ async function transform() {
|
|
|
529
588
|
else if (!hasSentry) results.sentrySkippedReason = 'no @sentry/nextjs';
|
|
530
589
|
await maybePause(flags, 'sentry-codemod');
|
|
531
590
|
|
|
591
|
+
results.eslintCodemod = stepEslintCodemod(cwd, flags).ok;
|
|
592
|
+
await maybePause(flags, 'eslint-codemod');
|
|
593
|
+
|
|
532
594
|
results.build = stepBuild(cwd, flags).ok;
|
|
533
595
|
} catch (err) {
|
|
534
596
|
log(`FATAL: ${err.message}`);
|
package/commands/plugins.ts
CHANGED
package/dist/commands/plugins.js
CHANGED
|
@@ -183,10 +183,6 @@ exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
183
183
|
name: 'Tamara Payment Extension',
|
|
184
184
|
value: 'pz-tamara-extension'
|
|
185
185
|
},
|
|
186
|
-
{
|
|
187
|
-
name: 'Similar Products',
|
|
188
|
-
value: 'pz-similar-products'
|
|
189
|
-
},
|
|
190
186
|
{
|
|
191
187
|
name: 'Hepsipay',
|
|
192
188
|
value: 'pz-hepsipay'
|
package/package.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useSentryUncaughtErrors } from '@akinon/next/hooks';
|
|
4
|
-
|
|
5
|
-
export default function GlobalError({
|
|
6
|
-
error,
|
|
7
|
-
reset
|
|
8
|
-
}: {
|
|
9
|
-
error: Error & { digest?: string };
|
|
10
|
-
reset: () => void;
|
|
11
|
-
}) {
|
|
12
|
-
useSentryUncaughtErrors(error);
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<html>
|
|
16
|
-
<body>
|
|
17
|
-
<h2>Something went wrong!</h2>
|
|
18
|
-
<button onClick={() => reset()}>Try again</button>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
21
|
-
);
|
|
22
|
-
}
|