@coze-arch/cli 0.0.12-alpha.e3bedc → 0.0.13-alpha.281fed
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/lib/__templates__/expo/client/components/PrettyScrollbar.tsx +43 -0
- package/lib/__templates__/expo/client/components/Provider.tsx +10 -7
- package/lib/__templates__/expo/client/eslint.config.mjs +3 -0
- package/lib/__templates__/expo/eslint-plugins/expo/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/expo/rule.js +105 -0
- package/lib/__templates__/expo/eslint-plugins/expo/tech.md +108 -0
- package/lib/__templates__/nextjs/AGENTS.md +5 -0
- package/lib/__templates__/nextjs/eslint.config.mjs +15 -0
- package/lib/__templates__/taro/src/presets/dev-debug.ts +2 -2
- package/lib/__templates__/vite/AGENTS.md +5 -0
- package/lib/cli.js +210 -10
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect, ReactNode } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const STYLE_ID = 'coze-pretty-scrollbar';
|
|
5
|
+
|
|
6
|
+
function WebOnlyPrettyScrollbar({ children }: { children: ReactNode }) {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (Platform.OS === 'web') {
|
|
9
|
+
let style = document.getElementById(STYLE_ID);
|
|
10
|
+
if (!style) {
|
|
11
|
+
style = document.createElement('style');
|
|
12
|
+
style.id = STYLE_ID;
|
|
13
|
+
style.textContent = `
|
|
14
|
+
::-webkit-scrollbar {
|
|
15
|
+
width: 4px;
|
|
16
|
+
height: 4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
::-webkit-scrollbar-track {
|
|
20
|
+
background: transparent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
::-webkit-scrollbar-thumb {
|
|
24
|
+
background: rgba(0, 0, 0, 0.2);
|
|
25
|
+
border-radius: 2px;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
document.head.appendChild(style);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
const existingStyle = document.getElementById(STYLE_ID);
|
|
33
|
+
if (existingStyle) {
|
|
34
|
+
existingStyle.remove();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return <>{children}</>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { WebOnlyPrettyScrollbar }
|
|
@@ -2,17 +2,20 @@ import { AuthProvider } from '@/contexts/AuthContext';
|
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import { WebOnlyColorSchemeUpdater } from './ColorSchemeUpdater';
|
|
5
|
+
import { WebOnlyPrettyScrollbar } from './PrettyScrollbar'
|
|
5
6
|
import { HeroUINativeProvider } from '@/heroui';
|
|
6
7
|
|
|
7
8
|
function Provider({ children }: { children: ReactNode }) {
|
|
8
9
|
return <WebOnlyColorSchemeUpdater>
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
<WebOnlyPrettyScrollbar>
|
|
11
|
+
<AuthProvider>
|
|
12
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
13
|
+
<HeroUINativeProvider>
|
|
14
|
+
{children}
|
|
15
|
+
</HeroUINativeProvider>
|
|
16
|
+
</GestureHandlerRootView>
|
|
17
|
+
</AuthProvider>
|
|
18
|
+
</WebOnlyPrettyScrollbar>
|
|
16
19
|
</WebOnlyColorSchemeUpdater>
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -10,6 +10,7 @@ import reanimated from '../eslint-plugins/reanimated/index.js';
|
|
|
10
10
|
import reactnative from '../eslint-plugins/react-native/index.js';
|
|
11
11
|
import forbidEmoji from '../eslint-plugins/forbid-emoji/index.js';
|
|
12
12
|
import restrictLinearGradient from '../eslint-plugins/restrict-linear-gradient/index.js';
|
|
13
|
+
import expo from '../eslint-plugins/expo/index.js';
|
|
13
14
|
|
|
14
15
|
export default [
|
|
15
16
|
{
|
|
@@ -66,6 +67,7 @@ export default [
|
|
|
66
67
|
reactnative,
|
|
67
68
|
forbidEmoji,
|
|
68
69
|
restrictLinearGradient,
|
|
70
|
+
expo,
|
|
69
71
|
},
|
|
70
72
|
rules: {
|
|
71
73
|
// 关闭代码风格规则
|
|
@@ -90,6 +92,7 @@ export default [
|
|
|
90
92
|
'reanimated/ban-mix-use': 'error',
|
|
91
93
|
'forbidEmoji/no-emoji': 'error',
|
|
92
94
|
'restrictLinearGradient/no-linear-gradient-backgroundcolor': 'error',
|
|
95
|
+
'expo/require-globalcss-and-provider': 'error',
|
|
93
96
|
// 禁止使用 via.placeholder.com 服务
|
|
94
97
|
'no-restricted-syntax': [
|
|
95
98
|
'error',
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function normalizeFilename(filename) {
|
|
2
|
+
// ESLint may provide Windows paths; normalize to forward slashes for suffix checks.
|
|
3
|
+
return typeof filename === 'string' ? filename.replace(/\\/g, '/') : ''
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isTargetLayoutFile(context) {
|
|
7
|
+
return normalizeFilename(context.getFilename()).endsWith('/app/_layout.tsx')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isSideEffectImportOf(node, sourceValue) {
|
|
11
|
+
return (
|
|
12
|
+
node &&
|
|
13
|
+
node.type === 'ImportDeclaration' &&
|
|
14
|
+
node.source &&
|
|
15
|
+
node.source.type === 'Literal' &&
|
|
16
|
+
node.source.value === sourceValue &&
|
|
17
|
+
Array.isArray(node.specifiers) &&
|
|
18
|
+
node.specifiers.length === 0
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getProviderImportLocalName(node) {
|
|
23
|
+
if (
|
|
24
|
+
!node ||
|
|
25
|
+
node.type !== 'ImportDeclaration' ||
|
|
26
|
+
!node.source ||
|
|
27
|
+
node.source.type !== 'Literal' ||
|
|
28
|
+
node.source.value !== '@/components/Provider'
|
|
29
|
+
) {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const specifiers = Array.isArray(node.specifiers) ? node.specifiers : []
|
|
34
|
+
if (specifiers.length !== 1) return null
|
|
35
|
+
const [specifier] = specifiers
|
|
36
|
+
if (specifier.type !== 'ImportSpecifier') return null
|
|
37
|
+
if (!specifier.imported || specifier.imported.type !== 'Identifier') return null
|
|
38
|
+
if (!specifier.local || specifier.local.type !== 'Identifier') return null
|
|
39
|
+
|
|
40
|
+
// Allow `import { Provider } ...` and `import { Provider as XXX } ...`
|
|
41
|
+
if (specifier.imported.name !== 'Provider') return null
|
|
42
|
+
return specifier.local.name
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
meta: {
|
|
47
|
+
type: 'problem',
|
|
48
|
+
docs: {
|
|
49
|
+
description: 'Require global.css import and Provider usage in app/_layout.tsx',
|
|
50
|
+
recommended: 'error',
|
|
51
|
+
},
|
|
52
|
+
schema: [],
|
|
53
|
+
messages: {
|
|
54
|
+
missingGlobalCssImport: `app/_layout.tsx 必须引入 global.css 文件(参考写法:import '../global.css')`,
|
|
55
|
+
requireProviderImportAndUsage:
|
|
56
|
+
"app/_layout.tsx 必须从 @/components/Provider 导入 Provider(参考写法:import { Provider } from '@/components/Provider'),并使用导入的 Provider 包裹其余组件",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
create(context) {
|
|
61
|
+
if (!isTargetLayoutFile(context)) {
|
|
62
|
+
return {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let hasGlobalCssImport = false
|
|
66
|
+
let providerImportLocalName = null
|
|
67
|
+
let hasProviderJsxUsage = false
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
Program(node) {
|
|
71
|
+
const body = Array.isArray(node.body) ? node.body : []
|
|
72
|
+
for (const stmt of body) {
|
|
73
|
+
if (isSideEffectImportOf(stmt, '../global.css')) {
|
|
74
|
+
hasGlobalCssImport = true
|
|
75
|
+
}
|
|
76
|
+
const localName = getProviderImportLocalName(stmt)
|
|
77
|
+
if (localName) {
|
|
78
|
+
providerImportLocalName = localName
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
JSXOpeningElement(node) {
|
|
84
|
+
if (!node || !node.name) return
|
|
85
|
+
if (
|
|
86
|
+
providerImportLocalName &&
|
|
87
|
+
node.name.type === 'JSXIdentifier' &&
|
|
88
|
+
node.name.name === providerImportLocalName
|
|
89
|
+
) {
|
|
90
|
+
hasProviderJsxUsage = true
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
'Program:exit'(node) {
|
|
95
|
+
if (!hasGlobalCssImport) {
|
|
96
|
+
context.report({ node, messageId: 'missingGlobalCssImport' })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!providerImportLocalName || !hasProviderJsxUsage) {
|
|
100
|
+
context.report({ node, messageId: 'requireProviderImportAndUsage' })
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# expo 技术设计(layout 规则)
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
- 强约束 Expo Router 的 `app/_layout.tsx` 模板结构,避免初始化项目后被误删导致运行时样式/上下文缺失
|
|
5
|
+
- 校验 `app/_layout.tsx` 必须:
|
|
6
|
+
- 包含 `import '../global.css'`
|
|
7
|
+
- 使用从 `@/components/Provider` 导入的 Provider 组件作为 JSX 组件(允许 `import { Provider } ...` 或 `import { Provider as XXX } ...`)
|
|
8
|
+
- 实现轻量、易维护,不引入新依赖
|
|
9
|
+
|
|
10
|
+
## 规则命名与对外形态
|
|
11
|
+
- 插件目录:expo/eslint-plugins/expo
|
|
12
|
+
- 规则名:expo/require-globalcss-and-provider
|
|
13
|
+
- 规则类型:problem
|
|
14
|
+
- 默认等级:error
|
|
15
|
+
|
|
16
|
+
## 规则行为定义
|
|
17
|
+
### 报错
|
|
18
|
+
仅对目标文件 `app/_layout.tsx` 生效,满足以下任一情况报错:
|
|
19
|
+
- 缺少 side-effect import:`import '../global.css'`
|
|
20
|
+
- 缺少或不符合要求的 Provider 导入语句:
|
|
21
|
+
- 必须从 `@/components/Provider` 导入
|
|
22
|
+
- 必须为命名导入,导入名必须为 `Provider`
|
|
23
|
+
- 允许形式:
|
|
24
|
+
- `import { Provider } from '@/components/Provider';`
|
|
25
|
+
- `import { Provider as XXX } from '@/components/Provider';`
|
|
26
|
+
- 未在 JSX 中使用导入的 Provider 组件(例如:只导入但从未作为 JSX 组件出现)
|
|
27
|
+
|
|
28
|
+
### 不报错
|
|
29
|
+
- 非 `app/_layout.tsx` 文件不做检查
|
|
30
|
+
- 目标文件同时满足:
|
|
31
|
+
- 存在 `import '../global.css'`
|
|
32
|
+
- 存在 Provider 命名导入(允许 as 别名)
|
|
33
|
+
- JSX 中至少出现一次 `<Provider ...>` / `<Provider>` 或 `<XXX ...>` / `<XXX>`(XXX 为别名)
|
|
34
|
+
|
|
35
|
+
## 检测策略
|
|
36
|
+
### 目标文件判定
|
|
37
|
+
- 通过 `context.getFilename()` 获取当前文件路径
|
|
38
|
+
- 仅当路径以 `/app/_layout.tsx` 结尾时启用规则(同时兼容 Windows 路径分隔符做归一化)
|
|
39
|
+
|
|
40
|
+
### AST 访问节点
|
|
41
|
+
- `Program`:收集顶层 `ImportDeclaration`,判定是否存在目标 import
|
|
42
|
+
- `JSXOpeningElement` / `JSXElement`:判定是否使用 `<Provider>`
|
|
43
|
+
|
|
44
|
+
### Import 判定细则
|
|
45
|
+
- global.css:
|
|
46
|
+
- `ImportDeclaration.source.value === '../global.css'`
|
|
47
|
+
- `ImportDeclaration.specifiers.length === 0`(side-effect import)
|
|
48
|
+
- Provider import(严格形式):
|
|
49
|
+
- `ImportDeclaration.source.value === '@/components/Provider'`
|
|
50
|
+
- `ImportDeclaration.specifiers` 仅包含一个 `ImportSpecifier`
|
|
51
|
+
- `specifier.imported.name === 'Provider'`,`specifier.local.name` 允许为 `Provider` 或任意别名
|
|
52
|
+
|
|
53
|
+
## 报错信息
|
|
54
|
+
- messageId: missingGlobalCssImport
|
|
55
|
+
- 文案:`app/_layout.tsx 必须包含 "import '../global.css';"`
|
|
56
|
+
- messageId: requireProviderImportAndUsage
|
|
57
|
+
- 文案:`app/_layout.tsx 中必须从 @/components/Provider 导入 Provider(参考写法:import { Provider } from '@/components/Provider'),并使用导入的 Provider 包裹其余组件`
|
|
58
|
+
|
|
59
|
+
## 典型示例
|
|
60
|
+
### 触发
|
|
61
|
+
- 缺少 global.css:
|
|
62
|
+
- 未包含 `import '../global.css';`
|
|
63
|
+
- Provider import 形式不对:
|
|
64
|
+
- `import Provider from '@/components/Provider'`
|
|
65
|
+
- `import { Provider } from '@/components/provider'`
|
|
66
|
+
- Provider 未使用:
|
|
67
|
+
- 有 `import { Provider } ...`,但 JSX 中没有 `<Provider>`
|
|
68
|
+
|
|
69
|
+
### 不触发
|
|
70
|
+
```tsx
|
|
71
|
+
import { Provider } from '@/components/Provider';
|
|
72
|
+
import '../global.css';
|
|
73
|
+
|
|
74
|
+
export default function RootLayout() {
|
|
75
|
+
return (
|
|
76
|
+
<Provider>
|
|
77
|
+
{/* ... */}
|
|
78
|
+
</Provider>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Provider as AppProvider } from '@/components/Provider';
|
|
85
|
+
import '../global.css';
|
|
86
|
+
|
|
87
|
+
export default function RootLayout() {
|
|
88
|
+
return (
|
|
89
|
+
<AppProvider>
|
|
90
|
+
{/* ... */}
|
|
91
|
+
</AppProvider>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 边界与决策
|
|
97
|
+
- 仅做结构约束,不尝试自动修复(fixer)以避免在 TSX 顶部插入 import 引起格式与注释位置变化
|
|
98
|
+
- 仅检查 JSX 中的 `<Provider>` 使用,不做“必须最外层包裹”的结构分析,降低复杂度与误报
|
|
99
|
+
|
|
100
|
+
## 性能考虑
|
|
101
|
+
- 仅在命中目标文件时启用
|
|
102
|
+
- 仅扫描顶层 import 与 JSX 标签名,复杂度 O(n)
|
|
103
|
+
|
|
104
|
+
## 测试计划
|
|
105
|
+
- 缺少 global.css import:报 `missingGlobalCssImport`
|
|
106
|
+
- 未从 `@/components/Provider` 正确导入 Provider:报 `requireProviderImportAndUsage`
|
|
107
|
+
- Provider 未在 JSX 使用:报 `requireProviderImportAndUsage`
|
|
108
|
+
- 满足全部要求:不报错
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
|
|
43
43
|
## 开发规范
|
|
44
44
|
|
|
45
|
+
### 编码规范
|
|
46
|
+
|
|
47
|
+
- 默认按 TypeScript `strict` 心智写代码;优先复用当前作用域已声明的变量、函数、类型和导入,禁止引用未声明标识符或拼错变量名。
|
|
48
|
+
- 禁止隐式 `any` 和 `as any`;函数参数、返回值、解构项、事件对象、`catch` 错误在使用前应有明确类型或先完成类型收窄,并清理未使用的变量和导入。
|
|
49
|
+
|
|
45
50
|
### Hydration 问题防范
|
|
46
51
|
|
|
47
52
|
1. 严禁在 JSX 渲染逻辑中直接使用 typeof window、Date.now()、Math.random() 等动态数据。**必须使用 'use client' 并配合 useEffect + useState 确保动态内容仅在客户端挂载后渲染**;同时严禁非法 HTML 嵌套(如 <p> 嵌套 <div>)。
|
|
@@ -10,6 +10,15 @@ const syntaxRules = [
|
|
|
10
10
|
},
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
const nextConfigRestrictedSyntaxRules = [
|
|
14
|
+
{
|
|
15
|
+
selector:
|
|
16
|
+
'Property[key.name=/^(root|outputFileTracingRoot)$/] > Literal[value=/^\\//]',
|
|
17
|
+
message:
|
|
18
|
+
'禁止在 next.config 中写死绝对路径,请改用 path.resolve(__dirname, ...)、import.meta.dirname 或 process.cwd() 动态拼接。',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
13
22
|
const eslintConfig = defineConfig([
|
|
14
23
|
...nextVitals,
|
|
15
24
|
...nextTs,
|
|
@@ -19,6 +28,12 @@ const eslintConfig = defineConfig([
|
|
|
19
28
|
'no-restricted-syntax': ['error', ...syntaxRules],
|
|
20
29
|
},
|
|
21
30
|
},
|
|
31
|
+
{
|
|
32
|
+
files: ['next.config.ts'],
|
|
33
|
+
rules: {
|
|
34
|
+
'no-restricted-syntax': ['error', ...nextConfigRestrictedSyntaxRules],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
22
37
|
// Override default ignores of eslint-config-next.
|
|
23
38
|
globalIgnores([
|
|
24
39
|
// Default ignores of eslint-config-next:
|
|
@@ -3,11 +3,11 @@ import Taro from '@tarojs/taro';
|
|
|
3
3
|
/**
|
|
4
4
|
* 小程序调试工具
|
|
5
5
|
* 在开发版/体验版自动开启调试模式
|
|
6
|
-
*
|
|
6
|
+
* 支持微信小程序
|
|
7
7
|
*/
|
|
8
8
|
export function devDebug() {
|
|
9
9
|
const env = Taro.getEnv();
|
|
10
|
-
if (env === Taro.ENV_TYPE.WEAPP
|
|
10
|
+
if (env === Taro.ENV_TYPE.WEAPP) {
|
|
11
11
|
try {
|
|
12
12
|
const accountInfo = Taro.getAccountInfoSync();
|
|
13
13
|
const envVersion = accountInfo.miniProgram.envVersion;
|
package/lib/cli.js
CHANGED
|
@@ -2107,7 +2107,7 @@ const EventBuilder = {
|
|
|
2107
2107
|
};
|
|
2108
2108
|
|
|
2109
2109
|
var name = "@coze-arch/cli";
|
|
2110
|
-
var version = "0.0.
|
|
2110
|
+
var version = "0.0.13-alpha.281fed";
|
|
2111
2111
|
var description = "coze coding devtools cli";
|
|
2112
2112
|
var license = "MIT";
|
|
2113
2113
|
var author = "fanwenjie.fe@bytedance.com";
|
|
@@ -6037,9 +6037,115 @@ const detectTemplateKey = async (
|
|
|
6037
6037
|
}
|
|
6038
6038
|
};
|
|
6039
6039
|
|
|
6040
|
-
|
|
6040
|
+
const PATCH_FG_ENDPOINT =
|
|
6041
|
+
'https://bytegate.zijieapi.com/api/v1/workspace/feature_gates/values?workspace=CozeCoding';
|
|
6042
|
+
const PATCH_FG_TIMEOUT_MS = 2000;
|
|
6043
|
+
|
|
6044
|
+
const PATCH_FG_REQUEST_BODY = {
|
|
6045
|
+
byteGateClientUrl: 'https://bytegate.zijieapi.com',
|
|
6046
|
+
env: 'ONLINE',
|
|
6047
|
+
region: 'CN',
|
|
6048
|
+
workspace_name: 'CozeCoding',
|
|
6049
|
+
project_path: '/',
|
|
6050
|
+
};
|
|
6051
|
+
|
|
6052
|
+
|
|
6053
|
+
|
|
6054
|
+
|
|
6055
|
+
|
|
6056
|
+
const PATCH_CLI_FLAG = 'coding.arch.patch_cli';
|
|
6057
|
+
const PATCH_FLAGS_ENV_KEY = 'COZE_PATCH_FLAGS_JSON';
|
|
6058
|
+
|
|
6059
|
+
const isFlagEnabled = (
|
|
6060
|
+
flags,
|
|
6061
|
+
flag,
|
|
6062
|
+
) => flags[flag] === true;
|
|
6063
|
+
|
|
6064
|
+
const loadPatchFlags = async () => {
|
|
6065
|
+
const envFlags = process.env[PATCH_FLAGS_ENV_KEY];
|
|
6066
|
+
if (envFlags) {
|
|
6067
|
+
const parsed = safeJsonParse(envFlags, null);
|
|
6068
|
+
if (!parsed) {
|
|
6069
|
+
throw new Error(`Invalid JSON in ${PATCH_FLAGS_ENV_KEY}`);
|
|
6070
|
+
}
|
|
6071
|
+
return Object.entries(parsed).reduce(
|
|
6072
|
+
(acc, [key, value]) => {
|
|
6073
|
+
if (typeof value === 'boolean') {
|
|
6074
|
+
acc[key] = value;
|
|
6075
|
+
}
|
|
6076
|
+
return acc;
|
|
6077
|
+
},
|
|
6078
|
+
{},
|
|
6079
|
+
);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
const controller = new AbortController();
|
|
6083
|
+
const timeoutId = setTimeout(() => {
|
|
6084
|
+
controller.abort();
|
|
6085
|
+
}, PATCH_FG_TIMEOUT_MS);
|
|
6086
|
+
|
|
6087
|
+
let response;
|
|
6088
|
+
try {
|
|
6089
|
+
response = await fetch(PATCH_FG_ENDPOINT, {
|
|
6090
|
+
method: 'POST',
|
|
6091
|
+
headers: {
|
|
6092
|
+
'content-type': 'application/json',
|
|
6093
|
+
},
|
|
6094
|
+
body: JSON.stringify(PATCH_FG_REQUEST_BODY),
|
|
6095
|
+
signal: controller.signal,
|
|
6096
|
+
});
|
|
6097
|
+
} catch (error) {
|
|
6098
|
+
if (controller.signal.aborted) {
|
|
6099
|
+
throw new Error(
|
|
6100
|
+
`Failed to load patch flags: request timed out after ${PATCH_FG_TIMEOUT_MS}ms`,
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
6103
|
+
throw error;
|
|
6104
|
+
} finally {
|
|
6105
|
+
clearTimeout(timeoutId);
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6108
|
+
if (!response.ok) {
|
|
6109
|
+
throw new Error(`Failed to load patch flags: HTTP ${response.status}`);
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
const result = (await response.json())
|
|
6113
|
+
|
|
6114
|
+
;
|
|
6041
6115
|
|
|
6116
|
+
const values =
|
|
6117
|
+
'data' in result && result.data && typeof result.data === 'object'
|
|
6118
|
+
? result.data
|
|
6119
|
+
: result;
|
|
6120
|
+
|
|
6121
|
+
if (!values || typeof values !== 'object') {
|
|
6122
|
+
throw new Error('Failed to load patch flags: invalid response shape');
|
|
6123
|
+
}
|
|
6124
|
+
|
|
6125
|
+
return Object.entries(values).reduce(
|
|
6126
|
+
(acc, [key, value]) => {
|
|
6127
|
+
if (typeof value === 'boolean') {
|
|
6128
|
+
acc[key] = value;
|
|
6129
|
+
}
|
|
6130
|
+
return acc;
|
|
6131
|
+
},
|
|
6132
|
+
{},
|
|
6133
|
+
);
|
|
6134
|
+
};
|
|
6135
|
+
|
|
6136
|
+
const byFlag = (flag) => {
|
|
6137
|
+
const predicate = ((context) =>
|
|
6138
|
+
!context.isFlagEnabled(flag)) ;
|
|
6139
|
+
Object.defineProperty(predicate, 'flag', {
|
|
6140
|
+
value: flag,
|
|
6141
|
+
enumerable: true,
|
|
6142
|
+
configurable: false,
|
|
6143
|
+
writable: false,
|
|
6144
|
+
});
|
|
6145
|
+
return predicate;
|
|
6146
|
+
};
|
|
6042
6147
|
|
|
6148
|
+
function _optionalChain$3(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
6043
6149
|
const PATCH_ID$2 = 'taro/update-pack-script-for-tt-build@0.0.13';
|
|
6044
6150
|
const PACKAGE_JSON_FILE = 'package.json';
|
|
6045
6151
|
const PACK_SCRIPT_FILE = '.cozeproj/scripts/pack.sh';
|
|
@@ -6139,6 +6245,7 @@ const hasLegacyPackScript = async (projectFolder) => {
|
|
|
6139
6245
|
const patchTaroPackScript = {
|
|
6140
6246
|
id: PATCH_ID$2,
|
|
6141
6247
|
template: 'taro',
|
|
6248
|
+
disabled: byFlag('coding.arch.patch_taro_update_pack_script_for_tt_build'),
|
|
6142
6249
|
operations: [
|
|
6143
6250
|
{
|
|
6144
6251
|
kind: 'file-patch',
|
|
@@ -6205,6 +6312,7 @@ const readTemplateAgentsContent = async () =>
|
|
|
6205
6312
|
const patchTaroAgentsMd = {
|
|
6206
6313
|
id: PATCH_ID$1,
|
|
6207
6314
|
template: 'taro',
|
|
6315
|
+
disabled: byFlag('coding.arch.patch_taro_add_agents_md'),
|
|
6208
6316
|
operations: [
|
|
6209
6317
|
{
|
|
6210
6318
|
kind: 'create-file',
|
|
@@ -6316,6 +6424,13 @@ const createReplaceNpxWithPnpmPatch = (
|
|
|
6316
6424
|
) => ({
|
|
6317
6425
|
id: `${template}/replace-npx-with-pnpm@0.0.13`,
|
|
6318
6426
|
template,
|
|
6427
|
+
disabled: byFlag(
|
|
6428
|
+
template === 'nextjs'
|
|
6429
|
+
? 'coding.arch.patch_nextjs_replace_npx_with_pnpm'
|
|
6430
|
+
: template === 'vite'
|
|
6431
|
+
? 'coding.arch.patch_vite_replace_npx_with_pnpm'
|
|
6432
|
+
: 'coding.arch.patch_nuxt_vue_replace_npx_with_pnpm',
|
|
6433
|
+
),
|
|
6319
6434
|
operations: [
|
|
6320
6435
|
{
|
|
6321
6436
|
kind: 'file-patch',
|
|
@@ -6398,6 +6513,7 @@ const PATCH_ID = 'nextjs/next-output-tracing-root@0.0.13';
|
|
|
6398
6513
|
const patchNextOutputTracingRoot = {
|
|
6399
6514
|
id: PATCH_ID,
|
|
6400
6515
|
template: 'nextjs',
|
|
6516
|
+
disabled: byFlag('coding.arch.patch_nextjs_next_output_tracing_root'),
|
|
6401
6517
|
operations: [
|
|
6402
6518
|
{
|
|
6403
6519
|
kind: 'file-patch',
|
|
@@ -6465,6 +6581,67 @@ const getTemplatePatches = (
|
|
|
6465
6581
|
function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$2(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
6466
6582
|
const DEFAULT_LEGACY_VERSION = 'legacy';
|
|
6467
6583
|
|
|
6584
|
+
const loadFlagsWithFallback = async () => {
|
|
6585
|
+
try {
|
|
6586
|
+
return await loadPatchFlags();
|
|
6587
|
+
} catch (error) {
|
|
6588
|
+
logger.warn(
|
|
6589
|
+
`Patch skipped: failed to load feature gates (${
|
|
6590
|
+
error instanceof Error ? error.message : String(error)
|
|
6591
|
+
})`,
|
|
6592
|
+
);
|
|
6593
|
+
return {};
|
|
6594
|
+
}
|
|
6595
|
+
};
|
|
6596
|
+
|
|
6597
|
+
const isPatchDisabled = (
|
|
6598
|
+
patch,
|
|
6599
|
+
context,
|
|
6600
|
+
) =>
|
|
6601
|
+
typeof patch.disabled === 'function'
|
|
6602
|
+
? patch.disabled(context)
|
|
6603
|
+
: patch.disabled === true;
|
|
6604
|
+
|
|
6605
|
+
const getPatchFlag = (patch) =>
|
|
6606
|
+
typeof patch.disabled === 'function' &&
|
|
6607
|
+
'flag' in patch.disabled &&
|
|
6608
|
+
typeof (patch.disabled ).flag === 'string'
|
|
6609
|
+
? (patch.disabled ).flag
|
|
6610
|
+
: null;
|
|
6611
|
+
|
|
6612
|
+
const showFlags = (
|
|
6613
|
+
template,
|
|
6614
|
+
flags,
|
|
6615
|
+
context,
|
|
6616
|
+
) => {
|
|
6617
|
+
logger.info('Feature gates:');
|
|
6618
|
+
logger.info(
|
|
6619
|
+
` ${PATCH_CLI_FLAG} = ${isFlagEnabled(flags, PATCH_CLI_FLAG) ? 'true' : 'false'}`,
|
|
6620
|
+
);
|
|
6621
|
+
|
|
6622
|
+
const templatePatches = getTemplatePatches(template);
|
|
6623
|
+
if (templatePatches.length === 0) {
|
|
6624
|
+
logger.info(`No registered patches for template: ${template}`);
|
|
6625
|
+
return;
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
logger.info('Patch gates:');
|
|
6629
|
+
templatePatches.forEach(patch => {
|
|
6630
|
+
const flag = getPatchFlag(patch);
|
|
6631
|
+
if (!flag) {
|
|
6632
|
+
logger.info(` - ${patch.id}`);
|
|
6633
|
+
logger.info(' gate: (none)');
|
|
6634
|
+
return;
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
const enabled = context.isFlagEnabled(flag);
|
|
6638
|
+
logger.info(` - ${patch.id}`);
|
|
6639
|
+
logger.info(` flag: ${flag}`);
|
|
6640
|
+
logger.info(` value: ${enabled ? 'true' : 'false'}`);
|
|
6641
|
+
logger.info(` status: ${enabled ? 'enabled' : 'disabled by flag'}`);
|
|
6642
|
+
});
|
|
6643
|
+
};
|
|
6644
|
+
|
|
6468
6645
|
const loadExistingTomlConfig = async (
|
|
6469
6646
|
projectFolder,
|
|
6470
6647
|
) => {
|
|
@@ -6540,6 +6717,7 @@ const executePatch = async (
|
|
|
6540
6717
|
|
|
6541
6718
|
const appliedPatches = [...(_optionalChain$2([config, 'access', _5 => _5.project, 'optionalAccess', _6 => _6.appliedPatches]) || [])];
|
|
6542
6719
|
const projectVersion = _optionalChain$2([config, 'access', _7 => _7.project, 'optionalAccess', _8 => _8.version]);
|
|
6720
|
+
const flags = await loadFlagsWithFallback();
|
|
6543
6721
|
|
|
6544
6722
|
const context = {
|
|
6545
6723
|
cwd,
|
|
@@ -6548,11 +6726,26 @@ const executePatch = async (
|
|
|
6548
6726
|
projectVersion,
|
|
6549
6727
|
appliedPatches,
|
|
6550
6728
|
cozeConfig: config,
|
|
6729
|
+
flags,
|
|
6730
|
+
isFlagEnabled: flag => isFlagEnabled(flags, flag),
|
|
6551
6731
|
};
|
|
6552
6732
|
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6733
|
+
if (options.showFlags) {
|
|
6734
|
+
showFlags(template, flags, context);
|
|
6735
|
+
return;
|
|
6736
|
+
}
|
|
6737
|
+
|
|
6738
|
+
if (!context.isFlagEnabled(PATCH_CLI_FLAG)) {
|
|
6739
|
+
logger.info(`Patch skipped: feature gate ${PATCH_CLI_FLAG} is disabled`);
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
|
|
6743
|
+
const candidatePatches = getTemplatePatches(template).filter(patch => {
|
|
6744
|
+
if (isPatchDisabled(patch, context)) {
|
|
6745
|
+
return false;
|
|
6746
|
+
}
|
|
6747
|
+
return !appliedPatches.includes(patch.id);
|
|
6748
|
+
});
|
|
6556
6749
|
|
|
6557
6750
|
const matchedPatches = [];
|
|
6558
6751
|
for (const patch of candidatePatches) {
|
|
@@ -6618,12 +6811,19 @@ const registerCommand$3 = program => {
|
|
|
6618
6811
|
'Target directory to patch (defaults to current directory)',
|
|
6619
6812
|
)
|
|
6620
6813
|
.option('--dry-run', 'Show matched patches without modifying files')
|
|
6621
|
-
.
|
|
6622
|
-
|
|
6814
|
+
.option('--show-flags', 'Show feature gate status for this template')
|
|
6815
|
+
.action(
|
|
6816
|
+
async (
|
|
6623
6817
|
directory,
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6818
|
+
command,
|
|
6819
|
+
) => {
|
|
6820
|
+
await executePatch({
|
|
6821
|
+
directory,
|
|
6822
|
+
dryRun: _optionalChain$2([command, 'optionalAccess', _9 => _9.dryRun]),
|
|
6823
|
+
showFlags: _optionalChain$2([command, 'optionalAccess', _10 => _10.showFlags]),
|
|
6824
|
+
});
|
|
6825
|
+
},
|
|
6826
|
+
);
|
|
6627
6827
|
};
|
|
6628
6828
|
|
|
6629
6829
|
function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|