@open-dy/vibecoding-sdk-taro 0.1.0 → 0.1.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.
- package/dist/eslint/eslint-plugin-custom-navigation.mjs +12 -1
- package/dist/eslint/eslint-plugin-network-json-body.mjs +103 -0
- package/dist/eslint/taro.mjs +3 -0
- package/package.json +1 -1
- package/release-provenance.json +4 -4
- package/src/eslint/eslint-plugin-custom-navigation.mjs +12 -1
- package/src/eslint/eslint-plugin-network-json-body.mjs +103 -0
- package/src/eslint/taro.mjs +3 -0
|
@@ -217,15 +217,26 @@ const pagesMustUseCustomNavSpace = {
|
|
|
217
217
|
if (!/\/src\/pages\/.+\/index\.[jt]sx$/.test(filePath)) return {};
|
|
218
218
|
|
|
219
219
|
let used = false;
|
|
220
|
+
const sarImports = new Set();
|
|
221
|
+
const renderedComponents = new Set();
|
|
220
222
|
|
|
221
223
|
return {
|
|
222
224
|
ImportDeclaration(node) {
|
|
223
225
|
if (typeof node.source.value === 'string' && CUSTOM_NAV_IMPORTS.has(node.source.value)) {
|
|
224
226
|
used = true;
|
|
225
227
|
}
|
|
228
|
+
// SAR owns HUD safe spacing; adding host spacing would shrink the canvas.
|
|
229
|
+
if (typeof node.source.value === 'string' && /^(?:@\/|(?:\.\.\/)+)components\/(?:SarGame|sar-game)(?:\/index)?$/.test(node.source.value)) {
|
|
230
|
+
for (const specifier of node.specifiers) {
|
|
231
|
+
if (specifier.type === 'ImportDefaultSpecifier') sarImports.add(specifier.local.name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
JSXOpeningElement(node) {
|
|
236
|
+
if (node.name.type === 'JSXIdentifier') renderedComponents.add(node.name.name);
|
|
226
237
|
},
|
|
227
238
|
'Program:exit'(node) {
|
|
228
|
-
if (used) return;
|
|
239
|
+
if (used || [...sarImports].some(name => renderedComponents.has(name))) return;
|
|
229
240
|
context.report({
|
|
230
241
|
node,
|
|
231
242
|
message:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Inspect only statically known options; dynamic request builders need runtime tests.
|
|
2
|
+
const unknown = Symbol('unknown');
|
|
3
|
+
|
|
4
|
+
function propertyName(node) {
|
|
5
|
+
if (!node.computed && node.key.type === 'Identifier') return node.key.name;
|
|
6
|
+
return node.key.type === 'Literal' ? node.key.value : unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function property(object, name, ignoreCase = false) {
|
|
10
|
+
if (!object || object.type !== 'ObjectExpression') return unknown;
|
|
11
|
+
let result;
|
|
12
|
+
for (const item of object.properties) {
|
|
13
|
+
if (item.type !== 'Property') {
|
|
14
|
+
result = unknown;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const key = propertyName(item);
|
|
18
|
+
if (key === unknown) {
|
|
19
|
+
result = unknown;
|
|
20
|
+
} else if (typeof key === 'string' && (ignoreCase ? key.toLowerCase() === name : key === name)) {
|
|
21
|
+
result = item.value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function literal(node) {
|
|
28
|
+
if (node?.type === 'Literal') return node.value;
|
|
29
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
30
|
+
return node.quasis[0].value.cooked;
|
|
31
|
+
}
|
|
32
|
+
return unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function variableFor(context, node) {
|
|
36
|
+
let scope = context.sourceCode.getScope(node);
|
|
37
|
+
while (scope) {
|
|
38
|
+
const variable = scope.set.get(node.name);
|
|
39
|
+
if (variable) return variable;
|
|
40
|
+
scope = scope.upper;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isNetworkImport(context, node) {
|
|
45
|
+
if (node?.type !== 'Identifier') return false;
|
|
46
|
+
return variableFor(context, node)?.defs.some(def =>
|
|
47
|
+
def.type === 'ImportBinding' &&
|
|
48
|
+
def.node.type === 'ImportSpecifier' &&
|
|
49
|
+
(def.node.imported.name ?? def.node.imported.value) === 'Network' &&
|
|
50
|
+
/(?:^|\/)network(?:\.ts)?$/.test(def.parent.source.value),
|
|
51
|
+
) ?? false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isEmptyBody(context, node) {
|
|
55
|
+
if (node === undefined) return true;
|
|
56
|
+
if (node === unknown) return false;
|
|
57
|
+
if (node.type === 'UnaryExpression' && node.operator === 'void') return true;
|
|
58
|
+
if (node.type === 'Identifier' && node.name === 'undefined') {
|
|
59
|
+
return !variableFor(context, node)?.defs.length;
|
|
60
|
+
}
|
|
61
|
+
const value = literal(node);
|
|
62
|
+
return value === null || (typeof value === 'string' && value.trim() === '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
rules: {
|
|
67
|
+
'require-delete-json-body': {
|
|
68
|
+
meta: {
|
|
69
|
+
type: 'problem',
|
|
70
|
+
schema: [],
|
|
71
|
+
docs: { description: 'Require a non-empty JSON body for Network.request DELETE calls.' },
|
|
72
|
+
messages: {
|
|
73
|
+
empty: 'Network.request 的 DELETE 请求使用默认或显式 JSON Content-Type 时,必须传非空 data;无业务参数请传 data: {}。否则真机可能触发 Fastify FST_ERR_CTP_EMPTY_JSON_BODY。',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
create(context) {
|
|
77
|
+
return {
|
|
78
|
+
CallExpression(node) {
|
|
79
|
+
const callee = node.callee;
|
|
80
|
+
if (callee.type !== 'MemberExpression' || !isNetworkImport(context, callee.object)) return;
|
|
81
|
+
const methodName = callee.computed ? literal(callee.property) : callee.property.name;
|
|
82
|
+
if (methodName !== 'request') return;
|
|
83
|
+
const options = node.arguments[0];
|
|
84
|
+
const method = literal(property(options, 'method'));
|
|
85
|
+
if (typeof method !== 'string' || method.toUpperCase() !== 'DELETE') return;
|
|
86
|
+
const header = property(options, 'header');
|
|
87
|
+
if (header !== undefined) {
|
|
88
|
+
const contentType = property(header, 'content-type', true);
|
|
89
|
+
if (contentType === unknown) return;
|
|
90
|
+
if (contentType !== undefined) {
|
|
91
|
+
const value = literal(contentType);
|
|
92
|
+
if (typeof value !== 'string' || !/^application\/json(?:\s*;|\s*$)/i.test(value.trim())) return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isEmptyBody(context, property(options, 'data'))) {
|
|
96
|
+
context.report({ node: options, messageId: 'empty' });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
package/dist/eslint/taro.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import importPlugin from 'eslint-plugin-import';
|
|
|
7
7
|
import globals from 'globals';
|
|
8
8
|
import microappPages from './eslint-plugin-microapp-pages.mjs';
|
|
9
9
|
import customNavigation from './eslint-plugin-custom-navigation.mjs';
|
|
10
|
+
import networkJsonBody from './eslint-plugin-network-json-body.mjs';
|
|
10
11
|
|
|
11
12
|
const REMOTE_CSS_IMPORT_PATTERN =
|
|
12
13
|
/@import\s+(?:url\(\s*['"]?((?:https?:)?\/\/[^'")\s]+)['"]?\s*\)|['"]((?:https?:)?\/\/[^'"\s]+)['"])/g;
|
|
@@ -474,6 +475,7 @@ export function createMicroappTaroEslintConfig(options = {}) {
|
|
|
474
475
|
files: ['src/**/*.{js,jsx,ts,tsx}'],
|
|
475
476
|
ignores: ['src/network.ts'],
|
|
476
477
|
plugins: {
|
|
478
|
+
networkJsonBody,
|
|
477
479
|
lucide: lucideColorCheckPlugin,
|
|
478
480
|
microappPages,
|
|
479
481
|
customNavigation,
|
|
@@ -482,6 +484,7 @@ export function createMicroappTaroEslintConfig(options = {}) {
|
|
|
482
484
|
rules: {
|
|
483
485
|
'customNavigation/require-custom-navigation-style': 'error',
|
|
484
486
|
'customNavigation/pages-must-use-custom-nav-space': 'error',
|
|
487
|
+
'networkJsonBody/require-delete-json-body': 'error',
|
|
485
488
|
'microappPages/nav-url-in-app-pages': 'error',
|
|
486
489
|
// 'microappPages/page-dirs-in-app-pages': 'error',
|
|
487
490
|
'microappPages/declared-pages-must-exist': 'error',
|
package/package.json
CHANGED
package/release-provenance.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"producer": "vibecoding-sdk-taro",
|
|
4
4
|
"packageName": "@open-dy/vibecoding-sdk-taro",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.1",
|
|
6
6
|
"source": {
|
|
7
|
-
"digest": "sha256-
|
|
8
|
-
"files":
|
|
7
|
+
"digest": "sha256-358d8b2d8be744d7b7c0913505fdd641cd4ae4c66bbb00a5655d95e068035659",
|
|
8
|
+
"files": 137
|
|
9
9
|
},
|
|
10
|
-
"payload": "sha256-
|
|
10
|
+
"payload": "sha256-dc27095cefee1baf948b40243298a9a922da704532f51a39f02cef732d57e0c1",
|
|
11
11
|
"toolchain": {
|
|
12
12
|
"node": "v22.22.3",
|
|
13
13
|
"packageManager": null
|
|
@@ -217,15 +217,26 @@ const pagesMustUseCustomNavSpace = {
|
|
|
217
217
|
if (!/\/src\/pages\/.+\/index\.[jt]sx$/.test(filePath)) return {};
|
|
218
218
|
|
|
219
219
|
let used = false;
|
|
220
|
+
const sarImports = new Set();
|
|
221
|
+
const renderedComponents = new Set();
|
|
220
222
|
|
|
221
223
|
return {
|
|
222
224
|
ImportDeclaration(node) {
|
|
223
225
|
if (typeof node.source.value === 'string' && CUSTOM_NAV_IMPORTS.has(node.source.value)) {
|
|
224
226
|
used = true;
|
|
225
227
|
}
|
|
228
|
+
// SAR owns HUD safe spacing; adding host spacing would shrink the canvas.
|
|
229
|
+
if (typeof node.source.value === 'string' && /^(?:@\/|(?:\.\.\/)+)components\/(?:SarGame|sar-game)(?:\/index)?$/.test(node.source.value)) {
|
|
230
|
+
for (const specifier of node.specifiers) {
|
|
231
|
+
if (specifier.type === 'ImportDefaultSpecifier') sarImports.add(specifier.local.name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
JSXOpeningElement(node) {
|
|
236
|
+
if (node.name.type === 'JSXIdentifier') renderedComponents.add(node.name.name);
|
|
226
237
|
},
|
|
227
238
|
'Program:exit'(node) {
|
|
228
|
-
if (used) return;
|
|
239
|
+
if (used || [...sarImports].some(name => renderedComponents.has(name))) return;
|
|
229
240
|
context.report({
|
|
230
241
|
node,
|
|
231
242
|
message:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Inspect only statically known options; dynamic request builders need runtime tests.
|
|
2
|
+
const unknown = Symbol('unknown');
|
|
3
|
+
|
|
4
|
+
function propertyName(node) {
|
|
5
|
+
if (!node.computed && node.key.type === 'Identifier') return node.key.name;
|
|
6
|
+
return node.key.type === 'Literal' ? node.key.value : unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function property(object, name, ignoreCase = false) {
|
|
10
|
+
if (!object || object.type !== 'ObjectExpression') return unknown;
|
|
11
|
+
let result;
|
|
12
|
+
for (const item of object.properties) {
|
|
13
|
+
if (item.type !== 'Property') {
|
|
14
|
+
result = unknown;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const key = propertyName(item);
|
|
18
|
+
if (key === unknown) {
|
|
19
|
+
result = unknown;
|
|
20
|
+
} else if (typeof key === 'string' && (ignoreCase ? key.toLowerCase() === name : key === name)) {
|
|
21
|
+
result = item.value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function literal(node) {
|
|
28
|
+
if (node?.type === 'Literal') return node.value;
|
|
29
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
30
|
+
return node.quasis[0].value.cooked;
|
|
31
|
+
}
|
|
32
|
+
return unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function variableFor(context, node) {
|
|
36
|
+
let scope = context.sourceCode.getScope(node);
|
|
37
|
+
while (scope) {
|
|
38
|
+
const variable = scope.set.get(node.name);
|
|
39
|
+
if (variable) return variable;
|
|
40
|
+
scope = scope.upper;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isNetworkImport(context, node) {
|
|
45
|
+
if (node?.type !== 'Identifier') return false;
|
|
46
|
+
return variableFor(context, node)?.defs.some(def =>
|
|
47
|
+
def.type === 'ImportBinding' &&
|
|
48
|
+
def.node.type === 'ImportSpecifier' &&
|
|
49
|
+
(def.node.imported.name ?? def.node.imported.value) === 'Network' &&
|
|
50
|
+
/(?:^|\/)network(?:\.ts)?$/.test(def.parent.source.value),
|
|
51
|
+
) ?? false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isEmptyBody(context, node) {
|
|
55
|
+
if (node === undefined) return true;
|
|
56
|
+
if (node === unknown) return false;
|
|
57
|
+
if (node.type === 'UnaryExpression' && node.operator === 'void') return true;
|
|
58
|
+
if (node.type === 'Identifier' && node.name === 'undefined') {
|
|
59
|
+
return !variableFor(context, node)?.defs.length;
|
|
60
|
+
}
|
|
61
|
+
const value = literal(node);
|
|
62
|
+
return value === null || (typeof value === 'string' && value.trim() === '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
rules: {
|
|
67
|
+
'require-delete-json-body': {
|
|
68
|
+
meta: {
|
|
69
|
+
type: 'problem',
|
|
70
|
+
schema: [],
|
|
71
|
+
docs: { description: 'Require a non-empty JSON body for Network.request DELETE calls.' },
|
|
72
|
+
messages: {
|
|
73
|
+
empty: 'Network.request 的 DELETE 请求使用默认或显式 JSON Content-Type 时,必须传非空 data;无业务参数请传 data: {}。否则真机可能触发 Fastify FST_ERR_CTP_EMPTY_JSON_BODY。',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
create(context) {
|
|
77
|
+
return {
|
|
78
|
+
CallExpression(node) {
|
|
79
|
+
const callee = node.callee;
|
|
80
|
+
if (callee.type !== 'MemberExpression' || !isNetworkImport(context, callee.object)) return;
|
|
81
|
+
const methodName = callee.computed ? literal(callee.property) : callee.property.name;
|
|
82
|
+
if (methodName !== 'request') return;
|
|
83
|
+
const options = node.arguments[0];
|
|
84
|
+
const method = literal(property(options, 'method'));
|
|
85
|
+
if (typeof method !== 'string' || method.toUpperCase() !== 'DELETE') return;
|
|
86
|
+
const header = property(options, 'header');
|
|
87
|
+
if (header !== undefined) {
|
|
88
|
+
const contentType = property(header, 'content-type', true);
|
|
89
|
+
if (contentType === unknown) return;
|
|
90
|
+
if (contentType !== undefined) {
|
|
91
|
+
const value = literal(contentType);
|
|
92
|
+
if (typeof value !== 'string' || !/^application\/json(?:\s*;|\s*$)/i.test(value.trim())) return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isEmptyBody(context, property(options, 'data'))) {
|
|
96
|
+
context.report({ node: options, messageId: 'empty' });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
package/src/eslint/taro.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import importPlugin from 'eslint-plugin-import';
|
|
|
7
7
|
import globals from 'globals';
|
|
8
8
|
import microappPages from './eslint-plugin-microapp-pages.mjs';
|
|
9
9
|
import customNavigation from './eslint-plugin-custom-navigation.mjs';
|
|
10
|
+
import networkJsonBody from './eslint-plugin-network-json-body.mjs';
|
|
10
11
|
|
|
11
12
|
const REMOTE_CSS_IMPORT_PATTERN =
|
|
12
13
|
/@import\s+(?:url\(\s*['"]?((?:https?:)?\/\/[^'")\s]+)['"]?\s*\)|['"]((?:https?:)?\/\/[^'"\s]+)['"])/g;
|
|
@@ -474,6 +475,7 @@ export function createMicroappTaroEslintConfig(options = {}) {
|
|
|
474
475
|
files: ['src/**/*.{js,jsx,ts,tsx}'],
|
|
475
476
|
ignores: ['src/network.ts'],
|
|
476
477
|
plugins: {
|
|
478
|
+
networkJsonBody,
|
|
477
479
|
lucide: lucideColorCheckPlugin,
|
|
478
480
|
microappPages,
|
|
479
481
|
customNavigation,
|
|
@@ -482,6 +484,7 @@ export function createMicroappTaroEslintConfig(options = {}) {
|
|
|
482
484
|
rules: {
|
|
483
485
|
'customNavigation/require-custom-navigation-style': 'error',
|
|
484
486
|
'customNavigation/pages-must-use-custom-nav-space': 'error',
|
|
487
|
+
'networkJsonBody/require-delete-json-body': 'error',
|
|
485
488
|
'microappPages/nav-url-in-app-pages': 'error',
|
|
486
489
|
// 'microappPages/page-dirs-in-app-pages': 'error',
|
|
487
490
|
'microappPages/declared-pages-must-exist': 'error',
|