feat(cli): add create command with interactive block selection
Publish to Gitea Registry / publish (push) Failing after 1m32s
Publish to Gitea Registry / publish (push) Failing after 1m32s
- register create command in Commander program - add @inquirer/prompts dependency for interactive prompts - extend Block type with optional repo field - scaffold create command module with initial implementation
This commit is contained in:
Generated
+413
-2
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@inquirer/prompts": "^8.5.2",
|
||||||
"commander": "^15.0.0",
|
"commander": "^15.0.0",
|
||||||
"js-yaml": "^5.0.0"
|
"js-yaml": "^5.0.0"
|
||||||
},
|
},
|
||||||
@@ -24,6 +25,334 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@inquirer/ansi": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/ansi/-/ansi-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/checkbox": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/checkbox/-/checkbox-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.7",
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/figures": "^2.0.7",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/confirm": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/confirm/-/confirm-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/core": {
|
||||||
|
"version": "11.2.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/core/-/core-11.2.1.tgz",
|
||||||
|
"integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.7",
|
||||||
|
"@inquirer/figures": "^2.0.7",
|
||||||
|
"@inquirer/type": "^4.0.7",
|
||||||
|
"cli-width": "^4.1.0",
|
||||||
|
"fast-wrap-ansi": "^0.2.0",
|
||||||
|
"mute-stream": "^3.0.0",
|
||||||
|
"signal-exit": "^4.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/editor": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/editor/-/editor-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/external-editor": "^3.0.3",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/expand": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/expand/-/expand-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/external-editor": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/external-editor/-/external-editor-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chardet": "^2.1.1",
|
||||||
|
"iconv-lite": "^0.7.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/figures": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/figures/-/figures-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/input": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/input/-/input-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/number": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/number/-/number-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/password": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/password/-/password-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.7",
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/prompts": {
|
||||||
|
"version": "8.5.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/prompts/-/prompts-8.5.2.tgz",
|
||||||
|
"integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/checkbox": "^5.2.1",
|
||||||
|
"@inquirer/confirm": "^6.1.1",
|
||||||
|
"@inquirer/editor": "^5.2.2",
|
||||||
|
"@inquirer/expand": "^5.1.1",
|
||||||
|
"@inquirer/input": "^5.1.2",
|
||||||
|
"@inquirer/number": "^4.1.1",
|
||||||
|
"@inquirer/password": "^5.1.1",
|
||||||
|
"@inquirer/rawlist": "^5.3.1",
|
||||||
|
"@inquirer/search": "^4.2.1",
|
||||||
|
"@inquirer/select": "^5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/rawlist": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/rawlist/-/rawlist-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/search": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/search/-/search-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/figures": "^2.0.7",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/select": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/select/-/select-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.7",
|
||||||
|
"@inquirer/core": "^11.2.1",
|
||||||
|
"@inquirer/figures": "^2.0.7",
|
||||||
|
"@inquirer/type": "^4.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inquirer/type": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "http://192.168.5.47:4873/@inquirer/type/-/type-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/js-yaml": {
|
"node_modules/@types/js-yaml": {
|
||||||
"version": "4.0.9",
|
"version": "4.0.9",
|
||||||
"resolved": "http://192.168.5.47:4873/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
"resolved": "http://192.168.5.47:4873/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
@@ -35,7 +364,7 @@
|
|||||||
"version": "22.19.21",
|
"version": "22.19.21",
|
||||||
"resolved": "http://192.168.5.47:4873/@types/node/-/node-22.19.21.tgz",
|
"resolved": "http://192.168.5.47:4873/@types/node/-/node-22.19.21.tgz",
|
||||||
"integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==",
|
"integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
@@ -47,6 +376,21 @@
|
|||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/chardet": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "http://192.168.5.47:4873/chardet/-/chardet-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cli-width": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "http://192.168.5.47:4873/cli-width/-/cli-width-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "15.0.0",
|
"version": "15.0.0",
|
||||||
"resolved": "http://192.168.5.47:4873/commander/-/commander-15.0.0.tgz",
|
"resolved": "http://192.168.5.47:4873/commander/-/commander-15.0.0.tgz",
|
||||||
@@ -56,6 +400,46 @@
|
|||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-string-truncated-width": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "http://192.168.5.47:4873/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-string-width": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/fast-string-width/-/fast-string-width-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-string-truncated-width": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-wrap-ansi": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-string-width": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "http://192.168.5.47:4873/js-yaml/-/js-yaml-5.0.0.tgz",
|
"resolved": "http://192.168.5.47:4873/js-yaml/-/js-yaml-5.0.0.tgz",
|
||||||
@@ -78,6 +462,33 @@
|
|||||||
"js-yaml": "bin/js-yaml.mjs"
|
"js-yaml": "bin/js-yaml.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mute-stream": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "http://192.168.5.47:4873/mute-stream/-/mute-stream-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "http://192.168.5.47:4873/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/signal-exit": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "http://192.168.5.47:4873/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "http://192.168.5.47:4873/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "http://192.168.5.47:4873/typescript/-/typescript-5.9.3.tgz",
|
||||||
@@ -96,7 +507,7 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "http://192.168.5.47:4873/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "http://192.168.5.47:4873/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@inquirer/prompts": "^8.5.2",
|
||||||
"commander": "^15.0.0",
|
"commander": "^15.0.0",
|
||||||
"js-yaml": "^5.0.0"
|
"js-yaml": "^5.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,343 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
import { checkbox, select, input } from "@inquirer/prompts";
|
||||||
|
import { resolve, basename } from "node:path";
|
||||||
|
import { writeFile } from "node:fs/promises";
|
||||||
|
import { resolveSource } from "../lib/config.js";
|
||||||
|
import { loadIndex } from "../lib/index-loader.js";
|
||||||
|
import { inferRepoUrl, cloneBlock, cleanupTemp } from "../lib/git.js";
|
||||||
|
import { applyTemplate } from "../lib/template.js";
|
||||||
|
import { compileAiRules } from "../lib/ai-compiler.js";
|
||||||
|
import type { Block, BrickJson } from "../types/index.js";
|
||||||
|
|
||||||
|
interface SelectedBlock {
|
||||||
|
block: Block;
|
||||||
|
branch: string;
|
||||||
|
variantLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STYLES: { value: string; label: string }[] = [
|
||||||
|
{ value: "standard", label: "标准多模块 — 根 pom.xml 聚合所有积木子模块" },
|
||||||
|
{
|
||||||
|
value: "bom",
|
||||||
|
label: "BOM 模式 — 根 pom + bom 子模块统一依赖版本管理",
|
||||||
|
},
|
||||||
|
{ value: "simple", label: "简单模式 — 不生成根 pom,积木模块平铺" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const AI_TOOLS: { value: string; label: string }[] = [
|
||||||
|
{ value: "claude", label: "Claude Code" },
|
||||||
|
{ value: "cursor", label: "Cursor" },
|
||||||
|
{ value: "copilot", label: "GitHub Copilot" },
|
||||||
|
{ value: "aider", label: "Aider" },
|
||||||
|
{ value: "generic", label: "通用(AI_CONTEXT.md)" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function registerCreate(program: Command): void {
|
||||||
|
program
|
||||||
|
.command("create <project-name>")
|
||||||
|
.description("创建新项目")
|
||||||
|
.action(async (projectName: string) => {
|
||||||
|
try {
|
||||||
|
await runCreate(projectName);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = (err as Error).message ?? "";
|
||||||
|
if (msg.includes("User force closed") || msg.includes("ExitPromptError")) {
|
||||||
|
console.log("\n已取消。");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCreate(projectName: string): Promise<void> {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
// 1. Load index
|
||||||
|
const source = resolveSource();
|
||||||
|
console.log(`[1/7] 拉取积木索引...`);
|
||||||
|
const index = await loadIndex(source);
|
||||||
|
|
||||||
|
if (index.blocks.length === 0) {
|
||||||
|
console.log("没有可用的积木。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Interactive prompts
|
||||||
|
// 2a. Select blocks
|
||||||
|
const chosenNames = await checkbox({
|
||||||
|
message: "选择要集成的积木(空格选择,回车确认):",
|
||||||
|
choices: index.blocks.map((b) => ({
|
||||||
|
name: `${b.name} — ${b.description}`,
|
||||||
|
value: b.name,
|
||||||
|
checked: true,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chosenNames.length === 0) {
|
||||||
|
console.log("未选择任何积木,已取消。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chosenBlocks = index.blocks.filter((b) =>
|
||||||
|
chosenNames.includes(b.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2b. Resolve dependencies (auto-add missing)
|
||||||
|
const resolved = resolveDeps(chosenBlocks, index.blocks);
|
||||||
|
if (resolved.added.length > 0) {
|
||||||
|
console.log(
|
||||||
|
` 自动补全依赖:${resolved.added.map((b) => b.name).join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2c. Select variant for blocks with multiple variants
|
||||||
|
const selected: SelectedBlock[] = [];
|
||||||
|
for (const block of resolved.all) {
|
||||||
|
let branch = block.defaultBranch;
|
||||||
|
let variantLabel = block.defaultBranch;
|
||||||
|
|
||||||
|
if (block.variants.length > 1) {
|
||||||
|
const chosen = await select({
|
||||||
|
message: `为 ${block.name} 选择变体:`,
|
||||||
|
choices: block.variants.map((v) => ({
|
||||||
|
name: `${v.label} (${v.branch})`,
|
||||||
|
value: v.branch,
|
||||||
|
})),
|
||||||
|
default: block.defaultBranch,
|
||||||
|
});
|
||||||
|
branch = chosen;
|
||||||
|
variantLabel =
|
||||||
|
block.variants.find((v) => v.branch === chosen)?.label ?? chosen;
|
||||||
|
}
|
||||||
|
|
||||||
|
selected.push({ block, branch, variantLabel });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2d. Project style
|
||||||
|
const style = await select({
|
||||||
|
message: "选择工程风格:",
|
||||||
|
choices: STYLES,
|
||||||
|
default: "standard",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2e. Variables
|
||||||
|
const defaultPkg = `com.example.${projectName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
||||||
|
|
||||||
|
const basePackage = await input({
|
||||||
|
message: "基础包名 (basePackage):",
|
||||||
|
default: defaultPkg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupId = await input({
|
||||||
|
message: "Maven groupId:",
|
||||||
|
default: basePackage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const javaVersion = await input({
|
||||||
|
message: "Java 版本:",
|
||||||
|
default: "17",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2f. AI tool
|
||||||
|
const aiTool = await select({
|
||||||
|
message: "选择 AI 工具:",
|
||||||
|
choices: AI_TOOLS,
|
||||||
|
default: "claude",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log("\n--- 配置摘要 ---");
|
||||||
|
console.log(` 项目名: ${projectName}`);
|
||||||
|
console.log(` 积木: ${selected.map((s) => `${s.block.name} (${s.variantLabel})`).join(", ")}`);
|
||||||
|
console.log(` 工程风格: ${STYLES.find((s) => s.value === style)?.label}`);
|
||||||
|
console.log(` 基础包名: ${basePackage}`);
|
||||||
|
console.log(` groupId: ${groupId}`);
|
||||||
|
console.log(` Java 版本: ${javaVersion}`);
|
||||||
|
console.log(` AI 工具: ${AI_TOOLS.find((t) => t.value === aiTool)?.label}`);
|
||||||
|
console.log("-----------------\n");
|
||||||
|
|
||||||
|
// 3. Clone blocks
|
||||||
|
console.log("[2/7] 拉取积木仓库...");
|
||||||
|
const blockDirs: { name: string; dir: string }[] = [];
|
||||||
|
|
||||||
|
for (const sel of selected) {
|
||||||
|
const url = inferRepoUrl(sel.block.name, sel.block.repo);
|
||||||
|
console.log(` clone ${url} (branch: ${sel.branch})...`);
|
||||||
|
const dir = await cloneBlock(url, sel.branch);
|
||||||
|
blockDirs.push({ name: sel.block.name, dir });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Apply templates
|
||||||
|
console.log("[3/7] 生成项目源码...");
|
||||||
|
const targetDir = resolve(cwd, projectName);
|
||||||
|
const vars: Record<string, string> = {
|
||||||
|
basePackage,
|
||||||
|
groupId,
|
||||||
|
projectName,
|
||||||
|
javaVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const bd of blockDirs) {
|
||||||
|
await applyTemplate(bd.dir, targetDir, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Generate AI rules
|
||||||
|
console.log("[4/7] 生成 AI 规则文件...");
|
||||||
|
await compileAiRules(blockDirs, targetDir, aiTool);
|
||||||
|
|
||||||
|
// 6. Generate root pom.xml
|
||||||
|
console.log("[5/7] 生成根 pom.xml...");
|
||||||
|
if (style !== "simple") {
|
||||||
|
const rootPom = buildRootPom(
|
||||||
|
projectName,
|
||||||
|
groupId,
|
||||||
|
javaVersion,
|
||||||
|
selected,
|
||||||
|
style
|
||||||
|
);
|
||||||
|
await writeFile(resolve(targetDir, "pom.xml"), rootPom, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Generate brick.json
|
||||||
|
console.log("[6/7] 生成 brick.json...");
|
||||||
|
const brickJson: BrickJson = {
|
||||||
|
projectName,
|
||||||
|
basePackage,
|
||||||
|
javaVersion,
|
||||||
|
aiTool,
|
||||||
|
style,
|
||||||
|
blocks: selected.map((s) => ({
|
||||||
|
name: s.block.name,
|
||||||
|
variant: s.variantLabel,
|
||||||
|
version: s.block.version,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
await writeFile(
|
||||||
|
resolve(targetDir, "brick.json"),
|
||||||
|
JSON.stringify(brickJson, null, 2),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 8. Cleanup temp dirs
|
||||||
|
console.log("[7/7] 清理临时文件...");
|
||||||
|
for (const bd of blockDirs) {
|
||||||
|
await cleanupTemp(bd.dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n项目已创建:${targetDir}`);
|
||||||
|
console.log("Done!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDeps(
|
||||||
|
chosen: Block[],
|
||||||
|
all: Block[]
|
||||||
|
): { all: Block[]; added: Block[] } {
|
||||||
|
const blockMap = new Map(all.map((b) => [b.name, b]));
|
||||||
|
const result = new Map(chosen.map((b) => [b.name, b]));
|
||||||
|
const added: Block[] = [];
|
||||||
|
|
||||||
|
// Iteratively resolve dependencies
|
||||||
|
let changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (const block of [...result.values()]) {
|
||||||
|
for (const depName of block.dependsOn) {
|
||||||
|
if (!result.has(depName)) {
|
||||||
|
const dep = blockMap.get(depName);
|
||||||
|
if (dep) {
|
||||||
|
result.set(depName, dep);
|
||||||
|
added.push(dep);
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
process.stderr.write(
|
||||||
|
`Warning: Dependency ${depName} not found in index\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { all: [...result.values()], added };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRootPom(
|
||||||
|
projectName: string,
|
||||||
|
groupId: string,
|
||||||
|
javaVersion: string,
|
||||||
|
selected: SelectedBlock[],
|
||||||
|
style: string
|
||||||
|
): string {
|
||||||
|
const modules = selected
|
||||||
|
.map((s) => ` <module>${projectName}-common-${s.block.module}</module>`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
if (style === "bom") {
|
||||||
|
return [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||||||
|
' xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">',
|
||||||
|
' <modelVersion>4.0.0</modelVersion>',
|
||||||
|
"",
|
||||||
|
` <groupId>${groupId}</groupId>`,
|
||||||
|
` <artifactId>${projectName}</artifactId>`,
|
||||||
|
" <version>0.0.0</version>",
|
||||||
|
" <packaging>pom</packaging>",
|
||||||
|
"",
|
||||||
|
` <name>${projectName}</name>`,
|
||||||
|
"",
|
||||||
|
" <properties>",
|
||||||
|
` <java.version>${javaVersion}</java.version>`,
|
||||||
|
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
||||||
|
` <brick.version>0.0.0</brick.version>`,
|
||||||
|
" </properties>",
|
||||||
|
"",
|
||||||
|
" <dependencyManagement>",
|
||||||
|
" <dependencies>",
|
||||||
|
...selected.map(
|
||||||
|
(s) =>
|
||||||
|
` <dependency>\n` +
|
||||||
|
` <groupId>${groupId}</groupId>\n` +
|
||||||
|
` <artifactId>${projectName}-common-${s.block.module}</artifactId>\n` +
|
||||||
|
` <version>${"$"}{brick.version}</version>\n` +
|
||||||
|
` </dependency>`
|
||||||
|
),
|
||||||
|
" </dependencies>",
|
||||||
|
" </dependencyManagement>",
|
||||||
|
"",
|
||||||
|
" <modules>",
|
||||||
|
` <module>${projectName}-common-bom</module>`,
|
||||||
|
modules,
|
||||||
|
" </modules>",
|
||||||
|
"</project>",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// standard
|
||||||
|
return [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||||||
|
' xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">',
|
||||||
|
' <modelVersion>4.0.0</modelVersion>',
|
||||||
|
"",
|
||||||
|
` <groupId>${groupId}</groupId>`,
|
||||||
|
` <artifactId>${projectName}</artifactId>`,
|
||||||
|
" <version>0.0.0</version>",
|
||||||
|
" <packaging>pom</packaging>",
|
||||||
|
"",
|
||||||
|
` <name>${projectName}</name>`,
|
||||||
|
"",
|
||||||
|
" <properties>",
|
||||||
|
` <java.version>${javaVersion}</java.version>`,
|
||||||
|
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
||||||
|
" </properties>",
|
||||||
|
"",
|
||||||
|
" <modules>",
|
||||||
|
modules,
|
||||||
|
" </modules>",
|
||||||
|
"</project>",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { registerList } from "./commands/list.js";
|
import { registerList } from "./commands/list.js";
|
||||||
|
import { registerCreate } from "./commands/create.js";
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
@@ -10,5 +11,6 @@ program
|
|||||||
.description("Brick CLI — 源码级 Spring Boot 脚手架");
|
.description("Brick CLI — 源码级 Spring Boot 脚手架");
|
||||||
|
|
||||||
registerList(program);
|
registerList(program);
|
||||||
|
registerCreate(program);
|
||||||
|
|
||||||
program.parse();
|
program.parse();
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||||||
|
import { join, dirname } from "node:path";
|
||||||
|
import { load } from "js-yaml";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import type { AiModule } from "../types/index.js";
|
||||||
|
|
||||||
|
const AI_FILE_MAP: Record<string, string> = {
|
||||||
|
claude: "CLAUDE.md",
|
||||||
|
cursor: ".cursorrules",
|
||||||
|
copilot: ".github/copilot-instructions.md",
|
||||||
|
aider: "CONVENTIONS.md",
|
||||||
|
generic: "AI_CONTEXT.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseAiModule(raw: string): AiModule {
|
||||||
|
const data = load(raw) as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
module: String(data.module ?? ""),
|
||||||
|
summary: String(data.summary ?? ""),
|
||||||
|
dos: Array.isArray(data.dos) ? data.dos.map(String) : [],
|
||||||
|
donts: Array.isArray(data.donts) ? data.donts.map(String) : [],
|
||||||
|
examples: Array.isArray(data.examples)
|
||||||
|
? data.examples.map((e: unknown) => {
|
||||||
|
const ex = e as Record<string, unknown>;
|
||||||
|
return { path: String(ex.path ?? ""), note: String(ex.note ?? "") };
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function compileAiRules(
|
||||||
|
blockDirs: { name: string; dir: string }[],
|
||||||
|
targetDir: string,
|
||||||
|
aiTool: string
|
||||||
|
): Promise<void> {
|
||||||
|
const compiled = await buildAiContent(blockDirs);
|
||||||
|
if (!compiled) return;
|
||||||
|
|
||||||
|
const fileName = AI_FILE_MAP[aiTool] ?? AI_FILE_MAP.generic;
|
||||||
|
const filePath = join(targetDir, fileName);
|
||||||
|
|
||||||
|
const dir = dirname(filePath);
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
await mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(filePath, compiled, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildAiContent(
|
||||||
|
blockDirs: { name: string; dir: string }[]
|
||||||
|
): Promise<string | null> {
|
||||||
|
const sections: string[] = [];
|
||||||
|
|
||||||
|
sections.push("# 项目 AI 编码规范\n");
|
||||||
|
sections.push(
|
||||||
|
"本文件由 Brick CLI 自动生成,整合了以下积木的 AI 规范。\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
const moduleList = blockDirs.map((b) => `- ${b.name}`).join("\n");
|
||||||
|
sections.push(`## 项目模块\n\n${moduleList}\n`);
|
||||||
|
|
||||||
|
const allDos: string[] = [];
|
||||||
|
const allDonts: string[] = [];
|
||||||
|
|
||||||
|
for (const block of blockDirs) {
|
||||||
|
const moduleYaml = join(block.dir, "ai", "module.yaml");
|
||||||
|
const conventionsMd = join(block.dir, "ai", "conventions.md");
|
||||||
|
|
||||||
|
if (!existsSync(moduleYaml)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = await readFile(moduleYaml, "utf-8");
|
||||||
|
const mod = parseAiModule(raw);
|
||||||
|
|
||||||
|
sections.push(`### ${mod.module} — ${mod.summary}\n`);
|
||||||
|
|
||||||
|
if (mod.dos.length > 0) {
|
||||||
|
sections.push("**DO:**\n");
|
||||||
|
for (const d of mod.dos) {
|
||||||
|
sections.push(`- ${d}`);
|
||||||
|
allDos.push(`[${mod.module}] ${d}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod.donts.length > 0) {
|
||||||
|
sections.push("**DON'T:**\n");
|
||||||
|
for (const d of mod.donts) {
|
||||||
|
sections.push(`- ${d}`);
|
||||||
|
allDonts.push(`[${mod.module}] ${d}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// module.yaml is optional
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append conventions.md content if it exists
|
||||||
|
if (existsSync(conventionsMd)) {
|
||||||
|
try {
|
||||||
|
const raw = await readFile(conventionsMd, "utf-8");
|
||||||
|
sections.push(raw);
|
||||||
|
sections.push("");
|
||||||
|
} catch {
|
||||||
|
// conventions.md is optional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global summary
|
||||||
|
if (allDos.length > 0 || allDonts.length > 0) {
|
||||||
|
sections.push("## 全局约束 (摘要)\n");
|
||||||
|
if (allDos.length > 0) {
|
||||||
|
sections.push("**必须遵守:**\n");
|
||||||
|
for (const d of allDos) {
|
||||||
|
sections.push(`- ${d}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
}
|
||||||
|
if (allDonts.length > 0) {
|
||||||
|
sections.push("**严格禁止:**\n");
|
||||||
|
for (const d of allDonts) {
|
||||||
|
sections.push(`- ${d}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join("\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { exec } from "node:child_process";
|
||||||
|
import { rm } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
const GITEA_BASE = "https://gitea.synoth.com/synoth";
|
||||||
|
|
||||||
|
export function inferRepoUrl(blockName: string, repo?: string): string {
|
||||||
|
if (repo) return repo;
|
||||||
|
return `${GITEA_BASE}/${blockName}.git`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execAsync(command: string): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(command, (error, stdout, stderr) => {
|
||||||
|
if (error) reject(error);
|
||||||
|
else resolve({ stdout, stderr });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cloneBlock(
|
||||||
|
repoUrl: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<string> {
|
||||||
|
const dir = join(tmpdir(), `brick-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execAsync(
|
||||||
|
`git clone --depth 1 --branch "${branch}" "${repoUrl}" "${dir}"`
|
||||||
|
);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = (err as Error).message;
|
||||||
|
if (msg.includes("not found") || msg.includes("does not exist")) {
|
||||||
|
process.stderr.write(
|
||||||
|
`Error: Repository or branch not found: ${repoUrl} (branch: ${branch})\n`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
process.stderr.write(
|
||||||
|
`Error: Failed to clone ${repoUrl}: ${msg}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupTemp(dir: string): Promise<void> {
|
||||||
|
await rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { cp, readFile, writeFile, rename, readdir, mkdir } from "node:fs/promises";
|
||||||
|
import { join, dirname } from "node:path";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
|
||||||
|
export async function applyTemplate(
|
||||||
|
sourceDir: string,
|
||||||
|
targetDir: string,
|
||||||
|
vars: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const templateDir = join(sourceDir, "template");
|
||||||
|
|
||||||
|
if (!existsSync(templateDir)) {
|
||||||
|
process.stderr.write(`Error: template/ not found in ${sourceDir}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const varsToReplace: Record<string, string> = {
|
||||||
|
...vars,
|
||||||
|
basePkgPath: vars.basePackage.replace(/\./g, "/"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 1: copy template/ to target
|
||||||
|
await cp(templateDir, targetDir, { recursive: true });
|
||||||
|
|
||||||
|
// Step 2: replace variables in all file contents
|
||||||
|
await replaceInFiles(targetDir, varsToReplace);
|
||||||
|
|
||||||
|
// Step 3: rename directories containing {{basePkgPath}}
|
||||||
|
await renamePlaceholders(targetDir, varsToReplace);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceInFiles(
|
||||||
|
dir: string,
|
||||||
|
vars: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const entries = await readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = join(dir, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
// Skip .git
|
||||||
|
if (entry.name === ".git") continue;
|
||||||
|
await replaceInFiles(fullPath, vars);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
let content = await readFile(fullPath, "utf-8");
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(vars)) {
|
||||||
|
const placeholder = `{{${key}}}`;
|
||||||
|
if (content.includes(placeholder)) {
|
||||||
|
content = content.replaceAll(placeholder, value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
await writeFile(fullPath, content, "utf-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renamePlaceholders(
|
||||||
|
dir: string,
|
||||||
|
vars: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
// Walk directory tree bottom-up to rename {{basePkgPath}} segments
|
||||||
|
const dirsToRename = await findPlaceholderDirs(dir);
|
||||||
|
|
||||||
|
// Sort by depth descending (deepest first)
|
||||||
|
dirsToRename.sort((a, b) => b.depth - a.depth);
|
||||||
|
|
||||||
|
for (const { path } of dirsToRename) {
|
||||||
|
const parent = dirname(path);
|
||||||
|
const oldName = path.split(/[/\\]/).pop()!;
|
||||||
|
let newName = oldName;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(vars)) {
|
||||||
|
newName = newName.replaceAll(`{{${key}}}`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newName !== oldName) {
|
||||||
|
const dest = join(parent, newName);
|
||||||
|
await mkdir(dirname(dest), { recursive: true });
|
||||||
|
await rename(path, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findPlaceholderDirs(
|
||||||
|
dir: string
|
||||||
|
): Promise<{ path: string; depth: number }[]> {
|
||||||
|
const results: { path: string; depth: number }[] = [];
|
||||||
|
const entries = await readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory() || entry.name === ".git") continue;
|
||||||
|
|
||||||
|
const fullPath = join(dir, entry.name);
|
||||||
|
const sub = await findPlaceholderDirs(fullPath);
|
||||||
|
results.push(...sub);
|
||||||
|
|
||||||
|
if (entry.name.includes("{{")) {
|
||||||
|
const depth = fullPath.replace(/\\/g, "/").split("/").length;
|
||||||
|
results.push({ path: fullPath, depth });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export interface Block {
|
|||||||
language: string;
|
language: string;
|
||||||
defaultBranch: string;
|
defaultBranch: string;
|
||||||
module: string;
|
module: string;
|
||||||
|
repo?: string;
|
||||||
variants: BlockVariant[];
|
variants: BlockVariant[];
|
||||||
dependsOn: string[];
|
dependsOn: string[];
|
||||||
}
|
}
|
||||||
@@ -17,3 +18,24 @@ export interface Block {
|
|||||||
export interface BlockIndex {
|
export interface BlockIndex {
|
||||||
blocks: Block[];
|
blocks: Block[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BrickJson {
|
||||||
|
projectName: string;
|
||||||
|
basePackage: string;
|
||||||
|
javaVersion: string;
|
||||||
|
aiTool: string;
|
||||||
|
style: string;
|
||||||
|
blocks: {
|
||||||
|
name: string;
|
||||||
|
variant: string;
|
||||||
|
version: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiModule {
|
||||||
|
module: string;
|
||||||
|
summary: string;
|
||||||
|
dos: string[];
|
||||||
|
donts: string[];
|
||||||
|
examples: { path: string; note: string }[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user