從零搭建滿足權限校驗等需求的前端命令行工具(腳手架)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"項目背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開發一款內部使用的腳手架,對工程的管理以及後期框架底層版本的更新迭代,可以節省很多資源。基於此目的,以及命令行腳手架的成長過程需要把控、使用腳手架項目需跟進的情況下,我們提出腳手架一期需滿足如下能力:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":" 腳手架更新升級能力 ","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行開發命令時,自動校驗腳手架是否需要升級,保證最新腳手架版本。 ","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":" 腳手架權限校驗能力(內部使用) ","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"腳手架僅限內部使用,用戶需先獲得權限,且項目需要獲得證書。","attrs":{}}]},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":" 初始化項目模板能力 ","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"開發環境/生產環境構建能力","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面從零開始介紹。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一、腳手架是什麼","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"腳手架是爲了保證各施工過程順利而搭設的工作平臺,這是百度百科上對腳手架的定義。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端中,我們耳熟能詳的腳手架有create-react-app、vue-cli、yeoman等這類通過命令行工具生成的,也有可以直接使用的模板工程,如:html5-boilerplate/react-boilerplate等。從這些腳手架上看它提供的能力,個人理解爲兩點:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"提供工程的基礎架構代碼模板能力。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"建設穩健的工作流保證工程運作。","attrs":{}}]}],"attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"二、創建npm包","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"1、npm包必備基本項","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"package.json  // 包的基本信息\nREADE.md      // 文檔\nindex.js      // 入口文件","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2、package.json配置文件詳解","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"{\n  \"name\": \"crxcli\",     // 包名\n  \"version\": \"1.0.0\",   // 版本名\n  \"author\": \"\",         // 作者\n  \"description\": \"\",    // 描述\n  \"main\": \"index.js\",   // 入口文件\n  \"scripts\": {          // 聲明npm腳本指令:npm run test\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"bin\":{},             // 聲明被放到PATH中的可執行文件\n  \"bugs\":{},            // 項目提交issues的地址\n  \"dependencies\": {},    // 生產環境依賴,npm install --save\n  \"devDependencies\": {}, // 開發環境依賴,npm install --save-dev\n  \"engines\": {},         // 指定項目工作環境\n  \"homepage\": \"\",        // 項目官網地址\n  \"peerDependencies\":{}, // 嚴格約束插件使用的版本\n  \"private\":\"\",          // 配置私有庫,爲true,npm不會發布\n  \"publishConfig\": \"\",   // 在publish-time使用的配置集合 \n  \"repository\": \"\",      // 代碼存放的地址(git)\n  \"license\": \"ISC\"       // 許可證\n  ...\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3、創建可執行腳本文件","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"新建目錄結構","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// 創建項目\nmkdir crxCli\ncd crxCli\nnpm init // 初始化配置文件\n\n// 新建可執行腳本文件\ncrxCli/\n bin/\n crx.js // 可執行腳本文件","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"配置package.json文件","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"{\n ...\n  \"bin\":{\n    \"crx\":\"bin/crx.js\"\n  }\n ...\n}","attrs":{}}]},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"項目與npm模塊建立連接","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"cd crxCli && npm link // 全局link\n// 或者\ncd crxCli && npm link 模塊名(package.json中的name)\n\n// 解除link\nnpm unlink 模塊名","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建立連接後,可以看到在/npm/node_modules路徑下創建了一個軟鏈接,此時便可以使用crx指令進行模塊調試。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"三、常見開發包","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"commander // 完整的 node.js 命令行解決方案,\nsemver    // npm版本解析器\nchalk     // 添加終端字符串樣式\nminimist  // 參數選擇解析器\ndotenv    // 可將環境變量從.env文件加載到process.env中\ndotenv-expand // 在dotenv頂部添加變量擴展\ninquirer  // 用於與命令行交互的工具\nrimraf    // 刪除文件\ndownload-git-repo // 下載git項目\nfs-extra  // 文件操作","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"四、搭建命令行腳手架","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們基於項目背景,設計下整個業務流程,如圖:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"流程圖設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、登錄及模板初始化流程圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/92/9279f3a405845176f55e3f973030d431.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、執行開發環境流程圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/ad7607d140731815c63ffb0f2f01e2be.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"1、命令行設計","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// 登錄命令行\ncrx login\n// 登出命令\ncrx loginout\n// 初始化項目模板\ncrx init \n// 執行開發環境\ncrx dev\n// 執行生產環境\ncrx build\n// 查看用戶信息\ncrx userinfo","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2、目錄結構設計","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"crxCli/\n bin/\n crx.js       // 命令行腳本\n crx-init.js     // 項目初始化腳本\n crx-login.js    // 登錄腳本\n crx-loginout.js // 登出腳本\n crx-dev.js      // 開發腳本\n crx-build.js    // 生產腳本\n lib/ // 工具庫\n access.js // 權限相關\n ...\n ...\n package.json // 配置文件\n READEME.md","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3、命令行腳本實現","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// bin/crx.js \n\n#! /usr/bin/env node \n// 聲明腳本執行的環境\n\nconst { option } = require(\"commander\")\nconst program = require(\"commander\")\nconst version = require(\"../package.json\").version\n\nprogram\n // 版本信息\n .version(version,'-v, --version')\n // 幫助信息的首行提示\n .usage(' [options]')\n // 初始化項目的命令\n .command('init [project]','generate project by template')\n // 開發環境\n .command('dev','start dev server')\n // 生產環境\n .command('build','build source')\n // 登錄命令\n .command('login', 'user login')\n // 登出命令\n .command('logout', 'user logout')用戶\n // 查看當前用戶信息\n .command('userinfo', 'who am i')\n .action(function(cmd){\n if(['init','dev','build','login', 'logout',].indexOf(cmd) === -1){\n console.log('unsupported aid-cli command')\n process.exit(1)\n }\n })\n \nprogram.parse(process.argv)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"4、登錄流程","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// bin/crx-login.js 文件\n\n#! /usr/bin/env node\nprocess.env.NODE_ENV = 'production'\n\nconst program = require('commander')\n// 命令行交互工具\nconst inquirer = require('inquirer')\nconst chalk = require('chalk')\nconst { merge } = require('lodash')\nconst { saveAuthorized } = require('../lib/access')\nconst { login, fetchUserProjects } = require('../lib/api')\n\nprogram.option('--outer', 'outer network', false)\nprogram.parse(process.argv)\n// 判斷使用登錄網絡地址: 外網地址 || 內網\nconst outer = program.outer || false\n\ninquirer.prompt([\n {\n type: 'input',\n name: 'username',\n message: '請輸入用戶名',\n },\n {\n type: 'password',\n name: 'password',\n message: '請輸入密碼',\n mask: '*',\n },\n]).then(answers => {\n // 命令行交互獲取的用戶名/密碼\n const username = answers.username\n const password = answers.password\n // axios請求登錄接口請求用戶信息\n login(username, password, outer).then(({user, token}) => {\n let auth = merge({}, user, { token })\n // 獲取用戶授權項目\n fetchUserProjects(auth.username === 'admin', auth, outer).then(projects => {\n // 保存.authorized 用戶授權信息\n saveAuthorized(merge({}, auth, { projects }))\n console.log(chalk.yellow('登錄成功'))\n })\n }).catch(err => {\n console.log(chalk.red(err.response ? err.response.data : err))\n })\n})","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// lib/access.js 文件\n\nconst { resolve, join } = require('path')\nconst fs = require('fs-extra')\n\nconst confPath = \"***\"\nconst key = \"****\"\nconst iv = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f])\n\n/**\n * AES是一種常用的對稱加密算法,加解密都用同一個密鑰.\n * 加密結果通常有兩種表示方法:hex和base64\n */\n\n// 加密\nfunction aesEncrypt(data) {\n const cipher = createCipher('aes192', key);\n let crypted = cipher.update(data, 'utf8', 'hex');\n crypted += cipher.final('hex');\n return crypted;\n}\n// 解密\nfunction aesDecrypt(encrypted) {\n const decipher = createDecipher('aes192', key);\n let decrypted = decipher.update(encrypted, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n return decrypted;\n}\n// 保存授權信息文件\nfunction saveAuthorized(data) {\n fs.ensureDirSync(confPath)\n fs.writeFileSync(join(confPath, '.authorized'), \n aesEncrypt(JSON.stringify(data)))\n}\n// 刪除授權文件\nfunction deleteAuthorized() {\n fs.removeSync(join(confPath, '.authorized'))\n}\n// 判斷授權文件是否存在\nfunction authorizedExists() {\n return fs.existsSync(join(confPath, '.authorized'))\n}\n// 獲取授權文件解析信息\nfunction getAuthorized() {\n let ret = fs.readFileSync(join(confPath, '.authorized'), 'utf8')\n return JSON.parse(aesDecrypt(ret))\n}\nconst { resolve, join } = require('path')\nconst fs = require('fs-extra')","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"5、登出","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// bin/crx-loginout.js\n\n#! /usr/bin/env node\n\nprocess.env.NODE_ENV = 'production'\n\nconst chalk = require('chalk')\nconst { deleteAuthorized, authorizedExists } = require('../lib/access')\n\nif (authorizedExists()) {\n deleteAuthorized()\n console.log(chalk.yellow('註銷成功'))\n} else {\n console.log(chalk.red('用戶未登錄'))\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"6、初始化項目模板","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"初始化項目命令行:","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"crx init ","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"代碼實現:","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// bin/crx-init.js\n\n#! /usr/bin/env node\n\nconst program = require('commander')\nconst chalk = require('chalk')\nconst path = require('path')\nconst fs = require('fs')\nconst inquirer = require('inquirer')\nconst rm = require('rimraf').sync\n\nconst config = require('../lib/config')\nconst generator = require('../lib/generator')\nconst { authorizedExists, getAuthorized } = require('../lib/access')\n\nprogram.usage('[project]')\nprogram.option('-r, --repo [value]', 'choose specified repo')\nprogram.option('--outer', 'outer network', false)\n\nprogram.on('--help', function() {\n console.log(`\n Examples:\n ${chalk.cyan(' # create a new project by specified template')}\n crx init Todo\n `)\n})\n\nprogram.parse(process.argv)\n\n// 命令行參數不存在的提示\nif (!program.args.length) {\n program.help()\n}\n\nlet projectName = program.args[0]\nlet projectPath = path.resolve(projectName)\n\nif (projectName === '[object Object]') {\n console.log(chalk.red('name required!'))\n program.help()\n process.exit(1)\n}\n\nconst questions = config.questions\n\n// 判斷當前目錄是否存在同項目名\nif (fs.existsSync(projectPath)) {\n inquirer\n .prompt([\n {\n type: 'confirm',\n name: 'yes',\n message: 'current project directory is not empty,continue?'\n }\n ])\n .then(function(answer) {\n if (answer.yes) {\n // 刪除目錄\n rm(projectPath)\n // 確定模板問題,下載模板代碼\n ask(questions, generator)\n }\n })\n} else {\n ask(questions, generator)\n}\n\nfunction ask(_questions, cb) {\n let choices = _questions[0].choices\n if (authorizedExists()) {\n // 獲取授權項目信息\n const auth = getAuthorized()\n let templates = {}\n // 獲取授權項目中可供下載模板類型\n auth.projects.forEach((project) => {\n if (Array.isArray(project.templates)) {\n project.templates.forEach((v) => {\n templates[v.name] = v.value\n })\n }\n })\n // 初始化命令行交互問題\n _questions[0].choices = choices = choices\n .map(v => ({\n // 獲取gitlab對應的模板代碼下載地址\n name: v.name, value: config.template(v.value, program.outer)\n }))\n .... // 其他代碼省略\n }\n _questions.push({\n type: 'confirm',\n name: 'yes',\n message: '確定以上問題的答案嗎?'\n })\n\n inquirer.prompt(_questions).then(function(answers) {\n if (answers.yes) {\n // 根據應答下載模板代碼\n cb(projectName, projectPath, answers, answers.template)\n } else {\n ask(cb)\n }\n })\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下載模板代碼示例:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const ora = require('ora')\nconst execSync = require('execa').shellSync\nconst path = require('path')\nconst fs = require('fs-extra')\nconst chalk = require('chalk')\nconst downloadGitRepo = require('download-git-repo')\nconst { saveHistory } = require('./api')\nconst { authorizedExists, getAuthorized } = require('./access')\n\nfunction prepare(projectName, destPath, answers, repo) {\n generator(projectName, destPath, answers, repo)\n}\n\nfunction download(repo, destPath) {\n // eslint-disable-next-line no-undef\n return new Promise(function(resolve, reject) {\n downloadGitRepo(repo, destPath, function(err) {\n if (err) reject(err)\n resolve(true)\n })\n })\n}\n\nfunction writePKG(pkgPath, data) {\n let pkg = fs.readJsonSync(pkgPath)\n pkg = assign({}, pkg, data)\n fs.writeJsonSync(pkgPath, pkg)\n}\n\nfunction generator(projectName, destPath, answers, repo) {\n // 創建目錄\n fs.mkdirsSync(destPath)\n let _dest = 'template: ' + answers.template\n\n let spinner = ora(` 下載模板代碼: ${_dest} `)\n spinner.start()\n // 下載模板代碼\n return download(repo, destPath).then(function() {\n spinner.stop()\n // 合併模板代碼的package.json與命令行交互工具的初始化\n writePKG(path.resolve(projectName, 'package.json'), {\n name: projectName,\n version: answers.version,\n description: answers.desc,\n author: answers.author,\n license: answers.license\n })\n\n try {\n // 進入項目,安裝項目依賴\n execSync(`cd ${destPath} && npm install`, { stdio: 'inherit' })\n } catch (err) {\n console.log(err)\n process.exit(1)\n }\n if (authorizedExists) {\n const auth = getAuthorized()\n // 更新代碼授權信息\n saveHistory(projectName, auth.id, 'init').catch(()=> {})\n }\n var completeMsg = `成功創建項目: '${projectName}'`\n console.log(chalk.yellow(completeMsg))\n }).catch(function(err) {\n console.log(chalk.red(`\\n 無法下載 ${_dest}`), err)\n process.exit(1)\n })\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"7、開發環境流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此處僅展示部分判斷流程代碼,關於webpack開發環境構建相關此處不介紹,後續將整理webpack相關文章。","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"執行命令行","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"crx dev","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"代碼實現","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢測是否需要升級腳手架相關代碼:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let axios = require('axios')\nlet semver = require('semver')\nlet chalk = require('chalk')\nlet inquirer = require(\"inquirer\")\nlet execSync = require('execa').shellSync\nlet platform = require('os').platform()\nlet pkgJSON = require('../package.json')\n\nmodule.exports = function(done, forceUpdate) {\n if (!semver.satisfies(process.version, pkgJSON.engines.node)) {\n console.log(chalk.red(\n '您的 nodeJS 版本必須滿足: >=' + pkgJSON.engines.node + '.x'\n ))\n if (platform === 'darwin') {\n console.log(`推薦使用 ${chalk.cyan('https://github.com/creationix/nvm')} 升級和管理 nodeJS 版本`)\n } else if (platform === 'win32') {\n console.log(`推薦前往 ${chalk.cyan('https://nodejs.org/')} 下載 nodeJS 穩定版`)\n }\n process.exit(1)\n }\n\n const cliName = \"發佈npm的腳手架名稱\"\n axios.get(`https://registry.npm.taobao.org/${cliName}`, {timeout: 8000}).then(function(ret) {\n if (ret.status === 200) {\n let latest = ret.data['dist-tags'].latest\n let local = pkgJSON.version\n if (semver.lt(local, latest)) {\n console.log(chalk.yellow('發現可升級的 aid-cli 新版本.'))\n console.log('最新: ' + chalk.green(latest))\n console.log('當前: ' + chalk.gray(local))\n if (forceUpdate) {\n try {\n execSync(`npm i ${cliName} -g`, { stdio: 'inherit' })\n done()\n } catch (err) {\n console.error(err)\n process.exit(1)\n }\n } else {\n inquirer.prompt([{\n type: \"confirm\",\n name: 'yes',\n message: \"是否立刻升級?\"\n }]).then(function(answer) {\n if (answer.yes) {\n try {\n execSync(`npm i ${cliName} -g`, { stdio: 'inherit' })\n done()\n } catch (err) {\n console.error(err)\n process.exit(1)\n }\n } else {\n done()\n }\n })\n }\n } else {\n done()\n }\n } else {\n done()\n }\n }).catch(function() {\n console.log(chalk.yellow('腳手架更新檢測超時'))\n done()\n })\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"證書校驗流程相關代碼:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function checkAccess(cb, type, outer) {\n let task = type === 'dev' ? '啓動開發服務' : '打包項目'\n let spinner = ora('校驗項目...')\n spinner.start()\n // 判斷項目是否受限\n const isRestricted = checkRestricted() \n if (isRestricted) {\n const path = resolve('package.json')\n if (fs.existsSync(path)) {\n const projectName = fs.readJSONSync(path).name\n // 用戶是否登錄獲取授權\n if (authorizedExists()) {\n let auth = getAuthorized()\n // 當前項目是否獲取授權\n let project = find(auth.projects, (v) => v.name === projectName)\n if (project) {\n // 項目證書是否存在\n if (certExists(projectName)) {\n // 檢查項目證書是否過期\n checkCert(projectName, () => {\n saveHistory(projectName, auth.id, type, outer)\n spinner.succeed(chalk.yellow(`校驗完成,開始${task}`))\n cb()\n }, spinner)\n } else {\n spinner.stop()\n // spinner.fail(chalk.red('受限項目,開發者證書不存在'))\n inquirer.prompt([\n {\n type: 'confirm',\n name: 'yes',\n message: '開發者證書不存在, 是否生成證書?'\n }\n ])\n .then(function(answer) {\n if (answer.yes) {\n spinner.text = chalk.yellow('安裝開發者證書...')\n createCert(auth.id, project.id, projectName, os.homedir(), auth, outer).then(ret => {\n saveCert(projectName, ret.data)\n saveHistory(projectName, auth.id, type, outer)\n spinner.succeed(chalk.yellow(`開發者證書安裝完成,開始${task}`))\n cb()\n }).catch(err => {\n console.log(chalk.red(err.response ? err.response.data : err))\n process.exit(1)\n })\n } else {\n console.log(chalk.yellow('bye'))\n process.exit(1)\n }\n })\n }\n } else {\n spinner.fail(chalk.red('用戶不屬於項目 ' + projectName))\n process.exit(1)\n }\n } else {\n spinner.fail(chalk.red('用戶未登錄'))\n process.exit(1)\n }\n } else {\n spinner.fail(chalk.red('當前項目的 package.json 文件不存在'))\n process.exit(1)\n }\n } else {\n // console.log(chalk.yellow('非受限項目,無須授權'))\n spinner.succeed(chalk.yellow(`校驗完成,開始${task}`))\n cb()\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"五、腳手架的發佈","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下僅演示個人賬戶下發布unscoped包的案例。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"1、註冊npm賬戶","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://docs.npmjs.com/cli/v6/commands/npm-publish","title":""},"content":[{"type":"text","text":"註冊地址","attrs":{}}]}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"全名\n郵箱\n用戶名 // 重要,發佈scoped包時會用到\n密碼","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2、全局安裝nrm","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"npm i nrm -g","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"nrm是npm倉庫管理的軟件,用於npm倉庫的快速切換,常見命令:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"nrm   // 查看可用命令\nnrm ls   // 查看已配置的所有倉庫\nnrm test  // 測試所有倉庫的響應時間\nnrm add    // 新增倉庫\nnrm use     // 切換倉庫","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3、發佈","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"npm publish","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"若報錯未登陸:npm ERR! code ENEEDAUTH ,則執行登錄","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"npm adduser","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"若報錯倉庫地址不對:npm ERR! code ENEEDAUTH,則先執行切換倉庫","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"nrm ls // 查看當前倉庫地址\nnrm use 倉庫名  // 切換倉庫\nnpm publish  // 發佈","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"六、總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此一款簡單的具備權限校驗的命令行工具腳手架便完成了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}}],"text":"e","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章