@akinon/next 1.33.1 → 1.34.0-rc.1

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 CHANGED
@@ -1,11 +1,28 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.33.1
3
+ ## 1.34.0-rc.1
4
4
 
5
5
  ### Patch Changes
6
6
 
7
7
  - 10123f9: ZERO-2632: rename projectzero-cli to projectzero
8
8
 
9
+ ## 1.34.0-rc.0
10
+
11
+ ### Minor Changes
12
+
13
+ - d09b677: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
14
+ - 6d4aadb: ZERO-2476: Auto install recommendenent extension
15
+ - ebb63ce: ZERO-2525: Fix category facet removal bug and add close icon to active filters
16
+ - 7cebe87: ZERO-2524: Add check monorepo utility function
17
+ - f0c23bc: ZERO-2135: add custom not found page
18
+ - 3420416: ZERO-2533: extend eslint config from @akinon/next
19
+ - 495d155: ZERO-2505: findBaseDir function is united and move on to the utils
20
+ - 40ad73e: ZERO-2504: add cookie filter to api client request
21
+ - 495d155: ZERO-2524: Zero-cli dist checks for changes and renders it to the terminal.
22
+ - f046f8e: ZERO-2575: update version for react-number-format
23
+ - 6b2972b: ZERO-2514: Fix handling of status 204 and return Commerce status code in client proxy API.
24
+ - 3e68768: ZERO-2578: Add osessionid check in middleware
25
+
9
26
  ## 1.33.0
10
27
 
11
28
  ## 1.32.0
package/api/client.ts CHANGED
@@ -2,6 +2,8 @@ import { ClientRequestOptions } from '../types';
2
2
  import { NextResponse } from 'next/server';
3
3
  import settings from 'settings';
4
4
  import logger from '../utils/log';
5
+ import formatCookieString from '../utils/format-cookie-string';
6
+ import cookieParser from 'set-cookie-parser';
5
7
 
