@shopify/shop-minis-react 0.3.0 → 0.3.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.
package/eslint/config.cjs CHANGED
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  // Import the plugin directly so consumers don't need to install it separately
10
+ const reactPlugin = require('eslint-plugin-react')
11
+
10
12
  const shopMinisPlugin = require('./index.cjs')
11
13
 
12
14
  module.exports = {
@@ -23,10 +25,14 @@ module.exports = {
23
25
  },
24
26
  plugins: {
25
27
  'shop-minis': shopMinisPlugin,
28
+ react: reactPlugin,
26
29
  },
27
30
  rules: {
31
+ 'no-console': 'warn',
32
+ 'react/no-danger': 'error',
28
33
  'shop-minis/no-internal-imports': 'error',
29
34
  'shop-minis/prefer-sdk-components': 'warn',
35
+ 'shop-minis/prefer-sdk-hooks': 'warn',
30
36
  'shop-minis/validate-manifest': 'error',
31
37
  },
32
38
  }
package/eslint/index.cjs CHANGED
@@ -6,12 +6,14 @@
6
6
 
7
7
  const noInternalImports = require('./rules/no-internal-imports.cjs')
8
8
  const preferSdkComponents = require('./rules/prefer-sdk-components.cjs')
9
+ const preferSdkHooks = require('./rules/prefer-sdk-hooks.cjs')
9
10
  const validateManifest = require('./rules/validate-manifest.cjs')
10
11
 
11
12
  module.exports = {
12
13
  rules: {
13
14
  'no-internal-imports': noInternalImports,
14
15
  'prefer-sdk-components': preferSdkComponents,
16
+ 'prefer-sdk-hooks': preferSdkHooks,
15
17
  'validate-manifest': validateManifest,
16
18
  },
17
19
  }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * ESLint rule to prefer SDK hooks over native browser APIs
3
+ * @fileoverview Enforce using Shop Minis SDK hooks instead of native browser APIs
4
+ */
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'suggestion',
9
+ docs: {
10
+ description:
11
+ 'Prefer Shop Minis SDK hooks over native browser APIs for better compatibility and functionality',
12
+ category: 'Best Practices',
13
+ recommended: true,
14
+ },
15
+ messages: {
16
+ preferAsyncStorage:
17
+ 'Use useAsyncStorage (or useSecureStorage for sensitive data) from @shopify/shop-minis-react instead of localStorage. The SDK hook provides async storage that works reliably in the Shop mini-app environment.',
18
+ },
19
+ schema: [
20
+ {
21
+ type: 'object',
22
+ properties: {
23
+ apis: {
24
+ type: 'object',
25
+ description: 'Map of native APIs to SDK hooks',
26
+ additionalProperties: {
27
+ type: 'string',
28
+ },
29
+ },
30
+ },
31
+ additionalProperties: false,
32
+ },
33
+ ],
34
+ },
35
+
36
+ create(context) {
37
+ // Default API mappings
38
+ const defaultApis = {
39
+ localStorage: 'useAsyncStorage',
40
+ sessionStorage: 'useAsyncStorage',
41
+ }
42
+
43
+ // Get user configuration or use defaults
44
+ const options = context.options[0] || {}
45
+ const apiMap = {
46
+ ...defaultApis,
47
+ ...(options.apis || {}),
48
+ }
49
+
50
+ return {
51
+ MemberExpression(node) {
52
+ // Check for direct access: localStorage.getItem()
53
+ if (
54
+ node.object.type === 'Identifier' &&
55
+ Object.hasOwn(apiMap, node.object.name)
56
+ ) {
57
+ const apiName = node.object.name
58
+ const sdkHook = apiMap[apiName]
59
+
60
+ context.report({
61
+ node: node.object,
62
+ messageId: 'preferAsyncStorage',
63
+ data: {
64
+ nativeApi: apiName,
65
+ sdkHook,
66
+ },
67
+ })
68
+ return
69
+ }
70
+
71
+ // Check for global access: window.localStorage or globalThis.localStorage
72
+ if (
73
+ node.object.type === 'MemberExpression' &&
74
+ node.object.object.type === 'Identifier' &&
75
+ (node.object.object.name === 'window' ||
76
+ node.object.object.name === 'globalThis') &&
77
+ node.object.property.type === 'Identifier' &&
78
+ Object.hasOwn(apiMap, node.object.property.name)
79
+ ) {
80
+ const apiName = node.object.property.name
81
+ const sdkHook = apiMap[apiName]
82
+
83
+ context.report({
84
+ node: node.object,
85
+ messageId: 'preferAsyncStorage',
86
+ data: {
87
+ nativeApi: apiName,
88
+ sdkHook,
89
+ },
90
+ })
91
+ }
92
+ },
93
+
94
+ // Also catch direct references to localStorage/sessionStorage
95
+ Identifier(node) {
96
+ // Only flag if it's being used, not just referenced in imports
97
+ const parent = node.parent
98
+
99
+ // Skip if it's part of an import statement
100
+ if (
101
+ parent.type === 'ImportSpecifier' ||
102
+ parent.type === 'ImportDefaultSpecifier'
103
+ ) {
104
+ return
105
+ }
106
+
107
+ // Skip if it's already part of a MemberExpression (handled above)
108
+ if (parent.type === 'MemberExpression' && parent.object === node) {
109
+ return
110
+ }
111
+
112
+ // Check if this is a direct reference to localStorage/sessionStorage
113
+ if (!Object.hasOwn(apiMap, node.name)) {
114
+ return
115
+ }
116
+
117
+ // Skip if it's the variable name being declared (e.g., const localStorage = ...)
118
+ if (parent.type === 'VariableDeclarator' && parent.id === node) {
119
+ return
120
+ }
121
+
122
+ context.report({
123
+ node,
124
+ messageId: 'preferAsyncStorage',
125
+ })
126
+ },
127
+ }
128
+ },
129
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -50,19 +50,20 @@
50
50
  "@types/react-window": "1.8.8",
51
51
  "@types/url-parse": "1.4.9",
52
52
  "@types/video.js": "7.3.58",
53
+ "@typescript-eslint/parser": "^7.0.0",
53
54
  "@vitejs/plugin-react": "4.5.1",
54
55
  "class-variance-authority": "0.7.1",
55
56
  "clsx": "2.1.1",
56
57
  "color": "4.2.3",
57
58
  "embla-carousel-react": "8.6.0",
59
+ "eslint": "^8.57.0",
60
+ "eslint-plugin-react": "^7.37.5",
58
61
  "js-base64": "3.7.7",
59
62
  "lodash": "4.17.21",
60
63
  "lucide-react": "0.513.0",
61
64
  "motion": "12.17.3",
62
65
  "next-themes": "0.4.6",
63
66
  "radix-ui": "1.4.2",
64
- "eslint": "^8.57.0",
65
- "@typescript-eslint/parser": "^7.0.0",
66
67
  "react-intersection-observer": "9.13.1",
67
68
  "react-resizable-panels": "3.0.2",
68
69
  "react-router": "7.7.0",