create-dwy 0.1.0

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.
Files changed (48) hide show
  1. package/bin/index.js +31 -0
  2. package/package.json +29 -0
  3. package/src/create.js +93 -0
  4. package/src/sync.js +75 -0
  5. package/src/utils.js +61 -0
  6. package/templates/claude-global/commands/.gitkeep +0 -0
  7. package/templates/claude-global/hooks/.gitkeep +0 -0
  8. package/templates/claude-global/rules/git-security.md +144 -0
  9. package/templates/claude-global/rules/python-code-style.md +875 -0
  10. package/templates/claude-global/rules/server-security.md +376 -0
  11. package/templates/claude-global/skills/.gitkeep +0 -0
  12. package/templates/project/web/.claude/commands/deploy.md +4 -0
  13. package/templates/project/web/.dwy.hbs +5 -0
  14. package/templates/project/web/CLAUDE.md.hbs +52 -0
  15. package/templates/project/web/backend/.python-version +1 -0
  16. package/templates/project/web/backend/Dockerfile +17 -0
  17. package/templates/project/web/backend/alembic/env.py.hbs +44 -0
  18. package/templates/project/web/backend/alembic/script.py.mako +24 -0
  19. package/templates/project/web/backend/alembic/versions/.gitkeep +0 -0
  20. package/templates/project/web/backend/alembic.ini.hbs +36 -0
  21. package/templates/project/web/backend/pyproject.toml.hbs +52 -0
  22. package/templates/project/web/backend/src/app/__init__.py +0 -0
  23. package/templates/project/web/backend/src/app/config.py.hbs +12 -0
  24. package/templates/project/web/backend/src/app/main.py.hbs +40 -0
  25. package/templates/project/web/backend/src/app/models/__init__.py +0 -0
  26. package/templates/project/web/backend/src/app/models/base.py +5 -0
  27. package/templates/project/web/backend/src/app/routers/__init__.py +0 -0
  28. package/templates/project/web/backend/src/app/routers/health.py +11 -0
  29. package/templates/project/web/backend/src/app/schemas/__init__.py +0 -0
  30. package/templates/project/web/backend/src/app/services/__init__.py +0 -0
  31. package/templates/project/web/backend/tests/__init__.py +0 -0
  32. package/templates/project/web/backend/tests/conftest.py.hbs +16 -0
  33. package/templates/project/web/deploy.sh.hbs +113 -0
  34. package/templates/project/web/docker-compose.dev.yml.hbs +19 -0
  35. package/templates/project/web/docker-compose.prod.yml.hbs +31 -0
  36. package/templates/project/web/frontend/index.html.hbs +12 -0
  37. package/templates/project/web/frontend/package.json.hbs +28 -0
  38. package/templates/project/web/frontend/src/App.vue +3 -0
  39. package/templates/project/web/frontend/src/api/client.ts +13 -0
  40. package/templates/project/web/frontend/src/components/.gitkeep +0 -0
  41. package/templates/project/web/frontend/src/main.ts +10 -0
  42. package/templates/project/web/frontend/src/router/index.ts +14 -0
  43. package/templates/project/web/frontend/src/stores/auth.ts +20 -0
  44. package/templates/project/web/frontend/src/style.css +1 -0
  45. package/templates/project/web/frontend/src/views/HomeView.vue +5 -0
  46. package/templates/project/web/frontend/tsconfig.app.json +14 -0
  47. package/templates/project/web/frontend/tsconfig.json +4 -0
  48. package/templates/project/web/frontend/vite.config.ts.hbs +16 -0
