├── .eslintignore ├── packages ├── devkit │ ├── .npmignore │ ├── src │ │ ├── fs │ │ │ ├── fixtures │ │ │ │ ├── dir-src │ │ │ │ │ ├── xxx.js │ │ │ │ │ ├── zzz.js │ │ │ │ │ ├── a.dd │ │ │ │ │ ├── abc.js │ │ │ │ │ ├── b.tar.gz │ │ │ │ │ ├── bbb.js │ │ │ │ │ ├── {rename} │ │ │ │ │ │ └── abc.js │ │ │ │ │ └── test.sh │ │ │ │ ├── src.js │ │ │ │ ├── bottom-dist.js │ │ │ │ ├── top-dist.js │ │ │ │ ├── copy-dist.js │ │ │ │ ├── copy-src.js │ │ │ │ ├── dir-dist-abc.js │ │ │ │ ├── after-dist.js │ │ │ │ └── before-dist.js │ │ │ ├── move.ts │ │ │ ├── index.ts │ │ │ ├── remove.ts │ │ │ ├── package.ts │ │ │ ├── package.test.ts │ │ │ ├── interface.ts │ │ │ └── copy-tpl.test.ts │ │ ├── error │ │ │ ├── interfaces.ts │ │ │ ├── handle-default.ts │ │ │ ├── register.ts │ │ │ ├── handle-npm-not-found.ts │ │ │ ├── utils.ts │ │ │ ├── index.ts │ │ │ ├── handle-eaddrinuse.ts │ │ │ ├── handle-enoent.ts │ │ │ ├── handle-module-not-found.ts │ │ │ ├── handle-eaddrinuse.test.ts │ │ │ └── locale │ │ │ │ └── index.ts │ │ ├── module │ │ │ ├── getEsModule.ts │ │ │ ├── online-exist.ts │ │ │ ├── local-exist.ts │ │ │ ├── index.ts │ │ │ ├── update.ts │ │ │ ├── local-list.ts │ │ │ ├── locale │ │ │ │ └── index.ts │ │ │ ├── index.test.ts │ │ │ ├── getReallyName.ts │ │ │ ├── online-list.ts │ │ │ └── install-one.ts │ │ ├── user │ │ │ ├── interface.ts │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── task │ │ │ ├── index.ts │ │ │ ├── has.ts │ │ │ ├── locale │ │ │ │ └── index.ts │ │ │ ├── run-function.ts │ │ │ ├── has.test.ts │ │ │ └── utils.ts │ │ ├── config │ │ │ ├── fixtures │ │ │ │ └── mock.config.ts │ │ │ ├── locale │ │ │ │ └── index.ts │ │ │ ├── index.test.ts │ │ │ ├── ast-analyze.ts │ │ │ └── index.ts │ │ ├── log │ │ │ ├── helpers │ │ │ │ └── logger-test.ts │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── intl │ │ │ ├── interfaces.ts │ │ │ └── index.ts │ │ ├── npm │ │ │ └── index.test.ts │ │ ├── index.ts │ │ ├── upgrade │ │ │ ├── locale │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── argv │ │ │ └── index.ts │ │ ├── env │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── home │ │ │ └── index.ts │ │ ├── cache │ │ │ ├── index.ts │ │ │ └── index.test.ts │ │ ├── report │ │ │ └── utils.ts │ │ ├── cli-config │ │ │ └── index.ts │ │ └── git │ │ │ └── index.ts │ ├── tsconfig.json │ ├── .prettierrc.js │ ├── tsconfig.compiler.json │ ├── scripts │ │ └── build.js │ └── package.json ├── core │ ├── install.js │ ├── .npmignore │ ├── uninstall.js │ ├── tsconfig.json │ ├── tsconfig.compiler.json │ ├── index.js │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── config.ts ├── module-list │ ├── README.md │ ├── .gitignore │ └── package.json └── commands │ ├── tsconfig.json │ ├── tsconfig.compiler.json │ ├── src │ ├── install.ts │ ├── utils.ts │ ├── index.ts │ ├── update.ts │ ├── version.ts │ ├── i.ts │ ├── locale.ts │ ├── clear.ts │ ├── config.ts │ ├── help.ts │ ├── list.ts │ └── init.ts │ └── package.json ├── .husky ├── pre-commit └── commit-msg ├── commitlint.config.js ├── docs ├── images │ ├── 新增权限.png │ ├── 新增词条.png │ ├── 添加菜单.png │ ├── 绑定权限.png │ ├── 绑定菜单.png │ ├── 绑定角色.png │ ├── 退出登录.png │ ├── 选择语言.png │ ├── 新增权限成功.png │ ├── 最终页面效果.png │ ├── 权限绑定展示.png │ ├── 查看菜单页.png │ ├── 测试用户登录.png │ ├── 点击添加词条.png │ ├── 登陆测试账号.png │ ├── 为测试用户绑定菜单.png │ ├── 只勾选测试页面即可.png │ ├── 添加角色完全体.png │ ├── 左侧测试页面 - 中文.png │ ├── 左侧测试页面 - 英文.png │ ├── tiny-pro-show.png │ └── feature_2012x914.png ├── use-install.md ├── use-summary.md ├── use-plugin.md ├── use-toolkit.md ├── dev-plugin.md ├── maintainer-guide.md ├── use-config.md ├── dev-toolkit.md └── use-cli.md ├── .prettierignore ├── config └── typescript │ ├── tsconfig.compiler.json │ ├── core │ ├── tsconfig.json │ ├── tsconfig.compiler.json │ └── tslint.json │ ├── tsconfig.json │ ├── package.json │ └── tslint.json ├── CHANGELOG.md ├── lerna.json ├── .prettierrc.js ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml ├── workflows │ └── issue-translator.yml ├── release.yml └── PULL_REQUEST_TEMPLATE.md ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── .eslintrc.js ├── package.json ├── CONTRIBUTING.zh-CN.md ├── README.zh-CN.md └── .all-contributorsrc /.eslintignore: -------------------------------------------------------------------------------- 1 | /**/template 2 | /packages/**/lib/* 3 | temp 4 | tmp 5 | /\.* -------------------------------------------------------------------------------- /packages/devkit/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | src 3 | lib/fs/fixtures 4 | dist/locales -------------------------------------------------------------------------------- /packages/core/install.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | require('./dist/install'); 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | dist / locales; 2 | tsconfig.compiler.json; 3 | temp 4 | src -------------------------------------------------------------------------------- /packages/core/uninstall.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | require('./dist/uninstall'); 3 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/xxx.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | a: 1, 3 | }; 4 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/zzz.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | a: 1, 3 | }; 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /docs/images/新增权限.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/新增权限.png -------------------------------------------------------------------------------- /docs/images/新增词条.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/新增词条.png -------------------------------------------------------------------------------- /docs/images/添加菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/添加菜单.png -------------------------------------------------------------------------------- /docs/images/绑定权限.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/绑定权限.png -------------------------------------------------------------------------------- /docs/images/绑定菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/绑定菜单.png -------------------------------------------------------------------------------- /docs/images/绑定角色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/绑定角色.png -------------------------------------------------------------------------------- /docs/images/退出登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/退出登录.png -------------------------------------------------------------------------------- /docs/images/选择语言.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/选择语言.png -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 -------------------------------------------------------------------------------- /docs/images/新增权限成功.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/新增权限成功.png -------------------------------------------------------------------------------- /docs/images/最终页面效果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/最终页面效果.png -------------------------------------------------------------------------------- /docs/images/权限绑定展示.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/权限绑定展示.png -------------------------------------------------------------------------------- /docs/images/查看菜单页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/查看菜单页.png -------------------------------------------------------------------------------- /docs/images/测试用户登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/测试用户登录.png -------------------------------------------------------------------------------- /docs/images/点击添加词条.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/点击添加词条.png -------------------------------------------------------------------------------- /docs/images/登陆测试账号.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/登陆测试账号.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | dist 4 | /packages/**/lib/* 5 | *.md 6 | assets -------------------------------------------------------------------------------- /config/typescript/tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./core/tsconfig.compiler.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/为测试用户绑定菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/为测试用户绑定菜单.png -------------------------------------------------------------------------------- /docs/images/只勾选测试页面即可.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/只勾选测试页面即可.png -------------------------------------------------------------------------------- /docs/images/添加角色完全体.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/添加角色完全体.png -------------------------------------------------------------------------------- /docs/images/左侧测试页面 - 中文.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/左侧测试页面 - 中文.png -------------------------------------------------------------------------------- /docs/images/左侧测试页面 - 英文.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/左侧测试页面 - 英文.png -------------------------------------------------------------------------------- /packages/module-list/README.md: -------------------------------------------------------------------------------- 1 | # modules 2 | 3 | 用于 opentiny list 功能,显示当前opentiny-cli 所支持的套件和插件。 4 | 5 | -------------------------------------------------------------------------------- /docs/images/tiny-pro-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/tiny-pro-show.png -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/feature_2012x914.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/docs/images/feature_2012x914.png -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/src.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hook', 3 | description: 'la la la', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/devkit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/bottom-dist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hook', 3 | description: 'la la la', 4 | }; 5 | // yiii 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/top-dist.js: -------------------------------------------------------------------------------- 1 | // yiii 2 | module.exports = { 3 | name: 'hook', 4 | description: 'la la la', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/a.dd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentiny/tiny-cli/HEAD/packages/devkit/src/fs/fixtures/dir-src/a.dd -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/copy-dist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'test', 3 | description: 'theReplaceValue', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/copy-src.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '<{%=name%}>', 3 | description: 'PLACEHOLDER', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-dist-abc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'test', 3 | description: 'theReplaceValue', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /config/typescript/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/abc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '<{%=name%}>', 3 | description: 'PLACEHOLDER', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/b.tar.gz: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '<{%=name%}>', 3 | description: 'PLACEHOLDER', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/bbb.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '<{%=name%}>', 3 | description: 'PLACEHOLDER', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'none', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2 7 | }; -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/after-dist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hook', 3 | attr1: 'value1', 4 | attr2: 'value2', 5 | description: 'la la la', 6 | }; 7 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/{rename}/abc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '<{%=name%}>', 3 | description: 'PLACEHOLDER', 4 | hook: 'hookPlace', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/before-dist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | attr1: 'value1', 3 | attr2: 'value2', 4 | name: 'hook', 5 | description: 'la la la', 6 | }; 7 | -------------------------------------------------------------------------------- /packages/module-list/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | test 4 | src/**.js 5 | .idea/* 6 | 7 | coverage 8 | .nyc_output 9 | *.log 10 | 11 | yarn.lock 12 | package-lock.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | ## v1.0.0-alpha.0 4 | 5 | `2022/12/12` 6 | 7 | ### 📢破坏性变更 8 | 9 | 无 10 | 11 | ### ✨新特性 12 | 13 | - 首个版本提交 14 | 15 | ### 🐞缺陷修复 16 | 17 | 无 18 | -------------------------------------------------------------------------------- /packages/commands/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/devkit/tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/commands/tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@opentiny/cli-typescript/tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages", 5 | "paths": { 6 | "@opentiny/*": ["./*/src"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/fixtures/dir-src/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #check user 3 | if [ -f /opt/cloud/billboard/service.flag ]; then 4 | rm -f /opt/cloud/billboard/service.flag 5 | fi 6 | pm2 start /opt/cbcssrservice/pm2.json 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "config/*", 4 | "packages/*" 5 | ], 6 | "version": "independent", 7 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 8 | "useNx": false, 9 | "useWorkspaces": false 10 | } 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, // 末尾保留分号 3 | singleQuote: true, // 使用单引号 4 | printWidth: 120, // 每行代码最大长度 5 | tabWidth: 2, // tab代表空格数 6 | useTabs: false, // 是否使用tab进行缩进,false表示用空格进行缩减 7 | trailingComma: 'es5', // 尾随逗号 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /packages/**/lib 3 | temp 4 | dist 5 | /packages/toolkits/docs/template/ng/public/assets 6 | /packages/toolkits/docs/template/ng/public/overviewimage 7 | /packages/toolkits/docs/.npmrc 8 | /packages/toolkits/docs/template/ng/tinyng 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Questions or need help 4 | url: https://github.com/opentiny/tiny-cli/discussions 5 | about: Add this WeChat(opentiny), we will invite you to the WeChat discussion group later. 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.ts] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | spaces_around_brackets = inside 11 | trim_trailing_whitespace = true 12 | quote_type = single 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /config/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opentiny/cli-typescript", 3 | "version": "1.0.1", 4 | "scripts": { 5 | "clean": "rm package-lock.json || true" 6 | }, 7 | "devDependencies": { 8 | "tslint": "5.9.1", 9 | "tslint-config-airbnb": "5.7.0", 10 | "tslint-config-prettier": "1.9.0" 11 | }, 12 | "dependencies": { 13 | "@types/node": "^12.6.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/module-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opentiny/cli-module-list", 3 | "version": "1.0.2", 4 | "tiny": { 5 | "@opentiny/tiny-toolkit-dev": "开发tiny-cli套件和插件", 6 | "@opentiny/tiny-plugin-link": "快速调试本地node_modules中的依赖包", 7 | "@opentiny/tiny-plugin-hwc": "快速管理和访问华为云服务资源", 8 | "@opentiny/tiny-toolkit-pro": "开箱即用的中后台前端/设计解决方案", 9 | "@opentiny/tiny-toolkit-docs": "组件库官网套件" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/typescript/core/tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": ["es2015", "dom"], 7 | "pretty": true, 8 | "strictNullChecks": true, 9 | "inlineSourceMap": false, 10 | "esModuleInterop": true, 11 | "declaration": true, 12 | "experimentalDecorators": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/issue-translator.yml: -------------------------------------------------------------------------------- 1 | name: 'issue-translator' 2 | on: 3 | issue_comment: 4 | types: [created] 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: usthe/issues-translate-action@v2.7 13 | with: 14 | IS_MODIFY_TITLE: false 15 | CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 16 | -------------------------------------------------------------------------------- /packages/devkit/src/error/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | -------------------------------------------------------------------------------- /packages/devkit/scripts/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 使用NCC进行打包 3 | * https://github.com/zeit/ncc 4 | */ 5 | const spawn = require('cross-spawn'); 6 | const path = require('path'); 7 | 8 | const devketPath = path.join(__dirname, '../'); 9 | 10 | console.log('build devkit ...'); 11 | spawn.sync( 12 | require.resolve('@vercel/ncc/dist/ncc/cli.js'), 13 | [ 14 | 'build', 15 | path.join(devketPath, 'src/index.ts'), 16 | '-o', 17 | path.join(__dirname, '../../devkit/dist'), 18 | '--v8-cache', 19 | '-e', 20 | ['shelljs'], 21 | ], 22 | { 23 | stdio: 'inherit', 24 | } 25 | ); 26 | console.log('build devkit success'); 27 | -------------------------------------------------------------------------------- /packages/devkit/src/module/getEsModule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | export default (fn: any) => { 13 | if (!fn) { 14 | return; 15 | } 16 | return fn.default || fn; 17 | }; 18 | -------------------------------------------------------------------------------- /docs/use-install.md: -------------------------------------------------------------------------------- 1 | # 使用环境安装 2 | 3 | tiny依赖 nodejs 、 npm 环境,若你还未安装好前端开发的环境,请先参考本文档进行安装。 4 | 5 | ## 安装 nodejs 与 npm 6 | 7 | *若已安装了 nodejs 与 npm ,该步可忽略。* 8 | 9 | 进入 [Node.js官网](https://nodejs.org/en/) 下载 Node.js 安装包, 并安装。 10 | 11 | 验证安装是否成功,可以在命令行中执行以下命令,查看 Node.js 版本及 NPM 版本: 12 | 13 | ``` 14 | $ node -v 15 | v16.18.1 16 | $ npm -v 17 | 8.19.2 18 | ``` 19 | > [NPM](https://www.npmjs.com/) 是 node.js 的包管理工具,用于管理包的依赖。类似于 Java 中的 Maven, Ruby 中的 Gem。 20 | 21 | ## 安装tiny 22 | 23 | 24 | ```bash 25 | $ npm install @opentiny/cli -g 26 | ``` 27 | 28 | 29 | 注意:mac用户如果提示没有权限,请在前面加上 `sudo` 命令。 30 | 31 | 验证是否安装成功,可以在命令行中执行以下命令,查看 tiny 的版本: 32 | 33 | ```bash 34 | $ tiny -v 35 | tiny v1.x.x 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/use-summary.md: -------------------------------------------------------------------------------- 1 | # 使用者文档 2 | 3 | ## tiny介绍 4 | 5 | tiny-cli是前端工程化的核心工具。。旨在为开发者提供一系列健壮的工具、套件、插件和工作流,基于统一的开发环境,保证团队开发过程的一致性和可复制性。 6 | 7 | tiny提供了类似[yeoman](http://yeoman.io/)的脚手架功能,让你快速的搭建本地开发环境;同时提供了类似于[gulp](http://gulpjs.com/)的插件机制,用于解决前端开发过程中的各类问题。 8 | 9 | tiny的设计思想来源于 npm,npm本身不具备太多的功能,只是一个包管理工具。tiny也是一样,**tiny本身不具备任何工程化能力**,tiny只是提供了一个让你写套件(脚手架)、写插件的平台,该平台可以安装、管理、运行对应的套件/插件。 10 | 11 | 12 | ## tiny功能特性 13 | 14 | 1. tiny-cli是一个跨平台的cli工具,兼容windows、mac、linux三个平台。 15 | 2. tiny-cli提供了比npm scripts更强大的任务流运行机制,较好的控制任务的同步与异步执行。 16 | 3. tiny-cli的模块支持自动化安装,直接运行对应的模块命令会自动判断本地是否已安装,若未安装会先进行安装再执行对应的命令。 17 | 4. tiny-cli提供了人性化的模块升级策略,模块可设置为自动升级或手动升级,或末位自动升级(模块更新版本号的z位时,使用者会自动升级该模块)。 18 | 5. tiny-cli提供人性化的错误提示,让错误更易懂。 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/devkit/src/user/interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | export interface UserInfo { 13 | name: string; 14 | email: string; 15 | id?: number; 16 | username?: string; 17 | state?: string; 18 | avatar_url?: string; 19 | [propName: string]: any; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noEmitOnError": true, 8 | "experimentalDecorators": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitOverride": true, 11 | "noUnusedParameters": false, 12 | "noUnusedLocals": false, 13 | "outDir": "./dist", 14 | "rootDir": ".", 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es2019", 18 | "lib": ["es2020"], 19 | "baseUrl": "", 20 | "rootDirs": ["."], 21 | "typeRoots": ["./node_modules/@types"], 22 | "types": ["node"], 23 | "paths": {}, 24 | "plugins": [] 25 | }, 26 | "exclude": [] 27 | } 28 | -------------------------------------------------------------------------------- /packages/devkit/src/task/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 汇集系统操作基本方法 14 | */ 15 | 16 | import has from './has'; 17 | import run from './run'; 18 | import runFunction from './run-function'; 19 | 20 | export default { 21 | run, 22 | runFunction, 23 | has 24 | }; 25 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/move.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import fs from 'fs-extra'; 13 | import logs from '../log/index'; 14 | 15 | const log = logs('core-fs'); 16 | 17 | export default function move(oldPath: string, newPath: string) { 18 | fs.renameSync(oldPath, newPath); 19 | log.success(`${oldPath} move to ${newPath}`); 20 | } 21 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - allcontributors[bot] 7 | categories: 8 | - title: tiny-toolkit-pro 9 | labels: 10 | - toolkit-pro 11 | - title: tiny-toolkit-docs 12 | labels: 13 | - toolkit-docs 14 | - title: tiny-toolkit-dev 15 | labels: 16 | - toolkit-dev 17 | - title: tiny-plugin-hwc 18 | labels: 19 | - plugin-hwc 20 | - title: tiny-plugin-link 21 | labels: 22 | - plugin-link 23 | - title: tiny-plugin-lint 24 | labels: 25 | - plugin-lint 26 | - title: cli 27 | labels: 28 | - cli 29 | - title: Other Changes 30 | labels: 31 | - documentation 32 | - refactoring 33 | - unit-test 34 | - ci -------------------------------------------------------------------------------- /packages/devkit/src/config/fixtures/mock.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | 'use strict'; 13 | 14 | module.exports = { 15 | toolkit: '@opentiny/tiny-toolkit-dev', 16 | // abc 插件 17 | abc: { 18 | xyz: 22 19 | }, 20 | // 任务列表 21 | tasks: { 22 | start: [ 23 | { 24 | command: 'echo 33' 25 | } 26 | ] 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /packages/devkit/src/log/helpers/logger-test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import logs from '../index'; 13 | 14 | const log = logs('core-module'); 15 | 16 | log.info('info message'); 17 | log.error('error-log'); 18 | log.error(new Error('an error')); 19 | log.warn('warn message'); 20 | log.info({ a: 1 }); 21 | log.info('a', 'b', 'c'); 22 | log.debug('debug message'); 23 | -------------------------------------------------------------------------------- /packages/devkit/src/config/locale/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 语言文件 14 | */ 15 | 16 | export default { 17 | zh: { 18 | readConfigError: '读取配置文件失败,请确认 {file} 文件是否有错误', 19 | moreDetail: '详细报错信息如下:' 20 | }, 21 | en: { 22 | readConfigError: 'Failed to read config, please double check {file}', 23 | moreDetail: 'details:' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /packages/devkit/src/intl/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 多语言文案格式 14 | */ 15 | export interface intlMessage { 16 | // 中文必须要有 17 | zh: object; 18 | // 英文可选 19 | en?: object; 20 | } 21 | 22 | /** 23 | * 可选的多语言文案 24 | */ 25 | export enum LocaleType { 26 | ZH = 'zh', 27 | EN = 'en' 28 | } 29 | 30 | export interface CacheLocale { 31 | locale: LocaleType; 32 | } 33 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import fs from 'fs-extra'; 13 | import copyTpl from './copy-tpl'; 14 | import move from './move'; 15 | import remove from './remove'; 16 | import packages from './package'; 17 | export { default as IPackageJSON } from './interface'; 18 | 19 | export default { 20 | ...fs, 21 | copyTpl, 22 | move, 23 | remove, 24 | ...packages 25 | }; 26 | -------------------------------------------------------------------------------- /packages/devkit/src/module/online-exist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import npm from '../npm/index'; 13 | /** 14 | * 线上模块是否存在 15 | */ 16 | async function onlineExist(name: string): Promise { 17 | const latest = await npm.latest(name); 18 | // 如果description 为 delete的话,则排查掉该模块,因为publish 之后,是不允许unpublish的 19 | return !!(latest && latest.description !== 'delete'); 20 | } 21 | 22 | export default onlineExist; 23 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/remove.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import fs from 'fs-extra'; 13 | import logs from '../log/index'; 14 | 15 | const log = logs('core-fs'); 16 | 17 | export default function remove(file: string) { 18 | if (!fs.existsSync(file)) { 19 | return log.warn(`${file} Directory or file does not exist, no need to delete`); 20 | } 21 | fs.removeSync(file); 22 | return log.success(`${file} successfully deleted`); 23 | } 24 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-default.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import logs from '../log/index'; 13 | import message from './locale/index'; 14 | import Intl from '../intl/index'; 15 | 16 | const log = logs('core-error'); 17 | 18 | export default async function (e: any) { 19 | const intl = new Intl(message); 20 | const ERROR_MSG = intl.get('intranetTips'); 21 | log.error(ERROR_MSG); 22 | e.stack && console.log(e.stack); 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /packages/commands/src/install.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import { logs, modules, Intl } from '@opentiny/cli-devkit'; 13 | import message from './locale/index'; 14 | 15 | const log = logs('core-commands'); 16 | 17 | export default async (cliArgs: any) => { 18 | const name = cliArgs.pop(); 19 | if (name) { 20 | await modules.installOne(name); 21 | } else { 22 | const intl = new Intl(message); 23 | log.warn(intl.get('installTips')); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /packages/devkit/src/module/local-exist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import home from '../home/index'; 13 | import fs from 'fs-extra'; 14 | import * as path from 'path'; 15 | 16 | /** 17 | * 模块是否存在 18 | */ 19 | function localExist(name: string): boolean { 20 | const modulePath = path.resolve(home.getModulesPath(), name); 21 | const pkgPath = path.resolve(modulePath, 'package.json'); 22 | 23 | return fs.existsSync(pkgPath); 24 | } 25 | 26 | export default localExist; 27 | -------------------------------------------------------------------------------- /packages/commands/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 设置字符串边距 14 | * @param str 15 | * @param width 16 | * @returns {string} 17 | */ 18 | export function getPadding(str: string, width: number): string { 19 | const spaceLen = (typeof width === 'undefined' ? 30 : width) - str.length; 20 | let padding = ''; 21 | 22 | padding += ' '; 23 | for (let i = 2; i < spaceLen; i += 1) { 24 | padding += '-'; 25 | } 26 | padding += ' '; 27 | return padding; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const fs = require('fs'); 3 | const vm = require('vm'); 4 | const m = require('module'); 5 | const readFileSync = fs.readFileSync; 6 | const writeFileSync = fs.writeFileSync; 7 | const Script = vm.Script; 8 | const wrap = m.wrap; 9 | const source = readFileSync(__dirname + '/dist/index.js.cache.js', 'utf-8'); 10 | const cachedData = 11 | !process.pkg && require('process').platform !== 'win32' && readFileSync(__dirname + '/dist/index.js.cache'); 12 | const script = new Script(wrap(source), cachedData ? { cachedData } : {}); 13 | script.runInThisContext()(exports, require, module, __filename, __dirname); 14 | // 移除发送请求时报warn,参考自:https://github.com/nodejs/node/issues/10802#issuecomment-616728458 15 | process.removeAllListeners('warning'); 16 | if (cachedData) 17 | process.on('exit', () => { 18 | try { 19 | writeFileSync(__dirname + '/dist/index.js.cache', script.createCachedData()); 20 | } catch (e) {} 21 | }); 22 | -------------------------------------------------------------------------------- /config/typescript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-airbnb", "tslint-config-prettier"], 3 | "rules": { 4 | "strict-boolean-expressions": false, 5 | "array-type": [true, "generic"], 6 | "prefer-array-literal": false, 7 | "arrow-return-shorthand": true, 8 | "ban-types": [ 9 | true, 10 | ["{}", "Use custom type instead."], 11 | ["Object", "Use custom type instead."], 12 | ["String", "Use 'string' instead."], 13 | ["Number", "Use 'number' instead."], 14 | ["Boolean", "Use 'boolean' instead."] 15 | ], 16 | "class-name": true, 17 | "no-any": false, 18 | "no-string-throw": true, 19 | "no-unused-expression": true, 20 | "no-unused-variable": true, 21 | "prefer-const": true, 22 | "radix": true, 23 | "variable-name": [ 24 | true, 25 | "check-format", 26 | "ban-keywords", 27 | "allow-leading-underscore", 28 | "allow-trailing-underscore" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/commands/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opentiny/cli-commands", 3 | "version": "1.0.1", 4 | "scripts": { 5 | "build": "tsc -p tsconfig.json", 6 | "watch": "tsc -p tsconfig.compiler.json --watch", 7 | "clean": "rm -rf lib/ && rm package-lock.json || true", 8 | "test": "npm run build && ava" 9 | }, 10 | "main": "./lib/index.js", 11 | "types": "./lib/index.d.ts", 12 | "dependencies": { 13 | "@opentiny/cli-devkit": "^1.0.0-alpha.0", 14 | "chalk": "^2.4.2", 15 | "fs-extra": "^8.1.0", 16 | "inquirer": "^6.5.0", 17 | "node-emoji": "^1.10.0", 18 | "yargs": "^13.3.0" 19 | }, 20 | "devDependencies": { 21 | "@opentiny/cli-typescript": "^1.0.0-alpha.0", 22 | "@types/node": "^12.6.2", 23 | "ava": "^4.0.1", 24 | "chai": "^4.2.0", 25 | "proxyquire": "^2.1.1", 26 | "sinon": "^7.3.2", 27 | "typescript": "^3.5.3" 28 | }, 29 | "ava": { 30 | "snapshotDir": "./snapshots" 31 | }, 32 | "gitHead": "7df28442b47d4dc15cc2285eff3a211c366ec627" 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature Request 2 | description: Propose new features to tiny-cli to improve it. 3 | title: '✨ [Feature]: ' 4 | labels: ['✨ feature'] 5 | body: 6 | - type: textarea 7 | id: feature-solve 8 | attributes: 9 | label: What problem does this feature solve 10 | description: | 11 | Explain your use case, context, and rationale behind this feature request. More importantly, what is the end user experience you are trying to build that led to the need for this feature? 12 | placeholder: Please Input 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: feature-api 17 | attributes: 18 | label: What does the proposed API look like 19 | description: | 20 | Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use Markdown to format your code blocks. 21 | placeholder: Please Input 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /packages/devkit/src/error/register.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc 注册错误处理器 14 | */ 15 | 16 | import utils from './utils'; 17 | /** 18 | * 注册错误处理器 19 | * @param {generatorFunction|Array} handle 错误处理器, 必须是 generator函数, 多个时可以传数组, 20 | * 进程报错时传入错误对象并依次调用, 21 | * 如果 catch 到错误并处理完请返回 true, 否则将继续往下执行其他处理器函数 22 | */ 23 | export default function register(handle: any) { 24 | if (Array.isArray(handle)) { 25 | handle.forEach((item) => { 26 | utils.register(item); 27 | }); 28 | } else { 29 | utils.register(handle); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/devkit/src/npm/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as npm from './index'; 13 | import test from 'ava'; 14 | 15 | test('# 测试安装状态', async (t) => { 16 | await npm.install('jquery'); 17 | t.pass(); 18 | }); 19 | 20 | test('# has 判断模块是否存在', async (t) => { 21 | const has = await npm.has('@opentiny/expense'); 22 | t.true(has); 23 | }); 24 | 25 | test('# lastes 获取模块的信息', async (t) => { 26 | const data = await npm.latest('@opentiny/expense'); 27 | t.true(typeof data === 'object'); 28 | t.is('@opentiny/expense', data!.name); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/devkit/src/task/has.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import utils from './utils'; 13 | 14 | /** 15 | * 一个任务列表里面是否存在有指定时间的任务 16 | * @param tasks , 任务列表,与 tiny.config.js 里面任务流数据格式保持一致 17 | * @param when {string} 可以不传, 或传 before , after 18 | * @returns {boolean} 19 | */ 20 | function has(tasks: any, when?: string) { 21 | if (!tasks) { 22 | return false; 23 | } 24 | if (!when) { 25 | if (tasks.length) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | return !!utils.classify(tasks)[when].length; 31 | } 32 | export default has; 33 | -------------------------------------------------------------------------------- /packages/devkit/src/task/locale/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 语言文件 14 | */ 15 | 16 | export default { 17 | zh: { 18 | commandError: '{command} 命令执行行失败', 19 | runCommand: '开始执行{command}{whenTips}任务', 20 | preTask: '前置', 21 | nextTask: '后置', 22 | runSuccess: '{command}{whenTips}任务执行成功' 23 | }, 24 | en: { 25 | commandError: 'Failed to excute {command}', 26 | runCommand: '{command} {whenTips} stared', 27 | preTask: 'Pre-task', 28 | nextTask: 'Post-task', 29 | runSuccess: '{command} {whenTips} completed' 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /packages/commands/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import i from './i'; 13 | import clear from './clear'; 14 | import help from './help'; 15 | import init from './init'; 16 | import list from './list'; 17 | import locale from './locale'; 18 | import main from './main'; 19 | import update from './update'; 20 | import version from './version'; 21 | import install from './install'; 22 | import config from './config'; 23 | 24 | export default { 25 | i, 26 | clear, 27 | help, 28 | init, 29 | list, 30 | locale, 31 | main, 32 | update, 33 | install, 34 | version, 35 | config, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/commands/src/update.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 更新模块 14 | */ 15 | 16 | import { modules, npm, Intl, logs } from '@opentiny/cli-devkit'; 17 | import message from './locale/index'; 18 | 19 | const log = logs('core-commands'); 20 | 21 | export default async function (cliArgs: string[]) { 22 | const name: string = cliArgs.pop() || ''; 23 | const intl = new Intl(message); 24 | const pkg = await npm.latest(name); 25 | if (pkg && pkg.version) { 26 | await modules.update(`${name}@${pkg.version}`); 27 | } else { 28 | log.error( 29 | intl.get('updateError', { 30 | name, 31 | }) 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/devkit/src/module/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import get from './get'; 13 | import getEsModule from './getEsModule'; 14 | import getReallyName from './getReallyName'; 15 | import installOne from './install-one'; 16 | import localExist from './local-exist'; 17 | import localList from './local-list'; 18 | import onlineExist from './online-exist'; 19 | import onlineList from './online-list'; 20 | import update from './update'; 21 | import utils from './utils'; 22 | 23 | export default { 24 | get, 25 | getReallyName, 26 | localExist, 27 | onlineExist, 28 | localList, 29 | onlineList, 30 | installOne, 31 | update, 32 | getEsModule, 33 | utils 34 | }; 35 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-npm-not-found.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import Intl from '../intl/index'; 13 | import logs from '../log/index'; 14 | import message from './locale/index'; 15 | 16 | const log = logs('core-error'); 17 | /** 18 | * 未找到npm包或安装 npm 包时出现其他错误时,进行提示 19 | */ 20 | export default async function (e: any) { 21 | const errMsg = e ? e.toString() : ''; 22 | const intl = new Intl(message); 23 | const regx = /install\s(.+)\serror/; 24 | const match = errMsg.match(regx); 25 | 26 | if (match) { 27 | log.debug('npm-not-found 捕获'); 28 | log.error(intl.get('npmNotFound', { module: match[1] })); 29 | return true; 30 | } 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /config/typescript/core/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-airbnb", "tslint-config-prettier"], 3 | "rules": { 4 | "strict-boolean-expressions": false, 5 | "array-type": [true, "generic"], 6 | "prefer-array-literal": false, 7 | "arrow-return-shorthand": true, 8 | "ban-types": [ 9 | true, 10 | ["{}", "Use custom type instead."], 11 | ["Object", "Use custom type instead."], 12 | ["String", "Use 'string' instead."], 13 | ["Number", "Use 'number' instead."], 14 | ["Boolean", "Use 'boolean' instead."] 15 | ], 16 | "class-name": true, 17 | "no-any": false, 18 | "no-string-throw": true, 19 | "no-unused-expression": true, 20 | "no-unused-variable": true, 21 | "prefer-const": true, 22 | "radix": true, 23 | "semicolon": [true, "always", "ignore-interfaces"], 24 | "quotemark": [true, "single", "avoid-escape", "avoid-template"], 25 | "trailing-comma": [true, { "multiline": "always", "singleline": "never" }], 26 | "variable-name": [ 27 | true, 28 | "check-format", 29 | "ban-keywords", 30 | "allow-leading-underscore", 31 | "allow-trailing-underscore" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 - present Tiny CLI Authors. 4 | Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opentiny/cli", 3 | "version": "1.1.1", 4 | "bin": { 5 | "tiny": "./lib/index.js" 6 | }, 7 | "engines": { 8 | "node": ">=8.0.0", 9 | "npm": ">= 3.0.0" 10 | }, 11 | "scripts": { 12 | "build": "tsc -p tsconfig.compiler.json", 13 | "link": "npm link", 14 | "watch": "tsc -p tsconfig.compiler.json --watch", 15 | "clean": "npm uninstall @opentiny/cli -g && rm -rf lib/ && rm package-lock.json || true", 16 | "test": "npm run build && ava" 17 | }, 18 | "main": "./lib/index.js", 19 | "types": "./lib/index.d.ts", 20 | "devDependencies": { 21 | "@types/node": "^12.6.2", 22 | "@vercel/ncc": "^0.27.0", 23 | "ava": "^4.0.1", 24 | "chai": "^4.2.0", 25 | "sinon": "^7.3.2", 26 | "typescript": "^3.5.3" 27 | }, 28 | "dependencies": { 29 | "@opentiny/cli-commands": "^1.0.0-alpha.0", 30 | "@opentiny/cli-devkit": "^1.0.0-alpha.0", 31 | "@opentiny/cli-typescript": "^1.0.0-alpha.0", 32 | "cross-spawn": "^7.0.3", 33 | "shelljs": "^0.8.3", 34 | "sudo-block": "^3.0.0", 35 | "v8-compile-cache": "^2.1.0" 36 | }, 37 | "ava": { 38 | "snapshotDir": "./snapshots" 39 | }, 40 | "gitHead": "7df28442b47d4dc15cc2285eff3a211c366ec627" 41 | } 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # PR 2 | 3 | ## PR Checklist 4 | 5 | Please check if your PR fulfills the following requirements: 6 | 7 | - [ ] The commit message follows our [Commit Message Guidelines](https://github.com/opentiny/tiny-cli/blob/main/CONTRIBUTING.md) 8 | - [ ] Tests for the changes have been added (for bug fixes / features) 9 | - [ ] Docs have been added / updated (for bug fixes / features) 10 | 11 | ## PR Type 12 | 13 | What kind of change does this PR introduce? 14 | 15 | 16 | 17 | - [ ] Bugfix 18 | - [ ] Feature 19 | - [ ] Code style update (formatting, local variables) 20 | - [ ] Refactoring (no functional changes, no api changes) 21 | - [ ] Build related changes 22 | - [ ] CI related changes 23 | - [ ] Documentation content changes 24 | - [ ] Other... Please describe: 25 | 26 | ## What is the current behavior? 27 | 28 | 29 | 30 | Issue Number: N/A 31 | 32 | ## What is the new behavior? 33 | 34 | ## Does this PR introduce a breaking change? 35 | 36 | - [ ] Yes 37 | - [ ] No 38 | 39 | 40 | 41 | ## Other information 42 | -------------------------------------------------------------------------------- /docs/use-plugin.md: -------------------------------------------------------------------------------- 1 | # 使用tiny插件 2 | 3 | 4 | ## tiny插件介绍 5 | 6 | tiny插件,是符合一定规则的 npm 模块,插件主要是用来扩展命令行,可以通过插件来增加 tiny 命令行的命令。**插件专注于某个比较单一的功能**,解决工作中零散、重复的任务。 7 | 8 | 插件是对套件的一个补充,套件解决的是前端工作的主要生命周期,而套件无法覆盖到的地方,将由插件来补充。 9 | 10 | 比如:在套件的源码打包与构建阶段(`tiny build`),套件的主要任务是将src目录的源码进行压缩、编译到build目录。而在这个过程中,可以同时使用一些辅助插件: 11 | 12 | 0. tiny eslint 插件,可以在build的过程中做代码规范检测 13 | 1. tiny console 插件,可以检测源码中是否包含有console信息 14 | 2. tiny check 插件,可以检测dependencies中的依赖是否有新版本 15 | 16 | 这些细小的功能点,本身具有业务通用性,不需要每个套件去实现一遍,将这些插件穿插在套件的生命周期中一起使用,相互配合,最终提高开发效率,改善开发体验。 17 | 18 | 19 | ## 在命令行中使用 20 | 21 | 插件的使用基本格式是:` tiny [pluginName] [command]` 22 | 23 | 其中: 24 | 25 | * `pluginName`为插件的名字,如`tiny-plugin-console`插件,`pluginName`则是`console` 26 | * `command`为插件的具体命令,每个插件的命令可能不一样,需要看插件文档。 27 | 28 | ```bash 29 | # 使用console插件的detect命令 30 | $ tiny console detect 31 | 32 | # 使用console插件的strip命令 33 | $ tiny console strip 34 | ``` 35 | 36 | ## 在配置文件中使用 37 | 38 | tiny的插件可以在`tiny.config.js`配置文件中使用。 39 | 40 | 如下面的例子,在`tiny build`命令中进行使用,执行`tiny build`后,会先执行`tiny console detect`命令的功能,再执行套件本身的`build`任务。 41 | 42 | ```js 43 | //tiny.config.js 44 | 45 | module.exports = { 46 | 47 | tasks : { 48 | //省略其他配置... 49 | build : [ 50 | { 51 | // console检测 52 | command : 'tiny console detect' 53 | } 54 | ] 55 | } 56 | }; 57 | ``` 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/package.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import fs from 'fs-extra'; 13 | import * as path from 'path'; 14 | import IPackageJSON from './interface'; 15 | 16 | /** 17 | * 读取package文件 18 | * @param cwd package.json 所在的目录,默认取值 process.cwd() 19 | */ 20 | export function readPackage(cwd?: string): IPackageJSON | null { 21 | cwd = cwd || process.cwd(); 22 | const pkgPath = path.resolve(cwd, 'package.json'); 23 | if (fs.existsSync(pkgPath)) { 24 | return fs.readJsonSync(pkgPath); 25 | } 26 | return null; 27 | } 28 | 29 | export function writePackage(pkg: IPackageJSON, cwd: string) { 30 | cwd = cwd || process.cwd(); 31 | const pkgPath = path.resolve(cwd, 'package.json'); 32 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), { encoding: 'utf8' }); 33 | } 34 | 35 | export default { 36 | readPackage, 37 | writePackage 38 | }; 39 | -------------------------------------------------------------------------------- /packages/devkit/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | export { default as argv } from './argv'; 13 | export { default as cache } from './cache'; 14 | export { default as cliConfig } from './cli-config'; 15 | export { default as config } from './config'; 16 | export { default as env } from './env'; 17 | export { default as error } from './error'; 18 | export { default as fs } from './fs'; 19 | export { default as home } from './home'; 20 | export { default as Intl } from './intl'; 21 | export { default as logs } from './log'; 22 | export { default as modules } from './module'; 23 | export { default as npm } from './npm'; 24 | export { default as report } from './report'; 25 | export { default as task } from './task'; 26 | export { default as upgrade } from './upgrade'; 27 | export { default as git } from './git/index'; 28 | export { default as user } from './user'; 29 | -------------------------------------------------------------------------------- /packages/commands/src/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import chalk from 'chalk'; 13 | import * as path from 'path'; 14 | import { config, cliConfig, home, logs } from '@opentiny/cli-devkit'; 15 | 16 | const log = logs('core-commands'); 17 | 18 | export default async function () { 19 | const bin = cliConfig.getBinName(); 20 | 21 | console.log(chalk.magenta(`${bin} v${process.env[cliConfig.PROCESS_ENV_CLI_VERSION]}`)); 22 | // 获取toolkit 23 | const toolkitName = config.getToolkitName(); 24 | if (toolkitName) { 25 | try { 26 | const pkgPath = path.join(home.getModulesPath(), toolkitName, 'package.json'); 27 | log.debug(`${toolkitName} package.json path = ${pkgPath}`); 28 | const pkg = require(pkgPath); 29 | console.log(chalk.magenta(`${toolkitName} v${pkg.version}`)); 30 | } catch (e) { 31 | log.debug(e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/package.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as path from 'path'; 13 | import fs from 'fs-extra'; 14 | import IPackageJSON from './interface'; 15 | import * as fsPkg from './package'; 16 | import test from 'ava'; 17 | 18 | const pkgMock: IPackageJSON = { 19 | varsion: '1.0.0', 20 | name: 'tiny' 21 | }; 22 | const pkgPath = path.join(__dirname, 'package.json'); 23 | 24 | test.before(() => { 25 | fs.outputJsonSync(pkgPath, pkgMock); 26 | }); 27 | 28 | test('# readPackage', (t) => { 29 | const pkg = fsPkg.readPackage(__dirname); 30 | t.is(pkg!.name, 'tiny'); 31 | }); 32 | 33 | test('# writePackage', (t) => { 34 | fsPkg.writePackage( 35 | { 36 | version: '1.0.1', 37 | name: 'tiny2' 38 | }, 39 | __dirname 40 | ); 41 | 42 | const pkg = require(path.join(__dirname, 'package.json')); 43 | t.is(pkg.name, 'tiny2'); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/devkit/src/error/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import { getScope } from '../cli-config/index'; 13 | 14 | const handleList: any = []; 15 | 16 | export default { 17 | /** 18 | * 处理模块名,返回正确的名称 19 | * abc/abc.js 20 | * @opentiny/abc/a.js 21 | * @opentiny/abc 22 | * abc 23 | */ 24 | pureModuleName(moduleName) { 25 | const modules = moduleName.split('/'); 26 | const scope = getScope(); 27 | let module = moduleName; 28 | 29 | if (modules.length > 1) { 30 | if (modules[0].indexOf(`@${scope}`) !== -1) { 31 | module = `${modules[0]}/${modules[1]}`; 32 | } else { 33 | module = modules[0]; 34 | } 35 | } 36 | return module; 37 | }, 38 | register(handle: any) { 39 | if (typeof handle === 'function') { 40 | handleList.push(handle); 41 | } 42 | }, 43 | getHandleList() { 44 | return handleList; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /packages/commands/src/i.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 快速安装npm包 14 | */ 15 | 16 | import { npm, fs, logs, Intl } from '@opentiny/cli-devkit'; 17 | import yargs from 'yargs'; 18 | import rimraf from 'rimraf'; 19 | import * as path from 'path'; 20 | import message from './locale/index'; 21 | 22 | const log = logs('core-commands'); 23 | const argv = yargs.help(false).argv; 24 | 25 | export default async function () { 26 | const modules = argv._.slice(1); 27 | const mPath = path.join(process.cwd(), 'node_modules'); 28 | delete argv._; 29 | delete argv.$0; 30 | if (argv.focus && fs.existsSync(mPath)) { 31 | const intl = new Intl(message); 32 | log.info(intl.get('removeNpm')); 33 | rimraf.sync(mPath); 34 | delete argv.focus; 35 | } 36 | 37 | if (modules.length) { 38 | await npm.install(modules, argv); 39 | } else { 40 | await npm.installDependencies(argv); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/devkit/src/module/update.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import Intl from '../intl/index'; 13 | import logs from '../log/index'; 14 | import message from './locale/index'; 15 | import installOne from './install-one'; 16 | import localList from './local-list'; 17 | 18 | const log = logs('core-module'); 19 | 20 | /** 21 | * 更新模块 22 | * @param name 23 | */ 24 | async function update(name: string | undefined) { 25 | const options = { 26 | type: 'update' 27 | }; 28 | if (name) { 29 | log.debug(`单独更新模块 ${name}`); 30 | await installOne(name, options); 31 | return; 32 | } 33 | const list = await localList(); 34 | log.debug('更新本地列表 %o', list); 35 | for (let i = 0; i < list.length; i += 1) { 36 | // todo 先全部重新安装 ,后面再做版本判断 37 | await installOne(list[i].name, options); 38 | } 39 | if (list.length === 0) { 40 | const intl = new Intl(message); 41 | log.success(intl.get('updateNone')); 42 | } 43 | } 44 | 45 | export default update; 46 | -------------------------------------------------------------------------------- /packages/devkit/src/error/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc 错误处理,先会调用之前注册过的错误处理,最后执行默认的处理 14 | */ 15 | 16 | import utils from './utils'; 17 | import logs from '../log/index'; 18 | import npmNotFound from './handle-npm-not-found'; 19 | import moduleNotFound from './handle-module-not-found'; 20 | import enoent from './handle-enoent'; 21 | import defaultError from './handle-default'; 22 | 23 | const log = logs('core-logs'); 24 | const innerList = [npmNotFound, moduleNotFound, enoent, defaultError]; 25 | 26 | /** 27 | * 错误处理器 28 | * @param {error} e 错误对象 29 | */ 30 | export default async function handle(e: any) { 31 | log.debug('error code = %s', e.code); 32 | log.debug(e.stack || e); 33 | const handList = utils.getHandleList().concat(innerList); 34 | // 发送错误日志 35 | 36 | for (let i = 0; i < handList.length; i += 1) { 37 | const res = await handList[i](e); 38 | if (res === true) { 39 | // 说明错误已被处理, 可以直接返回了 40 | return; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/commands/src/locale.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 切换语言环境 14 | */ 15 | 16 | import { Intl, logs } from '@opentiny/cli-devkit'; 17 | import inquirer, { Answer } from 'inquirer'; 18 | import message from './locale/index'; 19 | 20 | const log = logs('core-commands'); 21 | 22 | /** 23 | * 初始化环境 24 | */ 25 | export default async () => { 26 | let intl = new Intl(message); 27 | const divider = '- '; 28 | const answers: Answer = await inquirer.prompt([ 29 | { 30 | type: 'list', 31 | name: 'name', 32 | message: intl.get('switchLocaleTips'), 33 | choices: [ 34 | { 35 | name: `zh ${divider}中文`, 36 | value: 'zh', 37 | }, 38 | { 39 | name: `en ${divider}英文`, 40 | value: 'en', 41 | }, 42 | ], 43 | }, 44 | ]); 45 | 46 | // 设置env环境 47 | intl.setLocale(answers.name); 48 | 49 | intl = new Intl(message); 50 | log.success(intl.get('initLocalSuccess')); 51 | }; 52 | -------------------------------------------------------------------------------- /packages/devkit/src/upgrade/locale/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | export default { 13 | zh: { 14 | updateTips: '升级提示', 15 | recommendVersion: '推荐的版本是 {latest} , 本地版本是 {localVersion}, 建议升级后再使用,保证功能的稳定性', 16 | recommendedVersion: '推荐的版本是 {latest} , 本地版本是 {localVersion}, 为保证功能的稳定性,需进行更新', 17 | updateCommand: '请执行 {icon} {command} 来升级', 18 | updatingCommand: '系统正在执行 {icon} {command} 自动升级', 19 | ifUpdateError: '如果提示没有权限,请尝试' 20 | }, 21 | en: { 22 | updateTips: 'Upgrade tips', 23 | recommendVersion: 24 | 'Recommended version is {latest}, the local version is {localVersion}, it is recommended to upgrade and then use', 25 | recommendedVersion: 26 | 'Recommended version is {latest}, the local version is {localVersion}, and the system will be updated automatically to ensure the stability', 27 | updateCommand: 'Please use {icon} {command} to upgrade', 28 | updatingCommand: 'The system is performing {icon} {command} automatic upgrade', 29 | ifUpdateError: "If the prompt shows that you don't have permission, please try" 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-eaddrinuse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 端口被占用时的错误处理 14 | */ 15 | import logs from '../log/index'; 16 | import message from './locale/index'; 17 | import chalk from 'chalk'; 18 | import Intl from '../intl/index'; 19 | import * as os from 'os'; 20 | 21 | const log = logs('core-error'); 22 | 23 | function handleSolution(port: any) { 24 | const isWin = os.type().match(/^Win/); 25 | const intl = new Intl(message); 26 | if (!isWin) { 27 | return chalk.yellow(intl.get('winPidTips', { port })); 28 | } 29 | 30 | return chalk.yellow(intl.get('macPidTips', { port })); 31 | } 32 | 33 | // 处理 34 | export default async function (e: any) { 35 | if (e.code !== 'EADDRINUSE') { 36 | return false; 37 | } 38 | 39 | const match = e.message.match(/listen EADDRINUSE(.*):(\d+)/); 40 | 41 | if (match && match[2]) { 42 | const port = match[2]; 43 | const intl = new Intl(message); 44 | log.error(intl.get('helpTips', { port: chalk.green(port), solution: handleSolution(port) })); 45 | return true; 46 | } 47 | return false; 48 | } 49 | -------------------------------------------------------------------------------- /packages/devkit/src/task/run-function.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import { isGenerator, isGeneratorFunction } from './utils'; 13 | /** 14 | * 执行某个函数 15 | * 如果是 generator 类型,则使用 yield执行, 并在其后执行 next(0\) 16 | * 否则普通调用, 并传入 next 函数 17 | * @param options {object} 18 | * @param options.method {function} 函数体 19 | * @param options.args {array} 参数 20 | * @param options.next {function} 下一步执行方法 21 | * @return {mix} 函数体内的返回值 22 | */ 23 | async function runFunction(options: any) { 24 | const noop = () => {}; 25 | const method = options.method; 26 | const args = options.args || []; 27 | const next = options.next || noop; 28 | let res: any; 29 | if (isGeneratorFunction(method) || isGenerator(method)) { 30 | res = method(...args); 31 | next(); 32 | return res; 33 | } 34 | 35 | res = method(...args.concat(next)); 36 | // return 为 promise 对象的情况 37 | if (res && typeof res.then === 'function') { 38 | const res2: any = await res; 39 | next(); 40 | return res2; 41 | } else { 42 | next(); 43 | } 44 | return res; 45 | } 46 | 47 | export default runFunction; 48 | -------------------------------------------------------------------------------- /docs/use-toolkit.md: -------------------------------------------------------------------------------- 1 | # 使用tiny套件 2 | 3 | ## tiny套件介绍 4 | 5 | tiny套件是特定业务的整体解决方案模板,提供全链路开发解决方案,从最初本地项目初始化,到开发过程中的调试、构建和最终的代码发布上线等功能都由套件提供。 6 | 7 | 使用tiny套件,可以享受到一致化的开发体验。tiny对前端相关的开发生命周期进行了抽象,将所有开发过程划分为六个阶段,每个阶段由一个固定的子命令加以支持。不管开发哪种业务体验完全一致。不再需要记忆复杂冗长的命令,记住六个 tiny 固定子命令打遍天下都不怕。 8 | 9 | ## 套件固定命令 10 | 11 | ### tiny init 12 | 13 | **初始化项目结构** 14 | 15 | 使用套件进行业务开发时,一般 tiny init 是您需要执行的第一个命令,执行该命令后默认为会显示一个可用套件列表菜单待您选择,根据您的业务模型选择合适的套件后即会根据套件的设置初始化您的目录结构和一些基本配置信息。 16 | 17 | ### tiny start 18 | 19 | **开启本地开发环境** 20 | 21 | 使用 tiny start 命令可以在本地项目目录快速启动一个开发调试服务(如果是开发 Web 页面,一般套件会实现一个 Web Server)。同时会监听项目文件的变化,进行实时编译。 22 | 23 | ### tiny test 24 | 25 | **执行测试任务** 26 | 27 | 如果您的项目 /test 文件夹中提供了相关的单元测试脚本,执行 `tiny test` 时将自动运行这些单元测试。 28 | 29 | ### tiny build 30 | 31 | **源码构建与打包** 32 | 33 | 如果您想获取本地源代码构建后的内容,执行 `tiny build` 即可。tiny 会将源码进行压缩、合并最终输出到 `build`目录 34 | 35 | ### tiny publish 36 | 37 | **发布项目代码** 38 | 39 | 执行 `tiny publish` 后将执行项目仓库中对应开发分支下的代码发布任务。根据不同套件所支持业务的特点,最终将构建后的代码发布到对应的环境。比如 assets 代码可以发布到 CDN。 40 | 41 | ## tiny help 42 | 43 | **查看套件帮助** 44 | 45 | **在项目根目录下**,执行`tiny help`命令,会显示当前项目的套件帮助信息。不同套件可能有一些不同的命令。若不在项目根目录,执行该命令,只会显示 tiny的帮助信息。 46 | 47 | 48 | ## 例子 49 | 50 | 1. 初始化项目 51 | 52 | ```bash 53 | # 创建并进入项目文件夹 54 | $ mkdir my-project && cd $_ 55 | 56 | # 初始化开发环境 57 | $ tiny init 58 | ``` 59 | 60 | 0. 开启本地环境 61 | 62 | ```bash 63 | # 开启开发环境 64 | $ tiny start 65 | ``` 66 | 67 | 68 | 0. 项目编译及打包 69 | 70 | ```bash 71 | # 打包项目到指定的目录 72 | $ tiny build 73 | ``` 74 | 0. 发布项目代码为npm包 75 | 76 | ```bash 77 | # 将编译后的代码发布至CDN环境 78 | $ npm publish 79 | ``` 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /packages/devkit/src/argv/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import yargs from 'yargs'; 13 | import logs from '../log/index'; 14 | const argv = yargs.help(false).argv; 15 | const log = logs('core-argv'); 16 | 17 | export default () => { 18 | // cli所需的命令 19 | let command: string; 20 | // cli命令所需的参数 21 | let newArgv: string[] = []; 22 | 23 | // 特殊处理一下传入的参数 24 | // tiny -v 时候的处理 25 | if (!argv._.concat().pop() && (argv.v || argv.version)) { 26 | // 没有传入任何参数, 且有 -v 或 --version 27 | // 如果有传了参数,说明希望看到套件插件的版本,套件插件版本在 all.js 里面进行处理 28 | command = 'version'; 29 | } else if (argv.help || argv.h) { 30 | // 执行 tiny -h 或 tiny -help 的时候 31 | if (argv._.length === 1) { 32 | // 显示插件帮助信息 33 | command = argv._[0]; 34 | newArgv = ['help']; 35 | } else { 36 | command = 'help'; 37 | } 38 | } else if (argv._.length === 0) { 39 | command = 'help'; 40 | } else { 41 | newArgv = argv._.concat(); 42 | command = newArgv.splice(0, 1).pop() || ''; 43 | } 44 | log.debug('控制台传入的原始参数:', argv); 45 | log.debug('即将执行的tiny命令:', command); 46 | log.debug('tiny命令的参数:', newArgv); 47 | 48 | return { 49 | command, 50 | argv: newArgv 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /packages/devkit/src/task/has.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import test from 'ava'; 13 | import has from './has'; 14 | import { expect } from 'chai'; 15 | 16 | test('# has 判断是否有任务 有 $task 的情况', async (t) => { 17 | const tasks1 = [ 18 | { 19 | command: 'echo 1' 20 | }, 21 | { 22 | command: '$task' 23 | }, 24 | { 25 | command: 'echo 2' 26 | } 27 | ]; 28 | const tasks2 = [ 29 | { 30 | command: '$task' 31 | }, 32 | { 33 | command: 'echo 2' 34 | } 35 | ]; 36 | const res1 = has(tasks1, 'before'); 37 | const res2 = has(tasks1, 'after'); 38 | const res3 = has(tasks2, 'before'); 39 | const res4 = has(tasks2, 'after'); 40 | 41 | expect(res1).to.be.equal(true); 42 | expect(res2).to.be.equal(true); 43 | expect(res3).to.be.equal(false); 44 | expect(res4).to.be.equal(true); 45 | t.pass(); 46 | }); 47 | 48 | test('# 无 $task 的情况', async (t) => { 49 | const tasks1 = [ 50 | { 51 | command: 'echo 1' 52 | } 53 | ]; 54 | 55 | const res1 = has(tasks1, 'before'); 56 | const res2 = has(tasks1, 'after'); 57 | 58 | expect(res1).to.be.equal(true); 59 | expect(res2).to.be.equal(false); 60 | t.pass(); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/devkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opentiny/cli-devkit", 3 | "version": "1.0.1", 4 | "scripts": { 5 | "build": "tsc -p tsconfig.compiler.json", 6 | "build:dist": "node ./scripts/build.js", 7 | "watch": "tsc -p tsconfig.compiler.json --watch", 8 | "clean": "rm -rf lib/ && rm package-lock.json || true", 9 | "test": "npm run build && ava -v", 10 | "ava": "ava -v" 11 | }, 12 | "main": "./lib/index.js", 13 | "types": "./lib/index.d.ts", 14 | "dependencies": { 15 | "axios": "^0.21.1", 16 | "chai": "^4.2.0", 17 | "chalk": "^2.4.2", 18 | "cross-spawn": "^7.0.3", 19 | "dargs": "^7.0.0", 20 | "debug": "^4.1.1", 21 | "del": "^5.0.0", 22 | "ejs": "^2.6.2", 23 | "escodegen": "^1.11.1", 24 | "esprima": "^4.0.1", 25 | "esquery": "^1.0.1", 26 | "fs-extra": "^8.1.0", 27 | "git-rev-sync": "^3.0.2", 28 | "intl-messageformat": "^5.1.0", 29 | "lodash": "^4.17.15", 30 | "mem-fs": "^1.1.3", 31 | "mem-fs-editor": "^6.0.0", 32 | "node-emoji": "^1.10.0", 33 | "npm-run": "^5.0.1", 34 | "npminstall": "^7.9.0", 35 | "npmlog": "^4.1.2", 36 | "os": "^0.1.1", 37 | "os-locale": "^4.0.0", 38 | "ping": "^0.2.2", 39 | "rimraf": "^2.6.3", 40 | "semver": "^6.2.0", 41 | "shelljs": "^0.8.5", 42 | "sinon": "^7.3.2", 43 | "tosource": "^1.0.0", 44 | "winston": "^3.6.0", 45 | "write-file-atomic": "^3.0.0", 46 | "yargs": "^13.3.0" 47 | }, 48 | "devDependencies": { 49 | "@opentiny/cli-typescript": "^1.0.0-alpha.0", 50 | "@types/node": "^20.3.3", 51 | "@vercel/ncc": "^0.27.0", 52 | "ava": "^3.15.0", 53 | "typescript": "^4.7.4" 54 | }, 55 | "gitHead": "7df28442b47d4dc15cc2285eff3a211c366ec627" 56 | } 57 | -------------------------------------------------------------------------------- /packages/devkit/src/user/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc 根据用户当前 git 信息去获取用户相关信息 14 | */ 15 | 16 | import fs from 'fs-extra'; 17 | import * as path from 'path'; 18 | import * as utils from './utils'; 19 | import home from '../home/index'; 20 | import { FILE_USER } from '../cli-config/index'; 21 | import { UserInfo } from './interface'; 22 | 23 | /** 24 | * 获取当前电脑用户 25 | */ 26 | export function get(): UserInfo { 27 | let userInfo = utils.getUserFromFile(); 28 | if (!userInfo.email) { 29 | userInfo = utils.getUserFromGit(); 30 | // 获取之后再写入进去 31 | if (userInfo.name && userInfo.email) { 32 | const userFile = path.join(home.getHomePath(), FILE_USER); 33 | fs.outputJsonSync(userFile, userInfo, { spaces: 2 }); 34 | } 35 | } 36 | return userInfo; 37 | } 38 | 39 | /** 40 | * 写入user缓存 41 | * @param data 需要写入用户信息字段的数据 42 | */ 43 | export function set(data: any) { 44 | // 获取home下的tiny.user.json 45 | const userFile = path.join(home.getHomePath(), FILE_USER); 46 | if (fs.existsSync(userFile)) { 47 | const user = fs.readJsonSync(userFile); 48 | const userInfo = Object.assign(user, data); 49 | fs.outputJsonSync(userFile, userInfo, { spaces: 2 }); 50 | } 51 | } 52 | 53 | export default { 54 | get, 55 | set 56 | }; 57 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import './config'; 3 | import commands from '@opentiny/cli-commands'; 4 | import { argv, home, Intl, logs, upgrade, error } from '@opentiny/cli-devkit'; 5 | const pkg = require('../package.json'); 6 | const log = logs('core-core'); 7 | try { 8 | (async function (this: any) { 9 | // 家目录存在性检查 10 | home.initHomeDir(); 11 | 12 | // 初始化语言环境 13 | const intl = new Intl({ zh: {} }); 14 | intl.initLocale(); 15 | 16 | // 版本更新提示 17 | const needFocusUpdate = await upgrade({ 18 | name: pkg.name, 19 | version: pkg.version, 20 | }); 21 | 22 | if (needFocusUpdate) { 23 | log.error('\n当前cli工具版本太低,请升级到最新版后再使用\n'); 24 | process.exit(1); 25 | } 26 | 27 | // 核心运行命令 28 | const coreCommands = Object.keys(commands).filter((item) => { 29 | return item !== 'main'; 30 | }); 31 | 32 | const cliArgv = argv(); 33 | const command = cliArgv.command; 34 | const newArgv = cliArgv.argv; 35 | 36 | // 主入口日志上报 37 | if (!process.env.CLI_IS_CHILD_ENTRY) { 38 | process.env.CLI_IS_CHILD_ENTRY = 'true'; 39 | // report.entry(); 40 | } 41 | 42 | if (coreCommands.indexOf(command) === -1) { 43 | log.debug('进入套件,插件分支'); 44 | await commands.main.apply(this, [command, newArgv]); 45 | } else { 46 | log.debug('进入核心命令分支'); 47 | // init, install, install, uninstall, update ,version 等命令 48 | // 对 tiny.config.js 没有依赖, 也不执行自定义命令流 49 | await commands[command].apply(null, [newArgv]); 50 | } 51 | 52 | // 捕获异常 53 | process.on('uncaughtException', (err) => { 54 | log.debug(`进入未知错误${JSON.stringify(err)}`); 55 | error(err); 56 | }); 57 | })(); 58 | } catch (err) { 59 | error(err); 60 | } 61 | -------------------------------------------------------------------------------- /packages/devkit/src/task/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | const TOOLKIT_COMMAND_HOOK = '$task'; 13 | 14 | /** 15 | * 判断当前对象是否为 generator 对象 16 | * @param obj 17 | * @returns {boolean} 18 | */ 19 | export function isGenerator(obj: any) { 20 | return typeof obj.next === 'function' && typeof obj.throw === 'function'; 21 | } 22 | 23 | /** 24 | * 判断当前对象是否为 generator 函数 25 | * @param obj 26 | * @returns {boolean} 27 | */ 28 | export function isGeneratorFunction(obj: any) { 29 | const constructor = obj.constructor; 30 | if (!constructor) { 31 | return false; 32 | } 33 | if (constructor.name === 'GeneratorFunction' || constructor.displayName === 'GeneratorFunction') { 34 | return true; 35 | } 36 | return isGenerator(constructor.prototype); 37 | } 38 | export function classify(tasks: any[]) { 39 | let match = false; 40 | const before: any[] = []; 41 | const after: any[] = []; 42 | 43 | tasks.forEach((item) => { 44 | if (item.command && item.command === TOOLKIT_COMMAND_HOOK) { 45 | match = true; 46 | } else if (match) { 47 | after.push(item); 48 | } else { 49 | before.push(item); 50 | } 51 | }); 52 | const data: any = { 53 | before, 54 | after 55 | }; 56 | return data; 57 | } 58 | 59 | export default { 60 | isGenerator, 61 | isGeneratorFunction, 62 | classify 63 | }; 64 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 11 | 'prettier', 12 | ], 13 | parser: '@typescript-eslint/parser', 14 | parserOptions: { 15 | project: 'tsconfig.json', 16 | }, 17 | rules: { 18 | // 不必要的转义字符,设置为警告。 19 | // 在正则表达式中,特殊字符加个转义字符可以增强可读性。比如 /[\*]/ 一眼看过去知道是只是匹配"*"这个字符的. 而/[*]/可能会让人误解为一个通配符。 20 | 'no-useless-escape': 1, 21 | 'no-case-declarations': 'off', 22 | 'no-fallthrough': 'off', 23 | 'no-underscore-dangle': 'off', 24 | '@typescript-eslint/await-thenable': 'off', 25 | '@typescript-eslint/ban-types': 'off', 26 | '@typescript-eslint/no-empty-function': 'off', 27 | '@typescript-eslint/explicit-module-boundary-types': 'off', 28 | '@typescript-eslint/no-implied-eval': 'off', 29 | '@typescript-eslint/no-var-requires': 'off', 30 | '@typescript-eslint/no-unnecessary-type-assertion': 'off', 31 | '@typescript-eslint/no-unsafe-argument': 'off', 32 | '@typescript-eslint/no-unsafe-assignment': 'off', 33 | '@typescript-eslint/no-unsafe-call': 'off', 34 | '@typescript-eslint/no-unsafe-member-access': 'off', 35 | '@typescript-eslint/no-unsafe-return': 'off', 36 | '@typescript-eslint/no-unused-vars': 'off', 37 | '@typescript-eslint/prefer-regexp-exec': 'off', 38 | '@typescript-eslint/require-await': 'off', 39 | '@typescript-eslint/restrict-plus-operands': 'off', 40 | '@typescript-eslint/restrict-template-expressions': 'off', 41 | '@typescript-eslint/unbound-method': 'off', 42 | '@typescript-eslint/no-floating-promises': 'warn', 43 | 'prefer-spread': 'warn', 44 | 'no-empty': ['error', { allowEmptyCatch: true }], 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-cli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "init": "npm i && npx lerna bootstrap", 8 | "dev": "lerna run watch --stream --parallel", 9 | "dev:local": "esno ./packages/core/src/index.ts", 10 | "build": "lerna run build", 11 | "link": "lerna run link", 12 | "lint": "eslint . --ext .js,.ts --fix", 13 | "format": "prettier --write **/*{.vue,.js,.ts,.html,.json}", 14 | "publish": "lerna run build && lerna exec npm publish", 15 | "clean": "lerna clean && rm -rf node_modules && lerna run clean", 16 | "clean:dist": "lerna run clean", 17 | "commit": "git-cz", 18 | "prepare": "husky install" 19 | }, 20 | "keywords": [], 21 | "author": "", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@commitlint/cli": "^17.3.0", 25 | "@commitlint/config-conventional": "^17.3.0", 26 | "@typescript-eslint/eslint-plugin": "5.33.1", 27 | "@typescript-eslint/parser": "5.33.1", 28 | "babel-eslint": "^10.1.0", 29 | "chalk": "^5.2.0", 30 | "commitizen": "^4.2.5", 31 | "cz-conventional-changelog": "^3.3.0", 32 | "eslint": "^8.28.0", 33 | "eslint-config-prettier": "^8.5.0", 34 | "eslint-plugin-prettier": "^4.2.1", 35 | "esno": "^4.7.0", 36 | "husky": "^7.0.4", 37 | "lerna": "^5.4.2", 38 | "lint-staged": "^13.0.3", 39 | "prettier": "^2.8.0", 40 | "typescript": "4.7.4" 41 | }, 42 | "config": { 43 | "commitizen": { 44 | "path": "cz-conventional-changelog" 45 | } 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "lint-staged", 50 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 51 | } 52 | }, 53 | "lint-staged": { 54 | "*.{js,jsx,ts,tsx,vue}": [ 55 | "prettier --write", 56 | "eslint --fix" 57 | ] 58 | }, 59 | "workspaces": [ 60 | "packages/*" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/commands/src/clear.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 清除缓存 14 | */ 15 | 16 | import { cache, home, Intl, logs, cliConfig } from '@opentiny/cli-devkit'; 17 | import * as fs from 'fs'; 18 | import * as path from 'path'; 19 | import rimraf from 'rimraf'; 20 | import emoji from 'node-emoji'; 21 | import message from './locale/index'; 22 | 23 | const log = logs('core-commands'); 24 | 25 | export default async function () { 26 | const intl = new Intl(message); 27 | const cdnPath = path.join(home.getHomePath(), 'LocalCDNPath'); 28 | 29 | const bin = cliConfig.getBinName(); 30 | log.info(intl.get('startClear')); 31 | // 删除LocalCDNPath目录 32 | if (fs.existsSync(cdnPath)) { 33 | rimraf.sync(cdnPath); 34 | } 35 | cache.clear(); 36 | 37 | const result = home.cleanHomeDir(); 38 | if (!result.success) { 39 | log.error( 40 | `******************** ${emoji.get('warning')} ${emoji.get('warning')} ERROR ${emoji.get('warning')} ${emoji.get( 41 | 'warning' 42 | )} **********************` 43 | ); 44 | log.error( 45 | intl.get('clearfail', { 46 | tool: bin, 47 | removePath: result.removePath, 48 | }) 49 | ); 50 | log.error( 51 | `******************** ${emoji.get('warning')} ${emoji.get('warning')} ERROR ${emoji.get('warning')} ${emoji.get( 52 | 'warning' 53 | )} **********************` 54 | ); 55 | } else { 56 | log.success(intl.get('finishClear')); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-enoent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import _ from 'lodash'; 13 | import logs from '../log/index'; 14 | import Intl from '../intl/index'; 15 | import message from './locale/index'; 16 | 17 | const log = logs('core-error'); 18 | 19 | export default async function (e: any) { 20 | if (e.code !== 'ENOENT') { 21 | return false; 22 | } 23 | // 目前可能的值有spawn xx ENOENT;spawnSync xx ENOENT 24 | const match = e.message.match(/\s(.*)ENOENT/); 25 | const intl = new Intl(message); 26 | if (match && match[0]) { 27 | const module = match[1].trim(); 28 | const installer = 'npm'; 29 | log.error(intl.get('commandNotFound', { module })); 30 | // 本地模块 31 | if (module.indexOf('node_modules') !== -1) { 32 | const cmdArr = module.split('/'); 33 | const startIdx = _.indexOf(cmdArr, 'node_modules'); 34 | let runModule: any; 35 | 36 | // 直接运行命令 37 | if (module.indexOf('node_modules/.bin/') !== -1) { 38 | runModule = cmdArr[cmdArr.length - 1]; 39 | } else if (module.indexOf('node_modules/@ali') !== -1 && cmdArr.length >= 2) { 40 | // 本地文件直接运行 41 | runModule = `${cmdArr[startIdx + 1]}/${cmdArr[startIdx + 2]}`; 42 | } else { 43 | runModule = cmdArr[startIdx + 1]; 44 | } 45 | log.error(intl.get('fixLocalTips', { installer, runModule })); 46 | } else { 47 | log.error(intl.get('fixGlobalTips', { installer, module })); 48 | } 49 | return true; 50 | } 51 | return false; 52 | } 53 | -------------------------------------------------------------------------------- /docs/dev-plugin.md: -------------------------------------------------------------------------------- 1 | # 开发 tiny 插件 2 | 3 | > 如果您有一套不错的 nodejs 小工具推荐给身边的同事使用,开发一个 tiny 插件是一个很不错的方式. 4 | > 在您开发 tiny 插件前,请务必先查看目前是否已经有跟您的 nodejs 小工具功能相似的插件,若已经有了,您可以考虑加入该插件的开发,一起优化现有的插件. 5 | 6 | ## 基本约定 7 | 8 | ### 命名 9 | 10 | - package.json 里面的 name 字段命名方式必须以 @opentiny/tiny-plugin-xxx 格式书写 11 | 12 | ### 版本号 13 | 14 | - 遵守 [semver](http://semver.org/lang/zh-CN/) 版本规范 15 | 16 | ## 开发流程 17 | 18 | ### 初始化套件模板 19 | 20 | 进入项目目录, 执行 tiny init dev, 操作 [dev套件](https://github.com/opentiny/tiny-cli/blob/main/packages/toolkits/dev/README.md) 会做几件事: 21 | 22 | - 在当前目录下添加插件的基础模板 23 | - 将当前目录链到 tiny 模块目录下 24 | 25 | ### 插件件配置 26 | 27 | 在生成 的模板 的 package.json 里面会有一个 tinyOption 的配置,这些配置是tiny 核心调用套件的时候会读取的, 配置主要意义如下: 28 | 29 | ``` 30 | { 31 | "type": "plugin", //套件还是插件,此项不要修改 32 | "update": true, //是否自动更新,设置为true后,当插件发布新版本号后,tiny会自动更新插件到最新版本。 33 | "chName": "中文名" // 简单几个字介绍一下是什么类型的插件 34 | } 35 | ``` 36 | 37 | ### 接口实现 38 | 39 | 各插件根据自己的实际需求,自身的命令,最终只对外暴露一个对象即可(可以参考 [tiny-plugin-link](https://github.com/opentiny/tiny-cli/blob/main/packages/plugins/link/readme.md)的实现)。 40 | 41 | ### 使用 tiny.config.js 配置 42 | 43 | 在项目里面必须会有一个 tiny.config.js的配置文件,所有的插件配置都是在下面以插件名另起一个字段,假设现在有一个插件名为 tiny-plugin-npm,则其配置如下: 44 | 45 | ``` 46 | { 47 | npm: { 48 | //些处填插件需要使用的配置项目 49 | } 50 | } 51 | ``` 52 | 53 | 然后通过 config 方法获取值: 54 | 55 | ``` 56 | import { config } from '@opentiny/cli-devkit'; 57 | 58 | config.get('npm'); 59 | 60 | ``` 61 | 62 | ### 插件发布 63 | 64 | 插件发布前需要注意以下几项: 65 | 66 | - 必须按规范填写项目名 67 | - 描述信息尽量明确且简短,在 tiny list 时可以被用户查看到 68 | - changeLog 里面必须要有当前版本号的更新信息 69 | 70 | 最后执行发布: 71 | 72 | ``` 73 | npm publish 74 | ``` 75 | 76 | ## 插件调用机制 77 | 78 | tiny 插件实际是一个纯净的 npm 包,单独将插件拎出来也能独立运行。 79 | 80 | 插件对外暴露的是一个object,其中object的 `key` 即是 tiny 调用的命令,假设开发一个插件名称叫 `tiny-plugin-abc`,插件代码如下: 81 | 82 | ``` 83 | module.exports = { 84 | go : function(){ console.log('go') }, 85 | help: function(){ xxxx } 86 | } 87 | ``` 88 | 89 | 那么在tiny中运行这个插件,可以使用如下命令: 90 | 91 | ``` 92 | $ tiny abc go 93 | ``` 94 | 95 | 其中 `tiny` 是tiny工具命令,`abc` 是插件名,`go` 是插件的object key。 -------------------------------------------------------------------------------- /packages/devkit/src/module/local-list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 本地模块列表 14 | */ 15 | 16 | import home from '../home/index'; 17 | import logs from '../log/index'; 18 | import fs from 'fs-extra'; 19 | import * as path from 'path'; 20 | import utils from './utils'; 21 | 22 | const log = logs('core-module'); 23 | 24 | /** 25 | * 列出所有本地模块 26 | * @param options object {type (按类型筛选): 'toolkit | plugin'} 27 | * @returns {Array} 28 | */ 29 | function localList(options?: any) { 30 | options = options || {}; 31 | const modulesPath = home.getModulesPath(); 32 | let modulePkgs: any[] = []; 33 | 34 | const homePath = home.getHomePath(); 35 | const homePkgPath = path.resolve(homePath, 'package.json'); 36 | if (fs.existsSync(homePkgPath)) { 37 | const homePkg = fs.readJsonSync(homePkgPath); 38 | if (homePkg.dependencies) { 39 | Object.keys(homePkg.dependencies).forEach((item) => { 40 | const pkgPath = path.resolve(modulesPath, item, 'package.json'); 41 | if (fs.existsSync(pkgPath)) { 42 | const modPkg = fs.readJsonSync(pkgPath); 43 | modulePkgs.push({ 44 | name: modPkg.name, 45 | description: modPkg.description, 46 | chName: modPkg.tinyOption && modPkg.tinyOption.chName ? modPkg.tinyOption.chName : modPkg.description 47 | }); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | modulePkgs = options.type ? utils.moduleFilter(modulePkgs, options.type) : modulePkgs; 54 | 55 | log.debug('所有本地模块: %o', modulePkgs); 56 | 57 | return modulePkgs; 58 | } 59 | 60 | export default localList; 61 | -------------------------------------------------------------------------------- /packages/devkit/src/user/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import home from '../home/index'; 13 | import fs from 'fs-extra'; 14 | import * as path from 'path'; 15 | import logs from '../log/index'; 16 | import spawn from 'cross-spawn'; 17 | import { FILE_USER } from '../cli-config/index'; 18 | 19 | const log = logs('core-user'); 20 | 21 | export interface UserInfo { 22 | name: string; 23 | email: string; 24 | } 25 | 26 | /** 27 | * 从配置文件中获取用户信息 28 | */ 29 | export function getUserFromFile(): UserInfo { 30 | const userInfo = { 31 | name: '', 32 | email: '' 33 | }; 34 | // 获取home下的tiny.user.json 35 | const userFile = path.join(home.getHomePath(), FILE_USER); 36 | if (fs.existsSync(userFile)) { 37 | const user = fs.readJsonSync(userFile); 38 | userInfo.name = user.name; 39 | userInfo.email = user.email; 40 | } 41 | return userInfo; 42 | } 43 | 44 | /** 45 | * 从git的配置文件中获取用户信息 46 | */ 47 | export function getUserFromGit(): UserInfo { 48 | const userInfo = { 49 | name: '', 50 | email: '' 51 | }; 52 | 53 | const reg = /user\.name=([^\n]+)\nuser\.email=([^\n]+)/; 54 | try { 55 | const results = spawn.sync('git', ['config', '--list']); 56 | if (results.stdout) { 57 | const match = results.stdout.toString().match(reg); 58 | if (match && match.length > 1) { 59 | userInfo.name = match[1]; 60 | userInfo.email = match[2]; 61 | } else { 62 | const msg = 'git config --list 没有git 信息,请检查git是否正确配置了用户名和email'; 63 | log.debug(msg); 64 | } 65 | } else { 66 | const msg = '没有安装git'; 67 | log.debug(msg); 68 | } 69 | } catch (ex) { 70 | log.debug('tiny-user', ex); 71 | throw ex; 72 | } 73 | return userInfo; 74 | } 75 | -------------------------------------------------------------------------------- /docs/maintainer-guide.md: -------------------------------------------------------------------------------- 1 | # 维护者指南 2 | 3 | ## 依赖安装 4 | 5 | ```bash 6 | # npm install -g lerna # 如果没有安装, 请先安装lerna 7 | npm run init 8 | ``` 9 | 10 | ## 本地开发 11 | 12 | 请参考[贡献者指南](./contribution-guide.md) 13 | 14 | ## 构建产物 15 | 16 | ```bash 17 | npm run build 18 | ``` 19 | 20 | ## 本地联调 21 | 22 | ```bash 23 | npm run dev 24 | ``` 25 | 26 | ```bash 27 | # 新bash 28 | npm run link 29 | ``` 30 | 31 | 当出现 `lerna success - @opentiny/cli` 字样后代表link成功 32 | 33 | ## 发布测试包 34 | 35 | > 执行此步骤前,请您悉知如何修改环境变量 36 | > 37 | > Linux如何修改环境变量: https://wiki.archlinuxcn.org/wiki/%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F 38 | > 39 | > Windows如何修改环境变量: https://support.esri.com/zh-cn/knowledge-base/edit-an-environment-variable-1462478594981-000002146 40 | > 41 | > Macos如何修改环境变量: https://support.apple.com/zh-cn/guide/terminal/apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac 42 | 43 | 44 | **!!!在修改环境变量后请重新启动终端(Bash)!!!** 45 | 46 | **!!!在修改环境变量后请重新启动终端(Bash)!!!** 47 | 48 | **!!!在修改环境变量后请重新启动终端(Bash)!!!** 49 | 50 | 有时我们会需要发布一些测试包来测试构建产物是否能够正常使用,您可以根据下列指引来进行测试. 这里使用的是`@opentiny/tiny-toolkit-pro 51 | `作为演示 52 | 53 | 1. 自增`package.json`中的`version`字段 54 | 2. `package.json`文件中替换`@opentiny`为`@xxx`(其中xxx为您的npm名称) 55 | 3. 进入到你要发布子包的目录 56 | 4. 运行`npm publish --access=public` 57 | 5. 修改环境变量`TINY_SCOPE=xxx` (第一步中替换的结果, 但是不要包含@) 58 | 1. 例如 `@foo/tiny-toolkit-pro`,TINY_SCOPE应该是`foo`而不是`@foo` 59 | 6. 安装`npm i -g @opentiny/cli` (如果您安装完成可忽略该步骤) 60 | 7. `rm -rf ~/.tiny` 61 | 8. `tiny init pro` 62 | 63 | ### 发布测试包前检查清单 64 | 65 | - [ ] lerna已被安装 66 | - [ ] `npm run init`已被执行 67 | - [ ] `npm run build`已被执行 68 | - [ ] 需要发布的测试包`package.json`文件中`version`自增 69 | - [ ] 需要发布的测试包`package.json`文件中`@opentiny`被替换为`@<您的npm账号名>` 70 | - [ ] `npm publish --access=public`已被执行 71 | - [ ] 环境变量`TINY_SCOPE`已修改为您的npm账号名 72 | - [ ] 终端已重启 73 | - [ ] `npm i -g @opentiny/cli`已被执行 74 | - [ ] `rm -rf ~/.tiny`已被执行 75 | 76 | ## 发布正式包 77 | 78 | 与发布测试包相同,只是不需要替换`@opentiny`前缀 79 | 80 | ### 发布正式包前检查清单 81 | 82 | - [ ] lerna已被安装 83 | - [ ] `npm run init`已被执行 84 | - [ ] `npm run build`已被执行 85 | - [ ] 需要发布的正式包`package.json`文件中`version`自增 86 | - [ ] `npm publish --access=public`已被执行 87 | - [ ] 终端已重启 88 | - [ ] `npm i -g @opentiny/cli`已被执行 89 | - [ ] `rm -rf ~/.tiny`已被执行 90 | 91 | ## 遇到困难? 92 | 93 | 加官方小助手微信 opentiny-official,加入技术交流群 -------------------------------------------------------------------------------- /packages/commands/src/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 设置cnpmrc 14 | * 类似于 $ npm config set registry https://registry.npmmirror.com/ 15 | */ 16 | import * as path from 'path'; 17 | import os from 'os'; 18 | import { logs, npm, cliConfig } from '@opentiny/cli-devkit'; 19 | 20 | const log = logs('core-commands'); 21 | 22 | export default async (argv) => { 23 | // set 方法 24 | if (argv && argv.length === 3 && argv[0] === 'set') { 25 | const obj = {}; 26 | const key = argv[1]; 27 | const value = argv[2]; 28 | obj[key] = value; 29 | npm.setCnpmrc(obj); 30 | log.success(`已将 ${key}=${value} 写入到.cnpmrc文件中!`); 31 | } else if (argv && argv.length === 2 && argv[0] === 'get') { 32 | const config = npm.getCnpmrc(); 33 | const value = argv[1]; 34 | if (config[value]) { 35 | log.success(config[value]); 36 | } else { 37 | log.error(`${value} 不在.cnpmrc文件中`); 38 | } 39 | } else if (argv && argv.length === 2 && argv[0] === 'delete') { 40 | const config = npm.getCnpmrc(); 41 | const value = argv[1]; 42 | delete config[value]; 43 | npm.setCnpmrc(config); 44 | log.error(`${value} 从.cnpmrc文件中删除!`); 45 | } else if (argv && argv[0] === 'list') { 46 | const config = npm.getCnpmrc(); 47 | const root = os.homedir(); 48 | log.success(path.join(root, '.cnpmrc')); 49 | console.log(config); 50 | } else { 51 | const bin = cliConfig.getBinName(); 52 | log.warn(`config 参数不正确,用法举例: 53 | 54 | # 设置.cnpmrc 文件,写入registry 55 | ${bin} config set registry https://registry.npmmirror.com/ 56 | 57 | # 获取.cnpmrc 文件中registry的值 58 | ${bin} config get registry 59 | 60 | # 列出所有.cnpmrc文件配置 61 | ${bin} config list 62 | 63 | # 删除.cnpmrc文件中registry的配置 64 | ${bin} config delete 65 | `); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-module-not-found.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import logs from '../log/index'; 13 | import message from './locale/index'; 14 | import utils from './utils'; 15 | import npm from '../npm/index'; 16 | import Intl from '../intl/index'; 17 | import home from '../home'; 18 | 19 | const log = logs('core-error'); 20 | 21 | // 处理 22 | export default async function (e: any) { 23 | if (e.code !== 'MODULE_NOT_FOUND') { 24 | return false; 25 | } 26 | const match = e.message.match(/'(.*)'/); 27 | const intl = new Intl(message); 28 | const cwd = process.cwd(); 29 | // 排除 相对路径 ../ & ./ 的情况( . 开头) 30 | if (match && match[0] && match[0].indexOf('.') !== 1) { 31 | const module = utils.pureModuleName(match[1]); 32 | log.error(intl.get('moduleNotFound', { module })); 33 | 34 | let moduleCwd = home.getHomePath(); 35 | // 判断一下如果是项目文件中抛出的报错,则需要安装在项目文件夹中 36 | if (e.stack && e.stack.toString().indexOf(cwd) !== -1) { 37 | moduleCwd = cwd; 38 | // 这种情况下极有可能是本地的相关依赖没有安装,先全部执行一次安装 39 | await npm.installDependencies(); 40 | log.success(intl.get('installSuccess')); 41 | } 42 | 43 | try { 44 | // 安装所需的依赖 45 | await npm.install(module, { 46 | cwd: moduleCwd 47 | }); 48 | log.success(intl.get('installDone', { module, moduleCwd })); 49 | log.success(intl.get('installDoneTips')); 50 | return true; 51 | } catch (err) { 52 | log.error(intl.get('installError')); 53 | return false; 54 | } 55 | } else if (match && match.length === 2) { 56 | log.error(intl.get('notFound', { file: match[1] })); 57 | if (e.stack) { 58 | log.error(intl.get('detailError')); 59 | console.log(e.stack); 60 | } 61 | return true; 62 | } 63 | return false; 64 | } 65 | -------------------------------------------------------------------------------- /packages/commands/src/help.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 查看 tiny 及 套件的 帮助信息 14 | */ 15 | 16 | import { config, Intl, modules, cliConfig } from '@opentiny/cli-devkit'; 17 | import chalk from 'chalk'; 18 | import message from './locale/index'; 19 | 20 | /** 21 | * 显示tiny帮助 22 | */ 23 | function outHelpInfo(needToolkit?: boolean) { 24 | const intl = new Intl(message); 25 | const tool = cliConfig.getBinName(); 26 | const help = intl.get('help', { tool }); 27 | // 打印帮助信息 28 | console.log(chalk.cyan(help)); 29 | console.log(chalk.yellow(intl.get('helpTips'))); 30 | needToolkit && console.log(chalk.yellow(intl.get('helpToolkit'))); 31 | console.log(chalk.yellow(intl.get('helpPlugin', { tool }))); 32 | } 33 | 34 | function isGenerator(obj: any) { 35 | return typeof obj.next === 'function' && typeof obj.throw === 'function'; 36 | } 37 | 38 | /** 39 | * 判断当前对象是否为 generator 函数 40 | * @param obj 41 | * @returns {boolean} 42 | */ 43 | function isGeneratorFunction(obj: any) { 44 | const constructor = obj.constructor; 45 | if (!constructor) { 46 | return false; 47 | } 48 | if (constructor.name === 'GeneratorFunction' || constructor.displayName === 'GeneratorFunction') { 49 | return true; 50 | } 51 | return isGenerator(constructor.prototype); 52 | } 53 | 54 | export default async function () { 55 | const toolkit = config.getToolkitName(); 56 | const intl = new Intl(message); 57 | const tool = cliConfig.getBinName(); 58 | // 套件存在,则优先输出套件帮助信息 59 | if (toolkit) { 60 | const mod = await modules.get(toolkit); 61 | const help = modules.getEsModule(mod.help); 62 | if (help) { 63 | if (isGeneratorFunction(help)) { 64 | await help(); 65 | } else { 66 | help(); 67 | } 68 | console.log(chalk.cyan(intl.get('helpList', { tool }))); 69 | } 70 | outHelpInfo(); 71 | } else { 72 | outHelpInfo(true); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/devkit/src/log/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import test from 'ava'; 13 | import shelljs from 'shelljs'; 14 | import * as path from 'path'; 15 | import * as fs from 'fs-extra'; 16 | import process from 'process'; 17 | 18 | const logPath = path.join(__dirname, 'helpers/log'); 19 | test.before(() => { 20 | process.env.TINY_LOG_PATH = logPath; 21 | }); 22 | 23 | test.after(() => { 24 | fs.removeSync(logPath); 25 | }); 26 | 27 | test('# level检查CLI输出是否正确', (t) => { 28 | const targetScript = path.join(__dirname, 'helpers', 'logger-test.js'); 29 | const result = shelljs.exec(`node ${targetScript}`, { stdio: 'pipe' }).stdout.toString(); 30 | // 包含label 31 | t.true(result.includes('[core-module]')); 32 | // 打印字符串 33 | t.true(result.includes('info message')); 34 | // 打印对象 35 | t.true(result.includes('{"a":1}')); 36 | // 打印错误 37 | t.true(result.includes('Error: an error')); 38 | t.true(result.includes('at Object.')); 39 | // 打印多个参数 40 | t.true(result.includes('["a","b","c"]')); 41 | // debug不打印出来 42 | t.false(result.includes('debug message')); 43 | }); 44 | 45 | test('# level检查LOG文件输出是否正确', (t) => { 46 | const targetScript = path.join(__dirname, 'helpers', 'logger-test.js'); 47 | shelljs.exec(`node ${targetScript}`, { stdio: 'pipe' }); 48 | const logFile = path.join(logPath, `${new Date().toISOString().split('T')[0]}.log`); 49 | const result = fs.readFileSync(logFile); 50 | // 包含label 51 | t.true(result.includes('[core-module]')); 52 | // 不打印字符串 53 | t.false(result.includes('info message')); 54 | // 不打印对象 55 | t.false(result.includes('{"a":1}')); 56 | // 不打印多个参数 57 | t.false(result.includes('["a","b","c"]')); 58 | // 不打印debug 59 | t.false(result.includes('debug message')); 60 | // 打印错误 61 | t.true(result.includes('Error: an error')); 62 | t.true(result.includes('at Object.')); 63 | // 打印warn 64 | t.true(result.includes('warn message')); 65 | }); 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve tiny-cli 3 | title: '🐛 [Bug]: ' 4 | labels: ['🐛 bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please fill out the following carefully in order to better fix the problem. 10 | - type: input 11 | id: tiny-cli-version 12 | attributes: 13 | label: Version 14 | description: | 15 | ### **Check if the issue is reproducible with the latest stable version.** 16 | You can use the command `tiny -v` to view it 17 | placeholder: latest 18 | validations: 19 | required: true 20 | - type: input 21 | id: node-version 22 | attributes: 23 | label: node-version 24 | description: | 25 | ### **Node.js runtime environment version.** 26 | You can use the command `node -v` to view it 27 | placeholder: latest 28 | validations: 29 | required: true 30 | - type: textarea 31 | id: minimal-repo 32 | attributes: 33 | label: Link to minimal reproduction 34 | description: | 35 | **Provide a streamlined CodePen / CodeSandbox or GitHub repository link as much as possible. Please don't fill in a link randomly, it will only close your issue directly.** 36 | placeholder: Please Input 37 | - type: textarea 38 | id: reproduce 39 | attributes: 40 | label: Step to reproduce 41 | description: | 42 | **After the replay is turned on, what actions do we need to perform to make the bug appear? Simple and clear steps can help us locate the problem more quickly. Please clearly describe the steps of reproducing the issue. Issues without clear reproducing steps will not be repaired. If the issue marked with 'need reproduction' does not provide relevant steps within 7 days, it will be closed directly.** 43 | placeholder: Please Input 44 | validations: 45 | required: true 46 | - type: textarea 47 | id: expected 48 | attributes: 49 | label: What is expected 50 | placeholder: Please Input 51 | - type: textarea 52 | id: actually 53 | attributes: 54 | label: What is actually happening 55 | placeholder: Please Input 56 | - type: textarea 57 | id: additional-comments 58 | attributes: 59 | label: Any additional comments (optional) 60 | description: | 61 | **Some background / context of how you ran into this bug.** 62 | placeholder: Please Input 63 | -------------------------------------------------------------------------------- /docs/use-config.md: -------------------------------------------------------------------------------- 1 | # tiny配置文件 2 | 3 | 基于tiny的项目,每个项目根目录下均有`tiny.config.js`配置文件。 4 | 5 | 该文件可以配置当前项目的:tiny任务流、tiny插件配置、项目所使用的套件 6 | 7 | 8 | ## 配置文件格式 9 | 10 | `tiny.config.js`文件实际是一个object对象。 11 | 12 | 以下是一个配置文件的例子 13 | 14 | ```js 15 | // tiny.config.js 16 | 17 | module.exports = { 18 | // 组件所使用的tiny toolkit 19 | toolkitName: '@opentiny/tiny-toolkit-xxx', 20 | 21 | tasks: { 22 | start: [ 23 | { 24 | // 使用tiny link插件将当前目录链接到tiny 本地cdn目录 25 | command: 'tiny link' 26 | } 27 | ], 28 | 29 | build: [ 30 | { 31 | // 执行自定义脚本 32 | command: 'node custom.js' 33 | } 34 | ], 35 | 36 | publish: [ 37 | { 38 | command: 'npm publish' 39 | } 40 | ] 41 | }, 42 | 43 | // ci 插件所需的配置 44 | ci: { 45 | // 返回项目中的webpack配置 46 | getWebpackConfig() { 47 | return require('./webpack.config').dev(); 48 | } 49 | } 50 | }; 51 | ``` 52 | 53 | ## 套件配置说明 54 | 55 | 项目指定使用的套件使用 toolkit 字段来说明,如果当前项目没有合适的套件使用, 只有任务流的话,也可以不配置套件名及套件参数. 56 | 57 | 套件参数都写在 toolkitConfig 里面. 58 | 59 | 为了满足有些项目对某些项目匹配某个套件的大部分需求,就是一两个命令的需求不太一样,我们提供了重写套件命令的方式, 只需要传入以 命令 + Rewirte 的方法便可以了. 60 | 61 | ``` 62 | { 63 | toolkit: "@opentiny/tiny-toolkit-mod", // 套件名 64 | toolkitConfig: { 65 | port: 9000, // 本地服务器端口号 66 | open: true, // 是否自动打开浏览器 67 | log: true, // 是否打印本地服务器访问日志 68 | openTarget: "src/index.html", // 打开浏览器后自动打开目标页面 69 | liveload: false, // 是否自动刷新 70 | } 71 | } 72 | ``` 73 | 74 | 另外,根据不同的套件还会有一些不同字段. 75 | 76 | 77 | ## 插件配置说明 78 | 79 | 插件配置,是根据当前项目使用的插件不同而配置不同的,每个插件的配置写在与其插件名相同的字段下面,如以下是 awp 的插件配置: 80 | 81 | ``` 82 | { 83 | awp: { 84 | dailyAppID: 554, 85 | onlineAppID: 219, 86 | appDir: 'mcm', 87 | awpBuildDir: 'build' 88 | } 89 | } 90 | ``` 91 | 92 | ## 任务流配置 93 | 94 | 95 | 使用者可以在其自定义一些任务, 如果对应任务名有对应的套件命令,那么会在先执行用户自定义的任务,再执行套件的任务. 96 | 97 | 如果您配置了对应命令的任务, 那么执行时可以不需要配置套件名. 98 | 99 | 任务可以是函数式,也可以是命令式, 具体配置如下,只需要在 tiny.config.js 里面添加以下配置即可: 100 | 101 | ``` 102 | { 103 | tasks: { 104 | // start 前执行一些 shell 命令 105 | start: [{ 106 | command: 'echo test1' 107 | },{ 108 | command: 'tiny server', 109 | // 添加 async 为 true 选项,可以无须等待当前异步命令执行完成便可招待下面的命令 110 | async: true 111 | }], 112 | // build 前执行一些 gulp命令 113 | build: [{ 114 | command: 'gulp clean' 115 | }] 116 | } 117 | } 118 | ``` 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/dev-toolkit.md: -------------------------------------------------------------------------------- 1 | # 开发 tiny 套件 2 | 3 | > 如果您有一套不错的开发模式想推荐给身边的同事使用,开发一个tiny 套件是一个很不错的方式. 4 | > 在您开发 tiny 套件前,请务必先查看目前是否已经有跟您的开发模式相似的套件,若已经有了,您可以考虑加入该套件的开发,一起优化现有的套件. 5 | 6 | ## 基本约定 7 | 8 | ### 命名 9 | 10 | - package.json 里面的 name 字段命名方式必须以 @opentiny/tiny-toolkit-xxx 格式书写 11 | 12 | ### 版本号 13 | 14 | - 遵守 [semver](http://semver.org/lang/zh-CN/) 版本规范 15 | 16 | ## 开发流程 17 | 18 | 19 | ### 初始化套件模板 20 | 21 | 进入项目目录, 执行 tiny init dev, 操作 [dev套件](https://github.com/opentiny/tiny-cli/blob/main/packages/toolkits/dev/README.md) 会做几件事: 22 | 23 | - 在当前目录下添加套件的基础模板 24 | - 将当前目录链到 tiny 模块目录下 25 | 26 | ### 套件件配置 27 | 28 | 在生成 的模板 的 package.json 里面会有一个 tinyOption 的配置,这些配置是tiny 核心调用套件的时候会读取的, 配置主要意义如下: 29 | 30 | ``` 31 | { 32 | "type": "toolkit", //套件还是插件,此项不要修改 33 | "update": false, //是否自动更新,设置为true后,当插件发布新版本号后,tiny会自动更新插件到最新版本。 34 | "chName": "中文名" // 简单几个字介绍一下是什么类型的插件 35 | } 36 | ``` 37 | 38 | ### 接口实现 39 | 40 | 各插件根据自己的实际需求,自身的命令,最终只对外暴露一个对象即可(可以参考 [tiny-toolkit-dev](https://github.com/opentiny/tiny-cli/blob/main/packages/toolkits/dev/README.md)的实现)。 41 | 42 | ### 使用 tiny.config.js 配置 43 | 44 | 在项目里面必须会有一个 tiny.config.js的配置文件,所有的套件都要实现并按照这套规范来做,对于套件来说,需要实现以下几个配置 ,更多的配置是尽量用套件具体使用的打包工具对应的配置文件 来设置的,推置使用 webpack : 45 | 46 | ``` 47 | module.exports = { 48 | //指定当前项目使用的套件 49 | toolkit: "@opentiny/tiny-toolkit-xxx", 50 | 51 | //以下是套件的基本配置,所有的套件都只会有这几个参数 52 | //以下配置是非必填项目,默认配置就是下面这样 53 | toolkitConfig: { 54 | //tiny start时打开本地服务器的端口号 55 | port: 9000, 56 | //tiny start时,是否自动打开浏览器 57 | open: true, 58 | //tiny start打开浏览器时的默认页面(只有open为true时有效) 59 | openTarget: "src/index.html", 60 | //文件改变时是否自动刷新页面 61 | liveload: false 62 | } 63 | }; 64 | 65 | ``` 66 | 67 | ### 套件发布 68 | 69 | 套件发布前需要注意以下几项: 70 | 71 | - 必须按规范填写项目名 72 | - 描述信息尽量明确且简短,在 tiny list 时可以被用户查看到 73 | - changeLog 里面必须要有当前版本号的更新信息 74 | 75 | 最后执行发布: 76 | 77 | ``` 78 | tiny publish 79 | ``` 80 | 81 | 82 | ## 套件调用机制 83 | 84 | tiny 插件实际是一个纯净的 npm 包,单独将插件拎出来也能独立运行。 85 | 86 | 套件对外暴露的是一个object,其中object的 `key` 即是 tiny 调用的命令,假设开发一个套件名称叫 `tiny-toolkit-abc`,插件代码如下: 87 | 88 | ``` 89 | module.exports = { 90 | init : function(){ console.log('init') }, 91 | build : function(){ console.log('build') }, 92 | help: function(){ xxxx } 93 | } 94 | ``` 95 | 96 | 那么在tiny中运行这个套件,可以使用如下命令: 97 | 98 | ``` 99 | $ tiny init 100 | ``` 101 | 102 | 这里也许大家有个疑问,为什么执行`tiny init` 这个命令,tiny知道init命令来自哪个套件? 103 | 104 | 答案就在`tiny.config.js`中: 105 | 106 | ``` 107 | module.exports = { 108 | // 声明当前项目使用的套件 109 | toolkit : "@opentiny/tiny-toolkit-abc" 110 | } 111 | ``` -------------------------------------------------------------------------------- /packages/devkit/src/env/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import test from 'ava'; 13 | import fs from 'fs-extra'; 14 | import { PROCESS_ENV_HOME_PATH } from '../cli-config/index'; 15 | import * as path from 'path'; 16 | import * as Env from './index'; 17 | import { FILE_ENV, DEFAULT_HOME_FOLDER, PROCESS_ENV_CLI_ENV } from '../cli-config/index'; 18 | 19 | let envFile: string; 20 | let tinyHome: string; 21 | 22 | test.before(() => { 23 | // 先设置tiny的home目录为test/helpers; 24 | tinyHome = process.env[PROCESS_ENV_HOME_PATH] = path.join(__dirname, 'helpers'); 25 | fs.mkdirsSync(tinyHome); 26 | envFile = path.join(tinyHome, `${DEFAULT_HOME_FOLDER}/${FILE_ENV}`); 27 | }); 28 | 29 | test.after(() => { 30 | fs.removeSync(path.join(tinyHome)); 31 | delete process.env[PROCESS_ENV_HOME_PATH]; 32 | }); 33 | 34 | test('# Env.set 设置网络环境', (t) => { 35 | Env.set(Env.EnvType.Green); 36 | 37 | const file = fs.existsSync(envFile); 38 | const fileData = fs.readJsonSync(envFile); 39 | // 判断文件是否存在 40 | t.true(file); 41 | // 判断文件内容 42 | t.is(typeof fileData, 'object'); 43 | t.deepEqual(fileData, { env: 'Green' }); 44 | }); 45 | 46 | test('# Env.get 通过env环境变量判断来判断', (t) => { 47 | process.env[PROCESS_ENV_CLI_ENV] = 'Green'; 48 | const env: Env.EnvType = Env.get(); 49 | t.is(env, 'Green'); 50 | delete process.env[PROCESS_ENV_CLI_ENV]; 51 | }); 52 | 53 | test('# Env.get 多次调用可读取缓存', (t) => { 54 | // 设置为内网 55 | Env.set(Env.EnvType.Red); 56 | const env: Env.EnvType = Env.get(); 57 | t.is(env, 'Red'); 58 | // 第二次调用时应该走的是cache 59 | // 先手动改一下文件内容,再判断是否为true 60 | fs.outputJsonSync(envFile, { env: 'Green' }); 61 | const env2: Env.EnvType = Env.get(); 62 | t.is(env2, 'Red'); 63 | }); 64 | 65 | test('# Env.has 初始化后则存在配置文件', (t) => { 66 | Env.set(Env.EnvType.Yellow); 67 | const result = Env.has(); 68 | t.true(result); 69 | }); 70 | 71 | test('# Env.has 尚未初始化则不存在配置文件', (t) => { 72 | Env.remove(); 73 | const result = Env.has(); 74 | t.false(result); 75 | }); 76 | 77 | test('# Env.remove 不存在配置文件', (t) => { 78 | Env.set(Env.EnvType.Green); 79 | Env.remove(); 80 | const result = fs.existsSync(envFile); 81 | t.false(result); 82 | }); 83 | -------------------------------------------------------------------------------- /docs/use-cli.md: -------------------------------------------------------------------------------- 1 | # tiny基础命令详解 2 | 3 | tiny提供了三类命令: 4 | 5 | 1. tiny基础命令 6 | 2. tiny套件固定命令 7 | 3. tiny插件扩展命令 8 | 9 | 本文主要讲解tiny提供的基础命令,[tiny套件固定命令](docs/use-toolkit.md)及[tiny插件命令](docs/use-plugin.md)请跳转至对应的文档查看。 10 | 11 | ## 基础命令一览 12 | 13 | 可在终端输入`$ tiny -h` 查看tiny使用帮助 14 | 15 | ```bash 16 | $ tiny -h 17 | 18 | tiny 使用帮助: $ tiny [command] [options] 19 | 20 | $ tiny 显示tiny帮助信息,若目录下有使用的套件,则会同时显示套件的帮助信息 21 | $ tiny init [toolkitName] 初始化套件 22 | $ tiny update [name] 更新tiny模块 23 | $ tiny list [type] 插件列表 24 | $ tiny i 安装npm模块 25 | $ tiny clear 清空 tiny 的本地缓存 26 | $ tiny help 显示套件帮助信息 27 | $ tiny [name] 其他调用插件命令 28 | 29 | ``` 30 | 31 | ## tiny init [toolkitName] 32 | 33 | 初始化套件 34 | 35 | ```bash 36 | $ tiny init [toolkitName] 37 | ``` 38 | 39 | 其中`toolkitName`表示套件的名字。一般的套件名格式为:`tiny-toolkit-{toolkitName}`。如DEV套件:[tiny-toolkit-dev](http://github.com/opentiny/tiny-cli/tree/dev/packages/toolkits/dev),若要使用该套件可直接初始化: 40 | 41 | ```bash 42 | $ tiny init dev 43 | ``` 44 | 45 | 执行该命令后,会自动判断本地是否已安装了该套件,若已安装则直接初始化;若未安装,则自动在电脑中进行安装,安装完后再进行初始化操作。 46 | 47 | ### 例子 48 | 49 | ```bash 50 | # 创建一个叫toolkit-demo的空文件夹,并进入该文件夹 51 | $ mkdir toolkit-demo && cd $_ 52 | # 初始化dev套件 53 | $ tiny init dev 54 | ``` 55 | 56 | 57 | 58 | 59 | ## tiny i [name] 60 | 61 | 安装tiny模块(套件及插件)。该命令类似于:`npm i / npm install`. 62 | 与 npm install 的区别是,tiny内部调用的是`pnpm`来进行安装,安装速度比`npm`更快。**强烈建议用`tiny i`代替`npm install`** 63 | 64 | 65 | ```bash 66 | $ tiny install [name] 67 | ``` 68 | 69 | 其中`name`表示tiny的模块名称。套件名称格式为:`tiny-{toolkit-name}`,插件名称格式为:`tiny-{plugin-name}`,输入对应的名字即可安装。 70 | 71 | ### 例子 72 | 73 | ```bash 74 | # 安装jquery 到项目中 75 | $ tiny i jquery -S 76 | 77 | # 安装package.json中的依赖 78 | $ tiny i 79 | 80 | # 安装 4.2.0版本的 co模块 81 | $ tiny i co@4.2.0 82 | 83 | ``` 84 | 85 | ## tiny update [name] 86 | 87 | 更新tiny模块到最新版本。 88 | 89 | ### 例子 90 | 91 | ```bash 92 | # 更新dev套件到最新版本 93 | $ tiny update @opentiny/tiny-toolkit-dev 94 | 95 | # 更新link插件到最新版本 96 | $ tiny update @opentiny/tiny-plugin-link 97 | ``` 98 | 99 | ## tiny list [type] 100 | 101 | 显示tiny可用的模块列表。 102 | 103 | 其中 `type` 值为 `toolkit` 和 `plugin`。 104 | 105 | ### 例子 106 | 107 | ```bash 108 | # 显示tiny所有模块 109 | $ tiny list 110 | 111 | # 显示tiny所有套件 112 | $ tiny list toolkit 113 | 114 | # 显示tiny所有插件 115 | $ tiny list plugin 116 | ``` 117 | 118 | 119 | 120 | 121 | ## tiny clear 122 | 123 | 清空tiny本地缓存。 124 | 125 | 当tiny模块安装出现异常时,可使用该命令将tiny的缓存目录进行初始化。初始化之后,**会清空tiny安装过的所有tiny模块** 126 | 127 | ### 例子 128 | 129 | ```bash 130 | # 清空tiny本地缓存 131 | $ tiny clear 132 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 很高兴你有意愿参与 TinyCLI 开源项目的贡献,参与贡献的形式有很多种,你可以根据自己的特长和兴趣选择其中的一个或多个: 4 | 5 | - 报告[新缺陷](https://github.com/opentiny/tiny-cli/issues/new?template=bug-report.yml) 6 | - 为[已有缺陷](https://github.com/opentiny/tiny-cli/labels/bug)提供更详细的信息,比如补充截图、提供更详细的复现步骤、提供最小可复现demo链接等 7 | - 提交 Pull requests 修复文档中的错别字或让文档更清晰和完善 8 | - 添加官方小助手微信 opentiny-official,加入技术交流群参与讨论 9 | 10 | 当你亲自使用 TinyCLI 组件库,并参与多次以上形式的贡献,对 TinyCLI 逐渐熟悉之后,可以尝试做一些更有挑战的事情,比如: 11 | 12 | - 修复缺陷,可以先从 [Good-first issue](https://github.com/opentiny/tiny-cli/labels/good%20first%20issue) 开始 13 | - 实现新特性 14 | - 完善单元测试 15 | - 翻译文档 16 | - 参与代码检视 17 | 18 | ## 提交 Issue 19 | 20 | 如果你在使用 TinyCLI 组件过程中遇到问题,欢迎给我们提交 Issue,提交 Issue 之前,请先仔细阅读相关的[官方文档](https://opentiny.design),确认这是一个缺陷还是尚未实现的功能。 21 | 22 | 如果是一个缺陷,创建新 Issue 时选择 [Bug report](https://github.com/opentiny/tiny-cli/issues/new?template=bug-report.yml) 模板,标题遵循 `[toolkitName/pluginName/CliCore]缺陷简述` 的格式,比如:`[tiny-toolkit-xxx] xxx套件执行tiny start命令启动后提示xxx错误`。 23 | 24 | 报告缺陷的 Issue 主要需要填写以下信息: 25 | - tiny-cli 和 node 的版本号 26 | - 缺陷的表现,可截图辅助说明,如果有报错可贴上报错信息 27 | - 缺陷的复现步骤,最好能提供一个最小可复现 demo 链接 28 | 29 | 如果是一个新特性,则选择 [Feature request](https://github.com/opentiny/tiny-cli/issues/new?template=feature-request.yml) 模板,标题遵循 `[toolkitName/pluginName/CliCore]新特性简述` 的格式,比如:`[CliCore] 新增switch命令支持切换NPM源功能`。 30 | 31 | 新特性的 Issue 主要需要填写以下信息: 32 | - 该特性主要解决用户的什么问题 33 | - 该特性的 api 是什么样的 34 | 35 | ## 提交 PR 36 | 37 | 提交 PR 之前,请先确保你提交的内容是符合 TinyCLI 整体规划的,一般已经标记为 [bug](https://github.com/opentiny/tiny-cli/labels/bug) 的 Issue 是鼓励提交 PR 的,如果你不是很确定,可以创建一个 [Discussion](https://github.com/opentiny/tiny-cli/discussions) 进行讨论。 38 | 39 | 本地启动步骤: 40 | 41 | - 点击 [TinyCLI](https://github.com/opentiny/tiny-cli) 代码仓库右上角的 Fork 按钮,将上游仓库 Fork 到个人仓库 42 | - Clone 个人仓库到本地 43 | - 在 Tiny CLI 根目录下运行 npm init, 安装依赖 44 | - 运行 npm run dev,启动本地代码编译开发 45 | 46 | ```shell 47 | # username 为用户名,执行前请替换 48 | git clone git@github.com:username/tiny-cli.git 49 | cd tiny-cli 50 | git remote add upstream git@github.com:opentiny/tiny-cli.git 51 | npm i 52 | 53 | # 启动项目 54 | npm run dev 55 | ``` 56 | 57 | 提交 PR 的步骤: 58 | 59 | - 创建新分支 `git checkout -b username/feature1`,分支名字建议为 `username/feat-xxx` / `username/fix-xxx` 60 | - 本地编码 61 | - 遵循 Commit Message Format 规范进行提交,不符合提交规范的 PR 将不会被合并 62 | - 提交到远程仓库:git push origin branchName 63 | - (可选)同步上游仓库 dev 分支最新代码:git pull upstream dev 64 | - 打开 TinyCLI 代码仓库的 [Pull requests](https://github.com/opentiny/tiny-cli/pulls) 链接,点击 New pull request 按钮提交 PR 65 | - 项目 Committer 进行 Code Review,并提出意见 66 | - PR 作者根据意见调整代码,请注意一个分支发起了 PR 后,后续的 commit 会自动同步,无需重新提交 PR 67 | - 项目管理员合并 PR 68 | 69 | 贡献流程结束,感谢你的贡献! 70 | 71 | ## 加入开源社区 72 | 73 | 如果你对我们的开源项目感兴趣,欢迎通过以下方式加入我们的开源社区。 74 | 75 | - 添加官方小助手微信:opentiny-official,加入我们的技术交流群 76 | - 加入邮件列表:opentiny@googlegroups.com 77 | -------------------------------------------------------------------------------- /packages/commands/src/list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 列出所有的套件,插件 14 | */ 15 | 16 | import { Intl, logs, modules } from '@opentiny/cli-devkit'; 17 | import chalk from 'chalk'; 18 | import message from './locale/index'; 19 | import { getPadding } from './utils'; 20 | 21 | const log = logs('core-commands'); 22 | 23 | function printListByType(type: string, moduleList: any[]) { 24 | let tmpString; 25 | moduleList 26 | .filter((item) => !!item.name.match(type)) 27 | .forEach((item) => { 28 | const padding = getPadding(item.name, 35); 29 | 30 | tmpString = [' ', chalk.green(item.name), chalk.gray(padding), item.chName ? item.chName : '暂无描述'].join(''); 31 | console.log(tmpString); 32 | }); 33 | } 34 | 35 | export default async function (cliArgs: string[], _options: any) { 36 | const type = cliArgs.pop(); 37 | const intl = new Intl(message); 38 | const fixType = type === 'plugin' || type === 'toolkit' ? type : null; 39 | const textMap = { 40 | plugin: intl.get('plugin'), 41 | toolkit: intl.get('toolkit'), 42 | all: intl.get('toolkitAndPlugin'), 43 | }; 44 | const text = textMap[fixType || 'all']; 45 | const star = fixType ? '**' : ''; 46 | const param = { fixType }; 47 | 48 | log.debug('module params = %o', param); 49 | const local = modules.localList(param); 50 | const online = await modules.onlineList(param); 51 | 52 | const newList: any[] = []; 53 | 54 | // merge list 55 | const onlineKeys = online.map((item) => { 56 | newList.push(item); 57 | return item.name; 58 | }); 59 | 60 | local.forEach((item) => { 61 | if (onlineKeys.indexOf(item.name) === -1) { 62 | newList.push(item); 63 | } 64 | }); 65 | 66 | console.log( 67 | chalk.italic.magenta(`\r\n${star}************** ${text} ${intl.get('list')} ******************${star}\r\n`) 68 | ); 69 | 70 | if (!type) { 71 | console.log(chalk.magenta(intl.get('toolkitList'))); 72 | printListByType('toolkit', newList); 73 | console.log(chalk.magenta(intl.get('pluginList'))); 74 | printListByType('plugin', newList); 75 | } else { 76 | printListByType(type, newList); 77 | } 78 | console.log(chalk.italic.magenta('\r\n***************************************************\r\n')); 79 | } 80 | -------------------------------------------------------------------------------- /packages/devkit/src/error/handle-eaddrinuse.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import test from 'ava'; 13 | import handleEnoent from './handle-eaddrinuse'; 14 | 15 | test('#处理 EADDRINUSE 的异常 listen EADDRINUSE :::9000', async (t) => { 16 | // const intl = new Intl(message); 17 | // const locale = intl.getLocale(); 18 | const result: boolean = await handleEnoent({ 19 | code: 'EADDRINUSE', 20 | message: ` 21 | Error: listen EADDRINUSE :::9000 22 | at Object.exports._errnoException (util.js:1007:11) 23 | at exports._exceptionWithHostPort (util.js:1030:20) 24 | at Server._listen2 (net.js:1253:14) 25 | at listen (net.js:1289:10) 26 | at Server.listen (net.js:1385:5) 27 | at Application.app.listen (/Users/hugo/.tiny/node_modules/._koa@1.4.0@koa/lib/application.js:74:24) 28 | at Object.Commands.open (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:43:18) 29 | at Promise (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:140:30) 30 | at module.exports (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:134:10) 31 | ` 32 | }); 33 | t.true(result); 34 | }); 35 | 36 | test('# 处理 EADDRINUSE 的异常 listen EADDRINUSE 127.0.0.1:3000', async (t) => { 37 | const result = await handleEnoent({ 38 | code: 'EADDRINUSE', 39 | message: ` 40 | Error: listen EADDRINUSE 127.0.0.1:3000 41 | at Object.exports._errnoException (util.js:1007:11) 42 | at exports._exceptionWithHostPort (util.js:1030:20) 43 | at Server._listen2 (net.js:1253:14) 44 | at listen (net.js:1289:10) 45 | at Server.listen (net.js:1385:5) 46 | at Application.app.listen (/Users/hugo/.tiny/node_modules/._koa@1.4.0@koa/lib/application.js:74:24) 47 | at Object.Commands.open (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:43:18) 48 | at Promise (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:140:30) 49 | at module.exports (/Users/hugo/.tiny/node_modules/._@opentiny_tiny-plugin-server@1.2.3@@opentiny/tiny-plugin-server/index.js:134:10) 50 | ` 51 | }); 52 | t.true(result); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/devkit/src/module/locale/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 语言文件 14 | */ 15 | 16 | export default { 17 | zh: { 18 | autoUpdate: '{name} 设置了自动更新,正在执行更新操作...', 19 | autoUpdateZ: '检查到您本地版本为 {localVersion} , 自动为您升级到兼容版本 {autoZVersion} 中...', 20 | autoInstall: '本地尚未安装 {name} ,正在执行自动安装...', 21 | // install-one.js 22 | importPkgError: '您传入的包名有误,请输入正确的包名,如: @opentiny/tiny-toolkit-xxx,@opentiny/tiny-plugin-xxx', 23 | installSuccess: '{name} 安装成功', 24 | updateSuccess: '{name} 更新成功', 25 | // utils.js 26 | updateNone: '本地暂无可更新的模块', 27 | updateTo: '从 {localVersion} 升级至 {lastVersion}', 28 | updateVersion: '{lastVersion} 版本', 29 | localVersion: ' , 本地版本是 {localVersion} ', 30 | updateTips: '升级提示', 31 | recommendVersion: '{name} 推荐的版本是 {version}', 32 | recommendInstall: '请执行 {icon} {installTip} 来升级模块', 33 | includeUpdate: '包含以下更新:', 34 | installError: '{name} 安装报错,请确认该package是否存在!', 35 | getModuleErr: '{modulePath} 文件运行失败,请检查,错误信息如下:', 36 | }, 37 | en: { 38 | autoUpdate: '{name} has set an automatic update and is excuting an update operation...', 39 | autoUpdateZ: 40 | 'Your local version is {localVersion}, automatically upgrading to compatible version {autoZVersion} for you...', 41 | autoInstall: '{name} has not been installed, performing an automatic installation...', 42 | // install-one.js 43 | importPkgError: 44 | 'Package name is incorrect. Please re-enter the correct package name, eg: @opentiny/tiny-toolkit-xxx, @opentiny/tiny-plugin-xxx', 45 | installSuccess: '{name} install completed', 46 | updateSuccess: '{name} update completed', 47 | // utils.js 48 | updateNone: 'No update to the module', 49 | updateTo: 'Upgrade from {localVersion} to {lastVersion}', 50 | updateVersion: '{lastVersion} version', 51 | localVersion: ' , The local version is {localVersion} ', 52 | updateTips: 'Upgrade tips', 53 | recommendVersion: '{name}: recommended version is {version}', 54 | recommendInstall: 'Please execute {icon} {installTip} to upgrade the module', 55 | includeUpdate: 'Include the following updates:', 56 | installError: 'Install {name} package error, please confirm whether the package exists!', 57 | getModuleErr: '{modulePath} failed to run, more details:', 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Tiny CLI 2 | 3 | 简体中文 | [English](README.md) 4 | 5 | > Tiny CLI是前端工程化命令行工具 6 | 7 | [Tiny CLI官网](https://opentiny.design/tiny-cli/home) 8 | 9 | 10 | ## Installation 11 | 12 | 打开git bash、zsh等命令行工具,输入如下命令后回车: 13 | 14 | ```bash 15 | $ npm i @opentiny/cli -g 16 | ``` 17 | 18 | 等待片刻,待安装完成之后在终端执行 `$ tiny -v`,正常返回版本信息,表示安装成功。 19 | 20 | 注意:tiny-cli 依赖 nodejs(12.x以上) 、 npm 环境,在安装tiny时,请确保本机已安装了nodejs和npm 21 | 22 | ## Docs 23 | 24 | * [CLI工具设计文档](docs/tool-design.md) 25 | * 使用者文档 26 | * [tiny-cli介绍](docs/use-summary.md) 27 | * [安装tiny-cli](docs/use-install.md) 28 | * [tiny-cli基础命令详解](docs/use-cli.md) 29 | * [使用tiny-cli套件](docs/use-toolkit.md) 30 | * [使用tiny-cli插件](docs/use-plugin.md) 31 | * [tiny-cli配置文件](docs/use-config.md) 32 | * 开发者文档 33 | * [tiny-cli API](docs/api.md) 34 | * [套件开发指南](docs/dev-toolkit.md) 35 | * [插件开发指南](docs/dev-plugin.md) 36 | 37 | ## Usage 38 | 39 | 可在终端输入`$ tiny -h` 查看tiny使用帮助 40 | 41 | ```bash 42 | tiny 使用帮助: $ tiny [command] [options] 43 | 44 | $ tiny 显示tiny帮助信息,若目录下有使用的套件,则会同时显示套件的帮助信息 45 | $ tiny init [toolkitName] 初始化套件 46 | $ tiny update [name] 更新tiny模块 47 | $ tiny list [type] 插件列表 48 | $ tiny i 安装npm模块 49 | $ tiny clear 清空 tiny 的本地缓存 50 | $ tiny help 显示套件帮助信息 51 | $ tiny [name] 其他调用插件命令 52 | 53 | Options: 54 | 55 | -h, --help 显示tiny帮助信息 56 | -v, --version 显示tiny版本 57 | 58 | 59 | 提示: 60 | 套件 - 若想查看项目中所使用的套件帮助信息,请在项目根目录执行该命令. 61 | 插件 - 若想查看插件的帮助信息,请使用 tiny [name] help 命令, eg : tiny git help 62 | ``` 63 | 64 | ### Quick start 65 | 66 | 以 `@opentiny/tiny-toolkit-dev` 套件为例,讲解开发流程。 67 | 68 | 69 | 1. 初始化项目 70 | 71 | ```bash 72 | # 创建并进入项目文件夹 73 | $ mkdir my-project && cd $_ 74 | 75 | # 初始化dev的开发环境 76 | $ tiny init dev 77 | ``` 78 | 79 | 3. 开启本地环境 80 | 81 | ```bash 82 | # 开启dev的开发环境 83 | $ tiny start 84 | ``` 85 | 86 | 4. 项目编译及打包 87 | 88 | ```bash 89 | $ tiny build 90 | ``` 91 | 92 | ## ChangeLog 93 | 94 | [CHANGELOG.md](CHANGELOG.md) 95 | 96 | ## Support 97 | 98 | 开发及使用过程中的问题,可以在代码仓库新建issue 99 | 100 | ## 开发文档 101 | 102 | ### 前置条件 103 | 104 | * 确保已安装lerna,未安装可执行命令: `npm install --global lerna` 105 | * 确保node版本在v12.x及以上 106 | 107 | ### 本地开发调试命令 108 | 109 | 首次:`npm run init` 110 | 开发:`npm run dev` 111 | 构建:`npm run build` 112 | 本地验证: `npm run link` 113 | 114 | ### 构建流程 115 | 116 | 根目录执行`npm run build`即可,会打包出lib目录和dist目录。 117 | 118 | 119 | ## 参与贡献 120 | 121 | 如果你对我们的开源项目感兴趣,欢迎加入我们! 122 | 123 | 参与贡献之前请先阅读[贡献指南](CONTRIBUTING.md)。 124 | 125 | - 添加官方小助手微信 opentiny-official,加入技术交流群 126 | - 加入邮件列表 opentiny@googlegroups.com 127 | 128 | ## 开源协议 129 | 130 | [MIT](LICENSE) -------------------------------------------------------------------------------- /packages/devkit/src/module/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as path from 'path'; 13 | import test from 'ava'; 14 | import fs from 'fs-extra'; 15 | import home from '../home/index'; 16 | import module from './index'; 17 | import { PROCESS_ENV_HOME_PATH, PROCESS_ENV_HOME_FOLDE, PROCESS_ENV_BIN } from '../cli-config/index'; 18 | import { expect } from 'chai'; 19 | 20 | const mockCwd = path.resolve(__dirname); 21 | const homeFolder = 'fixtures'; 22 | 23 | /** 24 | * 创建一个虚拟模块 25 | * @param name 模块名 26 | */ 27 | function createPackage(name: string) { 28 | const pkg = { 29 | name, 30 | version: '1.0.0', 31 | description: name, 32 | main: 'index.js' 33 | }; 34 | const homeModulePath = home.getModulesPath(); 35 | const modulePath = path.join(homeModulePath, name); 36 | fs.ensureDirSync(modulePath); 37 | fs.outputJsonSync(path.join(modulePath, 'package.json'), pkg); 38 | // fs.copySync(path.join(mockCwd, homeFolder, 'index.js'), path.join(modulePath, 'index.js')); 39 | } 40 | 41 | // function clearPackage() { 42 | // fs.removeSync(path.join(home.getHomePath(), FILE_CACHE)); 43 | // fs.removeSync(home.getModulesPath()); 44 | // } 45 | 46 | /** 47 | * 初始化环境 48 | */ 49 | function initConfig() { 50 | process.env[PROCESS_ENV_HOME_PATH] = mockCwd; 51 | process.env[PROCESS_ENV_HOME_FOLDE] = homeFolder; 52 | process.env[PROCESS_ENV_BIN] = 'hugo'; 53 | 54 | createPackage('hugo-toolkit-abc'); 55 | createPackage('hugo-plugin-defg'); 56 | } 57 | 58 | test.beforeEach(() => { 59 | initConfig(); 60 | }); 61 | 62 | test.afterEach(() => { 63 | // clearPackage(); 64 | }); 65 | 66 | test('# 从线上 获取 一个模块', async (t) => { 67 | // 从线上获取 68 | const data = await module.get('hugo-toolkit-empty-module'); 69 | expect(data).to.be.an('object').to.have.property('start'); 70 | t.pass(); 71 | }); 72 | 73 | // test.only('# 从内网获取一个模块', async t => { 74 | // // 从线上获取 75 | // const data = await module.get('@opentiny/hybrid-interface'); 76 | // expect(data) 77 | // .to.be.an('object') 78 | // .to.have.property('start'); 79 | // t.pass() 80 | // }); 81 | 82 | // test('# 获取本地mock tiny 插件或套件', async t => { 83 | // // 从线上获取 84 | // const data = module.get('hugo-toolkit-abc'); 85 | // expect(data) 86 | // .to.be.an('object') 87 | // .to.have.property('start'); 88 | // t.pass(); 89 | // }); 90 | -------------------------------------------------------------------------------- /packages/devkit/src/env/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import fs from 'fs-extra'; 13 | import * as path from 'path'; 14 | import Debug from 'debug'; 15 | import home from '../home/index'; 16 | import { FILE_ENV, PROCESS_ENV_CLI_ENV } from '../cli-config/index'; 17 | 18 | const debug = Debug('core-env'); 19 | // 定义一个可枚举的环境变量 20 | export enum EnvType { 21 | Green = 'Green', 22 | Yellow = 'Yellow', 23 | Red = 'Red', 24 | None = 'None' 25 | } 26 | 27 | interface CacheEnv { 28 | env: EnvType; 29 | } 30 | 31 | // 缓存env对象 32 | let cacheEnv: CacheEnv | null; 33 | 34 | /** 35 | * 往配置文件(tiny.env.json)写入用户自定义的环境配置 36 | * @param env 37 | */ 38 | export function set(env: EnvType) { 39 | home.initHomeDir(); 40 | const envFile = path.join(home.getHomePath(), FILE_ENV); 41 | const envData = { 42 | env 43 | }; 44 | debug('set tiny env data : %o , set tiny to : %s', envData, envFile); 45 | cacheEnv = null; 46 | fs.outputJsonSync(envFile, envData); 47 | } 48 | 49 | /** 50 | * 获取当前用户的环境变量 51 | * 优先判断process.env.TINY_ENV变量 52 | * @returns {EnvType} 返回可枚举的环境类型 53 | */ 54 | export function get(): EnvType { 55 | // 如果有环境变量,则优先使用环境变量的值 56 | const envGlobal = process.env[PROCESS_ENV_CLI_ENV]; 57 | if (envGlobal && EnvType[envGlobal]) return EnvType[envGlobal]; 58 | 59 | // 由于该方法调用频繁,在这里使用一个cacheEnv对象做为缓存,避免频繁的IO操作 60 | let envData: CacheEnv = { env: EnvType.None }; 61 | if (cacheEnv) { 62 | envData = cacheEnv; 63 | } else { 64 | const envFile = path.join(home.getHomePath(), FILE_ENV); 65 | if (fs.existsSync(envFile)) { 66 | envData = fs.readJsonSync(envFile); 67 | cacheEnv = envData; 68 | } 69 | } 70 | if (envData && envData.env && EnvType[envData.env]) { 71 | return EnvType[envData.env]; 72 | } 73 | return EnvType.None; 74 | } 75 | 76 | /** 77 | * 判断cli环境配置文件(tiny.env.json)是否存在 78 | * 可用做cli环境是否已初始化的判断 79 | * @returns {boolean} 80 | */ 81 | export function has(): boolean { 82 | const envFile = path.join(home.getHomePath(), FILE_ENV); 83 | return fs.existsSync(envFile); 84 | } 85 | 86 | /** 87 | * 删除cli环境配置文件(tiny.env.json) 88 | */ 89 | export function remove(): void { 90 | fs.removeSync(path.join(home.getHomePath(), FILE_ENV)); 91 | cacheEnv = null; 92 | } 93 | 94 | export default { 95 | set, 96 | get, 97 | has, 98 | remove 99 | }; 100 | -------------------------------------------------------------------------------- /packages/devkit/src/module/getReallyName.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import logs from '../log/index'; 13 | import localExist from './local-exist'; 14 | import onlineExist from './online-exist'; 15 | import { DEFAULT_BIN, DEFAULT_SCOPE, getScope } from '../cli-config/index'; 16 | import utils from './utils'; 17 | 18 | const log = logs('core-module'); 19 | 20 | /** 21 | * 返回的模块信息 22 | */ 23 | export interface ModuleInfo { 24 | exist: boolean; // 模块是否存在 25 | isUseModule: boolean; // 是否使用tiny原生模块 26 | reallyName: string; // 实际运行的模块名称 27 | } 28 | 29 | /** 30 | * 获取实际可执行的套件或插件名称 31 | * 获取逻辑: 自定义本地套件/插件 -> cli本地套件/插件 -> 自定义线上套件/插件 -> cli线上套件/插件 32 | * @param name 套件或插件名,传入的是完整,必须带上 toolkit 或 plugin 33 | */ 34 | export default async function (name: string): Promise { 35 | // TODO 校验name的格式 36 | 37 | const prefix = utils.prefix(); 38 | const scope = getScope(); 39 | // 如果是自定义prefix的插件 40 | const isCustomPrefix = prefix !== DEFAULT_BIN; 41 | // 是否使用的是tiny插件 42 | let isUseModule = false; 43 | 44 | // tiny模块名称 @opentiny/{yy}-plugin-xxx 45 | const tinyName = name.replace(scope, DEFAULT_SCOPE).replace(prefix, DEFAULT_BIN); 46 | 47 | // 实际调用的插件名 48 | let reallyName = name; 49 | // 执行插件的方法 50 | let exist = localExist(name); 51 | log.debug(`本地 ${name} 模块: ${exist}`); 52 | if (!exist) { 53 | // 判断一下是不是自定义prefix的情况 54 | // 是的话走下面的逻辑 55 | if (isCustomPrefix) { 56 | exist = localExist(tinyName); 57 | log.debug(`本地tiny ${tinyName} 模块: ${exist}`); 58 | if (!exist) { 59 | // 查找线上版本 60 | exist = await onlineExist(name); 61 | log.debug(`线上 ${name} 模块: ${exist}`); 62 | if (!exist) { 63 | exist = await onlineExist(tinyName); 64 | log.debug(`线上tiny ${tinyName} 模块: ${exist}`); 65 | if (exist) { 66 | // 如果存在,则返回真实获取的名称 67 | reallyName = tinyName; 68 | isUseModule = true; 69 | } 70 | } 71 | } else { 72 | reallyName = tinyName; 73 | isUseModule = true; 74 | } 75 | } else { 76 | exist = await onlineExist(name); 77 | log.debug(`线上 ${name} 模块: ${exist}`); 78 | } 79 | } 80 | 81 | const moduleInfo: ModuleInfo = { 82 | exist, // 模块是否存在 83 | isUseModule, // 是否使用tiny原生模块 84 | reallyName // 实际运行的模块名称 85 | }; 86 | 87 | log.debug('当前实际的模块信息 %o', moduleInfo); 88 | 89 | return moduleInfo; 90 | } 91 | -------------------------------------------------------------------------------- /packages/devkit/src/module/online-list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import ping from 'ping'; 13 | import cache from '../cache/index'; 14 | import logs from '../log/index'; 15 | import utils from './utils'; 16 | import npm, { ModuleInfo, getRegistry } from '../npm/index'; 17 | import { getScope, getBinName, DEFAULT_BIN } from '../cli-config/index'; 18 | const log = logs('core-module'); 19 | 20 | /** 21 | * 获取列表, 缓存机制\ 22 | * @returns {*|Request|Array} 23 | */ 24 | async function onlineList(options: any): Promise { 25 | options = { 26 | cache: true, 27 | ...options 28 | }; 29 | 30 | const cacheKey = utils.ONLINE_MODULE_CACHE_KEY_OUT; 31 | let moduleList: ModuleInfo[] = (options.cache && cache.get(cacheKey)) || []; 32 | 33 | log.debug('get online list from cache %o', moduleList); 34 | try { 35 | if (!moduleList.length) { 36 | // 先ping一下,看是否有网络 37 | const scope = getScope(); 38 | const registry = getRegistry(`@${scope}/cli-module-list`); 39 | const pingApi = /((http|https):\/\/)?(.*?)\/.*/.exec(registry)?.[3]; 40 | 41 | const pingRes = await ping.promise.probe(pingApi); 42 | 43 | if (!pingRes || !pingRes.alive) { 44 | log.error(`Network connection error for ${pingApi}`); 45 | throw Error('Network connection error'); 46 | } 47 | const prefix = getBinName(); 48 | const pkg = await npm.latest(`@${scope}/cli-module-list`); 49 | // 数据不存在则直接返回原始数据 50 | if (!pkg) { 51 | return moduleList; 52 | } 53 | let modules = pkg.tiny || {}; 54 | // 非tiny的套件与tiny的套件合并到一起。 55 | if (prefix !== DEFAULT_BIN && pkg[prefix]) { 56 | modules = { ...pkg.tiny, ...pkg[prefix] }; 57 | } 58 | const list: any[] = Object.keys(modules); 59 | list.forEach((item) => { 60 | moduleList.push({ 61 | name: item, 62 | chName: modules![item], 63 | description: modules![item] 64 | }); 65 | }); 66 | // 如果没有列表,就不缓存了 67 | if (!moduleList.length) { 68 | cache.set(cacheKey, moduleList, { 69 | expires: 3600000 70 | }); 71 | } 72 | } 73 | } catch (e) { 74 | log.error(e); 75 | } 76 | 77 | moduleList = options.type ? utils.moduleFilter(moduleList, options.type) : moduleList; 78 | 79 | log.debug('所有线上模块: %o', moduleList); 80 | 81 | return moduleList; 82 | } 83 | 84 | export default onlineList; 85 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | export default interface IPackageJSON extends Object { 13 | name: string; 14 | version?: string; 15 | description?: string; 16 | keywords?: string[]; 17 | homepage?: string; 18 | bugs?: string | IBugs; 19 | license?: string; 20 | author?: string | IAuthor; 21 | contributors?: string[] | IAuthor[]; 22 | files?: string[]; 23 | main?: string; 24 | bin?: string | IBinMap; 25 | man?: string | string[]; 26 | directories?: IDirectories; 27 | repository?: string | IRepository; 28 | scripts?: IScriptsMap; 29 | config?: IConfig; 30 | dependencies?: IDependencyMap; 31 | devDependencies?: IDependencyMap; 32 | peerDependencies?: IDependencyMap; 33 | optionalDependencies?: IDependencyMap; 34 | bundledDependencies?: string[]; 35 | engines?: IEngines; 36 | os?: string[]; 37 | cpu?: string[]; 38 | preferGlobal?: boolean; 39 | private?: boolean; 40 | publishConfig?: IPublishConfig; 41 | tinyOption?: ITinyOption; 42 | changeLog?: IChangeLog[]; 43 | [propName: string]: any; 44 | } 45 | 46 | export interface IChangeLog { 47 | version: string; 48 | log: string[]; 49 | } 50 | 51 | export interface ITinyOption { 52 | type: string; 53 | update: boolean; 54 | chName: string; 55 | } 56 | 57 | /** 58 | * An author or contributor 59 | */ 60 | export interface IAuthor { 61 | name: string; 62 | email?: string; 63 | homepage?: string; 64 | } 65 | 66 | /** 67 | * A map of exposed bin commands 68 | */ 69 | export interface IBinMap { 70 | [commandName: string]: string; 71 | } 72 | 73 | /** 74 | * A bugs link 75 | */ 76 | export interface IBugs { 77 | email: string; 78 | url: string; 79 | } 80 | 81 | export interface IConfig { 82 | name?: string; 83 | config?: Object; 84 | } 85 | 86 | /** 87 | * A map of dependencies 88 | */ 89 | export interface IDependencyMap { 90 | [dependencyName: string]: string; 91 | } 92 | 93 | /** 94 | * CommonJS package structure 95 | */ 96 | export interface IDirectories { 97 | lib?: string; 98 | bin?: string; 99 | man?: string; 100 | doc?: string; 101 | example?: string; 102 | } 103 | 104 | export interface IEngines { 105 | node?: string; 106 | npm?: string; 107 | } 108 | 109 | export interface IPublishConfig { 110 | registry?: string; 111 | } 112 | 113 | /** 114 | * A project repository 115 | */ 116 | export interface IRepository { 117 | type: string; 118 | url: string; 119 | } 120 | 121 | export interface IScriptsMap { 122 | [scriptName: string]: string; 123 | } 124 | -------------------------------------------------------------------------------- /packages/core/src/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | // to use V8's code cache to speed up instantiation time 13 | require('v8-compile-cache'); 14 | process.removeAllListeners('warning'); 15 | import { cliConfig } from '@opentiny/cli-devkit'; 16 | import * as path from 'path'; 17 | import * as fs from 'fs'; 18 | const sudoBlock = require('sudo-block'); 19 | const pkg = require('../package.json'); 20 | const { 21 | PROCESS_ENV_CLI_PACKAGE, 22 | PROCESS_ENV_CLI_VERSION, 23 | PROCESS_ENV_BIN, 24 | PROCESS_ENV_CONFIG_FILE, 25 | DEFAULT_CONFIG_FILE, 26 | PROCESS_ENV_HOME_FOLDE, 27 | DEFAULT_BIN, 28 | DEFAULT_HOME_FOLDER, 29 | } = cliConfig; 30 | 31 | // 禁止 sudo 执行 tiny 命令 32 | sudoBlock(); 33 | 34 | const cliEnv = [ 35 | { 36 | key: PROCESS_ENV_CLI_PACKAGE, 37 | value: pkg.name, 38 | }, 39 | { 40 | key: PROCESS_ENV_CLI_VERSION, 41 | value: pkg.version, 42 | }, 43 | { 44 | key: PROCESS_ENV_BIN, 45 | value: DEFAULT_BIN, 46 | }, 47 | { 48 | key: PROCESS_ENV_CONFIG_FILE, 49 | value: DEFAULT_CONFIG_FILE, 50 | }, 51 | { 52 | key: PROCESS_ENV_HOME_FOLDE, 53 | value: DEFAULT_HOME_FOLDER, 54 | }, 55 | ]; 56 | 57 | /** 58 | * 初始化开发环境 59 | * 需要在require其他包之前先进行初始化 60 | * @param obj 61 | */ 62 | function initConfig(envs: any) { 63 | envs.forEach((item) => { 64 | if (!process.env[item.key]) { 65 | process.env[item.key] = item.value; 66 | } 67 | }); 68 | } 69 | 70 | // 运行前的一些初始化配置工作,这些内容将存于tiny的运行时 71 | initConfig(cliEnv); 72 | 73 | // Check if we need to profile this CLI run. 74 | if (process.env['TINY_CLI_PROFILING']) { 75 | let profiler: { 76 | startProfiling: (name?: string, recsamples?: boolean) => void; 77 | stopProfiling: (name?: string) => any; // tslint:disable-line:no-any 78 | }; 79 | try { 80 | profiler = require('v8-profiler-node8'); // tslint:disable-line:no-implicit-dependencies 81 | } catch (err) { 82 | throw new Error( 83 | `Could not require 'v8-profiler-node8'. You must install it separetely with ` + 84 | `'npm install v8-profiler-node8 --no-save'.\n\nOriginal error:\n\n${err}` 85 | ); 86 | } 87 | 88 | profiler.startProfiling(); 89 | 90 | const exitHandler = (options: { cleanup?: boolean; exit?: boolean }) => { 91 | if (options.cleanup) { 92 | const cpuProfile = profiler.stopProfiling(); 93 | fs.writeFileSync( 94 | path.resolve(process.cwd(), process.env['TINY_CLI_PROFILING'] || '') + '.cpuprofile', 95 | JSON.stringify(cpuProfile) 96 | ); 97 | } 98 | 99 | if (options.exit) { 100 | process.exit(); 101 | } 102 | }; 103 | 104 | process.on('exit', () => exitHandler({ cleanup: true })); 105 | process.on('SIGINT', () => exitHandler({ exit: true })); 106 | process.on('uncaughtException', () => exitHandler({ exit: true })); 107 | } 108 | -------------------------------------------------------------------------------- /packages/devkit/src/config/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | 'use strict'; 13 | 14 | import * as path from 'path'; 15 | // import proxyquire from 'proxyquire'; 16 | import fs from 'fs-extra'; 17 | import test, { ExecutionContext } from 'ava'; 18 | import * as config from './index'; 19 | import { DEFAULT_CONFIG_FILE } from '../cli-config/index'; 20 | import { expect } from 'chai'; 21 | 22 | test.before((t: ExecutionContext<{ mockCwd: string; source: string; mock: string; config?: object }>) => { 23 | const mockCwd = path.resolve(__dirname, 'fixtures'); 24 | const source = path.resolve(mockCwd, 'mock.config.js'); 25 | const mock = path.resolve(mockCwd, DEFAULT_CONFIG_FILE); 26 | // const config = proxyquire('../lib/index', {}); 27 | t.context = { 28 | mockCwd, 29 | source, 30 | mock 31 | // config 32 | }; 33 | fs.copySync(source, mock); 34 | }); 35 | 36 | test.after((t: ExecutionContext<{ mock: string }>) => { 37 | if (fs.existsSync(t.context.mock)) { 38 | fs.unlinkSync(t.context.mock); 39 | } 40 | }); 41 | 42 | test('# get 获取数据', (t: ExecutionContext<{ mockCwd: string }>) => { 43 | t.deepEqual(config.get('abc', t.context.mockCwd), { 44 | xyz: 22 45 | }); 46 | }); 47 | 48 | test('# set 设置数据', (t: ExecutionContext<{ mockCwd: string }>) => { 49 | const value = { 50 | xyz: 23 51 | }; 52 | config.set('abc', value, t.context.mockCwd); 53 | t.deepEqual(config.get('abc', t.context.mockCwd), value); 54 | }); 55 | 56 | test('# set value是一个字符串对象', (t: ExecutionContext<{ mockCwd: string }>) => { 57 | config.set( 58 | 'gg', 59 | ` 60 | //这是一行注释 61 | { 62 | "good" : "yes" 63 | } 64 | `, 65 | t.context.mockCwd 66 | ); 67 | t.deepEqual(config.get('gg', t.context.mockCwd), { 68 | good: 'yes' 69 | }); 70 | }); 71 | 72 | test('# set value是一个带.的字符串', (t: ExecutionContext<{ mockCwd: string }>) => { 73 | config.set('xx.yy', '123', t.context.mockCwd); 74 | t.deepEqual(config.get('xx', t.context.mockCwd), { 75 | yy: 123 76 | }); 77 | }); 78 | 79 | test('# set value是一个带.的字符串,复杂对象', (t: ExecutionContext<{ mockCwd: string }>) => { 80 | config.set( 81 | 'tasks.build', 82 | [ 83 | { 84 | command: 'echo 44' 85 | } 86 | ], 87 | t.context.mockCwd 88 | ); 89 | expect(config.get('tasks', t.context.mockCwd)).to.have.property('build'); 90 | expect(config.get('tasks', t.context.mockCwd).build[0]).to.have.property('command'); 91 | t.pass(); 92 | }); 93 | 94 | test('# getToolkitName 获取套件的名字', (t: ExecutionContext<{ mockCwd: string }>) => { 95 | const toolkit = config.getToolkitName(t.context.mockCwd); 96 | expect(toolkit).to.be.equal('@opentiny/tiny-toolkit-dev'); 97 | t.pass(); 98 | }); 99 | 100 | test('# getConfigName 获取配置文件的名称', (t) => { 101 | const name = config.getConfigName(); 102 | expect(name).to.be.equal('tiny.config.js'); 103 | t.pass(); 104 | }); 105 | -------------------------------------------------------------------------------- /packages/devkit/src/home/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import Debug from 'debug'; 13 | import * as path from 'path'; 14 | import * as fs from 'fs-extra'; 15 | import * as del from 'del'; 16 | import * as os from 'os'; 17 | import rimraf from 'rimraf'; 18 | import { PROCESS_ENV_HOME_PATH, PROCESS_ENV_HOME_FOLDE, DEFAULT_HOME_FOLDER } from '../cli-config/index'; 19 | 20 | const debug = Debug('core-home'); 21 | 22 | let userHomeFolder = ''; 23 | let userHomePath = ''; 24 | 25 | /** 26 | * 获取cli的home路径 27 | * TINY_HOME_FOLDER 作用:可以自定义tiny的核心目录,方便开发第三方cli工具进行定制 28 | * TINY_HOME 作用:方便单元测试时更改目录结构 29 | * @returns {string} 返回路径字符串 30 | */ 31 | export function getHomePath(): string { 32 | userHomeFolder = process.env[PROCESS_ENV_HOME_FOLDE] || DEFAULT_HOME_FOLDER; 33 | userHomePath = process.env[PROCESS_ENV_HOME_PATH] || os.homedir(); 34 | return path.resolve(userHomePath, userHomeFolder); 35 | } 36 | 37 | /** 38 | * 获取cli模块的安装路径 39 | * @returns {string} 返回路径字符串 40 | */ 41 | export function getModulesPath(): string { 42 | const cliPath = getHomePath(); 43 | const modulesPath = path.resolve(cliPath, 'node_modules'); 44 | debug('tiny module path = %s', modulesPath); 45 | return modulesPath; 46 | } 47 | 48 | /** 49 | * 初始化home目录,并将信息缓存至process.env中 50 | */ 51 | export function initHomeDir(): void { 52 | const cliPath = getHomePath(); 53 | if (!fs.existsSync(cliPath)) { 54 | fs.mkdirsSync(cliPath); 55 | } 56 | // 缓存home信息到env里面 57 | if (!process.env[PROCESS_ENV_HOME_FOLDE]) { 58 | process.env[PROCESS_ENV_HOME_FOLDE] = userHomeFolder; 59 | } 60 | if (!process.env[PROCESS_ENV_HOME_PATH]) { 61 | process.env[PROCESS_ENV_HOME_PATH] = userHomePath; 62 | } 63 | } 64 | 65 | export interface clearResult { 66 | success: boolean; 67 | removePath: string; 68 | } 69 | 70 | /** 71 | * 清理Home目录内容 72 | * 用户手工删除是没影响的,tiny会验证并初始化 73 | * 返回删除成功与否 74 | */ 75 | export function cleanHomeDir(): clearResult { 76 | const homePath = getHomePath(); 77 | const cliModulesPath = getModulesPath(); 78 | const result: clearResult = { 79 | success: true, 80 | removePath: cliModulesPath 81 | }; 82 | if (fs.existsSync(cliModulesPath)) { 83 | // 清除tiny.*.json的配置文件 84 | const paths = del.sync([`tiny.*.json`, `package.json`, `*.yaml`, 'package-lock.json'], { 85 | cwd: homePath 86 | }); 87 | debug('clear tiny.*.json = %o', paths); 88 | // windows下可能存在路径过长无法清除的情况,报错后提示手动删除 89 | try { 90 | rimraf.sync(cliModulesPath); 91 | } catch (e) { 92 | result.success = false; 93 | console.error(`${cliModulesPath} 删除失败,请手动删除该文件夹!`); 94 | } 95 | debug('remove tiny modules path = %s', cliModulesPath); 96 | } 97 | return result; 98 | } 99 | 100 | export const EntryModuleEnv = 'TINY_ENTRY_MODULE'; 101 | 102 | export default { 103 | getHomePath, 104 | getModulesPath, 105 | initHomeDir, 106 | cleanHomeDir, 107 | EntryModuleEnv 108 | }; 109 | -------------------------------------------------------------------------------- /packages/devkit/src/config/ast-analyze.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | 'use strict'; 13 | 14 | import * as esprima from 'esprima'; 15 | import esquery from 'esquery'; 16 | import * as escodegen from 'escodegen'; 17 | import toSource from 'tosource'; 18 | 19 | /** 20 | * 分析一下tiny.config.js,搜索key值,写入value,最终合并后输出源码 21 | * @param code tiny.config.js文件源码 22 | * @param key 需要插入或者修改的 key 23 | * @param value key对应的值 24 | * @returns {*} 25 | */ 26 | export default function (code, key, value) { 27 | let ast; 28 | // 支持 tasks.start 这种写法 29 | const keyArr = key.split('.'); 30 | const keySelect = keyArr.map((item) => `ObjectExpression > [key.name="${item}"]`); 31 | 32 | if (typeof value !== 'string') { 33 | value = toSource(value); 34 | } 35 | 36 | ast = esprima.parse(code, { range: true, tokens: true, comment: true }); 37 | ast = escodegen.attachComments(ast, ast.comments, ast.tokens); 38 | // 将需要插入的value进行ast转换 39 | const valueAST = esprima.parse(`var temp = ${value}`, { attachComment: true }); 40 | // 提取value的ast对象 41 | const keyMatches = esquery(valueAST, 'Program > VariableDeclaration > VariableDeclarator'); 42 | const pushAST = keyMatches[0].init; 43 | // 查找tiny.config中是否存在这个key 44 | const matches = esquery(ast, keySelect.join('>')); 45 | // tiny 最外层的对象 46 | const topMatches = esquery(ast, 'Program > ExpressionStatement > AssignmentExpression > ObjectExpression'); 47 | // 如果已经存在key的话,则替换值 48 | if (matches.length && matches[0].value) { 49 | matches[0].value = pushAST; 50 | } else if (keyArr.length === 1) { 51 | // 不存在key的情况 且是非数组的情况 52 | topMatches[0].properties.push({ 53 | type: 'Property', 54 | key: { 55 | type: 'Identifier', 56 | name: key 57 | }, 58 | value: pushAST 59 | }); 60 | } else { 61 | // 不存在key的情况 62 | const objMatches = esquery(topMatches[0], `ObjectExpression > [key.name="${keyArr[0]}"]`); 63 | // TODO 暂只处理 xxx.yyy 这种情况,若需要多级插入,比较麻烦,等有空了且存在这样的需求再考虑. 64 | // 存在xxx 不存在yyy 65 | if (objMatches.length) { 66 | objMatches[0].value.properties.push({ 67 | type: 'Property', 68 | key: { 69 | type: 'Identifier', 70 | name: keyArr[1] 71 | }, 72 | value: pushAST 73 | }); 74 | } else { 75 | // 不存在xxx 且不存在 yyy 76 | topMatches[0].properties.push({ 77 | type: 'Property', 78 | key: { 79 | type: 'Identifier', 80 | name: keyArr[0] 81 | }, 82 | value: { 83 | type: 'ObjectExpression', 84 | properties: [ 85 | { 86 | type: 'Property', 87 | key: { 88 | type: 'Identifier', 89 | name: keyArr[1] 90 | }, 91 | value: pushAST 92 | } 93 | ] 94 | } 95 | }); 96 | } 97 | } 98 | // 最后返回源码 99 | return escodegen.generate(ast, { comment: true }); 100 | } 101 | -------------------------------------------------------------------------------- /packages/devkit/src/cache/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 缓存模块 14 | */ 15 | 16 | import fs from 'fs-extra'; 17 | import * as path from 'path'; 18 | import home from '../home/index'; 19 | import logs from '../log/index'; 20 | import { FILE_CACHE } from '../cli-config/index'; 21 | 22 | const log = logs('core-cache'); 23 | 24 | /** 25 | * 获取缓存内容,如果不存在或已过期则返回 null 26 | * @param {string} key 缓存的键 27 | * @returns {mix} 28 | */ 29 | export function get(key: string): any { 30 | // 缓存中存在,则直接返回 31 | // 因为这个函数调用频率很高,缓存起来比较方便 32 | if (process.env[key]) { 33 | const cacheStr = decodeURIComponent(process.env[key] || ''); 34 | // 判断是否是字符串对象或数组 35 | if (cacheStr[0] === '{' || cacheStr[0] === '[') { 36 | return JSON.parse(decodeURIComponent(process.env[key] || '')); 37 | } 38 | return cacheStr; 39 | } 40 | const cacheFile = getCacheFile(); 41 | log.debug('tiny缓存文件的路径:', cacheFile); 42 | if (!key || !fs.existsSync(cacheFile)) { 43 | return null; 44 | } 45 | // 如果不是json文件,也不抛出异常 46 | let data = fs.readJsonSync(cacheFile, { throws: false }) || {}; 47 | if (typeof data !== 'object') { 48 | data = {}; 49 | } 50 | 51 | // 有效期判断 52 | if (data.__expires && data.__expires[key]) { 53 | if (data.__expires[key] < Date.now()) { 54 | return null; 55 | } 56 | } 57 | if (data[key]) { 58 | // 缓存经常获取,先存起来 59 | process.env[key] = encodeURIComponent(JSON.stringify(data[key])); 60 | return data[key]; 61 | } 62 | 63 | return null; 64 | } 65 | 66 | export interface CacheOption { 67 | expires?: number | null; 68 | } 69 | 70 | interface CacheFile { 71 | __expires: object; 72 | } 73 | 74 | /** 75 | * 设置缓存内容 76 | * @param key {string} 缓存的键 77 | * @param value {mix} 缓存的值 78 | * @param options {object} 79 | * @param options.expires {number} 有效时长,毫秒为单位, 如 1分钟为 360000 80 | * @returns {boolean} 81 | */ 82 | export function set(key: string, value: any, options?: CacheOption): void { 83 | const cacheFile = getCacheFile(); 84 | 85 | options = { 86 | expires: null, 87 | ...options 88 | }; 89 | 90 | let data: CacheFile = { __expires: {} }; 91 | if (fs.existsSync(cacheFile)) { 92 | data = fs.readJsonSync(cacheFile, { throws: false }) || {}; 93 | if (typeof data !== 'object') { 94 | data = { __expires: {} }; 95 | } 96 | } 97 | 98 | // 有效期处理 99 | data.__expires = data.__expires || {}; 100 | data.__expires[key] = options.expires ? Date.now() + options.expires : null; 101 | data[key] = value; 102 | fs.outputJsonSync(cacheFile, data, { spaces: 2 }); 103 | } 104 | 105 | /** 106 | * 获取缓存文件 107 | */ 108 | export function getCacheFile() { 109 | return path.resolve(home.getHomePath(), FILE_CACHE); 110 | } 111 | 112 | /** 113 | * 清除所有的缓存 114 | */ 115 | export function clear() { 116 | const cacheFile = getCacheFile(); 117 | fs.removeSync(cacheFile); 118 | } 119 | 120 | export default { 121 | get, 122 | set, 123 | getCacheFile, 124 | clear 125 | }; 126 | -------------------------------------------------------------------------------- /packages/devkit/src/module/install-one.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import cache from '../cache/index'; 13 | import home from '../home/index'; 14 | import Intl from '../intl/index'; 15 | import logs from '../log/index'; 16 | import npm from '../npm/index'; 17 | import message from './locale/index'; 18 | import utils from './utils'; 19 | import { DEFAULT_BIN, DEFAULT_SCOPE, getScope } from '../cli-config/index'; 20 | 21 | const log = logs('core-module'); 22 | 23 | /** 24 | * 25 | * @param name 模块名称,可能的格式{modulename}@{version} or {modulename} 26 | * @param options 27 | */ 28 | async function installOne(name: string, options?: any) { 29 | const prefix = utils.prefix(); 30 | const homeCwd = home.getHomePath(); 31 | const scope = getScope(); 32 | let version = 'latest'; 33 | const intl = new Intl(message); 34 | let pureName = ''; 35 | options = { 36 | type: 'install', 37 | ...options 38 | }; 39 | // 匹配套件名称,其中需要判断前缀是否是自定义的 40 | let match = name.match(new RegExp(`^(@${scope}/)?([A-Za-z0-9_-]*)-(toolkit|plugin)-`)); 41 | if (!match && scope != DEFAULT_SCOPE) { 42 | match = name.match(new RegExp(`^(@${DEFAULT_SCOPE}/)?([A-Za-z0-9_-]*)-(toolkit|plugin)-`)); 43 | } 44 | // 判断逻辑:前缀存在 且 前缀为自定义设置的 或者前缀是tiny 45 | if (!(match && match[2] && (match[2] === prefix || match[2] === DEFAULT_BIN))) { 46 | log.error(intl.get('importPkgError')); 47 | return; 48 | } 49 | 50 | // 判断是否带了 @版本号 51 | if (!new RegExp(`^(@${scope}/)?.+@.+$`).test(name)) { 52 | // 没带版本号 53 | pureName = name; 54 | // TODO option值哪里来 55 | if (options.lastPkg && options.lastPkg.version) { 56 | version = options.lastPkg.version; 57 | } 58 | name += `@${version}`; 59 | } else { 60 | const nameArr: string[] = name.split('@'); 61 | version = nameArr.pop() || 'latest'; 62 | pureName = nameArr.join('@'); 63 | } 64 | 65 | // 开始安装 66 | log.debug(`开始安装 ${name}`); 67 | utils.addModuleToDependencies(homeCwd, pureName, version); 68 | try { 69 | await npm.installDependencies({ 70 | cwd: homeCwd 71 | }); 72 | } catch (e) { 73 | utils.removeModuleToDependencies(homeCwd, pureName); 74 | log.error(intl.get('installError', { name: pureName })); 75 | log.error(e); 76 | process.exit(1); 77 | } 78 | 79 | // 设置缓存, 1天内不再检查 80 | cache.set(`${utils.UPDATE_CHECK_PRE}${pureName}`, true, { 81 | expires: utils.NO_TIP_PERIOD 82 | }); 83 | 84 | // 提示安装成功 85 | if (options.type === 'install') { 86 | log.success(intl.get('installSuccess', { name: pureName })); 87 | return; 88 | } 89 | 90 | log.success(intl.get('updateSuccess', { name: pureName })); 91 | // 打印更新日志 92 | if (!options.lastPkg) { 93 | options.lastPkg = await npm.latest(pureName); 94 | } 95 | if (!options.lastPkg) { 96 | return; 97 | } 98 | utils.updateLog(pureName, { 99 | localPkg: options.localPkg, 100 | lastPkg: options.lastPkg, 101 | level: 'success' 102 | }); 103 | } 104 | 105 | export default installOne; 106 | -------------------------------------------------------------------------------- /packages/devkit/src/intl/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * @desc: 多语言国际化文案处理类 14 | */ 15 | import fs from 'fs-extra'; 16 | import IntlMessageFormat from 'intl-messageformat'; 17 | import * as osLocale from 'os-locale'; 18 | import * as path from 'path'; 19 | import home from '../home/index'; 20 | import logs from '../log/index'; 21 | import { FILE_LOCALE, PROCESS_ENV_LOCALE } from '../cli-config/index'; 22 | import { intlMessage, CacheLocale, LocaleType } from './interfaces'; 23 | 24 | const log = logs('core-intl'); 25 | 26 | /** 27 | * 缓存的locale变量 28 | */ 29 | let cacheLocale: CacheLocale | null; 30 | 31 | export default class Intl { 32 | public message: intlMessage; 33 | public locale: LocaleType; 34 | constructor(message: intlMessage, locale?: LocaleType) { 35 | this.message = message; 36 | this.locale = locale || this.getLocale(); 37 | } 38 | 39 | /** 40 | * 初始化语言文件 41 | */ 42 | public initLocale(): void { 43 | // 如果有全局语言文件的话,则退出 44 | const localeGlobal = process.env[PROCESS_ENV_LOCALE]; 45 | if (localeGlobal) return; 46 | 47 | const localeFile = path.join(home.getHomePath(), FILE_LOCALE); 48 | // 初始化,获取本地电脑的语言环境 49 | if (!fs.existsSync(localeFile)) { 50 | const sysLocale: string = osLocale.sync() || 'zh'; 51 | const localLocale: LocaleType = LocaleType[sysLocale.substr(0, 2)] || LocaleType.ZH; 52 | this.setLocale(localLocale); 53 | } 54 | } 55 | /** 56 | * 获取语言信息 57 | * @returns 返回当前设置的多语言类型 58 | */ 59 | public getLocale(): LocaleType { 60 | // 如果有环境变量,则优先使用环境变量的值 61 | const localeGlobal = process.env[PROCESS_ENV_LOCALE]; 62 | if (localeGlobal && LocaleType[localeGlobal]) return LocaleType[localeGlobal]; 63 | 64 | // 由于该方法调用频繁,在这里使用一个cache对象做为缓存,避免频繁的IO操作 65 | let localeData: CacheLocale = { locale: LocaleType.ZH }; 66 | if (cacheLocale) { 67 | localeData = cacheLocale; 68 | } else { 69 | const localeFile = path.join(home.getHomePath(), FILE_LOCALE); 70 | if (fs.existsSync(localeFile)) { 71 | localeData = fs.readJsonSync(localeFile); 72 | cacheLocale = localeData; 73 | } 74 | } 75 | return localeData.locale; 76 | } 77 | 78 | /** 79 | * 设置语言信息 80 | * @param {locale} 多语言类型 81 | */ 82 | public setLocale(locale: LocaleType): void { 83 | const localeFile = path.join(home.getHomePath(), FILE_LOCALE); 84 | const localeData = { 85 | locale 86 | }; 87 | log.debug('set tiny locale data : %o', localeData); 88 | log.debug('set tiny to : %s', localeFile); 89 | cacheLocale = null; 90 | fs.outputJsonSync(localeFile, localeData); 91 | } 92 | 93 | /** 94 | * 获取所需的语言 95 | * @param key 96 | * @param values 语言中的变量信息 97 | */ 98 | public get(key: string, values?: object): string { 99 | const localeMessage = this.message[this.locale]; 100 | let msg = !localeMessage ? this.message[LocaleType.ZH][key] : localeMessage[key]; 101 | 102 | if (msg) { 103 | msg = new IntlMessageFormat(msg, this.locale); 104 | return msg.format(values); 105 | } 106 | log.warn(`intl key : ${key} not defined!`); 107 | log.debug('message = %o', this.message); 108 | return ''; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/devkit/src/log/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import debug from 'debug'; 13 | import * as path from 'path'; 14 | import { createLogger, format, transports } from 'winston'; 15 | import home from '../home/index'; 16 | import * as process from 'process'; 17 | const { combine, timestamp, colorize } = format; 18 | 19 | /** 20 | * 格式化额外信息 21 | * @param {*} meta 22 | * @returns 23 | */ 24 | const formatMeta = (meta) => { 25 | // You can format the splat yourself 26 | const splat = meta[Symbol.for('splat')]; 27 | let msg = ''; 28 | if (splat && splat.length) { 29 | try { 30 | msg = splat.length === 1 ? JSON.stringify(splat[0]) : JSON.stringify(splat); 31 | } catch (e) { 32 | console.error(e); 33 | } 34 | } 35 | return msg; 36 | }; 37 | 38 | const formatMessage = format.printf((info) => { 39 | const { level, message, label, timestamp, stack, ...metadata } = info; 40 | let output = message; 41 | if (message instanceof Error) { 42 | output = message.stack || message; 43 | } else if (typeof message === 'object') { 44 | output = JSON.stringify(message); 45 | } 46 | if (stack) { 47 | output += '\n' + stack; 48 | } 49 | return `${timestamp} [${label}]: ${output} ${formatMeta(metadata)}`; 50 | }); 51 | // 单例化logger对象,各文件间共享 52 | let _logger; 53 | function loggerSingleton() { 54 | if (_logger) { 55 | return _logger; 56 | } else { 57 | // 定义一个全局变量用于改变log存放位置,也方便测试使用。 58 | const homePath = process.env.TINY_LOG_PATH || path.join(home.getHomePath(), 'log'); 59 | _logger = createLogger({ 60 | level: 'verbose', 61 | format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), formatMessage), 62 | transports: [ 63 | // console输出加个颜色 64 | new transports.Console({ 65 | format: combine(colorize({ all: true, colors: { info: 'magenta', verbose: 'green' } })) 66 | }), 67 | // warn/error level的打印到文件 68 | new transports.File({ 69 | filename: path.join(homePath, `${new Date().toISOString().split('T')[0]}.log`), 70 | level: 'warn' 71 | }) 72 | ] 73 | }); 74 | return _logger; 75 | } 76 | } 77 | 78 | interface ILogger { 79 | info(...args: any[]): void; 80 | success(...args: any[]): void; 81 | warn(...args: any[]): void; 82 | error(...args: any[]): void; 83 | debug(...args: any[]): void; 84 | } 85 | 86 | /** 87 | * 基于winston输出日志 88 | * @param label: 标签,一般用于定位该日志是哪个模块输出的 89 | */ 90 | export default (label: string): ILogger => { 91 | return (function () { 92 | const methodLevels = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly']; 93 | const wLogger: any = {}; 94 | methodLevels.forEach((level) => { 95 | wLogger[level] = function (...args) { 96 | const message = args.length === 1 ? args[0] : args; 97 | // debug模式下继续使用debug包在命令行输出 98 | if (level === 'debug') { 99 | return debug(label).apply(null, args); 100 | } 101 | loggerSingleton().log({ 102 | level, 103 | label, 104 | message 105 | }); 106 | }; 107 | }); 108 | // 兼容原有success方法输出 109 | wLogger.success = wLogger.verbose; 110 | return wLogger; 111 | })(); 112 | }; 113 | -------------------------------------------------------------------------------- /packages/devkit/src/config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as fs from 'fs'; 13 | import * as path from 'path'; 14 | import Intl from '../intl/index'; 15 | import logs from '../log/index'; 16 | import message from './locale'; 17 | import astAnalyze from './ast-analyze'; 18 | import { PROCESS_ENV_CONFIG_FILE, DEFAULT_CONFIG_FILE, PROCESS_ENV_CONFIG_PATH } from '../cli-config/index'; 19 | 20 | const log = logs('core-config'); 21 | const CWD = process.cwd(); 22 | 23 | /** 24 | * 获取配置文件的名称 25 | * @return {string} 26 | */ 27 | export function getConfigName(): string { 28 | return process.env[PROCESS_ENV_CONFIG_FILE] || DEFAULT_CONFIG_FILE; 29 | } 30 | 31 | /** 32 | * 获取config.js的文件路径 33 | */ 34 | export function getConfigPath() { 35 | return process.env[PROCESS_ENV_CONFIG_PATH] || CWD; 36 | } 37 | 38 | /** 39 | * 当前目录下是否存在tiny.config.js文件 40 | * @param {string} dir 需要判断文件是否存在的目录,可选,默认取值:当前运行目录 41 | */ 42 | export function exist(dir?: string): boolean { 43 | const cwd = dir || getConfigPath(); 44 | const configPath = path.join(cwd, getConfigName()); 45 | return fs.existsSync(configPath); 46 | } 47 | 48 | /** 49 | * 获取整个pi.config.js文件的内容 50 | */ 51 | export function getAll(dir: string) { 52 | const cwd = dir || getConfigPath(); 53 | const configName = getConfigName(); 54 | // 先判断文件是否存在,存在的话才读取 55 | if (!exist(cwd)) { 56 | return null; 57 | } 58 | // 直接使用require的话,会有缓存, 需要先删除 require 的缓存 59 | const configPath = path.join(cwd, configName); 60 | delete require.cache[configPath]; 61 | try { 62 | const file = require(configPath); 63 | log.debug('get %s , file = %o', configName, file); 64 | return file; 65 | } catch (e) { 66 | const intl = new Intl(message); 67 | log.error(intl.get('readConfigError', { file: configName })); 68 | log.error(intl.get('moreDetail')); 69 | log.error(e && e.stack); 70 | return process.exit(1); 71 | } 72 | } 73 | 74 | /** 75 | * 根据key获取tiny.config.js的单个对象 76 | * @param key 配置的键名 77 | * @param dir 配置文件的路径 78 | * @return object 79 | */ 80 | export function get(key: string, dir?: string) { 81 | const file = this.getAll(dir); 82 | log.debug('key = %s ,all config = %o', key, file); 83 | return file ? file[key] : null; 84 | } 85 | 86 | /** 87 | * 获取套件的名字 88 | */ 89 | export function getToolkitName(dir?: string): string | null { 90 | const config = this.getAll(dir || ''); 91 | if (config && config.toolkit) { 92 | return config.toolkit; 93 | } 94 | return null; 95 | } 96 | 97 | /** 98 | * 设置tiny.config.js的属性值,写入相关内容 99 | * @param key tiny.config.js中的key 100 | * @param value key对应的value 101 | * @param dir 配置文件路径 102 | */ 103 | export function set(key: string, value: any, dir?: string) { 104 | const cwd = dir || getConfigPath(); 105 | const configName = getConfigName(); 106 | const filePath = path.join(cwd, configName); 107 | // 读取文件 108 | const code = fs.readFileSync(filePath, 'utf8'); 109 | const source = astAnalyze(code, key, value); 110 | log.debug('set %s file source string = %o', configName, source); 111 | fs.writeFileSync(filePath, source); 112 | } 113 | 114 | export default { 115 | exist, 116 | set, 117 | get, 118 | getAll, 119 | getConfigName, 120 | getConfigPath, 121 | getToolkitName 122 | }; 123 | -------------------------------------------------------------------------------- /packages/devkit/src/upgrade/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import cache from '../cache/index'; 13 | import Intl from '../intl/index'; 14 | import logs from '../log/index'; 15 | import npm from '../npm/index'; 16 | import cliConfig from '../cli-config/index'; 17 | import chalk from 'chalk'; 18 | import emoji from 'node-emoji'; 19 | import semver from 'semver'; 20 | import message from './locale/index'; 21 | 22 | const log = logs('core-upgrade'); 23 | 24 | const TIP_CACHE_KEY = '__versionTip'; 25 | 26 | /** 27 | * 升级提示,若发布新版本,会定时提醒是否需要更新 28 | * @param data object { "name" : 包名, "version" : 当前版本} 29 | */ 30 | async function updateTip(data: any): Promise { 31 | let needFocusUpdate = false; 32 | 33 | if (cache.get(TIP_CACHE_KEY)) { 34 | return needFocusUpdate; 35 | } 36 | 37 | const latest = await npm.latest(data.name); 38 | 39 | // 缓存设置为24小时,过24小时才重新提示升级 cli 40 | cache.set(TIP_CACHE_KEY, true, { 41 | expires: 86400000 42 | }); 43 | 44 | // latest 没有值,可能没有网络 45 | if (!latest) { 46 | return needFocusUpdate; 47 | } 48 | 49 | log.debug('%s current-version: %s, latest-version: %s', data.name, data.version, latest.version); 50 | 51 | // 当前版本是最新 52 | if (!semver.lt(data.version, latest.version)) { 53 | return needFocusUpdate; 54 | } 55 | 56 | // 当更新了Y位的话,启动自动升级机制 57 | needFocusUpdate = semver.diff(data.version, latest.version) === 'minor'; 58 | log.debug('needFocusUpdate =>', needFocusUpdate); 59 | 60 | // 当当前版本的y位有变化,强制更新cli 61 | const installer = cliConfig.getBinName() || 'npm'; 62 | const intl = new Intl(message); 63 | console.log('\n'); 64 | log.warn( 65 | `******************** ${emoji.get('warning')} ${emoji.get('warning')} ${intl.get('updateTips')} ${emoji.get( 66 | 'warning' 67 | )} ${emoji.get('warning')} **********************` 68 | ); 69 | log.warn( 70 | intl.get('recommendedVersion', { 71 | latest: chalk.green.bold(latest.version), 72 | localVersion: data.version 73 | }) 74 | ); 75 | // 区分一下自动升级与手动升级的文案 76 | if (needFocusUpdate) { 77 | log.warn( 78 | intl.get('updatingCommand', { 79 | icon: emoji.get('point_right'), 80 | command: chalk.bgRed.bold(` ${installer} i ${data.name} -g `) 81 | }) 82 | ); 83 | } else { 84 | log.warn( 85 | intl.get('updateCommand', { 86 | icon: emoji.get('point_right'), 87 | command: chalk.bgRed.bold(` ${installer} i ${data.name} -g`) 88 | }) 89 | ); 90 | } 91 | 92 | // linux & mac 下才提示 93 | if (process!.platform.indexOf('win') !== 0) { 94 | log.warn(`${intl.get('ifUpdateError')} ${chalk.red.bold(`sudo ${installer} install -g ${data.name}`)}`); 95 | } 96 | log.warn( 97 | `******************************${emoji.get('point_up_2')} ${emoji.get('point_up_2')} ******************************` 98 | ); 99 | console.log('\n'); 100 | 101 | if (needFocusUpdate) { 102 | // 执行 npm install -g @opentiny/cli 更新本地版本 103 | const depen = [`${data.name}`]; 104 | await npm.install(depen, { 105 | // global安装 106 | g: true 107 | }); 108 | 109 | log.success('当前cli工具已更新至最新版本'); 110 | 111 | // cli工具更新完成,终止进程 112 | process.exit(1); 113 | } 114 | 115 | return needFocusUpdate; 116 | } 117 | 118 | export default updateTip; 119 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitConvention": "angular", 8 | "contributors": [ 9 | { 10 | "login": "fengyon", 11 | "name": "fengyon", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/84690330?v=4", 13 | "profile": "https://github.com/fengyon", 14 | "contributions": [ 15 | "doc" 16 | ] 17 | }, 18 | { 19 | "login": "howling-wind", 20 | "name": "qihe", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/119645349?v=4", 22 | "profile": "https://github.com/howling-wind", 23 | "contributions": [ 24 | "doc" 25 | ] 26 | }, 27 | { 28 | "login": "Muyu-art", 29 | "name": "CatWithFish", 30 | "avatar_url": "https://avatars.githubusercontent.com/u/72800755?v=4", 31 | "profile": "https://github.com/Muyu-art", 32 | "contributions": [ 33 | "code" 34 | ] 35 | }, 36 | { 37 | "login": "GaoNeng-wWw", 38 | "name": "GaoNeng", 39 | "avatar_url": "https://avatars.githubusercontent.com/u/31283122?v=4", 40 | "profile": "https://github.com/GaoNeng-wWw", 41 | "contributions": [ 42 | "code" 43 | ] 44 | }, 45 | { 46 | "login": "hello-yezi", 47 | "name": "yezileyouyou", 48 | "avatar_url": "https://avatars.githubusercontent.com/u/75346211?v=4", 49 | "profile": "https://github.com/hello-yezi", 50 | "contributions": [ 51 | "doc" 52 | ] 53 | }, 54 | { 55 | "login": "aliceDxr", 56 | "name": "cecilia", 57 | "avatar_url": "https://avatars.githubusercontent.com/u/33685369?v=4", 58 | "profile": "https://github.com/aliceDxr", 59 | "contributions": [ 60 | "code" 61 | ] 62 | }, 63 | { 64 | "login": "h-ivy", 65 | "name": "huanghh", 66 | "avatar_url": "https://avatars.githubusercontent.com/u/119093441?v=4", 67 | "profile": "https://github.com/h-ivy", 68 | "contributions": [ 69 | "code" 70 | ] 71 | }, 72 | { 73 | "login": "wjiangwang", 74 | "name": "wjiangwang", 75 | "avatar_url": "https://avatars.githubusercontent.com/u/39005591?v=4", 76 | "profile": "https://github.com/wjiangwang", 77 | "contributions": [ 78 | "code" 79 | ] 80 | }, 81 | { 82 | "login": "kagol", 83 | "name": "Kagol", 84 | "avatar_url": "https://avatars.githubusercontent.com/u/9566362?v=4", 85 | "profile": "https://kagol.github.io/blogs", 86 | "contributions": [ 87 | "infra" 88 | ] 89 | }, 90 | { 91 | "login": "jishuai-cool", 92 | "name": "jishuai-cool", 93 | "avatar_url": "https://avatars.githubusercontent.com/u/68739663?v=4", 94 | "profile": "https://github.com/jishuai-cool", 95 | "contributions": [ 96 | "code" 97 | ] 98 | }, 99 | { 100 | "login": "hexqi", 101 | "name": "Hexqi", 102 | "avatar_url": "https://avatars.githubusercontent.com/u/18585869?v=4", 103 | "profile": "https://github.com/hexqi", 104 | "contributions": [ 105 | "code" 106 | ] 107 | }, 108 | { 109 | "login": "discreted66", 110 | "name": "liukun", 111 | "avatar_url": "https://avatars.githubusercontent.com/u/190872652?v=4", 112 | "profile": "https://github.com/discreted66", 113 | "contributions": [ 114 | "code" 115 | ] 116 | } 117 | ], 118 | "contributorsPerLine": 7, 119 | "skipCi": true, 120 | "repoType": "github", 121 | "repoHost": "https://github.com", 122 | "projectName": "tiny-cli", 123 | "projectOwner": "opentiny", 124 | "commitType": "docs" 125 | } 126 | -------------------------------------------------------------------------------- /packages/devkit/src/fs/copy-tpl.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 13 | 14 | import * as path from 'path'; 15 | import fs from 'fs-extra'; 16 | import test from 'ava'; 17 | import { expect } from 'chai'; 18 | import copyTpl from './copy-tpl'; 19 | 20 | const fixtures = path.resolve(__dirname, 'fixtures'); 21 | const dirSrc = path.resolve(fixtures, 'dir-src'); 22 | const dirDist = path.resolve(fixtures, 'dir-dist'); 23 | 24 | test.before(() => { 25 | if (!fs.existsSync(fixtures)) { 26 | fs.copySync(path.join(__dirname, '../../src/fs/fixtures'), fixtures); 27 | } 28 | 29 | if (fs.existsSync(dirDist)) { 30 | fs.removeSync(dirDist); 31 | } 32 | 33 | // 复制文件 34 | copyTpl( 35 | dirSrc, 36 | dirDist, 37 | { name: 'test', rename: 'newname-dir' }, 38 | { 39 | ignore: ['zzz.js'], 40 | ignoreEjs: [path.join(dirSrc, 'bbb.js')], 41 | rename: function (filename) { 42 | if (filename === 'xxx.js') { 43 | return 'yyy.js'; 44 | } 45 | return filename; 46 | }, 47 | notTextFile: ['.dd'] 48 | } 49 | ); 50 | }); 51 | 52 | test.after(() => { 53 | if (fs.existsSync(dirDist)) { 54 | fs.removeSync(dirDist); 55 | } 56 | }); 57 | 58 | test('# 创建目标目录', (t) => { 59 | expect(fs.existsSync(dirDist)).to.be.equals(true); 60 | t.pass(); 61 | }); 62 | 63 | test('# 创建目标目录下需要复制过来的文件', (t) => { 64 | const abc = path.resolve(dirDist, 'abc.js'); 65 | expect(fs.existsSync(abc)).to.be.equals(true); 66 | t.pass(); 67 | }); 68 | 69 | test('# 正常进行变量替换', (t) => { 70 | const abc = path.resolve(dirDist, 'abc.js'); 71 | const abcDist = path.resolve(dirSrc, '../dir-dist-abc.js'); 72 | expect(fs.existsSync(abc)).to.be.equals(fs.existsSync(abcDist)); 73 | t.pass(); 74 | }); 75 | 76 | test('# 不在目标目录下面创建需要忽略的文件', (t) => { 77 | expect(fs.existsSync(path.resolve(dirDist, 'zzz.js'))).to.be.equals(false); 78 | t.pass(); 79 | }); 80 | 81 | test('# 对文件名进行替换', (t) => { 82 | const yyy = path.resolve(dirDist, 'yyy.js'); 83 | const xxx = path.resolve(dirSrc, 'xxx.js'); 84 | expect(fs.existsSync(yyy)).to.be.equals(true); 85 | expect(fs.readFileSync(yyy)).to.be.deep.equals(fs.readFileSync(xxx)); 86 | t.pass(); 87 | }); 88 | 89 | test('# 忽略ejs文件', (t) => { 90 | const bbb = path.resolve(dirDist, 'bbb.js'); 91 | expect(fs.existsSync(bbb)).to.be.equals(true); 92 | const ejsTpl = fs.readFileSync(bbb).indexOf('<{%=name%}>') !== -1; 93 | expect(ejsTpl).to.be.equals(true); 94 | t.pass(); 95 | }); 96 | 97 | test('# copy非文本文件', (t) => { 98 | const gz = path.resolve(dirDist, 'a.tar.gz'); 99 | expect(fs.existsSync(gz)).to.be.equals(true); 100 | t.pass(); 101 | }); 102 | 103 | test.only('# copy非文本文件,参数中配置的非文本类型', (t) => { 104 | const gz = path.resolve(dirDist, 'a.dd'); 105 | expect(fs.existsSync(gz)).to.be.equals(true); 106 | t.pass(); 107 | }); 108 | 109 | test('# copy空目录过去', (t) => { 110 | const emptyDir = path.resolve(dirDist, 'empty-folder'); 111 | expect(fs.lstatSync(emptyDir).isDirectory()).to.be.equals(true); 112 | t.pass(); 113 | }); 114 | 115 | test('# 重命名文件夹', (t) => { 116 | const emptyDir = path.resolve(dirDist, 'newname-dir'); 117 | expect(fs.lstatSync(emptyDir).isDirectory()).to.be.equals(true); 118 | t.pass(); 119 | }); 120 | -------------------------------------------------------------------------------- /packages/devkit/src/error/locale/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | /** 13 | * 语言文件 14 | */ 15 | 16 | export default { 17 | zh: { 18 | // handle-default.js 19 | intranetTips: '运行报错, 请在issue中反馈问题:https://github.com/opentiny/tiny-cli/issues/new', 20 | winPidTips: ` # 看下是哪个PID占用的端口 21 | $ lsof -i :{port} 22 | # 杀进程 23 | $ kill -9 【pid】`, 24 | macPidTips: ` # 看下是哪个PID占用的端口 25 | $ netstat -anop tcp | find /i ":{port}" | find "LISTENING" 26 | # 杀进程 27 | $ taskkill /F /pid 【pid】`, 28 | helpTips: `运行失败,检测到当前端口号 {port} 已被其他程序占用 29 | 修复建议: 30 | 1. 切换到其他端口再试试 31 | 2. 请按下面的方法关闭占用端口的程序 32 | 33 | {solution} 34 | `, 35 | commandNotFound: '运行插件或套件时出现了错误, 未找到 {module} 命令,请看下是否有安装!', 36 | fixLocalTips: '修复建议: 在控制台执行 {installer} install {runModule} 试试!', 37 | fixGlobalTips: '修复建议: 在控制台执行 {installer} install -g {module} 试试!', 38 | moduleNotFound: '运行插件或套件时出现了错误, 未找到 {module} 模块! 正在尝试自动修复...', 39 | installSuccess: '成功安装当前项目中的依赖,请重新运行命令!', 40 | installDone: '已将 {module} 模块安装至 {moduleCwd} ,自动修复成功,请重新运行命令!', 41 | installDoneTips: '若重新运行仍然出现问题,请在命令前面加上:DEBUG=core-error 来获取详细错误堆栈', 42 | installError: 'sorry 自动修复失败, 请手动修复', 43 | notFound: '运行时出现了错误, 未找到 {file} 文件,请确认 {file} 是否存在!', 44 | detailError: '详细错误堆栈信息如下:', 45 | npmNotFound: '安装 {module} 出错,请确认网络是否正常及包名是否输入正确' 46 | }, 47 | en: { 48 | // handle-default.js 49 | intranetTips: 50 | 'Runtime Error, please report the issue: https://github.com/opentiny/tiny-cli/issues/new', 51 | winPidTips: ` # Look up for the PID which occupies the port 52 | $ lsof -i :{port} 53 | # Kill process 54 | $ kill -9 【pid】`, 55 | macPidTips: ` # Look up for the PID which occupies the port 56 | $ netstat -anop tcp | find /i ":{port}" | find "LISTENING" 57 | # Kill process 58 | $ taskkill /F /pid 【pid】`, 59 | helpTips: `The operation failed and the current port number {port} was detected to be occupied by other programs 60 | Fix suggestions: 61 | 1. Try switching to another port 62 | 2. Please follow the procedure below to close the program that occupies the port 63 | 64 | {solution} 65 | `, 66 | commandNotFound: 67 | 'The {module} command was not found, there was an error when running the plugin or toolkit. Please see if there is an installation!', 68 | fixLocalTips: 'Fix Suggestions: Try {installer} install {runModule} in terminal!', 69 | fixGlobalTips: 'Fix Suggestions: Try {installer} install -g {module} in terminal!', 70 | moduleNotFound: 71 | 'the {module} module was not found, there was an error when running the plugin or toolkit! Trying to fix it automatically ...', 72 | installSuccess: 'Re-run the command to successfully install dependencies in the current project!', 73 | installDone: 74 | 'The {module} module has been installed to {moduleCwd} and the automatic repair is successful. Please re-run the command!', 75 | installDoneTips: 76 | 'If there is still a problem while re-run, please add: 【 DEBUG=core-error 】 to the beginning of your command to get the detailed error stack', 77 | installError: 'sorry, the automatic repair failed, please repair manually', 78 | notFound: 'The {file} file was not found, there was an error in the run. Please make sure {file} exists!', 79 | detailError: 'Detailed error stack information is as follows:', 80 | npmNotFound: 'Install {module} error, please check whether the network is normal and the package name is correct' 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /packages/devkit/src/report/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as os from 'os'; 13 | import * as cp from 'child_process'; 14 | import home from '../home/index'; 15 | import user from '../user/index'; 16 | import * as path from 'path'; 17 | import git from '../git/index'; 18 | import { getBinName, PROCESS_ENV_CONFIG_FILE, PROCESS_ENV_CLI_VERSION } from '../cli-config/index'; 19 | import fs from '../fs/index'; 20 | import cache from '../cache/index'; 21 | import yargs from 'yargs'; 22 | 23 | const argv = yargs.help(false).argv; 24 | 25 | const execSync = cp.execSync; 26 | 27 | /** 28 | * 环境变量获取 29 | */ 30 | export const cacheEnvGetter = { 31 | nodeVersion() { 32 | return execSync('node -v') 33 | .toString() 34 | .replace(/[\nv]|\r/g, ''); 35 | }, 36 | user() { 37 | return user.get(); 38 | }, 39 | cliName() { 40 | return getBinName(); 41 | }, 42 | npmVersion() { 43 | try { 44 | return execSync('npm -v').toString().replace('\n', ''); 45 | } catch (e) { 46 | return null; 47 | } 48 | }, 49 | system() { 50 | return `${os.platform()} ${os.release()}`; 51 | } 52 | }; 53 | 54 | /** 55 | * 获取项目的环境信息 56 | * @param force 为true时 则获取实时信息,否则读取缓存 57 | * 对 npm, node 版本等重新获取,一般在报错的时候才传入 true 58 | * @returns {*} 59 | */ 60 | export function getProjectEnv(force?: boolean): any { 61 | let cacheEnv = cache.get('envCache'); 62 | if (!cacheEnv || force) { 63 | cacheEnv = {}; 64 | const cacheEnvKeys = Object.keys(cacheEnvGetter); 65 | cacheEnvKeys.forEach((item) => { 66 | cacheEnv[item] = cacheEnvGetter[item](); 67 | }); 68 | // 缓存12小时 69 | cache.set('envCache', cacheEnv, { expires: 60 * 60 * 24 }); 70 | } 71 | // 版本信息直接获取就行,不用缓存,因为可能更新会比较快 72 | cacheEnv['cliVersion'] = process.env[PROCESS_ENV_CLI_VERSION]; 73 | return cacheEnv; 74 | } 75 | 76 | /** 77 | * 获取项目相关环境 78 | * @param cwd 项目路径 79 | * @returns {object} 返回项目相关的信息 80 | branch 当前项目分支 81 | pkg 当前package.json 内容 82 | configFile 当前 tiny.config.js 内容 83 | repository 项目仓库url 84 | */ 85 | export function getProjectInfo(cwd: string): any { 86 | const branch = git.branch(cwd); 87 | const configPath = path.join(cwd, PROCESS_ENV_CONFIG_FILE); 88 | const pkg = fs.readPackage(cwd); 89 | const repository = git.repository(cwd); 90 | let configFile: any; 91 | // 判断tiny.config.js是否存在 92 | if (fs.existsSync(configPath)) { 93 | delete require.cache[configPath]; 94 | try { 95 | configFile = require(configPath); 96 | } catch (e) { 97 | configFile = null; 98 | } 99 | } 100 | 101 | return { 102 | branch, 103 | pkg, 104 | configFile, 105 | repository 106 | }; 107 | } 108 | 109 | /** 110 | * 获取当前执行的命令,移除用户路径 111 | */ 112 | export function getCommand(): string { 113 | const argvClone = { ...argv }; 114 | const cmd = [getBinName()].concat(argv._); 115 | delete argvClone._; 116 | delete argvClone.$0; 117 | 118 | Object.keys(argvClone).forEach((item) => { 119 | cmd.push(`--${item}`); 120 | if (argvClone[item] !== true) { 121 | cmd.push(argvClone[item]); 122 | } 123 | }); 124 | return cmd.join(' '); 125 | } 126 | 127 | /** 128 | * 获取模块的类型和版本 129 | * @param mod 模块名称 130 | */ 131 | export function getModuleVersion(mod: string) { 132 | const modPkgPath = path.join(home.getModulesPath(), mod, 'package.json'); 133 | const pkg = fs.existsSync(modPkgPath) ? fs.readJsonSync(modPkgPath, { throws: false }) : { version: null }; 134 | return pkg.version; 135 | } 136 | -------------------------------------------------------------------------------- /packages/devkit/src/cli-config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | // 忽略证书和warning相关信息的输出 13 | process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; 14 | process.env['NODE_NO_WARNINGS'] = '1'; 15 | /** 16 | * cli 多语言文案文件 17 | */ 18 | export const FILE_LOCALE = 'tiny.locale.json'; 19 | 20 | /** 21 | * cli 多语言文案文件 22 | */ 23 | export const FILE_CACHE = 'tiny.cache.json'; 24 | 25 | /** 26 | * cli 环境设置 27 | */ 28 | export const FILE_ENV = 'tiny.env.json'; 29 | 30 | /** 31 | * cli所需的项目配置文件 32 | */ 33 | export const DEFAULT_CONFIG_FILE = 'tiny.config.js'; 34 | 35 | /** 36 | * cli所需的项目配置文件 37 | */ 38 | export const FILE_USER = 'tiny.user.json'; 39 | 40 | /** 41 | * cli默认的根目录文件夹 42 | */ 43 | export const DEFAULT_HOME_FOLDER = '.tiny'; 44 | 45 | /** 46 | * cli运行命令名称 47 | */ 48 | export const DEFAULT_BIN = 'tiny'; 49 | 50 | /** 51 | * 项目scope 52 | */ 53 | export const DEFAULT_SCOPE = 'opentiny'; 54 | 55 | /** 56 | * process.env 中的locale设置 57 | */ 58 | export const PROCESS_ENV_LOCALE = 'TINY_LOCALE'; 59 | 60 | /** 61 | * process.env 中 cli的根目录所在的路径 62 | */ 63 | export const PROCESS_ENV_HOME_PATH = 'TINY_HOME_PATH'; 64 | 65 | /** 66 | * process.env 中 cli的根目录文件夹名称 67 | */ 68 | export const PROCESS_ENV_HOME_FOLDE = 'TINY_HOME_FOLDER'; 69 | 70 | /** 71 | * process.env 中 记录的cli配置文件名称key 72 | */ 73 | export const PROCESS_ENV_CONFIG_FILE = 'TINY_CONFIG_FILE'; 74 | 75 | /** 76 | * process.env 中 记录的cli配置文件路径key 77 | */ 78 | export const PROCESS_ENV_CONFIG_PATH = 'TINY_CONFIG_PATH'; 79 | 80 | /** 81 | * process.env 中记录cli当前环境的key 82 | */ 83 | export const PROCESS_ENV_CLI_ENV = 'TINY_ENV'; 84 | 85 | /** 86 | * process.env 中 记录的cli命令名称key 87 | */ 88 | export const PROCESS_ENV_BIN = 'TINY_BIN'; 89 | 90 | /** 91 | * process.env 中 记录项目scope的key 92 | */ 93 | export const PROCESS_ENV_SCOPE = 'TINY_SCOPE'; 94 | 95 | /** 96 | * process.env 中 记录的cli version key 97 | */ 98 | export const PROCESS_ENV_CLI_VERSION = 'TINY_VERSION'; 99 | 100 | /** 101 | * process.env 中 记录的cli name key 102 | */ 103 | export const PROCESS_ENV_CLI_PACKAGE = 'TINY_PACKAGE'; 104 | 105 | /** 106 | * process.env 中 记录用户在控制台输入的真实命令 107 | */ 108 | export const PROCESS_ENV_MODULE_ENTRY = 'TINY_MODULE_ENTRY'; 109 | 110 | /** 111 | * process.env 中记录当前cli的运行环境,可选的值有Node/FUXI 112 | */ 113 | export const PROCESS_ENV_RUN = 'TINY_RUN_ENV'; 114 | 115 | /** 116 | * CLI当前的运行环境 117 | */ 118 | export enum ENV_RUN { 119 | NONE = 'none', 120 | FUXI = 'fuxi' // TODO 121 | } 122 | 123 | /** 124 | * 获取运行时的TINY命令 125 | */ 126 | export function getBinName(): string { 127 | return process.env[PROCESS_ENV_BIN] || DEFAULT_BIN; 128 | } 129 | 130 | /** 131 | * 获取项目scope 132 | */ 133 | export function getScope(): string { 134 | return process.env[PROCESS_ENV_SCOPE] || DEFAULT_SCOPE; 135 | } 136 | 137 | /** 138 | * 139 | * @param name 140 | */ 141 | export function setModuleEntry(name: string): void { 142 | process.env[PROCESS_ENV_MODULE_ENTRY] = name; 143 | } 144 | 145 | export function getModuleEntry(): string { 146 | return process.env[PROCESS_ENV_MODULE_ENTRY] || ''; 147 | } 148 | 149 | export default { 150 | FILE_CACHE, 151 | FILE_ENV, 152 | FILE_LOCALE, 153 | FILE_USER, 154 | PROCESS_ENV_BIN, 155 | PROCESS_ENV_SCOPE, 156 | PROCESS_ENV_CLI_ENV, 157 | PROCESS_ENV_CONFIG_FILE, 158 | PROCESS_ENV_CONFIG_PATH, 159 | PROCESS_ENV_HOME_FOLDE, 160 | PROCESS_ENV_HOME_PATH, 161 | PROCESS_ENV_LOCALE, 162 | PROCESS_ENV_CLI_VERSION, 163 | PROCESS_ENV_CLI_PACKAGE, 164 | PROCESS_ENV_RUN, 165 | ENV_RUN, 166 | getScope, 167 | getBinName, 168 | getBinName1: getBinName, 169 | getModuleEntry, 170 | setModuleEntry, 171 | DEFAULT_BIN, 172 | DEFAULT_SCOPE, 173 | DEFAULT_CONFIG_FILE, 174 | DEFAULT_HOME_FOLDER 175 | }; 176 | -------------------------------------------------------------------------------- /packages/devkit/src/cache/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import * as path from 'path'; 13 | import fs from 'fs-extra'; 14 | import test, { ExecutionContext } from 'ava'; 15 | import { expect } from 'chai'; 16 | import { FILE_CACHE, PROCESS_ENV_HOME_PATH, PROCESS_ENV_HOME_FOLDE } from '../cli-config/index'; 17 | import * as cache from './index'; 18 | 19 | /** 20 | * 创建一个mock cache文件 21 | * @param cacheFile 22 | */ 23 | function createMockFile(cacheFile: string) { 24 | const testObj = {}; 25 | const testKey = 'test'; 26 | const testValue = 123; 27 | testObj[testKey] = testValue; 28 | fs.outputJsonSync(cacheFile, testObj); 29 | } 30 | 31 | test.before((t: ExecutionContext<{ home: string; cacheFile: string }>) => { 32 | const homePath = (process.env[PROCESS_ENV_HOME_PATH] = path.resolve(__dirname)); 33 | const homeFolder = (process.env[PROCESS_ENV_HOME_FOLDE] = 'fixtures'); 34 | t.context.home = path.join(homePath, homeFolder); 35 | t.context.cacheFile = path.join(homePath, homeFolder, FILE_CACHE); 36 | // 不存在则创建 37 | if (!fs.existsSync(t.context.home)) { 38 | fs.mkdirSync(path.join(t.context.home)); 39 | } 40 | }); 41 | 42 | test.after((t: ExecutionContext<{ home: string; cacheFile: string }>) => { 43 | if (fs.existsSync(t.context.cacheFile)) { 44 | fs.removeSync(t.context.cacheFile); 45 | fs.removeSync(t.context.home); 46 | } 47 | }); 48 | 49 | test('#cache.json 不存在的情况下 get 获取缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 50 | if (fs.existsSync(t.context.cacheFile)) { 51 | fs.unlinkSync(t.context.cacheFile); 52 | } 53 | expect(cache.get('testKey1')).to.be.equal(null); 54 | t.pass(); 55 | }); 56 | 57 | test('#cache.json 不存在的情况下 set 设置缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 58 | const key = 'testKey2'; 59 | const value = Math.random(); 60 | cache.set(key, value); 61 | const data = fs.readJsonSync(t.context.cacheFile); 62 | expect(data[key]).to.be.equals(value); 63 | t.pass(); 64 | }); 65 | 66 | test('# cache.json 存在的情况下 get 获取缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 67 | createMockFile(t.context.cacheFile); 68 | expect(cache.get('test')).to.be.equal(123); 69 | expect(cache.get('notExistKey')).to.be.equals(null); 70 | t.pass(); 71 | }); 72 | 73 | test('# cache.json 存在的情况下 set 设置缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 74 | const key = 'testKey2'; 75 | const value = Math.random(); 76 | cache.set(key, value); 77 | 78 | const data = fs.readJsonSync(t.context.cacheFile); 79 | expect(data[key]).to.be.equals(value); 80 | t.pass(); 81 | }); 82 | 83 | test('# cache.json 文件异常的情况 get 获取缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 84 | fs.outputFileSync(t.context.cacheFile, '123'); 85 | expect(cache.get('testKey')).to.be.equal(null); 86 | t.pass(); 87 | }); 88 | 89 | test('# cache.json 文件异常的情况 set 设置缓存', (t: ExecutionContext<{ cacheFile: string }>) => { 90 | const key = 'testKey2'; 91 | const value = Math.random(); 92 | cache.set(key, value); 93 | const data = fs.readJsonSync(t.context.cacheFile); 94 | expect(data[key]).to.be.equals(value); 95 | t.pass(); 96 | }); 97 | 98 | test('# 缓存有效期检测 有效期内正常获取', (t) => { 99 | const key = 'testKey2'; 100 | const value = Math.random(); 101 | 102 | cache.set(key, value, { 103 | expires: 100 104 | }); 105 | 106 | expect(cache.get('testKey2')).to.be.equal(value); 107 | 108 | t.pass(); 109 | }); 110 | 111 | test('# 缓存有效期检测 过了有效期后获取到的值为null', (t) => { 112 | const key = 'testKey3'; 113 | const value = Math.random(); 114 | 115 | cache.set(key, value, { 116 | expires: 20 117 | }); 118 | 119 | return new Promise((resolve) => { 120 | setTimeout(() => { 121 | expect(cache.get('testKey3')).to.be.equal(null); 122 | resolve(); 123 | t.pass(); 124 | }, 21); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /packages/commands/src/init.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import { config, Intl, logs, modules, task } from '@opentiny/cli-devkit'; 13 | import chalk from 'chalk'; 14 | import fs from 'fs-extra'; 15 | import inquirer, { Answer } from 'inquirer'; 16 | import yargs from 'yargs'; 17 | import { getPadding } from './utils'; 18 | import message from './locale/index'; 19 | 20 | const clientOptions = { ...yargs.help(false).argv }; 21 | 22 | const log = logs('core-commands'); 23 | const cwd = process.cwd(); 24 | 25 | async function runInit(name: string) { 26 | const module = await modules.getReallyName(name); 27 | const intl = new Intl(message); 28 | if (module.exist) { 29 | const moduleInfo = await modules.get(module.reallyName); 30 | // es module default export xx.default 31 | // moduleInfo = modules.getEsModule(moduleInfo); 32 | // 存在init方法的话,则调用 33 | if (moduleInfo.init) { 34 | const init = modules.getEsModule(moduleInfo.init); 35 | // 去掉 clientOptions 里面多余的字段 36 | delete clientOptions._; 37 | delete clientOptions.$0; 38 | // 执行插件的init方法 39 | await task.runFunction({ 40 | method: init, 41 | args: [clientOptions], 42 | }); 43 | } else { 44 | log.error(intl.get('notMethod')); 45 | } 46 | } else { 47 | const msg = intl.get('toolkitNotFound', { toolkit: module.reallyName }); 48 | log.error(msg); 49 | } 50 | } 51 | 52 | async function getName() { 53 | const choices: any[] = []; 54 | const onlineList = await modules.onlineList({ type: 'toolkit' }); 55 | const localList = modules.localList({ type: 'toolkit' }); 56 | const intl = new Intl(message); 57 | const onlineMap: any = {}; 58 | const addChoice = (item: any) => { 59 | const n = item.name.split('-toolkit-')[1]; 60 | const padding = getPadding(n, 15); 61 | const tmpString = [' ', chalk.green(n), chalk.gray(padding), item.chName ? item.chName : '暂无描述'].join(''); 62 | choices.push({ 63 | name: tmpString, 64 | value: item.name, 65 | }); 66 | }; 67 | 68 | onlineList.forEach((item) => { 69 | addChoice(item); 70 | onlineMap[item.name] = true; 71 | }); 72 | 73 | localList.forEach((item) => { 74 | if (!onlineMap[item.name]) { 75 | addChoice(item); 76 | } 77 | }); 78 | 79 | // 本地与远程无可用套件时 80 | if (!onlineList.length && !localList.length) { 81 | log.error(intl.get('noData')); 82 | return process.exit(1); 83 | } 84 | 85 | const answers: Answer = await inquirer.prompt([ 86 | { 87 | type: 'list', 88 | name: 'name', 89 | message: intl.get('toolkitInit'), 90 | choices, 91 | }, 92 | ]); 93 | return answers.name; 94 | } 95 | 96 | export default async function (args: string[]) { 97 | let name = args.pop() || ''; 98 | const intl = new Intl(message); 99 | if (!name) { 100 | // 未传入套件名,提示并列出可用套件名 101 | name = await getName(); 102 | } 103 | name = modules.utils.toolkitFullName(name); 104 | // 先判断tiny.config.js 是否存在 105 | // 存在的话,提示已初始化过了 106 | // 不存在的话再判断文件夹是否为空 107 | // 不为空的话则提示覆盖 108 | if (config.exist(cwd)) { 109 | log.warn(intl.get('toolkitReportInit')); 110 | log.warn(intl.get('toolkitInitTips', { file: config.getConfigName() })); 111 | return; 112 | } 113 | 114 | // 排除那些以点开头的文件 115 | const files = fs.readdirSync(cwd).filter((file) => file.indexOf('.') !== 0); 116 | 117 | if (files.length > 0) { 118 | log.warn(intl.get('fileExist')); 119 | const questions = [ 120 | { 121 | type: 'input', 122 | name: 'check', 123 | message: intl.get('confirmInit'), 124 | }, 125 | ]; 126 | 127 | const answers: any = await inquirer.prompt(questions); 128 | if (answers.check === 'y' || answers.check === 'Y') { 129 | await runInit(name); 130 | } 131 | } else { 132 | await runInit(name); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /packages/devkit/src/git/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 - present Tiny CLI Authors. 3 | * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. 4 | * 5 | * Use of this source code is governed by an MIT-style license. 6 | * 7 | * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, 8 | * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 10 | * 11 | */ 12 | import git from 'git-rev-sync'; 13 | import shelljs from 'shelljs'; 14 | import semver from 'semver'; 15 | import * as path from 'path'; 16 | import * as fs from 'fs'; 17 | 18 | const root = process.cwd(); 19 | 20 | function getShortCommitId(dir?: string) { 21 | return git.long(dir).substr(0, 7); 22 | } 23 | 24 | const parseStatus = function (str: string) { 25 | let branch_line; 26 | const status: any = { 27 | local_branch: null, 28 | remote_branch: null, 29 | remote_diff: null, 30 | clean: true, 31 | files: [] 32 | }; 33 | let result; 34 | const initial_commit_rx = /^## Initial commit on ([^\n]+)\s?$/; 35 | const lines: any = str.trim().split('\n'); 36 | branch_line = lines.shift(); 37 | 38 | result = branch_line.match(initial_commit_rx); 39 | if (result) { 40 | status.local_branch = result[1]; 41 | return status; 42 | } 43 | 44 | branch_line = branch_line.replace(/##\s+/, ''); 45 | 46 | const branches = branch_line.split('...'); 47 | status.local_branch = branches[0]; 48 | status.remote_diff = null; 49 | if (branches[1]) { 50 | result = branches[1].match(/^([^\s]+)/); 51 | status.remote_branch = result[1]; 52 | result = branches[1].match(/\[([^\]]+)\]/); 53 | status.remote_diff = result ? result[1] : null; 54 | } 55 | lines.forEach((s) => { 56 | if (s.match(/\S/)) { 57 | status.files.push(s); 58 | } 59 | }); 60 | status.clean = status.files.length === 0; 61 | return status; 62 | }; 63 | 64 | /** 65 | * 同步分支版本号到package中 66 | * 需满足条件 分支版本号格式为 {name}/x.y.z 67 | * @param pkg pkg 68 | */ 69 | function syncVersion(cwd?: string) { 70 | cwd = cwd || root; 71 | let branch: string = git.branch(cwd); 72 | const branchArr = branch.split('/'); 73 | if (branchArr && branchArr.length === 2) { 74 | branch = branchArr[1]; 75 | } 76 | // 判断一下 是否是标准的 x.y.z格式 77 | if (semver.valid(branch)) { 78 | try { 79 | const pkgPath = path.resolve(cwd, 'package.json'); 80 | const pkg = require(pkgPath); 81 | pkg.version = branch; 82 | fs.writeFileSync(path.resolve(cwd, 'package.json'), JSON.stringify(pkg, null, 2), { encoding: 'utf8' }); 83 | return branch; 84 | } catch (e) { 85 | return ''; 86 | } 87 | } 88 | return ''; 89 | } 90 | 91 | /** 92 | * 获取git的提交状态 93 | * return 94 | * { local_branch: 'xxx', 95 | remote_branch: null, 96 | remote_diff: null, 97 | clean: true/false, 98 | files: [] 99 | } 100 | */ 101 | git.status = function (cwd?: string) { 102 | cwd = cwd || root; 103 | const result = (shelljs.exec('git status --porcelain -b', { silent: true, cwd }).stdout.toString() || '').trim(); 104 | return parseStatus(result); 105 | }; 106 | 107 | /** 108 | * 获取项目URL 109 | * @returns {*} 110 | */ 111 | git.repository = function (cwd?: string) { 112 | cwd = cwd || root; 113 | let repository; 114 | 115 | try { 116 | repository = ( 117 | shelljs.exec('git config --get remote.origin.url', { silent: true, cwd }).stdout.toString() || '' 118 | ).trim(); 119 | // 有些git的url是http开头的,需要格式化为git@格式,方便统一处理 120 | const match = repository.match(/^(http|https):\/\/(.*?\.com)\/(.*)/); 121 | if (match && match.length > 3) { 122 | repository = `git@${match[2]}:${match[3]}`; 123 | } 124 | } catch (err) { 125 | console.error('git config 错误:', err.message); 126 | } 127 | return repository; 128 | }; 129 | 130 | /** 131 | * 获取项目的project name 和 group name 132 | */ 133 | git.project = function (cwd?: string) { 134 | cwd = cwd || root; 135 | const repository = git.repository(cwd); 136 | const match = repository.match(/git@(.*):(.*)/); 137 | if (match && match[2]) { 138 | // 2222 是huawei git的额外路径 139 | return match[2].replace(/\.git|2222\//g, ''); 140 | } 141 | }; 142 | 143 | git.short = getShortCommitId; 144 | git.syncVersion = syncVersion; 145 | /** 146 | * @exports tiny-git 147 | */ 148 | export default git; 149 | --------------------------------------------------------------------------------