@open-dy/vibecoding-sdk-taro 0.1.11 → 0.1.12

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,3 +1,4 @@
1
+ export { CARD_TYPE_RULES_VERSION } from '../../../eslint/taro.mjs'
1
2
  import { createMicroappTaroEslintConfig } from '../../../eslint/taro.mjs'
2
3
 
3
4
  /**
@@ -0,0 +1,96 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ /**
6
+ * @desc 校验卡片形态;缺少字段保留历史兴趣卡语义,不写回配置。
7
+ * @params {object} config 已解析的工程配置。
8
+ * @params {string} [source] 错误信息中的配置来源。
9
+ * @return {'interest'|'post'} 卡片形态。
10
+ * @example parseCardType({ cardType: 'post' })
11
+ */
12
+ export function parseCardType(config, source = 'card-routing.json') {
13
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
14
+ throw new Error(`${source} must contain an object`)
15
+ }
16
+ if (!Object.hasOwn(config, 'cardType')) return 'interest'
17
+ if (config.cardType !== 'interest' && config.cardType !== 'post') {
18
+ throw new Error(`${source}: cardType must be interest or post; got ${JSON.stringify(config.cardType)}`)
19
+ }
20
+ return config.cardType
21
+ }
22
+
23
+ /**
24
+ * @desc 从明确的宿主工程目录读取形态,供 CLI 与两端 SDK 共用。
25
+ * @params {{projectRoot: string}} options 宿主工程绝对路径;OpenCard 配置也传宿主目录。
26
+ * @return {{cardType: 'interest'|'post', configPath: string}} 形态及配置来源。
27
+ * @example readCardProfile({ projectRoot: '/workspace/project/card-app' })
28
+ */
29
+ export function readCardProfile({ projectRoot }) {
30
+ if (typeof projectRoot !== 'string' || !path.isAbsolute(projectRoot)) {
31
+ throw new Error('readCardProfile requires an absolute projectRoot')
32
+ }
33
+ const configPath = path.join(projectRoot, 'config/card-routing.json')
34
+ let raw
35
+ try {
36
+ raw = readFileSync(configPath, 'utf8')
37
+ } catch (error) {
38
+ if (error.code === 'ENOENT') return { cardType: 'interest', configPath }
39
+ throw error
40
+ }
41
+ let config
42
+ try {
43
+ config = JSON.parse(raw)
44
+ } catch (error) {
45
+ throw new Error(`${configPath} must contain valid JSON: ${error.message}`)
46
+ }
47
+ return { cardType: parseCardType(config, configPath), configPath }
48
+ }
49
+
50
+ function findEslintHost(filename, target) {
51
+ let directory = path.dirname(filename)
52
+ while (true) {
53
+ // Stop at the owning package/config instead of inheriting another project's
54
+ // routing. An OpenCard package belongs to its immediate host directory.
55
+ if (existsSync(path.join(directory, 'package.json')) ||
56
+ existsSync(path.join(directory, 'eslint.config.mjs')) ||
57
+ existsSync(path.join(directory, 'project.card.json'))) {
58
+ return target === 'opencard' ? path.dirname(directory) : directory
59
+ }
60
+ if (existsSync(path.join(directory, '.git'))) return undefined
61
+ const parent = path.dirname(directory)
62
+ if (parent === directory) return undefined
63
+ directory = parent
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @desc 创建作品卡文件匹配器;CLI 传入形态,直接 ESLint 从待检查文件定位工程。
69
+ * @params {{cardType?: 'interest'|'post', target: 'taro'|'opencard'}} options SDK 配置及目标端。
70
+ * @params {object} [environment] CLI 子进程环境。
71
+ * @return {(filename: string) => boolean} 是否启用作品卡配置。
72
+ * @example createPostCardMatcher({ target: 'taro' })('/workspace/app/src/index.ts')
73
+ */
74
+ export function createPostCardMatcher(options, environment = process.env) {
75
+ const configured = options.cardType ?? environment.VIBECODING_CARD_TYPE
76
+ // An explicitly supplied null must not silently fall back to legacy rules.
77
+ const explicit = options.cardType !== undefined
78
+ if (explicit || configured !== undefined) {
79
+ const cardType = parseCardType({ cardType: explicit ? options.cardType : configured }, 'ESLint cardType')
80
+ return () => cardType === 'post'
81
+ }
82
+ return filename => {
83
+ const projectRoot = findEslintHost(filename, options.target)
84
+ return projectRoot ? readCardProfile({ projectRoot }).cardType === 'post' : false
85
+ }
86
+ }
87
+
88
+ // The legacy shell entry uses the same parser as the SDK-owned ESLint configs.
89
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
90
+ try {
91
+ console.log(readCardProfile({ projectRoot: path.resolve(process.argv[2]) }).cardType)
92
+ } catch (error) {
93
+ console.error(error.message)
94
+ process.exitCode = 1
95
+ }
96
+ }
@@ -0,0 +1,82 @@
1
+ const TARO_SOURCES = new Set(['@tarojs/taro'])
2
+ const TAB_APIS = new Set([
3
+ 'switchTab', 'setTabBarItem', 'setTabBarStyle', 'showTabBar', 'hideTabBar',
4
+ 'setTabBarBadge', 'removeTabBarBadge', 'showTabBarRedDot', 'hideTabBarRedDot',
5
+ 'getTabBar', 'onTabItemTap', 'useTabItemTap',
6
+ ])
7
+
8
+ function key(node) {
9
+ return node?.type === 'Identifier' ? node.name
10
+ : node?.type === 'Literal' && typeof node.value === 'string' ? node.value : null
11
+ }
12
+
13
+ function platformObject(node, context, seen = new Set()) {
14
+ if (!node || seen.has(node)) return false
15
+ seen.add(node)
16
+ if (['TSAsExpression', 'TSNonNullExpression', 'ChainExpression'].includes(node.type)) {
17
+ return platformObject(node.expression, context, seen)
18
+ }
19
+ if (node.type === 'CallExpression') {
20
+ return node.callee.type === 'Identifier' && node.callee.name === 'require'
21
+ && TARO_SOURCES.has(node.arguments[0]?.value)
22
+ }
23
+ if (node.type === 'MemberExpression') {
24
+ return ['globalThis', 'window'].includes(node.object?.name)
25
+ && ['tt', 'Taro'].includes(key(node.property))
26
+ }
27
+ if (node.type !== 'Identifier') return false
28
+ let scope = context.sourceCode.getScope(node)
29
+ let variable
30
+ while (scope && !variable) { variable = scope.set.get(node.name); scope = scope.upper }
31
+ if (!variable?.defs.length) return ['tt', 'Taro'].includes(node.name)
32
+ return variable.defs.some(def => {
33
+ if (def.type === 'ImportBinding') {
34
+ return TARO_SOURCES.has(def.parent?.source?.value)
35
+ && ['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type)
36
+ }
37
+ return def.type === 'Variable' && platformObject(def.node.init, context, seen)
38
+ })
39
+ }
40
+
41
+ export default {
42
+ rules: {
43
+ 'no-platform-tabbar': {
44
+ meta: { type: 'problem', schema: [], messages: {
45
+ config: '作品卡不支持平台 tabBar(含 custom 模式)。请用普通组件绘制导航并实现页面或状态切换。',
46
+ api: '作品卡不支持 {{name}}。请使用普通页面路由或组件状态切换,不调用平台 TabBar。',
47
+ } },
48
+ create(context) {
49
+ const filename = context.filename.replaceAll('\\', '/')
50
+ const isAppConfig = /\/app\.config\.[cm]?[jt]s$/.test(filename)
51
+ const report = (node, name) => {
52
+ if (TAB_APIS.has(name)) context.report({ node, messageId: 'api', data: { name } })
53
+ }
54
+ return {
55
+ Property(node) {
56
+ if (isAppConfig && key(node.key) === 'tabBar') context.report({ node, messageId: 'config' })
57
+ if (node.parent.type === 'ObjectPattern' && platformObject(node.parent.parent?.init, context)) {
58
+ report(node, key(node.key))
59
+ }
60
+ },
61
+ ImportSpecifier(node) {
62
+ if (node.importKind !== 'type' && node.parent.importKind !== 'type'
63
+ && TARO_SOURCES.has(node.parent.source.value)) report(node, key(node.imported))
64
+ },
65
+ ExportSpecifier(node) {
66
+ if (TARO_SOURCES.has(node.parent.source?.value)) report(node, key(node.local))
67
+ },
68
+ MemberExpression(node) {
69
+ const name = key(node.property)
70
+ if (platformObject(node.object, context) || (name === 'getTabBar' && node.object.type === 'ThisExpression')) {
71
+ report(node, name)
72
+ }
73
+ },
74
+ JSXAttribute(node) {
75
+ const value = node.value?.type === 'JSXExpressionContainer' ? node.value.expression?.value : node.value?.value
76
+ if (node.name.name === 'openType' && value === 'switchTab') report(node, 'switchTab')
77
+ },
78
+ }
79
+ },
80
+ },
81
+ },
82
+ }
@@ -1,3 +1,6 @@
1
+ import postCard from './eslint-plugin-post-card.mjs';
2
+ import { createPostCardMatcher } from './card-profile.mjs';
3
+ export const CARD_TYPE_RULES_VERSION = 1;
1
4
  import eslint from '@eslint/js';
2
5
  import { fixupPluginRules } from '@eslint/compat';
3
6
  import tseslint from 'typescript-eslint';
@@ -381,6 +384,7 @@ const indexPageRestrictedSyntaxRules = [
381
384
  ];
382
385
 
383
386
  export function createMicroappTaroEslintConfig(options = {}) {
387
+ const matchesPostCard = createPostCardMatcher({ cardType: options.cardType, target: 'taro' });
384
388
  const lucideColorCheckPlugin = createLucideColorCheckPlugin(options.lucideImportSources)
385
389
  const pageRestrictedSyntaxRules = createPageRestrictedSyntaxRules(
386
390
  options.preferredUiImportBase,
@@ -581,6 +585,12 @@ export function createMicroappTaroEslintConfig(options = {}) {
581
585
  },
582
586
  );
583
587
 
588
+ defaults.push({
589
+ files: [['src/**/*.{js,jsx,ts,tsx}', matchesPostCard]],
590
+ settings: { cardType: 'post', cardTypeRulesVersion: CARD_TYPE_RULES_VERSION },
591
+ plugins: { postCard },
592
+ rules: { 'postCard/no-platform-tabbar': 'error' },
593
+ });
584
594
  const overrides = options.overrides
585
595
  if (!overrides) return defaults
586
596
  if (typeof overrides === 'function') return overrides(defaults) ?? defaults
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-dy/vibecoding-sdk-taro",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Runtime, UI, build and ESLint capabilities for Douyin Taro templates.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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.11",
5
+ "version": "0.1.12",
6
6
  "source": {
7
- "digest": "sha256-65487d3df50200d0d27332e26f04e9d3520d3313d31eae611483a3078b0f658c",
8
- "files": 159
7
+ "digest": "sha256-343c13406654fbfb96b1ae78b83b481b68135affa5484b7230646fda91568c26",
8
+ "files": 162
9
9
  },
10
- "payload": "sha256-ff279eab5b08f88997926c9180ecd6a50e8d0f724cf81a76f314f477b116cc40",
10
+ "payload": "sha256-e969e73830ccf0dfe00b29542c2b992d78d6640c59b04031c7ab9f3f754731de",
11
11
  "toolchain": {
12
12
  "node": "v22.22.3",
13
13
  "runtime": {
@@ -1,3 +1,4 @@
1
+ export { CARD_TYPE_RULES_VERSION } from '../../../eslint/taro.mjs'
1
2
  import { createMicroappTaroEslintConfig } from '../../../eslint/taro.mjs'
2
3
 
3
4
  /**
@@ -0,0 +1,2 @@
1
+ // SDK builds embed this shared, dependency-free module beside the ESLint entry.
2
+ export { createPostCardMatcher } from '../../../vibecoding-cli/src/config/card-profile.mjs'
@@ -0,0 +1,82 @@
1
+ const TARO_SOURCES = new Set(['@tarojs/taro'])
2
+ const TAB_APIS = new Set([
3
+ 'switchTab', 'setTabBarItem', 'setTabBarStyle', 'showTabBar', 'hideTabBar',
4
+ 'setTabBarBadge', 'removeTabBarBadge', 'showTabBarRedDot', 'hideTabBarRedDot',
5
+ 'getTabBar', 'onTabItemTap', 'useTabItemTap',
6
+ ])
7
+
8
+ function key(node) {
9
+ return node?.type === 'Identifier' ? node.name
10
+ : node?.type === 'Literal' && typeof node.value === 'string' ? node.value : null
11
+ }
12
+
13
+ function platformObject(node, context, seen = new Set()) {
14
+ if (!node || seen.has(node)) return false
15
+ seen.add(node)
16
+ if (['TSAsExpression', 'TSNonNullExpression', 'ChainExpression'].includes(node.type)) {
17
+ return platformObject(node.expression, context, seen)
18
+ }
19
+ if (node.type === 'CallExpression') {
20
+ return node.callee.type === 'Identifier' && node.callee.name === 'require'
21
+ && TARO_SOURCES.has(node.arguments[0]?.value)
22
+ }
23
+ if (node.type === 'MemberExpression') {
24
+ return ['globalThis', 'window'].includes(node.object?.name)
25
+ && ['tt', 'Taro'].includes(key(node.property))
26
+ }
27
+ if (node.type !== 'Identifier') return false
28
+ let scope = context.sourceCode.getScope(node)
29
+ let variable
30
+ while (scope && !variable) { variable = scope.set.get(node.name); scope = scope.upper }
31
+ if (!variable?.defs.length) return ['tt', 'Taro'].includes(node.name)
32
+ return variable.defs.some(def => {
33
+ if (def.type === 'ImportBinding') {
34
+ return TARO_SOURCES.has(def.parent?.source?.value)
35
+ && ['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type)
36
+ }
37
+ return def.type === 'Variable' && platformObject(def.node.init, context, seen)
38
+ })
39
+ }
40
+
41
+ export default {
42
+ rules: {
43
+ 'no-platform-tabbar': {
44
+ meta: { type: 'problem', schema: [], messages: {
45
+ config: '作品卡不支持平台 tabBar(含 custom 模式)。请用普通组件绘制导航并实现页面或状态切换。',
46
+ api: '作品卡不支持 {{name}}。请使用普通页面路由或组件状态切换,不调用平台 TabBar。',
47
+ } },
48
+ create(context) {
49
+ const filename = context.filename.replaceAll('\\', '/')
50
+ const isAppConfig = /\/app\.config\.[cm]?[jt]s$/.test(filename)
51
+ const report = (node, name) => {
52
+ if (TAB_APIS.has(name)) context.report({ node, messageId: 'api', data: { name } })
53
+ }
54
+ return {
55
+ Property(node) {
56
+ if (isAppConfig && key(node.key) === 'tabBar') context.report({ node, messageId: 'config' })
57
+ if (node.parent.type === 'ObjectPattern' && platformObject(node.parent.parent?.init, context)) {
58
+ report(node, key(node.key))
59
+ }
60
+ },
61
+ ImportSpecifier(node) {
62
+ if (node.importKind !== 'type' && node.parent.importKind !== 'type'
63
+ && TARO_SOURCES.has(node.parent.source.value)) report(node, key(node.imported))
64
+ },
65
+ ExportSpecifier(node) {
66
+ if (TARO_SOURCES.has(node.parent.source?.value)) report(node, key(node.local))
67
+ },
68
+ MemberExpression(node) {
69
+ const name = key(node.property)
70
+ if (platformObject(node.object, context) || (name === 'getTabBar' && node.object.type === 'ThisExpression')) {
71
+ report(node, name)
72
+ }
73
+ },
74
+ JSXAttribute(node) {
75
+ const value = node.value?.type === 'JSXExpressionContainer' ? node.value.expression?.value : node.value?.value
76
+ if (node.name.name === 'openType' && value === 'switchTab') report(node, 'switchTab')
77
+ },
78
+ }
79
+ },
80
+ },
81
+ },
82
+ }
@@ -1,3 +1,6 @@
1
+ import postCard from './eslint-plugin-post-card.mjs';
2
+ import { createPostCardMatcher } from './card-profile.mjs';
3
+ export const CARD_TYPE_RULES_VERSION = 1;
1
4
  import eslint from '@eslint/js';
2
5
  import { fixupPluginRules } from '@eslint/compat';
3
6
  import tseslint from 'typescript-eslint';
@@ -381,6 +384,7 @@ const indexPageRestrictedSyntaxRules = [
381
384
  ];
382
385
 
383
386
  export function createMicroappTaroEslintConfig(options = {}) {
387
+ const matchesPostCard = createPostCardMatcher({ cardType: options.cardType, target: 'taro' });
384
388
  const lucideColorCheckPlugin = createLucideColorCheckPlugin(options.lucideImportSources)
385
389
  const pageRestrictedSyntaxRules = createPageRestrictedSyntaxRules(
386
390
  options.preferredUiImportBase,
@@ -581,6 +585,12 @@ export function createMicroappTaroEslintConfig(options = {}) {
581
585
  },
582
586
  );
583
587
 
588
+ defaults.push({
589
+ files: [['src/**/*.{js,jsx,ts,tsx}', matchesPostCard]],
590
+ settings: { cardType: 'post', cardTypeRulesVersion: CARD_TYPE_RULES_VERSION },
591
+ plugins: { postCard },
592
+ rules: { 'postCard/no-platform-tabbar': 'error' },
593
+ });
584
594
  const overrides = options.overrides
585
595
  if (!overrides) return defaults
586
596
  if (typeof overrides === 'function') return overrides(defaults) ?? defaults