package/bin/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander'
4
+ import { createProject } from '../src/create.js'
5
+ import { syncClaude, syncProjectClaude } from '../src/sync.js'
6
+
7
+ program
8
+ .name('dwy')
9
+ .description('Project scaffolding and Claude Code config sync')
10
+ .version('0.1.0')
11
+
12
+ program
13
+ .command('create [name]')
14
+ .description('Create a new project from template')
15
+ .action(createProject)
16
+
17
+ program
18
+ .command('sync <target>')
19
+ .description('Sync configuration (claude | project-claude)')
20
+ .action(async (target) => {
21
+ if (target === 'claude') {
22
+ await syncClaude()
23
+ } else if (target === 'project-claude') {
24
+ await syncProjectClaude()
25
+ } else {
26
+ console.error(`Unknown sync target: ${target}. Use "claude" or "project-claude".`)
27
+ process.exit(1)
28
+ }
29
+ })
30
+
31
+ program.parse()
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "create-dwy",
3
+ "version": "0.1.0",
4
+ "description": "Project scaffolding and Claude Code config sync CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "dwy": "./bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates"
13
+ ],
14
+ "keywords": ["cli", "scaffold", "fastapi", "vue"],
15
+ "license": "MIT",
16
+ "author": "danweiyuan",
17
+ "dependencies": {
18
+ "commander": "^12.0.0",
19
+ "inquirer": "^9.0.0",
20
+ "handlebars": "^4.7.0",
21
+ "fs-extra": "^11.0.0",
22
+ "chalk": "^5.0.0",
23
+ "simple-git": "^3.0.0"
24
+ },
25
+ "publishConfig": {
26
+ "registry": "https://registry.npmjs.org/",
27
+ "access": "public"
28
+ }
29
+ }
package/src/create.js ADDED
@@ -0,0 +1,93 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'path'
3
+ import inquirer from 'inquirer'
4
+ import { renderTemplate, chalk, PACKAGE_ROOT } from './utils.js'
5
+
6
+ export async function createProject(name) {
7
+ console.log(chalk.blue('Creating a new project...\n'))
8
+
9
+ const answers = await inquirer.prompt([
10
+ {
11
+ type: 'input',
12
+ name: 'projectName',
13
+ message: '项目名称:',
14
+ default: name || 'my-app',
15
+ validate: (v) => /^[a-z0-9-]+$/.test(v) || '只允许小写字母、数字和连字符',
16
+ },
17
+ {
18
+ type: 'input',
19
+ name: 'projectDescription',
20
+ message: '项目描述:',
21
+ default: '',
22
+ },
23
+ {
24
+ type: 'list',
25
+ name: 'template',
26
+ message: '选择项目模板:',
27
+ choices: [
28
+ { name: 'web — Vue + FastAPI', value: 'web' },
29
+ { name: 'mobile — 移动端 + FastAPI (预留)', value: 'mobile', disabled: '即将推出' },
30
+ { name: 'backend — 纯 FastAPI 服务 (预留)', value: 'backend', disabled: '即将推出' },
31
+ ],
32
+ },
33
+ {
34
+ type: 'confirm',
35
+ name: 'includeDolphindb',
36
+ message: '需要 DolphinDB 吗?',
37
+ default: false,
38
+ },
39
+ {
40
+ type: 'input',
41
+ name: 'sshAlias',
42
+ message: 'SSH 部署别名:',
43
+ default: (a) => `${a.projectName}-server`,
44
+ },
45
+ {
46
+ type: 'input',
47
+ name: 'devDbPort',
48
+ message: '本地开发 DB 端口:',
49
+ default: '5432',
50
+ },
51
+ {
52
+ type: 'input',
53
+ name: 'devRedisPort',
54
+ message: '本地开发 Redis 端口:',
55
+ default: '6379',
56
+ },
57
+ {
58
+ type: 'input',
59
+ name: 'backendPort',
60
+ message: '生产 Backend 端口:',
61
+ default: '8001',
62
+ },
63
+ ])
64
+
65
+ const context = {
66
+ ...answers,
67
+ dbName: answers.projectName.replace(/-/g, '_'),
68
+ dbUser: answers.projectName.replace(/-/g, '_'),
69
+ year: new Date().getFullYear(),
70
+ createdAt: new Date().toISOString().split('T')[0],
71
+ dwyVersion: '0.1.0',
72
+ }
73
+
74
+ const templateDir = path.join(PACKAGE_ROOT, 'templates', 'project', context.template)
75
+ const destDir = path.resolve(process.cwd(), context.projectName)
76
+
77
+ if (await fs.pathExists(destDir)) {
78
+ console.error(chalk.red(`Error: directory "${context.projectName}" already exists.`))
79
+ process.exit(1)
80
+ }
81
+
82
+ console.log(chalk.gray(`\nGenerating project in ${destDir}...`))
83
+ await fs.ensureDir(destDir)
84
+ await renderTemplate(templateDir, destDir, context)
85
+
86
+ console.log(chalk.green(`\n✅ 项目 ${context.projectName} 创建成功!\n`))
87
+ console.log(chalk.gray('Next steps:'))
88
+ console.log(chalk.gray(` cd ${context.projectName}`))
89
+ console.log(chalk.gray(' git init && git add . && git commit -m "init"'))
90
+ console.log(chalk.gray(' docker compose -f docker-compose.dev.yml up -d'))
91
+ console.log(chalk.gray(' cd backend && uv venv && uv pip install -e ".[dev]"'))
92
+ console.log(chalk.gray(' cd frontend && pnpm install && pnpm dev'))
93
+ }
package/src/sync.js ADDED
@@ -0,0 +1,75 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'path'
3
+ import { ensureRepoCache, copyDir, chalk } from './utils.js'
4
+
5
+ export async function syncClaude() {
6
+ console.log(chalk.blue('Syncing global Claude configuration...'))
7
+
8
+ const repoDir = await ensureRepoCache()
9
+ const sourceDir = path.join(repoDir, 'templates', 'claude-global')
10
+ const targetDir = path.join(process.env.HOME, '.claude')
11
+
12
+ if (!await fs.pathExists(sourceDir)) {
13
+ console.error(chalk.red('Error: claude-global templates not found in repo'))
14
+ process.exit(1)
15
+ }
16
+
17
+ const subdirs = ['rules', 'skills', 'commands', 'hooks']
18
+ let syncedCount = 0
19
+
20
+ for (const subdir of subdirs) {
21
+ const src = path.join(sourceDir, subdir)
22
+ if (await fs.pathExists(src)) {
23
+ const dest = path.join(targetDir, subdir)
24
+ await copyDir(src, dest)
25
+ const files = await fs.readdir(src).catch(() => [])
26
+ syncedCount += files.length
27
+ console.log(chalk.green(` ✓ ${subdir}/ — ${files.length} files`))
28
+ }
29
+ }
30
+
31
+ const settingsSrc = path.join(sourceDir, 'settings.json')
32
+ if (await fs.pathExists(settingsSrc)) {
33
+ const settingsDest = path.join(targetDir, 'settings.json')
34
+ const newSettings = await fs.readJson(settingsSrc)
35
+
36
+ if (await fs.pathExists(settingsDest)) {
37
+ const existing = await fs.readJson(settingsDest)
38
+ const merged = { ...existing, ...newSettings }
39
+ await fs.writeJson(settingsDest, merged, { spaces: 2 })
40
+ console.log(chalk.green(' ✓ settings.json — merged'))
41
+ } else {
42
+ await fs.writeJson(settingsDest, newSettings, { spaces: 2 })
43
+ console.log(chalk.green(' ✓ settings.json — created'))
44
+ }
45
+ syncedCount++
46
+ }
47
+
48
+ console.log(chalk.blue(`\nDone! Synced ${syncedCount} items to ~/.claude/`))
49
+ }
50
+
51
+ export async function syncProjectClaude() {
52
+ console.log(chalk.blue('Syncing project Claude configuration...'))
53
+
54
+ const dwyFile = path.join(process.cwd(), '.dwy')
55
+ if (!await fs.pathExists(dwyFile)) {
56
+ console.error(chalk.red('Error: .dwy marker file not found. Is this a dwy project?'))
57
+ console.error(chalk.gray('Run "dwy create" to create a new project, or create .dwy manually.'))
58
+ process.exit(1)
59
+ }
60
+
61
+ const marker = await fs.readJson(dwyFile)
62
+ const template = marker.template || 'web'
63
+
64
+ const repoDir = await ensureRepoCache()
65
+ const sourceDir = path.join(repoDir, 'templates', 'project', template, '.claude')
66
+ const targetDir = path.join(process.cwd(), '.claude')
67
+
68
+ if (!await fs.pathExists(sourceDir)) {
69
+ console.error(chalk.red(`Error: template "${template}" not found in repo`))
70
+ process.exit(1)
71
+ }
72
+
73
+ await copyDir(sourceDir, targetDir)
74
+ console.log(chalk.blue('Done! Synced .claude/ for current project.'))
75
+ }
package/src/utils.js ADDED
@@ -0,0 +1,61 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'path'
3
+ import Handlebars from 'handlebars'
4
+ import { simpleGit } from 'simple-git'
5
+ import chalk from 'chalk'
6
+ import { fileURLToPath } from 'url'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+
10
+ export const PACKAGE_ROOT = path.resolve(__dirname, '..')
11
+ export const CACHE_DIR = path.join(process.env.HOME, '.dwy', 'cache')
12
+ export const DWY_REPO_URL = 'https://gitee.com/snailyuanyuan/dwy-cli.git'
13
+
14
+ export async function ensureRepoCache() {
15
+ await fs.ensureDir(CACHE_DIR)
16
+ const repoDir = path.join(CACHE_DIR, 'dwy')
17
+
18
+ if (await fs.pathExists(path.join(repoDir, '.git'))) {
19
+ console.log(chalk.gray('Pulling latest templates...'))
20
+ const git = simpleGit(repoDir)
21
+ await git.pull()
22
+ } else {
23
+ console.log(chalk.gray('Cloning template repo...'))
24
+ await simpleGit().clone(DWY_REPO_URL, repoDir)
25
+ }
26
+
27
+ return repoDir
28
+ }
29
+
30
+ export async function renderTemplate(templateDir, destDir, context) {
31
+ const entries = await fs.readdir(templateDir, { withFileTypes: true })
32
+
33
+ for (const entry of entries) {
34
+ const srcPath = path.join(templateDir, entry.name)
35
+ let destName = entry.name
36
+
37
+ if (entry.isDirectory()) {
38
+ const destPath = path.join(destDir, destName)
39
+ await fs.ensureDir(destPath)
40
+ await renderTemplate(srcPath, destPath, context)
41
+ continue
42
+ }
43
+
44
+ if (destName.endsWith('.hbs')) {
45
+ destName = destName.slice(0, -4)
46
+ const template = await fs.readFile(srcPath, 'utf-8')
47
+ const rendered = Handlebars.compile(template)(context)
48
+ await fs.writeFile(path.join(destDir, destName), rendered)
49
+ } else if (destName === '.gitkeep') {
50
+ continue
51
+ } else {
52
+ await fs.copy(srcPath, path.join(destDir, destName))
53
+ }
54
+ }
55
+ }
56
+
57
+ export async function copyDir(src, dest) {
58
+ await fs.copy(src, dest, { overwrite: true })
59
+ }
60
+
61
+ export { chalk }
File without changes
File without changes
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: git-security
3
+ description: Git 提交安全规则
4
+ type: claude-rule
5
+ tags: [security, git]
6
+ version: 1.0.0
7
+ author: chances
8
+ ---
9
+
10
+ # Git 提交安全规则
11
+
12
+ 每次执行 `git add` 或 `git commit` 前必须遵循以下规则。违反时必须**立即停止并提示用户确认**。
13
+
14
+ ## 一、禁止提交的文件
15
+
16
+ ### 绝对禁止(无论任何理由)
17
+ 以下文件**永远不得**被 `git add`:
18
+
19
+ | 文件类型 | 示例 |
20
+ |---|---|
21
+ | 环境变量文件 | `.env`、`.env.local`、`.env.production`、`*.env` |
22
+ | SSH 密钥 | `id_rsa`、`id_ed25519`、`*.pem`、`*.key` |
23
+ | 证书文件 | `*.p12`、`*.pfx`、`*.jks`、`*.keystore` |
24
+ | 数据库文件 | `*.sql`(含真实数据的)、`*.dump`、`*.sqlite` |
25
+ | 原始数据 | `*.xlsx`、`*.csv`(含用户/学生等敏感数据的) |
26
+ | 日志文件 | `*.log` |
27
+ | Docker 卷数据 | `pgdata/`、`cache/*.json` |
28
+
29
+ ### 检测方法
30
+ 在执行 `git add` 前,检查暂存区是否包含上述文件:
31
+ ```bash
32
+ git diff --cached --name-only | grep -iE '\.(env|pem|key|p12|pfx|jks|dump|sqlite|log)$'
33
+ ```
34
+
35
+ ## 二、禁止提交的内容
36
+
37
+ ### 总则:所有示例必须使用假值
38
+ **禁止在代码、文档、规则文件、注释中出现任何真实的 API Key、Token、密码、IP 地址等敏感信息,即使是作为"示例"也不允许。** 所有示例必须使用明显的假值/脱敏值(如 `sk-xxxxxxxxxxxx`、`your-key-here`、`192.168.x.x`)。GitHub Secret Scanning 不区分上下文,真实格式的 key 即使写在文档示例中也会触发泄露告警。
39
+
40
+ ### 必须扫描的敏感模式
41
+ 对每个将要提交的文件内容,检查是否包含以下模式:
42
+
43
+ | 类型 | 匹配模式 | 示例(均为假值) |
44
+ |---|---|---|
45
+ | API Key | `sk-[a-zA-Z0-9]{20,}` | `sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
46
+ | AWS Key | `AKIA[0-9A-Z]{16}` | `AKIAIOSFODNN7EXAMPLE` |
47
+ | 密码赋值 | `password\s*[:=]\s*["'][^"']{4,}` | `password = "abc123"` |
48
+ | 数据库连接串 | `://\w+:[^@\s]+@` | `postgresql://user:pass@host` |
49
+ | 私钥内容 | `-----BEGIN (RSA |EC )?PRIVATE KEY-----` | PEM 私钥 |
50
+ | Token | `token\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}` | `token = "ghp_xxxx"` |
51
+ | Secret | `secret\s*[:=]\s*["'][^"']{8,}` | `secret = "my-secret"` |
52
+ | 服务器 IP+端口 | `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+` | `192.168.1.1:8080` |
53
+ | SSH 连接 | `ssh\s+.*@\d{1,3}\.\d{1,3}` | `ssh user@192.168.1.1` |
54
+
55
+ ### 例外:允许的模式
56
+ 以下情况**不算**违规:
57
+ - `.env.example` 中的占位符(如 `DEEPSEEK_API_KEY=your-key-here`)
58
+ - 代码中从环境变量读取的引用(如 `os.getenv("API_KEY")`)
59
+ - `localhost` 或 `127.0.0.1` 的连接串
60
+ - 文档中的示例/假数据(如 `sk-xxxxxxxxxxxx`)
61
+ - 配置文件中的空字符串默认值(如 `api_key: str = ""`)
62
+
63
+ ## 三、提交前检查流程
64
+
65
+ 每次 `git commit` 前必须执行:
66
+
67
+ ```
68
+ 1. 检查暂存文件列表 → 是否包含禁止文件类型?
69
+ 2. 检查文件内容差异 → 是否包含敏感模式?
70
+ 3. 检查 .gitignore → 是否已排除敏感目录?
71
+ 4. 通过 → 执行提交
72
+ 5. 未通过 → 停止,提示用户具体违规项
73
+ ```
74
+
75
+ ### 自动检查命令
76
+ ```bash
77
+ # 检查暂存区的敏感文件
78
+ git diff --cached --name-only | grep -iE '\.(env|pem|key|log|dump|sqlite)$'
79
+
80
+ # 检查暂存区内容中的敏感信息
81
+ git diff --cached -U0 | grep -iE '(sk-[a-zA-Z0-9]{20}|AKIA[0-9A-Z]{16}|-----BEGIN.*PRIVATE KEY|password\s*[:=]\s*"[^"]{4,}"|://\w+:[^@\s]+@[^l])'
82
+ ```
83
+
84
+ ## 四、.gitignore 最低要求
85
+
86
+ 每个项目的 `.gitignore` 必须至少包含:
87
+
88
+ ```gitignore
89
+ # 环境变量
90
+ .env
91
+ *.env
92
+ .env.*
93
+
94
+ # 密钥
95
+ *.pem
96
+ *.key
97
+ *.p12
98
+
99
+ # 缓存与编译
100
+ __pycache__/
101
+ *.pyc
102
+ node_modules/
103
+ dist/
104
+
105
+ # 日志
106
+ *.log
107
+
108
+ # 数据库
109
+ *.sqlite
110
+ *.dump
111
+ ```
112
+
113
+ 如果项目的 `.gitignore` 缺少以上条目,提交前必须先补全。
114
+
115
+ ## 五、已提交敏感数据的处理
116
+
117
+ 如果发现敏感数据已存在于 git 历史中:
118
+
119
+ 1. **立即通知用户**,说明哪些数据已泄露
120
+ 2. **建议轮换凭证**(API Key、密码等)
121
+ 3. **提供清理命令**(但不自动执行,等用户确认):
122
+ ```bash
123
+ # 从所有历史中删除文件
124
+ git filter-repo --path <file> --invert-paths --force
125
+
126
+ # 或使用 BFG
127
+ bfg --delete-files <filename>
128
+ ```
129
+
130
+ ## 六、违规处理流程
131
+
132
+ 当检测到提交操作包含敏感数据时:
133
+
134
+ 1. **立即停止** `git add` 或 `git commit`
135
+ 2. **列出具体违规项**:
136
+ - 文件名和行号
137
+ - 匹配到的敏感模式类型
138
+ - 敏感内容的部分摘要(如 `sk-5522****dc3`,脱敏显示)
139
+ 3. **提供建议**:
140
+ - 将文件加入 `.gitignore`
141
+ - 将敏感值移到 `.env`
142
+ - 使用环境变量替代硬编码
143
+ 4. **等待用户明确确认**后才能继续
144
+ 5. 如用户确认提交,添加注释:`# GIT-SECURITY: 用户已确认提交此内容`