一起来写一个多用途的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
