@minus-ai/create-skill 0.1.0-beta.1 → 0.1.0-beta.11
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/index.mjs +51 -7
- package/package.json +2 -2
- package/templates/.minus/session-counter +1 -0
- package/templates/README.md.tpl +6 -4
- package/templates/asin/main.tsx.tpl +1 -2
- package/templates/custom/main.tsx.tpl +1 -2
- package/templates/default/main.tsx.tpl +1 -2
- package/templates/file/main.tsx.tpl +1 -2
- package/templates/frontend-package.json.tpl +2 -2
- package/templates/keyword/main.tsx.tpl +1 -2
- package/templates/main.tsx.tpl +1 -2
- package/templates/pnpm-workspace.yaml.tpl +13 -0
- package/templates/root-package.json.tpl +11 -4
- package/templates/vite.config.ts.tpl +1 -1
package/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createInterface } from 'node:readline/promises'
|
|
4
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs'
|
|
5
5
|
import { execSync } from 'node:child_process'
|
|
6
6
|
import { createServer } from 'node:net'
|
|
7
7
|
import { join, dirname } from 'node:path'
|
|
@@ -15,6 +15,46 @@ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json')
|
|
|
15
15
|
|
|
16
16
|
const DEFAULT_PLATFORM_URL = 'http://47.107.144.22:18990'
|
|
17
17
|
|
|
18
|
+
// create-skill 内 node 大版本的唯一写死处。模板里的 engines.node / volta.node /
|
|
19
|
+
// @types/node 全部由它派生,不再各自硬编码。与 minus-plugin lib/toolchain.sh 的
|
|
20
|
+
// NODE_TARGET 同义——两仓各自持有同一 major(create-skill 是独立 npm 包,发布时读不到
|
|
21
|
+
// toolchain.sh,故不做运行时跨仓耦合)。升级大版本只改这一个值。
|
|
22
|
+
const NODE_MAJOR_FLOOR = 24
|
|
23
|
+
|
|
24
|
+
function nodeMajorOf(v) {
|
|
25
|
+
return parseInt(String(v).split('.')[0], 10) || 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 解析「生成项目实际会跑的那个 node」的确切 patch。Volta 不认 major-only/range,
|
|
29
|
+
// volta.node 必须是确切 patch,所以这里返回一个真实存在的 x.y.z。三档:
|
|
30
|
+
// 1. Volta 已装 ≥floor 的最新 node image —— 项目 dev(经 ~/.volta/bin/pnpm)真正会用
|
|
31
|
+
// 且已安装 → 零下载。
|
|
32
|
+
// 2. 没有合格 Volta image,但 create-skill 自身在跑的 node ≥floor —— pin 它(真实存在,
|
|
33
|
+
// 进项目时 Volta 可能下载,但非凭空写死)。
|
|
34
|
+
// 3. 哪里都找不到 ≥floor 的 node → 不编造版本,报错退出。
|
|
35
|
+
function resolveProjectNodeVersion() {
|
|
36
|
+
const imageDir = join(homedir(), '.volta', 'tools', 'image', 'node')
|
|
37
|
+
if (existsSync(imageDir)) {
|
|
38
|
+
const best = readdirSync(imageDir)
|
|
39
|
+
.filter(name => /^\d+\.\d+\.\d+$/.test(name) && nodeMajorOf(name) >= NODE_MAJOR_FLOOR)
|
|
40
|
+
.sort((a, b) => {
|
|
41
|
+
const pa = a.split('.').map(Number)
|
|
42
|
+
const pb = b.split('.').map(Number)
|
|
43
|
+
return pb[0] - pa[0] || pb[1] - pa[1] || pb[2] - pa[2]
|
|
44
|
+
})[0]
|
|
45
|
+
if (best) return best
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const running = process.versions.node
|
|
49
|
+
if (nodeMajorOf(running) >= NODE_MAJOR_FLOOR) return running
|
|
50
|
+
|
|
51
|
+
console.error(
|
|
52
|
+
`\n ✗ 未找到 Node ${NODE_MAJOR_FLOOR}+。请安装 Node ${NODE_MAJOR_FLOOR}(推荐 https://volta.sh:\n` +
|
|
53
|
+
` curl https://get.volta.sh | bash 后 volta install node@${NODE_MAJOR_FLOOR})后重试。`
|
|
54
|
+
)
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
function findAvailablePort(start = 4001) {
|
|
19
59
|
return new Promise(resolve => {
|
|
20
60
|
const server = createServer()
|
|
@@ -28,7 +68,7 @@ function findAvailablePort(start = 4001) {
|
|
|
28
68
|
// ── Naming helpers ──────────────────────────────────────
|
|
29
69
|
|
|
30
70
|
function idToFolder(id) {
|
|
31
|
-
return id.replace(/[
|
|
71
|
+
return id.trim().replace(/[\s/\\]+/g, '-')
|
|
32
72
|
}
|
|
33
73
|
|
|
34
74
|
function idToClassName(id) {
|
|
@@ -476,7 +516,8 @@ async function main() {
|
|
|
476
516
|
const port = String(await findAvailablePort(4001))
|
|
477
517
|
const apiKey = skillResult.apiKey
|
|
478
518
|
|
|
479
|
-
const
|
|
519
|
+
const nodeVersion = resolveProjectNodeVersion()
|
|
520
|
+
const vars = { skillId: skillResult.id, folder, className, displayName, description, namespace, port, nodeVersion, nodeMajor: String(NODE_MAJOR_FLOOR) }
|
|
480
521
|
|
|
481
522
|
// Backend files (pipeline.py from input-type subdir)
|
|
482
523
|
writeOut(join(targetDir, 'pipeline.py'), render(readTemplate('pipeline.py.tpl', inputType), vars))
|
|
@@ -504,6 +545,10 @@ async function main() {
|
|
|
504
545
|
- 输入组件:\`AmazonSearchBar\` + \`CountrySelect\`(来自 @minus/platform-widgets)
|
|
505
546
|
- 校验函数:\`validateAsins(value)\` — 校验 ASIN 格式,返回 { asins: string[], error? }
|
|
506
547
|
- 支持单个或多个 ASIN 输入(逗号/换行分隔)
|
|
548
|
+
- 数量限制:\`validateAsins(value, { min, max })\`
|
|
549
|
+
- 仅允许一个:\`validateAsins(value, { min: 1, max: 1 })\`
|
|
550
|
+
- 一个或多个:省略第二个参数
|
|
551
|
+
- 至少两个:\`validateAsins(value, { min: 2 })\`
|
|
507
552
|
- 修改输入验证规则:编辑 frontend/src/main.tsx 中的 handleSubmit 函数
|
|
508
553
|
- 修改 pipeline 步骤:编辑 pipeline.py 中的 step_N 方法`,
|
|
509
554
|
keyword: `## 模板能力(inputType: keyword)
|
|
@@ -554,7 +599,7 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
554
599
|
|
|
555
600
|
### 开发手册
|
|
556
601
|
写前端代码前,读以下开发手册:
|
|
557
|
-
- 读 \`${
|
|
602
|
+
- 读 \`${platformUrl}/runtime/frontend-guide/doc.md\`
|
|
558
603
|
- 手册包含:前后端数据契约(StepOutcome → StepRenderCtx)、Widget 选型与用法(defineWidgetStep、内置 Interactive/Display Widget)、自定义 Widget 开发、多语言接入。
|
|
559
604
|
|
|
560
605
|
### 后端 SDK 参考
|
|
@@ -564,8 +609,8 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
564
609
|
|
|
565
610
|
### 前端 SDK 参考
|
|
566
611
|
@minus/* 包通过平台 CDN 加载,本地无源码。写前端代码前,读以下文档了解可用 API:
|
|
567
|
-
- Widget 框架:读 \`${
|
|
568
|
-
- 平台组件:读 \`${
|
|
612
|
+
- Widget 框架:读 \`${platformUrl}/runtime/widget-framework/docs.md\`
|
|
613
|
+
- 平台组件:读 \`${platformUrl}/runtime/platform-widgets/docs.md\`
|
|
569
614
|
|
|
570
615
|
修改前端代码后,同步更新 \`frontend/src/locales/\` 下的多语言文件。
|
|
571
616
|
|
|
@@ -595,7 +640,6 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
595
640
|
writeOut(join(targetDir, 'frontend/tsconfig.json'), render(readTemplate('tsconfig.json.tpl'), vars))
|
|
596
641
|
writeOut(join(targetDir, 'frontend/src/main.tsx'), render(readTemplate('main.tsx.tpl', inputType), vars))
|
|
597
642
|
writeOut(join(targetDir, 'frontend/src/locales/zh-CN.json'), render(readTemplate('zh-CN.json.tpl', inputType), vars))
|
|
598
|
-
writeOut(join(targetDir, 'frontend/src/locales/en-US.json'), render(readTemplate('en-US.json.tpl', inputType), vars))
|
|
599
643
|
|
|
600
644
|
// 类型声明文件(让 tsc build 能识别 @minus/* 模块)
|
|
601
645
|
writeOut(join(targetDir, 'frontend/src/minus-runtime.d.ts'), `declare module '@minus/*';\n`)
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2
|
package/templates/README.md.tpl
CHANGED
|
@@ -6,7 +6,7 @@ Minus Skill 项目,由 `create-skill` 脚手架生成。
|
|
|
6
6
|
|
|
7
7
|
| 依赖 | 最低版本 | 说明 |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| Node.js | >=
|
|
9
|
+
| Node.js | >= {{nodeMajor}} | 前端构建和开发服务 |
|
|
10
10
|
| pnpm | >= 9 | 包管理(全局 store 共享,多 skill 不重复下载) |
|
|
11
11
|
| Python | >= 3.12 | 后端 Pipeline 运行时 |
|
|
12
12
|
| uv | >= 0.4 | Python 包管理(全局缓存,秒级安装) |
|
|
@@ -72,7 +72,7 @@ pnpm run dev
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
这会同时启动:
|
|
75
|
-
- **后端** — `uvicorn` 监听 `http://
|
|
75
|
+
- **后端** — `uvicorn` 监听 `http://127.0.0.1:{{port}}`,修改 `.py` 文件自动重载
|
|
76
76
|
- **前端** — `vite` 开发服务,修改前端代码自动热更新
|
|
77
77
|
|
|
78
78
|
单独启动前端:
|
|
@@ -107,10 +107,12 @@ pnpm run build
|
|
|
107
107
|
|
|
108
108
|
| 命令 | 说明 |
|
|
109
109
|
|---|---|
|
|
110
|
-
| `pnpm run dev` |
|
|
110
|
+
| `pnpm run dev` | 启动开发环境(mac/Linux,前后端) |
|
|
111
|
+
| `pnpm run dev:backend` | 仅启动后端(mac/Linux) |
|
|
112
|
+
| `pnpm run dev:win` | 启动开发环境(Windows,前后端) |
|
|
113
|
+
| `pnpm run dev:win:backend` | 仅启动后端(Windows) |
|
|
111
114
|
| `pnpm run build` | 构建前端产物 |
|
|
112
115
|
| `cd frontend && pnpm exec vite` | 仅启动前端 |
|
|
113
|
-
| `.venv/bin/uvicorn server:app --port {{port}} --reload --env-file .env.local` | 仅启动后端 |
|
|
114
116
|
|
|
115
117
|
## 进入开发模式(Claude Code)
|
|
116
118
|
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { AmazonSearchBar, CountrySelect, SearchSubmitButton, validateAsins, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -71,7 +70,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
71
70
|
const rootEl = document.getElementById('root');
|
|
72
71
|
if (!rootEl) throw new Error('#root not found');
|
|
73
72
|
|
|
74
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
73
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
75
74
|
|
|
76
75
|
function SkillRoot() {
|
|
77
76
|
const t = useT();
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -68,7 +67,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
68
67
|
const rootEl = document.getElementById('root');
|
|
69
68
|
if (!rootEl) throw new Error('#root not found');
|
|
70
69
|
|
|
71
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
70
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
72
71
|
|
|
73
72
|
function SkillRoot() {
|
|
74
73
|
const t = useT();
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { CompletionPanel, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -61,7 +60,7 @@ if (!rootEl) throw new Error('#root not found');
|
|
|
61
60
|
|
|
62
61
|
const skillMessages = mergeMessages(
|
|
63
62
|
platformWidgetMessages,
|
|
64
|
-
{ 'zh-CN': zhCN as Record<string, string
|
|
63
|
+
{ 'zh-CN': zhCN as Record<string, string> },
|
|
65
64
|
);
|
|
66
65
|
|
|
67
66
|
function SkillRoot() {
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { FilePicker, uploadFile, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -79,7 +78,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
79
78
|
const rootEl = document.getElementById('root');
|
|
80
79
|
if (!rootEl) throw new Error('#root not found');
|
|
81
80
|
|
|
82
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
81
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
83
82
|
|
|
84
83
|
function SkillRoot() {
|
|
85
84
|
const t = useT();
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"sonner": "^2.0.7"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.
|
|
15
|
-
"@types/node": "^
|
|
14
|
+
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.12",
|
|
15
|
+
"@types/node": "^{{nodeMajor}}.0.0",
|
|
16
16
|
"@types/react": "^18.3.3",
|
|
17
17
|
"@types/react-dom": "^18.3.0",
|
|
18
18
|
"@vitejs/plugin-react": "^4.3.1",
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { AmazonSearchBar, CountrySelect, SearchSubmitButton, validateKeywords, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -71,7 +70,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
71
70
|
const rootEl = document.getElementById('root');
|
|
72
71
|
if (!rootEl) throw new Error('#root not found');
|
|
73
72
|
|
|
74
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
73
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
75
74
|
|
|
76
75
|
function SkillRoot() {
|
|
77
76
|
const t = useT();
|
package/templates/main.tsx.tpl
CHANGED
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig, type StepR
|
|
|
4
4
|
import { AmazonSearchBar, CompletionPanel, CountrySelect, SearchSubmitButton, validateAsins, validateKeywords, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
type InputType = 'keyword' | 'asin';
|
|
10
9
|
|
|
@@ -146,7 +145,7 @@ if (!rootEl) throw new Error('#root not found');
|
|
|
146
145
|
|
|
147
146
|
const skillMessages = mergeMessages(
|
|
148
147
|
platformWidgetMessages,
|
|
149
|
-
{ 'zh-CN': zhCN as Record<string, string
|
|
148
|
+
{ 'zh-CN': zhCN as Record<string, string> },
|
|
150
149
|
);
|
|
151
150
|
|
|
152
151
|
function SkillRoot() {
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
packages:
|
|
2
2
|
- frontend
|
|
3
|
+
|
|
4
|
+
# pnpm 11 的 minimumReleaseAge 门槛会拒装"发布太近"的版本,导致内部包刚发布的新版被压回旧版。
|
|
5
|
+
# @minus-ai/* 是平台内部高频迭代包,豁免门槛以保证新建项目总能拿到最新版。
|
|
6
|
+
minimumReleaseAgeExclude:
|
|
7
|
+
- '@minus-ai/*'
|
|
8
|
+
|
|
9
|
+
# pnpm 11.4 起用 allowBuilds 审批构建脚本;只写 onlyBuiltDependencies 会被忽略并报 ERR_PNPM_IGNORED_BUILDS(install exit 1)。
|
|
10
|
+
allowBuilds:
|
|
11
|
+
esbuild: true
|
|
12
|
+
|
|
13
|
+
# 兼容 pnpm 10.16+ 的旧机制,与 allowBuilds 共存无害。
|
|
14
|
+
onlyBuiltDependencies:
|
|
15
|
+
- esbuild
|
|
@@ -3,16 +3,23 @@
|
|
|
3
3
|
"description": "{{description}}",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">={{nodeMajor}}"
|
|
8
|
+
},
|
|
9
|
+
"volta": {
|
|
10
|
+
"node": "{{nodeVersion}}",
|
|
11
|
+
"pnpm": "11.4.0"
|
|
12
|
+
},
|
|
6
13
|
"workspaces": ["frontend"],
|
|
7
14
|
"scripts": {
|
|
8
15
|
"dev": "minus-dev-cleanup && mkdir -p .minus && echo $$ > .minus/dev.pid && concurrently -n skill,fe \".venv/bin/uvicorn server:app --port {{port}} --reload --reload-include '*.py' --reload-include '.env.local' --env-file .env.local\" \"cd frontend && pnpm exec vite\"",
|
|
16
|
+
"dev:backend": "minus-dev-cleanup && mkdir -p .minus && echo $$ > .minus/backend.pid && .venv/bin/uvicorn server:app --port {{port}} --reload --reload-include '*.py' --reload-include '.env.local' --env-file .env.local",
|
|
17
|
+
"dev:win": "minus-dev --port {{port}}",
|
|
18
|
+
"dev:win:backend": "minus-dev --port {{port}} --backend-only",
|
|
9
19
|
"build": "cd frontend && pnpm run build"
|
|
10
20
|
},
|
|
11
21
|
"devDependencies": {
|
|
12
|
-
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.
|
|
22
|
+
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.12",
|
|
13
23
|
"concurrently": "^9.1.2"
|
|
14
|
-
},
|
|
15
|
-
"pnpm": {
|
|
16
|
-
"onlyBuiltDependencies": ["esbuild"]
|
|
17
24
|
}
|
|
18
25
|
}
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { minusDev } from '@minus-ai/dev-vite-plugin';
|
|
5
5
|
|
|
6
6
|
export default defineConfig({
|
|
7
|
-
plugins: [react(), ...minusDev({ localBackend: 'http://
|
|
7
|
+
plugins: [react(), ...minusDev({ localBackend: 'http://127.0.0.1:{{port}}' })],
|
|
8
8
|
build: {
|
|
9
9
|
target: 'esnext',
|
|
10
10
|
outDir: path.resolve(__dirname, '../static'),
|