6
8
  interface RouteParams {
7
9
  params: {
@@ -113,6 +115,14 @@ async function proxyRequest(...args) {
113
115
 
114
116
  try {
115
117
  const request = await fetch(url, fetchOptions);
118
+
119
+ // Using NextResponse.json with status 204 will cause an error
120
+ if (request.status === 204) {
121
+ return new Response(null, {
122
+ status: 204
123
+ });
124
+ }
125
+
116
126
  let response = {} as any;
117
127
 
118
128
  try {
@@ -131,20 +141,26 @@ async function proxyRequest(...args) {
131
141
  );
132
142
  }
133
143
 
134
- const setCookieHeader = request.headers.get('set-cookie');
144
+ const setCookieHeaders = req.headers.getSetCookie();
145
+ const exceptCookieKeys = ['pz-locale', 'pz-currency'];
146
+
147
+ const isExcludedCookie = (name: string) => exceptCookieKeys.includes(name);
148
+
149
+ const filteredCookies = cookieParser
150
+ .parse(setCookieHeaders)
151
+ .filter((cookie) => !isExcludedCookie(cookie.name));
152
+
135
153
  const responseHeaders: any = {};
136
154
 
137
- if (setCookieHeader) {
138
- responseHeaders['set-cookie'] = setCookieHeader;
155
+ if (filteredCookies.length > 0) {
156
+ responseHeaders['set-cookie'] = filteredCookies
157
+ .map(formatCookieString)
158
+ .join(', ');
139
159
  }
140
160
 
141
- const statusCode = new RegExp(/^20./).test(request.status.toString())
142
- ? 200
143
- : request.status;
144
-
145
161
  return NextResponse.json(
146
162
  options.responseType === 'text' ? { result: response } : response,
147
- { status: statusCode, headers: responseHeaders }
163
+ { status: request.status, headers: responseHeaders }
148
164
  );
149
165
  } catch (error) {
150
166
  logger.error('Client proxy request failed', error);
@@ -0,0 +1,27 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function findBaseDir() {
6
+ let currentDir = __dirname;
7
+ while (currentDir !== path.resolve(currentDir, '..')) {
8
+ if (fs.existsSync(path.join(currentDir, 'turbo.json'))) {
9
+ return currentDir;
10
+ }
11
+ currentDir = path.resolve(currentDir, '..');
12
+ }
13
+ return null;
14
+ }
15
+
16
+ const BASE_DIR = findBaseDir();
17
+
18
+ if (BASE_DIR) {
19
+ const extensions = ['bilal-akinon.pznext'];
20
+ extensions.forEach((extension) => {
21
+ try {
22
+ execSync(`code --install-extension ${extension}`, { stdio: 'inherit' });
23
+ } catch (error) {
24
+ console.error(`Error installing ${extension}:`);
25
+ }
26
+ });
27
+ }
@@ -1,13 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
-
5
- function findBaseDir() {
6
- const insideNodeModules = __dirname.includes('node_modules');
7
- return insideNodeModules
8
- ? process.cwd()
9
- : path.resolve(__dirname, '../../../apps/projectzeronext');
10
- }
4
+ const findBaseDir = require('../utils/find-base-dir');
11
5
 
12
6
  const BASE_DIR = findBaseDir();
13
7
  const getFullPath = (relativePath) => path.join(BASE_DIR, relativePath);
@@ -3,16 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const spawn = require('cross-spawn');
6
-
7
- function findBaseDir() {
8
- const insideNodeModules = __dirname.includes('node_modules');
9
-
10
- if (insideNodeModules) {
11
- return path.resolve(__dirname, '../../../../');
12
- } else {
13
- return path.resolve(__dirname, '../../../apps/projectzeronext');
14
- }
15
- }
6
+ const findBaseDir = require('../utils/find-base-dir');
16
7
 
17
8
  const BASE_DIR = findBaseDir();
18
9
 
@@ -0,0 +1,21 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const hashDirectory = require('../utils/hash-directory');
4
+ const checkMonorepo = require('../utils/check-monorepo');
5
+
6
+ const BASE_DIR = checkMonorepo();
7
+
8
+ if (!BASE_DIR) {
9
+ process.exit(0);
10
+ }
11
+
12
+ const packageDistPath = path.join(
13
+ __dirname,
14
+ '../../../packages/projectzero-cli/dist/commands'
15
+ );
16
+
17
+ const hash = hashDirectory(packageDistPath);
18
+ fs.writeFileSync(
19
+ path.join(__dirname, '../../../packages/projectzero-cli/dist/dist-hash.txt'),
20
+ hash
21
+ );
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const runScript = require('./run-script');
4
+
4
5
  runScript('pz-install-theme.js');
6
+ runScript('pz-pre-check-dist.js');
package/bin/pz-predev.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const runScript = require('./run-script');
4
+
5
+ runScript('pz-install-extensions.js');
4
6
  runScript('pz-check-env.js');
5
7
  runScript('pz-install-theme.js');
@@ -1,17 +1,28 @@
1
1
  import clsx from 'clsx';
2
- import { forwardRef, FocusEvent, useState } from 'react';
2
+ import { forwardRef, FocusEvent, useState, Ref } from 'react';
3
3
  import { Controller } from 'react-hook-form';
4
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
4
+ import { PatternFormat, PatternFormatProps } from 'react-number-format';
5
5
  import { InputProps } from '../types';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
 
8
+ const PatternFormatWithRef = forwardRef(
9
+ (props: PatternFormatProps, ref: Ref<HTMLInputElement>) => {
10
+ return <PatternFormat {...props} getInputRef={ref} />;
11
+ }
12
+ );
13
+ PatternFormatWithRef.displayName = 'PatternFormatWithRef';
14
+
8
15
  export const Input = forwardRef<
9
16
  HTMLInputElement,
10
17
  InputProps &
11
18
  Pick<
12
- NumberFormatProps,
13
- 'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
14
- >
19
+ PatternFormatProps,
20
+ 'mask' | 'allowEmptyFormatting' | 'onValueChange'
21
+ > & {
22
+ format?: string;
23
+ defaultValue?: string;
24
+ type?: string;
25
+ }
15
26
  >((props, ref) => {
16
27
  const [focused, setFocused] = useState(false);
17
28
  const [hasValue, setHasValue] = useState(false);
@@ -37,6 +48,7 @@ export const Input = forwardRef<
37
48
  ),
38
49
  props.className
39
50
  );
51
+
40
52
  const inputProps: any = {
41
53
  id,
42
54
  ref,
@@ -79,14 +91,14 @@ export const Input = forwardRef<
79
91
  <Controller
80
92
  name={props.name ?? ''}
81
93
  control={props.control}
82
- defaultValue={false}
83
94
  render={({ field }) => (
84
- <NumberFormat
95
+ <PatternFormatWithRef
85
96
  format={format}
86
97
  mask={mask ?? ''}
87
98
  {...rest}
88
99
  {...field}
89
100
  {...inputProps}
101
+ type={props.type as 'text' | 'password' | 'tel'}
90
102
  />
91
103
  )}
92
104
  />
@@ -1,11 +1,11 @@
1
1
  import { useMemo } from 'react';
2
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
2
+ import { NumericFormat, NumericFormatProps } from 'react-number-format';
3
3
  import { getCurrency } from '@akinon/next/utils';
4
4
 
5
5
  import { useLocalization } from '@akinon/next/hooks';
6
6
  import { PriceProps } from '../types';
7
7
 
8
- export const Price = (props: NumberFormatProps & PriceProps) => {
8
+ export const Price = (props: NumericFormatProps & PriceProps) => {
9
9
  const {
10
10
  value,
11
11
  currencyCode,
@@ -39,7 +39,7 @@ export const Price = (props: NumberFormatProps & PriceProps) => {
39
39
  );
40
40
 
41
41
  return (
42
- <NumberFormat
42
+ <NumericFormat
43
43
  value={useNegative ? `-${useNegativeSpace}${_value}` : _value}
44
44
  {...{
45
45
  [useCurrencyAfterPrice ? 'suffix' : 'prefix']: currency
@@ -5,7 +5,6 @@ module.exports = {
5
5
  es2021: true
6
6
  },
7
7
  extends: [
8
- 'eslint:recommended',
9
8
  'next/core-web-vitals',
10
9
  'eslint:recommended',
11
10
  'plugin:@typescript-eslint/recommended',
@@ -18,7 +17,7 @@ module.exports = {
18
17
  env: {
19
18
  node: true
20
19
  },
21
- files: ['.eslintrc.{js,cjs}', 'middlewares/default.ts'],
20
+ files: ['eslint.config.{js,cjs}', 'middlewares/default.ts'],
22
21
  rules: {
23
22
  '@akinon/projectzero/check-middleware-order': 'error'
24
23
  },
@@ -28,7 +28,11 @@ function reducer(state: InitialState, action: ACTIONTYPE) {
28
28
  case 'setPage':
29
29
  return { ...state, page: action.payload };
30
30
  case 'setLimit':
31
- return { ...state, limit: action.payload };
31
+ return {
32
+ ...state,
33
+ limit: action.payload,
34
+ last: Math.ceil(state.total / action.payload)
35
+ };
32
36
  default:
33
37
  throw new Error();
34
38
  }
@@ -46,10 +50,11 @@ export default function usePagination(
46
50
  () => new URLSearchParams(searchParams.toString()),
47
51
  [searchParams]
48
52
  );
53
+
49
54
  const { page, limit } = useMemo(
50
55
  () => ({
51
56
  page: _page || Number(searchParams.get('page')) || 1,
52
- limit: _limit || Number(searchParams.get('limit'))
57
+ limit: _limit || Number(searchParams.get('limit')) || 12
53
58
  }),
54
59
  [searchParams, _page, _limit]
55
60
  );
@@ -60,6 +65,7 @@ export default function usePagination(
60
65
  last: _last || Math.ceil(_total / limit) || 1,
61
66
  total: _total
62
67
  };
68
+
63
69
  const [state, dispatch] = useReducer(reducer, initialState);
64
70
 
65
71
  useEffect(() => {
@@ -93,23 +99,24 @@ export default function usePagination(
93
99
  [dispatch]
94
100
  );
95
101
 
96
- const pageList = useMemo(() => {
97
- return Array.from({ length: state.last }, (_, i) => {
98
- urlSearchParams.set('page', (i + 1).toString());
99
-
100
- return {
101
- page: i + 1,
102
- url: `${pathname}?${urlSearchParams.toString()}`
103
- };
104
- });
105
- }, [state.last, pathname, urlSearchParams]);
102
+ const pageList = useMemo(
103
+ () =>
104
+ Array.from({ length: state.last }, (_, i) => {
105
+ urlSearchParams.set('page', (i + 1).toString());
106
+ return {
107
+ page: i + 1,
108
+ url: `${pathname}?${urlSearchParams.toString()}`
109
+ };
110
+ }),
111
+ [state.last, pathname, urlSearchParams]
112
+ );
106
113
 
107
114
  const prev = useMemo(() => {
108
115
  if (state.page > 1) {
109
116
  urlSearchParams.set('page', (Number(state.page) - 1).toString());
110
117
  return `${pathname}?${urlSearchParams.toString()}`;
111
118
  }
112
- return null;
119
+ return '#';
113
120
  }, [state.page, pathname, urlSearchParams]);
114
121
 
115
122
  const next = useMemo(() => {
@@ -117,7 +124,7 @@ export default function usePagination(
117
124
  urlSearchParams.set('page', (Number(state.page) + 1).toString());
118
125
  return `${pathname}?${urlSearchParams.toString()}`;
119
126
  }
120
- return null;
127
+ return '#';
121
128
  }, [state.page, state.last, pathname, urlSearchParams]);
122
129
 
123
130
  return {
@@ -33,6 +33,7 @@ const withCompleteGpay =
33
33
  async (req: PzNextRequest, event: NextFetchEvent) => {
34
34
  const url = req.nextUrl.clone();
35
35
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
36
37
 
37
38
  if (url.search.indexOf('GPayCompletePage') === -1) {
38
39
  return middleware(req, event);
@@ -50,6 +51,24 @@ const withCompleteGpay =
50
51
  try {
51
52
  const body = await streamToString(req.body);
52
53
 
54
+ if (!sessionId) {
55
+ logger.warn(
56
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
+ {
58
+ middleware: 'complete-masterpass',
59
+ ip
60
+ }
61
+ );
62
+
63
+ return NextResponse.redirect(
64
+ `${url.origin}${getUrlPathWithLocale(
65
+ '/orders/checkout/',
66
+ req.cookies.get('pz-locale')?.value
67
+ )}`,
68
+ 303
69
+ );
70
+ }
71
+
53
72
  const request = await fetch(requestUrl, {
54
73
  method: 'POST',
55
74
  headers: requestHeaders,
@@ -33,6 +33,7 @@ const withCompleteMasterpass =
33
33
  async (req: PzNextRequest, event: NextFetchEvent) => {
34
34
  const url = req.nextUrl.clone();
35
35
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
36
37
 
37
38
  if (url.search.indexOf('MasterpassCompletePage') === -1) {
38
39
  return middleware(req, event);
@@ -50,6 +51,24 @@ const withCompleteMasterpass =
50
51
  try {
51
52
  const body = await streamToString(req.body);
52
53
 
54
+ if (!sessionId) {
55
+ logger.warn(
56
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
+ {
58
+ middleware: 'complete-masterpass',
59
+ ip
60
+ }
61
+ );
62
+
63
+ return NextResponse.redirect(
64
+ `${url.origin}${getUrlPathWithLocale(
65
+ '/orders/checkout/',
66
+ req.cookies.get('pz-locale')?.value
67
+ )}`,
68
+ 303
69
+ );
70
+ }
71
+
53
72
  const request = await fetch(requestUrl, {
54
73
  method: 'POST',
55
74
  headers: requestHeaders,
@@ -153,6 +153,7 @@ const withPzDefault =
153
153
 
154
154
  req.middlewareParams = {
155
155
  commerceUrl,
156
+ found: true,
156
157
  rewrites: {}
157
158
  };
158
159
 
@@ -186,6 +187,19 @@ const withPzDefault =
186
187
  locale.length ? `${locale}/` : ''
187
188
  }${currency}${prettyUrl ?? pathnameWithoutLocale}`;
188
189
 
190
+ if (
191
+ !req.middlewareParams.found &&
192
+ Settings.customNotFoundEnabled
193
+ ) {
194
+ let pathname = url.pathname
195
+ .replace(/\/+$/, '')
196
+ .split('/');
197
+ url.pathname = url.pathname.replace(
198
+ pathname.pop(),
199
+ 'pz-not-found'
200
+ );
201
+ }
202
+
189
203
  Settings.rewrites.forEach((rewrite) => {
190
204
  url.pathname = url.pathname.replace(
191
205
  rewrite.source,
@@ -26,6 +26,7 @@ export {
26
26
  export interface PzNextRequest extends NextRequest {
27
27
  middlewareParams: {
28
28
  commerceUrl: string;
29
+ found: boolean;
29
30
  rewrites: {
30
31
  locale?: string;
31
32
  prettyUrl?: string;
@@ -98,6 +98,8 @@ const withPrettyUrl =
98
98
  return middleware(req, event);
99
99
  }
100
100
 
101
+ req.middlewareParams.found = false;
102
+
101
103
  return middleware(req, event);
102
104
  };
103
105
 
@@ -34,6 +34,7 @@ const withRedirectionPayment =
34
34
  const url = req.nextUrl.clone();
35
35
  const searchParams = new URLSearchParams(url.search);
36
36
  const ip = req.headers.get('x-forwarded-for') ?? '';
37
+ const sessionId = req.cookies.get('osessionid');
37
38
 
38
39
  if (searchParams.get('page') !== 'RedirectionPageCompletePage') {
39
40
  return middleware(req, event);
@@ -46,12 +47,29 @@ const withRedirectionPayment =
46
47
  Cookie: req.headers.get('cookie') ?? '',
47
48
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
49
  'x-forwarded-for': ip
49
-
50
50
  };
51
51
 
52
52
  try {
53
53
  const body = await streamToString(req.body);
54
54
 
55
+ if (!sessionId) {
56
+ logger.warn(
57
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
58
+ {
59
+ middleware: 'redirection-payment',
60
+ ip
61
+ }
62
+ );
63
+
64
+ return NextResponse.redirect(
65
+ `${url.origin}${getUrlPathWithLocale(
66
+ '/orders/checkout/',
67
+ req.cookies.get('pz-locale')?.value
68
+ )}`,
69
+ 303
70
+ );
71
+ }
72
+
55
73
  const request = await fetch(requestUrl, {
56
74
  method: 'POST',
57
75
  headers: requestHeaders,
@@ -33,6 +33,7 @@ const withThreeDRedirection =
33
33
  async (req: PzNextRequest, event: NextFetchEvent) => {
34
34
  const url = req.nextUrl.clone();
35
35
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
36
37
 
37
38
  if (url.search.indexOf('CreditCardThreeDSecurePage') === -1) {
38
39
  return middleware(req, event);
@@ -50,6 +51,24 @@ const withThreeDRedirection =
50
51
  try {
51
52
  const body = await streamToString(req.body);
52
53
 
54
+ if (!sessionId) {
55
+ logger.warn(
56
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
+ {
58
+ middleware: 'three-d-redirection',
59
+ ip
60
+ }
61
+ );
62
+
63
+ return NextResponse.redirect(
64
+ `${url.origin}${getUrlPathWithLocale(
65
+ '/orders/checkout/',
66
+ req.cookies.get('pz-locale')?.value
67
+ )}`,
68
+ 303
69
+ );
70
+ }
71
+
53
72
  const request = await fetch(requestUrl, {
54
73
  method: 'POST',
55
74
  headers: requestHeaders,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/next",
3
3
  "description": "Core package for Project Zero Next",
4
- "version": "1.33.1",
4
+ "version": "1.34.0-rc.1",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -25,14 +25,16 @@
25
25
  "react-redux": "8.1.3",
26
26
  "react-string-replace": "1.1.1",
27
27
  "redis": "4.5.1",
28
- "semver": "7.5.4"
28
+ "semver": "7.5.4",
29
+ "set-cookie-parser": "2.6.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/react-redux": "7.1.30",
33
+ "@types/set-cookie-parser": "2.4.7",
32
34
  "@typescript-eslint/eslint-plugin": "6.7.4",
33
35
  "@typescript-eslint/parser": "6.7.4",
34
36
  "eslint": "^8.14.0",
35
- "@akinon/eslint-plugin-projectzero": "1.33.1",
37
+ "@akinon/eslint-plugin-projectzero": "1.34.0-rc.1",
36
38
  "eslint-config-prettier": "8.5.0"
37
39
  }
38
40
  }
@@ -36,6 +36,7 @@ export type FacetChoice = {
36
36
  quantity: number;
37
37
  is_selected: boolean;
38
38
  url?: string;
39
+ real_depth?: string;
39
40
  [key: string]: any;
40
41
  };
41
42
 
package/types/index.ts CHANGED
@@ -181,6 +181,7 @@ export interface Settings {
181
181
  extraPaymentTypes?: string[];
182
182
  masterpassJsUrl?: string;
183
183
  };
184
+ customNotFoundEnabled: boolean;
184
185
  }
185
186
 
186
187
  export interface CacheOptions {
@@ -0,0 +1,15 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function checkMonorepo() {
5
+ let currentDir = __dirname;
6
+ while (currentDir !== path.resolve(currentDir, '..')) {
7
+ if (fs.existsSync(path.join(currentDir, 'turbo.json'))) {
8
+ return currentDir;
9
+ }
10
+ currentDir = path.resolve(currentDir, '..');
11
+ }
12
+ return null;
13
+ }
14
+
15
+ module.exports = checkMonorepo;
@@ -0,0 +1,13 @@
1
+ const path = require('path');
2
+
3
+ function findBaseDir() {
4
+ const insideNodeModules = __dirname.includes('node_modules');
5
+
6
+ if (insideNodeModules) {
7
+ return path.resolve(__dirname, '../../../../');
8
+ } else {
9
+ return path.resolve(__dirname, '../../../apps/projectzeronext');
10
+ }
11
+ }
12
+
13
+ module.exports = findBaseDir;
@@ -0,0 +1,27 @@
1
+ export default function formatCookieString(cookieObj: Record<string, any>) {
2
+ const cookieParts = [`${cookieObj.name}=${cookieObj.value}`];
3
+
4
+ if (cookieObj.expires) {
5
+ cookieParts.push(`Expires=${cookieObj.expires.toUTCString()}`);
6
+ }
7
+ if (cookieObj.maxAge) {
8
+ cookieParts.push(`Max-Age=${cookieObj.maxAge}`);
9
+ }
10
+ if (cookieObj.path) {
11
+ cookieParts.push(`Path=${cookieObj.path}`);
12
+ }
13
+ if (cookieObj.domain) {
14
+ cookieParts.push(`Domain=${cookieObj.domain}`);
15
+ }
16
+ if (cookieObj.secure) {
17
+ cookieParts.push('Secure');
18
+ }
19
+ if (cookieObj.httpOnly) {
20
+ cookieParts.push('HttpOnly');
21
+ }
22
+ if (cookieObj.sameSite) {
23
+ cookieParts.push(`SameSite=${cookieObj.sameSite}`);
24
+ }
25
+
26
+ return cookieParts.join('; ');
27
+ }
@@ -0,0 +1,18 @@
1
+ const { createHash } = require('crypto');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function hashDirectory(directory) {
6
+ const files = fs.readdirSync(directory).sort();
7
+ const hash = createHash('sha256');
8
+ files.forEach((file) => {
9
+ const filePath = path.join(directory, file);
10
+ if (fs.statSync(filePath).isFile()) {
11
+ const fileBuffer = fs.readFileSync(filePath);
12
+ hash.update(fileBuffer);
13
+ }
14
+ });
15
+ return hash.digest('hex');
16
+ }
17
+
18
+ module.exports = hashDirectory;