当前位置: 首页 > Web前端 > JavaScript

一起来写一个多用途的GithubAction

时间:2023-03-26 21:58:13 JavaScript

一起来写一个多用途的GithubAction前言快速入门0.从模板初始化项目1.在根目录添加action.yml2.创建入口index.ts3.获取参数和github上下文4。在你的main函数中填写逻辑5.将结果打包到指定目录6.发布到githubmarketplace开始进阶之旅0.条件编译1.代码拆分2.添加条件变量并协调动作和npm包的写法3.重载获取参数4.重载获取Octokit实例5.更改打包配置6.发布到npmunittest结束参考文档源码一起写一个多用途的GithubAction前言GithubActions我觉得大家都或多或少知道,也用过类似的产品。本文从开发、测试、构建的角度设计了一个GithubAction,使其可以方便的复用代码逻辑,同时发布到GithubMarketplace、npm等平台。快速入门0.从模板初始化项目快速创建一个tsrolluplib项目。我通常使用自己的模板(sonofmagic/npm-lib-rollup-template)。当然没关系,npminit-y也是可以的。1.在根目录下添加一个action.yml文件,告诉Github这个仓库是一个Action。Github指南中给出的示例如下:name:'HelloWorld'#RequiredRequiredGitHubActionnamedescription:'Greesomeoneandrecordthetime'#RequiredRequireddescriptioninputs:#Inputwho-to-greet:#idofinputdescription:'Whotogreet'#参数说明required:true#是否必填default:'World'#该参数为字符串,文档中未指定其他类型outputs:#输出时间:#输出的iddescription:'Thetimewegreetyou'runs:using:'node16'#Runtimemain:'index.js'#Executionentryfrom在这个配置文件中,我们大致可以将其分为5类元数据:Descriptioncategory:name,author,description这些字段描述了动作是什么。输入参数:输入下的字段用于将参数传递给操作。Outputparameters:在outputs字段下,用于定义输出参数字段runs:用于定义运行时相关的配置,JavaScriptaction和Docker容器action有不同的配置。本文主要介绍JavaScriptactionstyle:branding字段主要用于GithubMarketplace上的图标和颜色。这样我们就可以定义自己的元数据action.yml:name:'github-repository-distributor'description:'github-repository-distributor'inputs:token:#idofinputdescription:'therepoPATorGITHUB_TOKEN'required:trueusername:description:'githubusernametogeneratemarkdownfiles'required:truemotto:description:'whetheraddpoweredbyfooter(boolean)'default:'true'#注意这里是一个字符串#....title:description:'mainmarkdownh1title'onlyPrivate:description:'onlyincludeprivaterepos(boolean)'default:'false'runs:using:'node16'main:'lib/index.js'品牌:图标:'arrow-up-circle'color:'green'2.创建入口index.tsasyncfunctionmain(){//dosomething}main()3.获取参数和github上下文这里需要引入@actions/core和@actions/github@actions/core它包含了大量的动作核心方法,我们依赖它来获取参数,导出变量,或者获取秘钥等。@actions/github主要包括Github上下文还有一个@octokit/core,可以直接帮我们调用Github的restapi接口。通过这种方式,我们可以像这样获取输入中的参数:token')constusername=core.getInput('username')//getBooleanInput本质上是一个parseBoolean(core.getInput('key'))constmotto=core.getBooleanInput('motto')constfilepath=core.getInput('filepath')consttitle=core.getInput('title')constincludeFork=core.getBooleanInput('includeFork')constincludeArchived=core.getBooleanInput('includeArchived')constonlyPrivate=core.getBooleanInput('onlyPrivate')返回{token,username,motto,filepath,title,includeFork,includeArchived,onlyPrivate}}当然我们也可以很方便的获取到context和octokit例子中的信息:importgithubfrom'@actions/github'//使用仓库名actiongithub.context.repo.repo//token是repoPAT或者GITHUB_TOKENoctokit=github.getOc??tokit(token)//获取一个人的仓库constres=awaitoctokit.rest.repos.listForUser({username:'sonofmagic',per_page:20,page:1,sort:'updated'})4.在你的主函数中填写逻辑让我们回到入口点并填写异步逻辑functioninthecodemain(){constoptions=getActionOptions()//dosomething}main()5.将结果打包到指定目录这里我将打包后的结果输出到lib文件中。值得注意的是,官方文档使用@vercel/ncc(webpack),将node_modules/*提交到Github。这里我们优化一下,使用rollup打包,直接把依赖放到构建产品中。从“@rollup/plugin-typescript”导入typescript从“@rollup/plugin-node-resolve”导入{nodeResolve}从“@rollup/plugin-commonjs”导入commonjs从“@rollup/plugin-json”导入json导入pkgfrom'./package.json'import{terser}from'rollup-plugin-terser'constisDev=process.env.NODE_ENV==='development'/**@type{import('rollup').RollupOptions}*/constconfig={input:'src/index.ts',output:{dir:'lib',format:'cjs',exports:'auto'},plugins:[//不喜欢lib太大而无法压缩terser(),json(),nodeResolve({preferBuiltins:true}),commonjs(),typescript({tsconfig:'./tsconfig.build.json',sourceMap:isDev})],外部:[...(pkg.dependencies?Object.keys(pkg.dependencies):[]),'fs/promises']}exportdefaultconfig然后gitaddlib/*添加构建产品并提交。这样一来,lib中大量“无用”的代码也被提交到了Github。6、发布到githubmarketplace在手机上下载MicrosoftAuthenticator软件,然后扫描绑定Github的Twofactor的二维码,这样你的GithubAction就可以成功发布到插件市场了。庆祝你的成功!开始进阶之旅,当然笔者想介绍的远不止这些,不然标题中的“多用途”二字也不会提了。接下来,我们需要同时抽取这个包的主要逻辑,发布为npm包,然后通过mock的上下文构建单元测试用例。怎么做?核心其实很简单:代码切分和条件编译0。条件编译对于我们开发者来说已经很熟悉了。一些不可达的代码可以通过条件编译直接去掉。比如我们发布成npm包给用户,那么@actions自然就不需要/core和@actions/github了。然后打包的时候直接杀掉即可。实现方式有很多,比如webpack.DefinePlugin、@rollup/plugin-replace、esbuild#define等。1.借助打包工具也很容易实现代码拆分。例如,我们最初引入它使用静态写法:import{getActionOptions}from'./action'然后我们将其改为async/await来动态导入asyncfunctionmian(){const{getActionOptions}=awaitimport('./action')}这样,除了默认的输出配置外,打包工具还会生成[name].js的entryFile,以及一些[name]-[hash].js的chunkFile,交给运行时使用动态加载。2、添加条件变量,协调action和npm包的编写。这里我们添加一个布尔变量declarevar__isAction__:boolean。不同的。那么我们可以根据这两点进行编译期重载:3.重载获取参数我们可以这样写参数:如果(__isAction__){const{getActionOptions}=awaitimport('./action')opt=getActionOptions()}else{opt=options}returndefu,UserDefinedOptions>(opt,getDefaults())asUserDefinedOptions}这样打包的时候就可以确定代码的走向了。4.重载获取Octokit实例我们可以这样写Octokit实例:const{token}=optionsletoctokitif(__isAction__){const{github}=awaitimport('./action')octokit=github.getOc??tokit(token)}else{const{Octokit}=awaitimport('@octokit/rest')//require()octokit=newOctokit({auth:token})}这样的动作去@actions/github,默认去@octokit/rest,得到的Octokit也是一致的。5.更改打包配置我们添加BUILD_TARGET环境变量,当值为action时对Action进行打包,默认为npm包。这样我们很容易可以编写出这样的rollup.config.js:importtypescriptfrom'@rollup/plugin-typescript'import{nodeResolve}from'@rollup/plugin-node-resolve'importcommonjsfrom'@rollup/plugin-commonjs'从'@rollup/plugin-json'导入json从'./package.json'导入pkg'从'@rollup/plugin-replace'导入替换'从'rollup-plugin-terser'constisDev=process.env.NODE_ENV==='development'constisAction=process.env.BUILD_TARGET==='action'/**@type{import('rollup').OutputOptions}*/constnpmOutput={file:pkg.main,格式:'cjs',sourcemap:isDev,exports:'auto'}/**@type{import('rollup').OutputOptions}*/constactionOutput={dir:'lib',格式:'cjs',exports:'auto'}/**@type{import('rollup').RollupOptions}*/constconfig={input:'src/index.ts',output:isAction?actionOutput:npmOutput,插件:[isAction?terser():undefined,replace({preventAssignment:true,values:{__isAction__:JSON.stringify(isAction)}}),json(),nodeResolve({preferBuiltins:true}),commonjs(),typescript({tsconfig:isAction?'./tsconfig.action.json':'./tsconfig.build.json',sourceMap:isDev})],external:[...(pkg.dependencies?Object.keys(pkg.dependencies):[]),'fs/promises']}exportdefaultconfig可见,打包的配置也随着buildtarget的不同,使用不同的配置比如:npmOutput和actionOutput,两个rollup#OutputOptionsconfig.action.json和tsconfig.build.json,两个ts配置。6.发布到npm在package.json中添加打包说明和npminclude文件!{"scripts":{"build":"yarnclean&&yarndts&&cross-envNODE_ENV=productionrollup-c","build:action":"yarncleanlib&&cross-envNODE_ENV=productionBUILD_TARGET=actionrollup-c",},"files":["dist"]}构建完成后,执行yarnpublish,大功告成!其实单元测试也是采用同样的原理。在单元测试用例执行之前,可以劫持获取参数的方法和获取github上下文的方法,通过这种方式进行单元测试。限于篇幅,本文不做过多介绍。主要是给大家一个写GithubAction的思路。有兴趣的可以一起讨论。参考文档使用tmate调试GitHubActions在githubmarketplace上列出地址GitHubActions/Creatingactions(指南)GitHubActions的元数据语法源代码github-repository-distributor