@shopify/shop-minis-react 0.3.0 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import { __module as t } from "../../../../../../_virtual/index11.js";
1
+ import { __module as t } from "../../../../../../_virtual/index10.js";
2
2
  import { __require as z } from "../../../is-arrayish@0.3.2/node_modules/is-arrayish/index.js";
3
3
  var l;
4
4
  function v() {
@@ -1,4 +1,4 @@
1
- import { __module as r } from "../../../../../../../_virtual/index10.js";
1
+ import { __module as r } from "../../../../../../../_virtual/index11.js";
2
2
  import { __require as o } from "../cjs/use-sync-external-store-shim.production.js";
3
3
  import { __require as i } from "../cjs/use-sync-external-store-shim.development.js";
4
4
  var e;
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,131 @@
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 from @shopify/shop-minis-react instead of localStorage. The SDK hook provides async storage that works reliably in the Shop mini-app environment.',
18
+ preferSecureStorage:
19
+ 'Use useSecureStorage from @shopify/shop-minis-react instead of localStorage for sensitive data. The SDK hook provides encrypted storage.',
20
+ },
21
+ schema: [
22
+ {
23
+ type: 'object',
24
+ properties: {
25
+ apis: {
26
+ type: 'object',
27
+ description: 'Map of native APIs to SDK hooks',
28
+ additionalProperties: {
29
+ type: 'string',
30
+ },
31
+ },
32
+ },
33
+ additionalProperties: false,
34
+ },
35
+ ],
36
+ },
37
+
38
+ create(context) {
39
+ // Default API mappings
40
+ const defaultApis = {
41
+ localStorage: 'useAsyncStorage',
42
+ sessionStorage: 'useAsyncStorage',
43
+ // Future additions will go here:
44
+ // navigator.geolocation: 'useGeolocation',
45
+ // window.history: 'useNavigation',
46
+ }
47
+
48
+ // Get user configuration or use defaults
49
+ const options = context.options[0] || {}
50
+ const apiMap = {
51
+ ...defaultApis,
52
+ ...(options.apis || {}),
53
+ }
54
+
55
+ return {
56
+ MemberExpression(node) {
57
+ // Check for direct access: localStorage.getItem()
58
+ if (node.object.type === 'Identifier' && apiMap[node.object.name]) {
59
+ const apiName = node.object.name
60
+ const sdkHook = apiMap[apiName]
61
+
62
+ context.report({
63
+ node: node.object,
64
+ messageId: 'preferAsyncStorage',
65
+ data: {
66
+ nativeApi: apiName,
67
+ sdkHook,
68
+ },
69
+ })
70
+ return
71
+ }
72
+
73
+ // Check for global access: window.localStorage or globalThis.localStorage
74
+ if (
75
+ node.object.type === 'MemberExpression' &&
76
+ node.object.object.type === 'Identifier' &&
77
+ (node.object.object.name === 'window' ||
78
+ node.object.object.name === 'globalThis') &&
79
+ node.object.property.type === 'Identifier' &&
80
+ apiMap[node.object.property.name]
81
+ ) {
82
+ const apiName = node.object.property.name
83
+ const sdkHook = apiMap[apiName]
84
+
85
+ context.report({
86
+ node: node.object,
87
+ messageId: 'preferAsyncStorage',
88
+ data: {
89
+ nativeApi: apiName,
90
+ sdkHook,
91
+ },
92
+ })
93
+ }
94
+ },
95
+
96
+ // Also catch direct references to localStorage/sessionStorage
97
+ Identifier(node) {
98
+ // Only flag if it's being used, not just referenced in imports
99
+ const parent = node.parent
100
+
101
+ // Skip if it's part of an import statement
102
+ if (
103
+ parent.type === 'ImportSpecifier' ||
104
+ parent.type === 'ImportDefaultSpecifier'
105
+ ) {
106
+ return
107
+ }
108
+
109
+ // Skip if it's already part of a MemberExpression (handled above)
110
+ if (parent.type === 'MemberExpression' && parent.object === node) {
111
+ return
112
+ }
113
+
114
+ // Check if this is a direct reference to localStorage/sessionStorage
115
+ if (!apiMap[node.name]) {
116
+ return
117
+ }
118
+
119
+ // Skip if it's being declared as a variable
120
+ if (parent.type === 'VariableDeclarator' && parent.init === node) {
121
+ return
122
+ }
123
+
124
+ context.report({
125
+ node,
126
+ messageId: 'preferAsyncStorage',
127
+ })
128
+ },
129
+ }
130
+ },
131
+ }
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.1",
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",