@shopify/shop-minis-react 0.1.7 → 0.1.8

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,5 @@
1
- var r = {};
1
+ var e = { exports: {} };
2
2
  export {
3
- r as __exports
3
+ e as __module
4
4
  };
5
5
  //# sourceMappingURL=index4.js.map
@@ -1,5 +1,6 @@
1
- var e = { exports: {} };
1
+ import { __require as r } from "../shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js";
2
+ var i = r();
2
3
  export {
3
- e as __module
4
+ i as l
4
5
  };
5
6
  //# sourceMappingURL=index5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -1,6 +1,6 @@
1
- import { __require as r } from "../shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js";
1
+ import { __require as r } from "../shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js";
2
2
  var i = r();
3
3
  export {
4
- i as l
4
+ i as s
5
5
  };
6
6
  //# sourceMappingURL=index6.js.map
@@ -1,6 +1,5 @@
1
- import { __require as r } from "../shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js";
2
- var i = r();
1
+ var r = {};
3
2
  export {
4
- i as s
3
+ r as __exports
5
4
  };
6
5
  //# sourceMappingURL=index7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index7.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
1
+ {"version":3,"file":"index7.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,23 +1,23 @@
1
- import { useMemo as e } from "react";
2
- import { parseUrl as s } from "../../utils/parseUrl.js";
3
- const t = () => {
4
- const { initialUrl: r } = window.minisParams;
5
- return e(() => {
1
+ import { useMemo as i } from "react";
2
+ import { parseUrl as n } from "../../utils/parseUrl.js";
3
+ const m = () => {
4
+ const { initialUrl: r, handle: e } = window.minisParams;
5
+ return i(() => {
6
6
  if (!r)
7
7
  return {
8
8
  path: void 0,
9
9
  queryParams: void 0,
10
10
  hash: void 0
11
11
  };
12
- const a = s(r);
12
+ const a = n(r), t = `/mini/${e}`;
13
13
  return {
14
- path: a.pathname,
14
+ path: a.pathname.startsWith(t) ? a.pathname.replace(t, "") : a.pathname,
15
15
  queryParams: a.query,
16
16
  hash: a.hash
17
17
  };
18
- }, [r]);
18
+ }, [e, r]);
19
19
  };
20
20
  export {
21
- t as useDeeplink
21
+ m as useDeeplink
22
22
  };
23
23
  //# sourceMappingURL=useDeeplink.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDeeplink.js","sources":["../../../src/hooks/navigation/useDeeplink.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {parseUrl} from '../../utils/parseUrl'\n\ninterface UseDeeplinkReturnType {\n /**\n * The path of the deeplink.\n */\n path?: string\n /**\n * The query parameters of the deeplink.\n */\n queryParams?: {[key: string]: string | undefined}\n /**\n * The hash of the deeplink.\n */\n hash?: string\n}\n\nexport const useDeeplink = (): UseDeeplinkReturnType => {\n const {initialUrl} = window.minisParams\n\n return useMemo(() => {\n if (!initialUrl) {\n return {\n path: undefined,\n queryParams: undefined,\n hash: undefined,\n }\n }\n\n const parsedUrl = parseUrl(initialUrl)\n\n return {\n path: parsedUrl.pathname,\n queryParams: parsedUrl.query,\n hash: parsedUrl.hash,\n }\n }, [initialUrl])\n}\n"],"names":["useDeeplink","initialUrl","useMemo","parsedUrl","parseUrl"],"mappings":";;AAmBO,MAAMA,IAAc,MAA6B;AAChD,QAAA,EAAC,YAAAC,MAAc,OAAO;AAE5B,SAAOC,EAAQ,MAAM;AACnB,QAAI,CAACD;AACI,aAAA;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,QACb,MAAM;AAAA,MACR;AAGI,UAAAE,IAAYC,EAASH,CAAU;AAE9B,WAAA;AAAA,MACL,MAAME,EAAU;AAAA,MAChB,aAAaA,EAAU;AAAA,MACvB,MAAMA,EAAU;AAAA,IAClB;AAAA,EAAA,GACC,CAACF,CAAU,CAAC;AACjB;"}
1
+ {"version":3,"file":"useDeeplink.js","sources":["../../../src/hooks/navigation/useDeeplink.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {parseUrl} from '../../utils/parseUrl'\n\ninterface UseDeeplinkReturnType {\n /**\n * The path of the deeplink.\n */\n path?: string\n /**\n * The query parameters of the deeplink.\n */\n queryParams?: {[key: string]: string | undefined}\n /**\n * The hash of the deeplink.\n */\n hash?: string\n}\n\nexport const useDeeplink = (): UseDeeplinkReturnType => {\n const {initialUrl, handle} = window.minisParams\n\n return useMemo(() => {\n if (!initialUrl) {\n return {\n path: undefined,\n queryParams: undefined,\n hash: undefined,\n }\n }\n\n const parsedUrl = parseUrl(initialUrl)\n const deeplinkPathnamePrefix = `/mini/${handle}`\n\n return {\n path: parsedUrl.pathname.startsWith(deeplinkPathnamePrefix)\n ? parsedUrl.pathname.replace(deeplinkPathnamePrefix, '')\n : parsedUrl.pathname,\n queryParams: parsedUrl.query,\n hash: parsedUrl.hash,\n }\n }, [handle, initialUrl])\n}\n"],"names":["useDeeplink","initialUrl","handle","useMemo","parsedUrl","parseUrl","deeplinkPathnamePrefix"],"mappings":";;AAmBO,MAAMA,IAAc,MAA6B;AACtD,QAAM,EAAC,YAAAC,GAAY,QAAAC,EAAM,IAAI,OAAO;AAEpC,SAAOC,EAAQ,MAAM;AACnB,QAAI,CAACF;AACI,aAAA;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,QACb,MAAM;AAAA,MACR;AAGI,UAAAG,IAAYC,EAASJ,CAAU,GAC/BK,IAAyB,SAASJ,CAAM;AAEvC,WAAA;AAAA,MACL,MAAME,EAAU,SAAS,WAAWE,CAAsB,IACtDF,EAAU,SAAS,QAAQE,GAAwB,EAAE,IACrDF,EAAU;AAAA,MACd,aAAaA,EAAU;AAAA,MACvB,MAAMA,EAAU;AAAA,IAClB;AAAA,EAAA,GACC,CAACF,GAAQD,CAAU,CAAC;AACzB;"}
@@ -1,4 +1,4 @@
1
- import { s as r } from "../../../../../../../../_virtual/index7.js";
1
+ import { s as r } from "../../../../../../../../_virtual/index6.js";
2
2
  function s() {
3
3
  return r.useSyncExternalStore(
4
4
  e,
@@ -1,4 +1,4 @@
1
- import { __module as q } from "../../../../../../../../_virtual/index5.js";
1
+ import { __module as q } from "../../../../../../../../_virtual/index4.js";
2
2
  import { __require as F } from "../../../../../global@4.4.0/node_modules/global/window.js";
3
3
  import { __require as N } from "../../../../../@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/extends.js";
4
4
  import { __require as J } from "../../../../../is-function@1.0.2/node_modules/is-function/index.js";
@@ -2,7 +2,7 @@ import L from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-ut
2
2
  import T from "../../../../../../../_virtual/window.js";
3
3
  import { forEachMediaGroup as Z } from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/media-groups.js";
4
4
  import J from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js";
5
- import { l as Q } from "../../../../../../../_virtual/index6.js";
5
+ import { l as Q } from "../../../../../../../_virtual/index5.js";
6
6
  /*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */
7
7
  const w = (e) => !!e && typeof e == "object", E = (...e) => e.reduce((n, t) => (typeof t != "object" || Object.keys(t).forEach((r) => {
8
8
  Array.isArray(n[r]) && Array.isArray(t[r]) ? n[r] = n[r].concat(t[r]) : w(n[r]) && w(t[r]) ? n[r] = E(n[r], t[r]) : n[r] = t[r];
@@ -1,4 +1,4 @@
1
- import { __exports as i } from "../../../../../../_virtual/index4.js";
1
+ import { __exports as i } from "../../../../../../_virtual/index7.js";
2
2
  var c;
3
3
  function d() {
4
4
  if (c) return i;
@@ -0,0 +1,201 @@
1
+ # Shop Minis ESLint Plugin
2
+
3
+ Custom ESLint rules for Shop Minis apps. ESLint is included with the SDK.
4
+
5
+ ## Quick Setup
6
+
7
+ Create **`eslint.config.js`** (NOT `.eslintrc.js`):
8
+
9
+ ```javascript
10
+ const shopMinisConfig = require('@shopify/shop-minis-react/eslint/config')
11
+
12
+ module.exports = [shopMinisConfig]
13
+ ```
14
+
15
+ **That's it!** TypeScript and JSX are supported out of the box.
16
+
17
+ **Important:**
18
+ - File must be named `eslint.config.js` (no dot, no "rc")
19
+ - This will lint all `.js`, `.jsx`, `.ts`, `.tsx` files in your project
20
+ - TypeScript and JSX parsing is configured automatically
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ # Check for errors
26
+ npx eslint .
27
+
28
+ # Auto-fix (converts <img> to <Image> AND adds import!)
29
+ npx eslint . --fix
30
+ ```
31
+
32
+ ## What You Get
33
+
34
+ - ✅ No internal imports allowed
35
+ - ✅ Warnings for `<img>`, `<button>`, `<label>` tags (auto-fixes with imports)
36
+ - ✅ Manifest scope validation - ensures manifest.json has scopes for hooks you use (auto-fixes manifest)
37
+
38
+ ## Rules
39
+
40
+ ### `no-internal-imports`
41
+
42
+ Prevents importing from internal SDK directories.
43
+
44
+ ```tsx
45
+ // ❌ Error
46
+ import {something} from '@shopify/shop-minis-react/internal'
47
+
48
+ // ✅ Correct
49
+ import {Component} from '@shopify/shop-minis-react'
50
+ ```
51
+
52
+ ### `prefer-sdk-components`
53
+
54
+ Suggests using SDK components instead of native HTML elements. **Fully auto-fixable** - fixes both tags and imports!
55
+
56
+ **Before:**
57
+ ```tsx
58
+ const MyComponent = () => (
59
+ <img src="product.jpg" alt="Product" />
60
+ )
61
+ ```
62
+
63
+ **After running `npx eslint . --fix`:**
64
+ ```tsx
65
+ import {Image} from '@shopify/shop-minis-react'
66
+
67
+ const MyComponent = () => (
68
+ <Image src="product.jpg" alt="Product" />
69
+ )
70
+ ```
71
+
72
+ **Supported Components:**
73
+ - `<img>` → `<Image>`
74
+ - `<button>` → `<Button>`
75
+ - `<label>` → `<Label>`
76
+
77
+ **Auto-fix does TWO things:**
78
+ 1. ✅ Replaces native element with SDK component
79
+ 2. ✅ Adds import statement automatically (or adds to existing import)
80
+
81
+ ### `validate-manifest`
82
+
83
+ Validates `src/manifest.json` configuration for scopes and permissions. **Auto-fixable** - adds missing values to manifest!
84
+
85
+ #### Scopes
86
+
87
+ Checks that hooks have required scopes:
88
+
89
+ ```tsx
90
+ // If you use this hook:
91
+ import {useCurrentUser} from '@shopify/shop-minis-react'
92
+
93
+ // Manifest must include:
94
+ {
95
+ "scopes": ["USER_SETTINGS_READ"]
96
+ }
97
+ ```
98
+
99
+ **Scope Requirements:**
100
+ - `useCurrentUser` → `USER_SETTINGS_READ`
101
+ - `useSavedProducts` → `FAVORITES`
102
+ - `useOrders` → `ORDERS`
103
+
104
+ #### Permissions
105
+
106
+ Checks for native permission usage:
107
+
108
+ ```tsx
109
+ // If you use this hook:
110
+ import {useImagePicker} from '@shopify/shop-minis-react'
111
+
112
+ // Or browser APIs:
113
+ navigator.mediaDevices.getUserMedia({video: true})
114
+
115
+ // Manifest must include:
116
+ {
117
+ "permissions": ["CAMERA"]
118
+ }
119
+ ```
120
+
121
+ **Supported Permissions:**
122
+ - `CAMERA` - Required for `useImagePicker` hook or getUserMedia video
123
+ - `MICROPHONE` - Required for getUserMedia audio
124
+ - `MOTION` - Required for DeviceOrientation/DeviceMotion events
125
+
126
+ #### Trusted Domains
127
+
128
+ Checks that external URLs are in trusted_domains. Detects:
129
+
130
+ **Network Requests:**
131
+ - `fetch('https://api.example.com/data')`
132
+ - `new XMLHttpRequest().open('GET', 'https://...')`
133
+ - `new WebSocket('wss://api.example.com')`
134
+ - `new EventSource('https://api.example.com/events')`
135
+ - `navigator.sendBeacon('https://analytics.example.com')`
136
+ - `window.open('https://external.com')`
137
+
138
+ **Media & Resources:**
139
+ - `<img src="https://cdn.shopify.com/image.jpg" />`
140
+ - `<video src="https://videos.example.com/video.mp4" />`
141
+ - `<video poster="https://cdn.example.com/poster.jpg" />`
142
+ - `<audio src="https://audio.example.com/sound.mp3" />`
143
+ - `<source src="https://media.example.com/video.mp4" />`
144
+ - `<track src="https://cdn.example.com/captions.vtt" />`
145
+ - `<object data="https://cdn.example.com/file.pdf" />`
146
+ - `<embed src="https://cdn.example.com/file.swf" />`
147
+ - `<form action="https://api.example.com/submit" />`
148
+
149
+ **Note:** External scripts (`<script>`), stylesheets (`<link>`), and iframes are not supported and excluded from validation.
150
+
151
+ **Example manifest:**
152
+ ```json
153
+ {
154
+ "trusted_domains": [
155
+ "api.example.com",
156
+ "cdn.shopify.com",
157
+ "videos.example.com"
158
+ ]
159
+ }
160
+ ```
161
+
162
+ **Wildcard support:**
163
+ ```json
164
+ {
165
+ "trusted_domains": [
166
+ "*.shopify.com", // Matches any Shopify subdomain
167
+ "api.example.com", // Exact domain
168
+ "cdn.example.com/assets" // Specific path
169
+ ]
170
+ }
171
+ ```
172
+
173
+ **Auto-fix:**
174
+ ```bash
175
+ npx eslint . --fix
176
+ # Automatically updates src/manifest.json
177
+ ```
178
+
179
+ **Example errors:**
180
+ ```
181
+ Hook "useCurrentUser" requires scope "USER_SETTINGS_READ" in src/manifest.json.
182
+ Hook "useImagePicker" requires permission "CAMERA" in src/manifest.json.
183
+ fetch() call loads from "api.example.com" which is not in trusted_domains.
184
+ <img> src attribute loads from "cdn.shopify.com" which is not in trusted_domains.
185
+ ```
186
+
187
+ ## Extending Rules
188
+
189
+ To add more component mappings to `prefer-sdk-components`, edit `eslint/rules/prefer-sdk-components.cjs`:
190
+
191
+ ```javascript
192
+ const defaultComponents = {
193
+ img: 'Image',
194
+ button: 'Button',
195
+ label: 'Label',
196
+ input: 'Input', // Add this
197
+ a: 'TransitionLink', // Add this
198
+ }
199
+ ```
200
+
201
+ All consumers automatically get new rules - no config changes needed!
@@ -0,0 +1,32 @@
1
+ /* eslint-disable import/extensions */
2
+ /**
3
+ * ESLint config for projects using @shopify/shop-minis-react
4
+ * @fileoverview Recommended ESLint configuration for Shop Minis apps
5
+ *
6
+ * This config uses ESLint flat config format (supported in ESLint 8.57+)
7
+ */
8
+
9
+ // Import the plugin directly so consumers don't need to install it separately
10
+ const shopMinisPlugin = require('./index.cjs')
11
+
12
+ module.exports = {
13
+ files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
14
+ languageOptions: {
15
+ parser: require('@typescript-eslint/parser'),
16
+ ecmaVersion: 'latest',
17
+ sourceType: 'module',
18
+ parserOptions: {
19
+ ecmaFeatures: {
20
+ jsx: true,
21
+ },
22
+ },
23
+ },
24
+ plugins: {
25
+ 'shop-minis': shopMinisPlugin,
26
+ },
27
+ rules: {
28
+ 'shop-minis/no-internal-imports': 'error',
29
+ 'shop-minis/prefer-sdk-components': 'warn',
30
+ 'shop-minis/validate-manifest': 'error',
31
+ },
32
+ }
@@ -0,0 +1,17 @@
1
+ /* eslint-disable import/extensions */
2
+ /**
3
+ * ESLint plugin for @shopify/shop-minis-react
4
+ * @fileoverview Custom ESLint rules for Shop Minis React SDK
5
+ */
6
+
7
+ const noInternalImports = require('./rules/no-internal-imports.cjs')
8
+ const preferSdkComponents = require('./rules/prefer-sdk-components.cjs')
9
+ const validateManifest = require('./rules/validate-manifest.cjs')
10
+
11
+ module.exports = {
12
+ rules: {
13
+ 'no-internal-imports': noInternalImports,
14
+ 'prefer-sdk-components': preferSdkComponents,
15
+ 'validate-manifest': validateManifest,
16
+ },
17
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ESLint rule to prevent importing from internal directories
3
+ * @fileoverview Disallow importing from @shopify/shop-minis-react/internal
4
+ */
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description:
11
+ 'Disallow importing from internal directories of @shopify/shop-minis-react',
12
+ category: 'Best Practices',
13
+ recommended: true,
14
+ },
15
+ messages: {
16
+ noInternalImports:
17
+ 'Do not import from "{{importPath}}". Internal APIs are not part of the public API and may change without notice. Use the public exports from @shopify/shop-minis-react instead.',
18
+ },
19
+ schema: [],
20
+ },
21
+
22
+ create(context) {
23
+ return {
24
+ ImportDeclaration(node) {
25
+ const importPath = node.source.value
26
+
27
+ // Check if importing from internal directory
28
+ if (
29
+ importPath.includes('@shopify/shop-minis-react/internal') ||
30
+ importPath.includes('@shopify/shop-minis-react/src/internal')
31
+ ) {
32
+ context.report({
33
+ node: node.source,
34
+ messageId: 'noInternalImports',
35
+ data: {
36
+ importPath,
37
+ },
38
+ })
39
+ }
40
+ },
41
+ }
42
+ },
43
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ESLint rule to prefer SDK components over native HTML elements
3
+ * @fileoverview Enforce using Shop Minis SDK components instead of native HTML
4
+ */
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'suggestion',
9
+ docs: {
10
+ description:
11
+ 'Prefer Shop Minis SDK components over native HTML elements for better functionality and styling',
12
+ category: 'Best Practices',
13
+ recommended: true,
14
+ },
15
+ fixable: 'code',
16
+ messages: {
17
+ preferSdkComponent:
18
+ 'Use <{{sdkComponent}}> from @shopify/shop-minis-react instead of <{{nativeElement}}>. The SDK component provides optimized performance, consistent styling, and additional features.',
19
+ },
20
+ schema: [
21
+ {
22
+ type: 'object',
23
+ properties: {
24
+ components: {
25
+ type: 'object',
26
+ description: 'Map of native elements to SDK components',
27
+ additionalProperties: {
28
+ type: 'string',
29
+ },
30
+ },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ ],
35
+ },
36
+
37
+ create(context) {
38
+ // Default component mappings
39
+ const defaultComponents = {
40
+ img: 'Image',
41
+ button: 'Button',
42
+ label: 'Label',
43
+ // Future additions will go here:
44
+ // input: 'Input',
45
+ // a: 'Link',
46
+ }
47
+
48
+ // Get user configuration or use defaults
49
+ const options = context.options[0] || {}
50
+ const componentMap = {
51
+ ...defaultComponents,
52
+ ...(options.components || {}),
53
+ }
54
+
55
+ // eslint-disable-next-line @shopify/prefer-module-scope-constants
56
+ const SDK_PACKAGE = '@shopify/shop-minis-react'
57
+
58
+ return {
59
+ JSXOpeningElement(node) {
60
+ const elementName = node.name.name
61
+
62
+ // Check if this is a native element we want to replace
63
+ if (componentMap[elementName]) {
64
+ const sdkComponent = componentMap[elementName]
65
+
66
+ context.report({
67
+ node,
68
+ messageId: 'preferSdkComponent',
69
+ data: {
70
+ nativeElement: elementName,
71
+ sdkComponent,
72
+ },
73
+ fix(fixer) {
74
+ const sourceCode = context.getSourceCode()
75
+ const openingElement = node
76
+ const jsxElement = node.parent
77
+
78
+ // Get the closing element if it exists
79
+ const closingElement = jsxElement.closingElement
80
+
81
+ const fixes = []
82
+
83
+ // Fix opening tag
84
+ const openingTagStart = openingElement.name.range[0]
85
+ const openingTagEnd = openingElement.name.range[1]
86
+ fixes.push(
87
+ fixer.replaceTextRange(
88
+ [openingTagStart, openingTagEnd],
89
+ sdkComponent
90
+ )
91
+ )
92
+
93
+ // Fix closing tag if it exists (not self-closing)
94
+ if (closingElement) {
95
+ const closingTagStart = closingElement.name.range[0]
96
+ const closingTagEnd = closingElement.name.range[1]
97
+ fixes.push(
98
+ fixer.replaceTextRange(
99
+ [closingTagStart, closingTagEnd],
100
+ sdkComponent
101
+ )
102
+ )
103
+ }
104
+
105
+ // Add import if it doesn't exist
106
+ const program = sourceCode.ast
107
+ const imports = program.body.filter(
108
+ importNode => importNode.type === 'ImportDeclaration'
109
+ )
110
+
111
+ // Check if we already import from the SDK
112
+ const sdkImport = imports.find(
113
+ importNode => importNode.source.value === SDK_PACKAGE
114
+ )
115
+
116
+ if (sdkImport) {
117
+ // Check if this component is already imported
118
+ const hasComponent = sdkImport.specifiers.some(
119
+ spec =>
120
+ spec.type === 'ImportSpecifier' &&
121
+ spec.imported.name === sdkComponent
122
+ )
123
+
124
+ if (!hasComponent) {
125
+ // Add component to existing import
126
+ const lastSpecifier =
127
+ sdkImport.specifiers[sdkImport.specifiers.length - 1]
128
+ fixes.push(
129
+ fixer.insertTextAfter(lastSpecifier, `, ${sdkComponent}`)
130
+ )
131
+ }
132
+ } else {
133
+ // Add new import statement
134
+ const firstNode = program.body[0]
135
+ const importStatement = `import {${sdkComponent}} from '${SDK_PACKAGE}'\n`
136
+
137
+ if (firstNode) {
138
+ fixes.push(fixer.insertTextBefore(firstNode, importStatement))
139
+ } else {
140
+ fixes.push(
141
+ fixer.insertTextAfterRange([0, 0], importStatement)
142
+ )
143
+ }
144
+ }
145
+
146
+ return fixes
147
+ },
148
+ })
149
+ }
150
+ },
151
+ }
152
+ },
153
+ }