@shopify/cli-hydrogen 8.4.0 → 8.4.2

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.
@@ -1,5 +1,96 @@
1
1
  # skeleton
2
2
 
3
+ ## 2024.7.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Update Shopify CLI and cli-kit dependencies to 3.66.1 ([#2512](https://github.com/Shopify/hydrogen/pull/2512)) by [@frandiox](https://github.com/frandiox)
8
+
9
+ - createCartHandler supplies updateGiftCardCodes method ([#2298](https://github.com/Shopify/hydrogen/pull/2298)) by [@wizardlyhel](https://github.com/wizardlyhel)
10
+
11
+ - Fix menu links in side panel not working on mobile devices ([#2450](https://github.com/Shopify/hydrogen/pull/2450)) by [@wizardlyhel](https://github.com/wizardlyhel)
12
+
13
+ ```diff
14
+ // /app/components/Header.tsx
15
+
16
+ export function HeaderMenu({
17
+ menu,
18
+ primaryDomainUrl,
19
+ viewport,
20
+ publicStoreDomain,
21
+ }: {
22
+ menu: HeaderProps['header']['menu'];
23
+ primaryDomainUrl: HeaderProps['header']['shop']['primaryDomain']['url'];
24
+ viewport: Viewport;
25
+ publicStoreDomain: HeaderProps['publicStoreDomain'];
26
+ }) {
27
+ const className = `header-menu-${viewport}`;
28
+ + const {close} = useAside();
29
+
30
+ - function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
31
+ - if (viewport === 'mobile') {
32
+ - event.preventDefault();
33
+ - window.location.href = event.currentTarget.href;
34
+ - }
35
+ - }
36
+
37
+ return (
38
+ <nav className={className} role="navigation">
39
+ {viewport === 'mobile' && (
40
+ <NavLink
41
+ end
42
+ - onClick={closeAside}
43
+ + onClick={close}
44
+ prefetch="intent"
45
+ style={activeLinkStyle}
46
+ to="/"
47
+ >
48
+ Home
49
+ </NavLink>
50
+ )}
51
+ {(menu || FALLBACK_HEADER_MENU).items.map((item) => {
52
+ if (!item.url) return null;
53
+
54
+ // if the url is internal, we strip the domain
55
+ const url =
56
+ item.url.includes('myshopify.com') ||
57
+ item.url.includes(publicStoreDomain) ||
58
+ item.url.includes(primaryDomainUrl)
59
+ ? new URL(item.url).pathname
60
+ : item.url;
61
+ return (
62
+ <NavLink
63
+ className="header-menu-item"
64
+ end
65
+ key={item.id}
66
+ - onClick={closeAside}
67
+ + onClick={close}
68
+ prefetch="intent"
69
+ style={activeLinkStyle}
70
+ to={url}
71
+ >
72
+ {item.title}
73
+ </NavLink>
74
+ );
75
+ })}
76
+ </nav>
77
+ );
78
+ }
79
+ ```
80
+
81
+ - Add localization support to consent privacy banner ([#2457](https://github.com/Shopify/hydrogen/pull/2457)) by [@juanpprieto](https://github.com/juanpprieto)
82
+
83
+ - Updated dependencies [[`d633e49a`](https://github.com/Shopify/hydrogen/commit/d633e49aff244a985c58ec77fc2796c9c1cd5df4), [`1b217cd6`](https://github.com/Shopify/hydrogen/commit/1b217cd68ffd5362d201d4bd225ec72e99713461), [`d929b561`](https://github.com/Shopify/hydrogen/commit/d929b5612ec28e53ec216844add33682f131aba7), [`664a09d5`](https://github.com/Shopify/hydrogen/commit/664a09d57ef5d3c67da947a4e8546527c01e37c4), [`0c1e511d`](https://github.com/Shopify/hydrogen/commit/0c1e511df72e9605534bb9c960e86d5c9a4bf2ea), [`eefa8203`](https://github.com/Shopify/hydrogen/commit/eefa820383fa93657ca214991f6099ce9268a4ee)]:
84
+ - @shopify/hydrogen@2024.7.5
85
+ - @shopify/remix-oxygen@2.0.7
86
+
87
+ ## 2024.7.5
88
+
89
+ ### Patch Changes
90
+
91
+ - Updated dependencies [[`b0d3bc06`](https://github.com/Shopify/hydrogen/commit/b0d3bc0696d266fcfc4eb93d0a4adb9ccb56ade6)]:
92
+ - @shopify/hydrogen@2024.7.4
93
+
3
94
  ## 2024.7.4
4
95
 
5
96
  ### Patch Changes
@@ -1,6 +1,8 @@
1
1
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
2
  import type {CartLayout} from '~/components/CartMain';
3
3
  import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
+ import { useRef } from 'react';
5
+ import { FetcherWithComponents } from '@remix-run/react';
4
6
 
5
7
  type CartSummaryProps = {
6
8
  cart: OptimisticCart<CartApiQueryFragment | null>;
@@ -25,6 +27,7 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
25
27
  </dd>
26
28
  </dl>
27
29
  <CartDiscounts discountCodes={cart.discountCodes} />
30
+ <CartGiftCard giftCardCodes={cart.appliedGiftCards} />
28
31
  <CartCheckoutActions checkoutUrl={cart.checkoutUrl} />
29
32
  </div>
30
33
  );
@@ -99,3 +102,79 @@ function UpdateDiscountForm({
99
102
  </CartForm>
100
103
  );
101
104
  }
105
+
106
+ function CartGiftCard({
107
+ giftCardCodes,
108
+ }: {
109
+ giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
110
+ }) {
111
+ const appliedGiftCardCodes = useRef<string[]>([]);
112
+ const giftCardCodeInput = useRef<HTMLInputElement>(null);
113
+ const codes: string[] = giftCardCodes?.map(({lastCharacters}) => `***${lastCharacters}`) || [];
114
+
115
+ function saveAppliedCode(code: string) {
116
+ const formattedCode = code.replace(/\s/g, ''); // Remove spaces
117
+ if (!appliedGiftCardCodes.current.includes(formattedCode)) {
118
+ appliedGiftCardCodes.current.push(formattedCode);
119
+ }
120
+ giftCardCodeInput.current!.value = '';
121
+ }
122
+
123
+ function removeAppliedCode() {
124
+ appliedGiftCardCodes.current = [];
125
+ }
126
+
127
+ return (
128
+ <div>
129
+ {/* Have existing gift card applied, display it with a remove option */}
130
+ <dl hidden={!codes.length}>
131
+ <div>
132
+ <dt>Applied Gift Card(s)</dt>
133
+ <UpdateGiftCardForm>
134
+ <div className="cart-discount">
135
+ <code>{codes?.join(', ')}</code>
136
+ &nbsp;
137
+ <button onSubmit={() => removeAppliedCode}>Remove</button>
138
+ </div>
139
+ </UpdateGiftCardForm>
140
+ </div>
141
+ </dl>
142
+
143
+ {/* Show an input to apply a discount */}
144
+ <UpdateGiftCardForm giftCardCodes={appliedGiftCardCodes.current} saveAppliedCode={saveAppliedCode}>
145
+ <div>
146
+ <input type="text" name="giftCardCode" placeholder="Gift card code" ref={giftCardCodeInput} />
147
+ &nbsp;
148
+ <button type="submit">Apply</button>
149
+ </div>
150
+ </UpdateGiftCardForm>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ function UpdateGiftCardForm({
156
+ giftCardCodes,
157
+ saveAppliedCode,
158
+ children,
159
+ }: {
160
+ giftCardCodes?: string[];
161
+ saveAppliedCode?: (code: string) => void;
162
+ removeAppliedCode?: () => void;
163
+ children: React.ReactNode;
164
+ }) {
165
+ return (
166
+ <CartForm
167
+ route="/cart"
168
+ action={CartForm.ACTIONS.GiftCardCodesUpdate}
169
+ inputs={{
170
+ giftCardCodes: giftCardCodes || [],
171
+ }}
172
+ >
173
+ {(fetcher: FetcherWithComponents<any>) => {
174
+ const code = fetcher.formData?.get('giftCardCode');
175
+ if (code) saveAppliedCode && saveAppliedCode(code as string);
176
+ return children;
177
+ }}
178
+ </CartForm>
179
+ );
180
+ }
@@ -48,20 +48,14 @@ export function HeaderMenu({
48
48
  publicStoreDomain: HeaderProps['publicStoreDomain'];
49
49
  }) {
50
50
  const className = `header-menu-${viewport}`;
51
-
52
- function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
53
- if (viewport === 'mobile') {
54
- event.preventDefault();
55
- window.location.href = event.currentTarget.href;
56
- }
57
- }
51
+ const {close} = useAside();
58
52
 
59
53
  return (
60
54
  <nav className={className} role="navigation">
61
55
  {viewport === 'mobile' && (
62
56
  <NavLink
63
57
  end
64
- onClick={closeAside}
58
+ onClick={close}
65
59
  prefetch="intent"
66
60
  style={activeLinkStyle}
67
61
  to="/"
@@ -84,7 +78,7 @@ export function HeaderMenu({
84
78
  className="header-menu-item"
85
79
  end
86
80
  key={item.id}
87
- onClick={closeAside}
81
+ onClick={close}
88
82
  prefetch="intent"
89
83
  style={activeLinkStyle}
90
84
  to={url}
@@ -108,6 +108,12 @@ export const CART_QUERY_FRAGMENT = `#graphql
108
108
  fragment CartApiQuery on Cart {
109
109
  updatedAt
110
110
  id
111
+ appliedGiftCards {
112
+ lastCharacters
113
+ amountUsed {
114
+ ...Money
115
+ }
116
+ }
111
117
  checkoutUrl
112
118
  totalQuantity
113
119
  buyerIdentity {
@@ -48,10 +48,10 @@ interface UrlWithTrackingParams {
48
48
 
49
49
  /**
50
50
  * A utility function that appends tracking parameters to a URL. Tracking parameters are
51
- * used internally by shopify to enhance search results and admin dashboards.
51
+ * used internally by Shopify to enhance search results and admin dashboards.
52
52
  * @example
53
53
  * ```ts
54
- * const url = 'www.example.com';
54
+ * const baseUrl = 'www.example.com';
55
55
  * const trackingParams = 'utm_source=shopify&utm_medium=shopify_app&utm_campaign=storefront';
56
56
  * const params = { foo: 'bar' };
57
57
  * const term = 'search term';
@@ -73,6 +73,10 @@ export async function loader(args: LoaderFunctionArgs) {
73
73
  consent: {
74
74
  checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
75
75
  storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
76
+ withPrivacyBanner: true,
77
+ // localize the privacy banner
78
+ country: args.context.storefront.i18n.country,
79
+ language: args.context.storefront.i18n.language,
76
80
  },
77
81
  });
78
82
  }
@@ -94,9 +98,7 @@ async function loadCriticalData({context}: LoaderFunctionArgs) {
94
98
  // Add other queries here, so that they are loaded in parallel
95
99
  ]);
96
100
 
97
- return {
98
- header,
99
- };
101
+ return {header};
100
102
  }
101
103
 
102
104
  /**
@@ -48,6 +48,20 @@ export async function action({request, context}: ActionFunctionArgs) {
48
48
  result = await cart.updateDiscountCodes(discountCodes);
49
49
  break;
50
50
  }
51
+ case CartForm.ACTIONS.GiftCardCodesUpdate: {
52
+ const formGiftCardCode = inputs.giftCardCode;
53
+
54
+ // User inputted gift card code
55
+ const giftCardCodes = (
56
+ formGiftCardCode ? [formGiftCardCode] : []
57
+ ) as string[];
58
+
59
+ // Combine gift card codes already applied on cart
60
+ giftCardCodes.push(...inputs.giftCardCodes);
61
+
62
+ result = await cart.updateGiftCardCodes(giftCardCodes);
63
+ break;
64
+ }
51
65
  case CartForm.ACTIONS.BuyerIdentityUpdate: {
52
66
  result = await cart.updateBuyerIdentity({
53
67
  ...inputs.buyerIdentity,
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2024.7.4",
5
+ "version": "2024.7.6",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -16,8 +16,8 @@
16
16
  "dependencies": {
17
17
  "@remix-run/react": "^2.10.1",
18
18
  "@remix-run/server-runtime": "^2.10.1",
19
- "@shopify/hydrogen": "2024.7.3",
20
- "@shopify/remix-oxygen": "^2.0.6",
19
+ "@shopify/hydrogen": "2024.7.5",
20
+ "@shopify/remix-oxygen": "^2.0.7",
21
21
  "graphql": "^16.6.0",
22
22
  "graphql-tag": "^2.12.6",
23
23
  "isbot": "^3.8.0",
@@ -28,9 +28,9 @@
28
28
  "@graphql-codegen/cli": "5.0.2",
29
29
  "@remix-run/dev": "^2.10.1",
30
30
  "@remix-run/eslint-config": "^2.10.1",
31
- "@shopify/cli": "3.65.1",
31
+ "@shopify/cli": "~3.66.1",
32
32
  "@shopify/hydrogen-codegen": "^0.3.1",
33
- "@shopify/mini-oxygen": "^3.0.4",
33
+ "@shopify/mini-oxygen": "^3.0.5",
34
34
  "@shopify/oxygen-workers-types": "^4.1.2",
35
35
  "@shopify/prettier-config": "^1.1.2",
36
36
  "@total-typescript/ts-reset": "^0.4.2",
@@ -72,6 +72,11 @@ export type CartApiQueryFragment = Pick<
72
72
  StorefrontAPI.Cart,
73
73
  'updatedAt' | 'id' | 'checkoutUrl' | 'totalQuantity' | 'note'
74
74
  > & {
75
+ appliedGiftCards: Array<
76
+ Pick<StorefrontAPI.AppliedGiftCard, 'lastCharacters'> & {
77
+ amountUsed: Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>;
78
+ }
79
+ >;
75
80
  buyerIdentity: Pick<
76
81
  StorefrontAPI.CartBuyerIdentity,
77
82
  'countryCode' | 'email' | 'phone'
@@ -33,10 +33,13 @@ class Build extends Command {
33
33
  description: "Watches for changes and rebuilds the project writing output to disk.",
34
34
  env: "SHOPIFY_HYDROGEN_FLAG_WATCH"
35
35
  }),
36
- // For the classic compiler:
37
36
  "bundle-stats": Flags.boolean({
38
37
  description: "Show a bundle size summary after building. Defaults to true, use `--no-bundle-stats` to disable.",
39
38
  allowNo: true
39
+ }),
40
+ "force-client-sourcemap": Flags.boolean({
41
+ description: "Client sourcemapping is avoided by default because it makes backend code visible in the browser. Use this flag to force enabling it.",
42
+ env: "SHOPIFY_HYDROGEN_FLAG_FORCE_CLIENT_SOURCEMAP"
40
43
  })
41
44
  };
42
45
  async run() {
@@ -78,6 +81,7 @@ async function runBuild({
78
81
  useCodegen = false,
79
82
  codegenConfigPath,
80
83
  sourcemap = true,
84
+ forceClientSourcemap,
81
85
  disableRouteWarning = false,
82
86
  lockfileCheck = true,
83
87
  assetPath = "/",
@@ -121,8 +125,8 @@ async function runBuild({
121
125
  build: {
122
126
  emptyOutDir: true,
123
127
  copyPublicDir: true,
124
- // Disable client sourcemaps in production
125
- sourcemap: process.env.NODE_ENV !== "production" && sourcemap,
128
+ // Disable client sourcemaps in production by default
129
+ sourcemap: forceClientSourcemap ?? (process.env.NODE_ENV !== "production" && sourcemap),
126
130
  watch: watch ? {} : null
127
131
  },
128
132
  plugins: [
@@ -187,6 +187,7 @@ function hasOutdatedDependencies({
187
187
  ...release.dependencies,
188
188
  ...release.devDependencies
189
189
  }).some(([name, version]) => {
190
+ if (name === "@shopify/cli") return false;
190
191
  const currentDependencyVersion = currentDependencies?.[name];
191
192
  if (!currentDependencyVersion) return false;
192
193
  const isDependencyOutdated = semver.gt(
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ declare class Build extends Command {
11
11
  static flags: {
12
12
  watch: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
13
13
  'bundle-stats': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
14
+ 'force-client-sourcemap': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
14
15
  diff: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
15
16
  codegen: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
16
17
  'codegen-config-path': _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
package/dist/lib/flags.js CHANGED
@@ -77,7 +77,7 @@ const commonFlags = {
77
77
  },
78
78
  sourcemap: {
79
79
  sourcemap: Flags.boolean({
80
- description: "Controls whether sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
80
+ description: "Controls whether server sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
81
81
  env: "SHOPIFY_HYDROGEN_FLAG_SOURCEMAP",
82
82
  default: true,
83
83
  allowNo: true
@@ -7,7 +7,6 @@ import { renderTasks, renderInfo } from '@shopify/cli-kit/node/ui';
7
7
  import { downloadExternalRepo, downloadMonorepoTemplates } from '../template-downloader.js';
8
8
  import { applyTemplateDiff } from '../template-diff.js';
9
9
  import { getCliCommand } from '../shell.js';
10
- import { replaceFileContent } from '../file.js';
11
10
  import { createAbortHandler, handleProjectLocation, handleLanguage, createInitialCommit, handleDependencies, commitAll, renderProjectReady } from './common.js';
12
11
 
13
12
  const DEMO_STORE_REPO = "shopify/hydrogen-demo-store";
@@ -30,16 +29,6 @@ async function setupRemoteTemplate(options, controller) {
30
29
  return applyTemplateDiff(project.directory, sourcePath, skeletonPath);
31
30
  }
32
31
  await copyFile(sourcePath, project.directory);
33
- await replaceFileContent(
34
- joinPath(project.directory, "package.json"),
35
- false,
36
- (content) => (
37
- // Remove the cli plugin dependency from the package.json because it's
38
- // only used for monorepo development. This line is present in non-diff
39
- // examples like `express` when scaffolding a new project:
40
- content.replace(/^\s*"@shopify\/cli-hydrogen": "[^"]+",?\n/m, "")
41
- )
42
- );
43
32
  }).catch(abort);
44
33
  const supportsTranspilation = await fileExists(
45
34
  joinPath(downloaded.sourcePath, "tsconfig.json")
@@ -126,16 +126,10 @@ ${colors.dim(
126
126
  async copyDiffBuild() {
127
127
  const target = joinPath(diffDirectory, "dist");
128
128
  await removeFile(target);
129
- await Promise.all([
130
- cp(joinPath(targetDirectory, "dist"), target, {
131
- force: true,
132
- recursive: true
133
- }),
134
- copyFile(
135
- joinPath(targetDirectory, ".env"),
136
- joinPath(diffDirectory, ".env")
137
- )
138
- ]);
129
+ await cp(joinPath(targetDirectory, "dist"), target, {
130
+ force: true,
131
+ recursive: true
132
+ });
139
133
  },
140
134
  /**
141
135
  * Brings the generated d.ts files back to the original project.
@@ -183,9 +177,6 @@ async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir) {
183
177
  await mergePackageJson(diffDirectory, targetDirectory, {
184
178
  ignoredKeys: ["h2:diff"],
185
179
  onResult: (pkgJson) => {
186
- if (pkgJson.dependencies) {
187
- delete pkgJson.dependencies["@shopify/cli-hydrogen"];
188
- }
189
180
  for (const key of ["build", "dev", "preview"]) {
190
181
  const scriptLine = pkgJson.scripts?.[key];
191
182
  if (pkgJson.scripts?.[key] && typeof scriptLine === "string") {
@@ -22,7 +22,7 @@
22
22
  "type": "option"
23
23
  },
24
24
  "sourcemap": {
25
- "description": "Controls whether sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
25
+ "description": "Controls whether server sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
26
26
  "env": "SHOPIFY_HYDROGEN_FLAG_SOURCEMAP",
27
27
  "name": "sourcemap",
28
28
  "allowNo": true,
@@ -80,6 +80,13 @@
80
80
  "name": "bundle-stats",
81
81
  "allowNo": true,
82
82
  "type": "boolean"
83
+ },
84
+ "force-client-sourcemap": {
85
+ "description": "Client sourcemapping is avoided by default because it makes backend code visible in the browser. Use this flag to force enabling it.",
86
+ "env": "SHOPIFY_HYDROGEN_FLAG_FORCE_CLIENT_SOURCEMAP",
87
+ "name": "force-client-sourcemap",
88
+ "allowNo": false,
89
+ "type": "boolean"
83
90
  }
84
91
  },
85
92
  "hasDynamicHelp": false,
@@ -675,7 +682,7 @@
675
682
  "type": "boolean"
676
683
  },
677
684
  "sourcemap": {
678
- "description": "[Classic Remix Compiler] Controls whether sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
685
+ "description": "[Classic Remix Compiler] Controls whether server sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
679
686
  "env": "SHOPIFY_HYDROGEN_FLAG_SOURCEMAP",
680
687
  "name": "sourcemap",
681
688
  "allowNo": true,
@@ -1751,5 +1758,5 @@
1751
1758
  ]
1752
1759
  }
1753
1760
  },
1754
- "version": "8.4.0"
1761
+ "version": "8.4.2"
1755
1762
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "8.4.0",
7
+ "version": "8.4.2",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "scripts": {
@@ -34,9 +34,9 @@
34
34
  "dependencies": {
35
35
  "@ast-grep/napi": "0.11.0",
36
36
  "@oclif/core": "3.26.5",
37
- "@shopify/cli-kit": "3.65.1",
37
+ "@shopify/cli-kit": "3.66.1",
38
38
  "@shopify/oxygen-cli": "4.4.9",
39
- "@shopify/plugin-cloudflare": "3.65.1",
39
+ "@shopify/plugin-cloudflare": "3.66.1",
40
40
  "ansi-escapes": "^6.2.0",
41
41
  "chokidar": "3.5.3",
42
42
  "cli-truncate": "^4.0.0",
@@ -56,7 +56,7 @@
56
56
  "@graphql-codegen/cli": "^5.0.2",
57
57
  "@remix-run/dev": "^2.1.0",
58
58
  "@shopify/hydrogen-codegen": "^0.3.1",
59
- "@shopify/mini-oxygen": "^3.0.4",
59
+ "@shopify/mini-oxygen": "^3.0.5",
60
60
  "graphql-config": "^5.0.3",
61
61
  "vite": "^5.1.0"
62
62
  },