@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 +6 -0
- package/eslint/index.cjs +2 -0
- package/eslint/rules/prefer-sdk-hooks.cjs +129 -0
- package/package.json +4 -3
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.
|
|
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",
|