├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── findPackage.js ├── getDistTag.js ├── package.json └── test ├── test-findPackage.js ├── test-getDistTag.js └── utils.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "favoyang", 10 | "name": "Favo Yang", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/125390?v=4", 12 | "profile": "http://littlebigfun.com", 13 | "contributions": [ 14 | "code", 15 | "maintenance" 16 | ] 17 | }, 18 | { 19 | "login": "am1goo", 20 | "name": "Pavel \"am1goo\" Shestakov", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/5287406?v=4", 22 | "profile": "https://gaijinent.com/", 23 | "contributions": [ 24 | "code" 25 | ] 26 | }, 27 | { 28 | "login": "James-Frowen", 29 | "name": "James Frowen", 30 | "avatar_url": "https://avatars.githubusercontent.com/u/23101891?v=4", 31 | "profile": "https://github.com/James-Frowen", 32 | "contributions": [ 33 | "bug" 34 | ] 35 | } 36 | ], 37 | "contributorsPerLine": 7, 38 | "projectName": "openupm-pipelines", 39 | "projectOwner": "openupm", 40 | "repoType": "github", 41 | "repoHost": "https://github.com", 42 | "skipCi": true, 43 | "commitConvention": "angular", 44 | "commitType": "docs" 45 | } 46 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/public 4 | !.vuepress 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | "eslint:recommended", 8 | "plugin:vue/recommended", 9 | "prettier/vue", 10 | "plugin:prettier/recommended" 11 | ], 12 | rules: { 13 | "vue/component-name-in-template-casing": ["error", "PascalCase"], 14 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 15 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: openupm 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x] 11 | name: CI - node ${{ matrix.node-version }} 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: set node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - name: npm install 19 | run: npm install 20 | - name: npm test 21 | run: npm test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | /package-lock.json 30 | 31 | TEST-RESULTS.xml 32 | 33 | .vscode 34 | 35 | result.json 36 | 37 | repo 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present Zhongwei (Favo) Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Pipelines for OpenUPM 2 | 3 | A customized proxy builder to build and publish upm package using `Azure Pipelines`. 4 | 5 | ## Prepare Azura 6 | 7 | Prepare a service connection 8 | - Visit https://dev.azure.com/openupm/openupm 9 | - Project settings > Service connections > New service connection > npm 10 | - Connection name, `openupm` 11 | - Registry URL, `https://package.openupm.com` 12 | - Personal Token... 13 | 14 | ## Build with REST API 15 | 16 | Required variables 17 | 18 | { 19 | repo_url: 'https://...', 20 | repo_branch: 'master', 21 | package_name: 'com.yourcompany.package...' 22 | } 23 | 24 | If no variables are provided, the build will be [abort as failed](https://github.com/lextm/vstsabort). 25 | 26 | Api reference: [azure-devops-rest-5.1](https://docs.microsoft.com/en-us/rest/api/azure/devops/build/builds/queue?view=azure-devops-rest-5.1). 27 | 28 | ```bash 29 | http --ignore-stdin \ 30 | -v \ 31 | -a username:token \ 32 | post https://dev.azure.com/openupm/openupm/_apis/build/builds?api-version=5.1 \ 33 | definition:='{ "id": 1 }' \ 34 | parameters:='"{ \"repo_url\": \"https://...\", ... }"' 35 | ``` 36 | 37 | The `parameters` argument is [a stringified dictionary](https://stackoverflow.com/questions/34343084/start-a-build-and-passing-variables-through-vsts-rest-api/36339920#36339920). 38 | 39 | ## Build with azure-devops-node-api 40 | 41 | https://github.com/Microsoft/azure-devops-node-api 42 | 43 | ```javascript 44 | const azureDevops = require("azure-devops-node-api"); 45 | 46 | const token = ''; 47 | const endpoint = 'https://dev.azure.com/openupm'; 48 | const definitionId = 1; 49 | const project = 'openupm'; 50 | 51 | const buildPipelines = async function () { 52 | let authHandler = azureDevops.getPersonalAccessTokenHandler(token); 53 | let conn = new azureDevops.WebApi(endpoint, authHandler); 54 | var buildApi = await conn.getBuildApi(); 55 | let build = await buildApi.queueBuild({ 56 | definition: { 57 | id: definitionId 58 | }, 59 | parameters: 60 | JSON.stringify( 61 | { 62 | repo_url: 'https://...', 63 | ... 64 | } 65 | ) 66 | }, project); 67 | cconsole.log(build); 68 | }; 69 | ``` 70 | 71 | ## Contributors 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
Favo Yang
Favo Yang

💻 🚧
Pavel
Pavel "am1goo" Shestakov

💻
James Frowen
James Frowen

🐛
85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # OpenUPM azure pipelines 2 | name: $(Build.BuildId) 3 | 4 | trigger: none 5 | 6 | variables: 7 | ### context variables 8 | # repoUrl: https://github.com/owner/reponame 9 | # repoBranch: master 10 | # packageName: com.namespace.pkgname 11 | # packageVersion: semver 12 | 13 | ### pre-defined variables 14 | repoDir: repo 15 | registryUrl: https://package.openupm.com 16 | distTag: latest 17 | 18 | ### dynamic variables 19 | # distTag: latest 20 | # packageFolder: path-to-package.json 21 | 22 | pool: 23 | vmImage: "ubuntu-latest" 24 | 25 | steps: 26 | - script: | 27 | echo repoUrl: $REPOURL 28 | echo repoBranch: $REPOBRANCH 29 | echo packageName: $PACKAGENAME 30 | echo packageVersion: $PACKAGEVERSION 31 | [[ -z "$REPOURL" ]] && echo 'variable repoUrl not found' && exit 1 32 | [[ -z "$REPOBRANCH" ]] && echo 'variable repoBranch not found' && exit 1 33 | [[ -z "$PACKAGENAME" ]] && echo 'variable packageName not found' && exit 1 34 | [[ -z "$PACKAGEVERSION" ]] && echo 'variable packageVersion not found' && exit 1 35 | echo "##vso[task.setvariable variable=NODE_OPTIONS]--max_old_space_size=2048" 36 | exit 0 37 | displayName: "Prepare context variables" 38 | 39 | - script: | 40 | echo "##vso[build.updatebuildnumber]$(packageName)-$(packageVersion)-$(Build.BuildId)" 41 | displayName: "Update build number" 42 | 43 | - task: NodeTool@0 44 | inputs: 45 | versionSpec: "18.x" 46 | displayName: "Install Node.js" 47 | 48 | - task: Npm@1 49 | inputs: 50 | command: custom 51 | customCommand: "install -g npm@10.4.0" 52 | displayName: "Upgrade npm to v10.4.0" 53 | 54 | - task: Npm@1 55 | inputs: 56 | command: custom 57 | customCommand: "install -g husky" 58 | displayName: "Install husky globally" 59 | 60 | - task: Npm@1 61 | inputs: 62 | command: install 63 | displayName: "Install dependencies" 64 | 65 | - task: DeleteFiles@1 66 | inputs: 67 | contents: $(repoDir) 68 | displayName: "Clean $(repoDir) folder" 69 | 70 | - script: | 71 | git config --global url."https://github.com/".insteadOf git@github.com: 72 | git config --global url."https://".insteadOf git:// 73 | displayName: Git config 74 | 75 | - script: GIT_CLONE_PROTECTION_ACTIVE=false git clone --recursive --shallow-submodules --depth 1 --branch $(repoBranch) $(repoUrl) $(repoDir) 76 | displayName: "Clone to $(repoDir) repository" 77 | 78 | - script: | 79 | # Get LATEST_VERSION 80 | LATEST_VERSION=`npm --registry=$(registryUrl) show $(packageName) version` || LATEST_VERSION=0.0.0 81 | [[ -z "$LATEST_VERSION" ]] && LATEST_VERSION=0.0.0 82 | echo LATEST_VERSION: $LATEST_VERSION 83 | echo packageVersion: $(packageVersion) 84 | # Get DIST_TAG 85 | DIST_TAG=`node getDistTag.js $(packageVersion) $LATEST_VERSION` 86 | echo DIST_TAG: $DIST_TAG 87 | echo "##vso[task.setvariable variable=distTag;]$DIST_TAG" 88 | displayName: "Set distTag variable" 89 | 90 | - script: | 91 | npm install -g json 92 | echo Locate package.json for: $(packageName) 93 | node findPackage.js $(packageName) $(repoDir) result.json 94 | if [[ ! -f "result.json" ]]; then 95 | echo "Error: ENOENT, error path package.json with name=$(packageName)" 96 | exit 1 97 | fi 98 | PACKAGEFOLDER=$(cat result.json | json 'dirname') 99 | echo "PACKAGEFOLDER: $PACKAGEFOLDER" 100 | echo "##vso[task.setvariable variable=packageFolder;]$PACKAGEFOLDER" 101 | displayName: "Detect package.json location" 102 | 103 | - script: | 104 | cd "$(packageFolder)" 105 | if [ -f "package.json" ]; then 106 | npm install -g json 107 | json -I -f package.json -e 'this.publishConfig = { registry: "https://package.openupm.com" }' 108 | cat package.json 109 | fi 110 | displayName: "Override publishConfig" 111 | 112 | - task: Npm@1 113 | inputs: 114 | command: custom 115 | customCommand: "publish --tag=$(distTag) --registry=$(registryUrl)" 116 | customEndpoint: openupm 117 | workingDir: $(packageFolder) 118 | displayName: "Publish to OpenUPM" 119 | -------------------------------------------------------------------------------- /findPackage.js: -------------------------------------------------------------------------------- 1 | // Find package folder 2 | const fs = require("fs"); 3 | const findit = require("findit2"); 4 | const path = require("path"); 5 | const relative = require("relative"); 6 | 7 | const findPackage = function (packageName, searchPath) { 8 | return new Promise((resolve, reject) => { 9 | try { 10 | searchPath = path.resolve(process.cwd(), searchPath); 11 | } catch (err) { 12 | reject("searchPath does not exist"); 13 | } 14 | const finder = findit(searchPath); 15 | // eslint-disable-next-line no-unused-vars 16 | finder.on("file", (file, stat, stop) => { 17 | const basename = path.basename(file); 18 | if (basename == "package.json") { 19 | const dirname = relative(process.cwd(), path.dirname(file)); 20 | let pkg = null; 21 | try { 22 | pkg = require(path.join(file)); 23 | } catch (err) { 24 | return; 25 | } 26 | if (pkg.name != packageName) { 27 | console.debug( 28 | `Mismatched package name in ${path.join(dirname, basename)}: actual=${pkg.name}, expected=${packageName}` 29 | ); 30 | return; 31 | } 32 | console.debug(`Found package name in ${path.join(dirname, basename)}`); 33 | finder.stop(); 34 | resolve({ dirname, pkg, file }); 35 | } 36 | }); 37 | finder.on("end", () => { 38 | resolve(null); 39 | }); 40 | finder.on("error", () => { 41 | finder.stop(); 42 | reject("find package.json error"); 43 | }); 44 | }); 45 | }; 46 | 47 | if (require.main === module) { 48 | if (process.argv.length < 5) { 49 | console.log( 50 | "Usage: node findPackage.js " 51 | ); 52 | process.exit(1); 53 | } else { 54 | findPackage(process.argv[2], process.argv[3]) 55 | .then((result) => { 56 | if (result) { 57 | // Write to output file 58 | const outputFilename = process.argv[4]; 59 | const content = JSON.stringify(result); 60 | try { 61 | fs.writeFileSync(outputFilename, content); 62 | console.log("Saved to " + outputFilename); 63 | } catch (err) { 64 | console.error(err); 65 | } 66 | } 67 | }) 68 | .catch(() => {}); 69 | } 70 | } 71 | 72 | module.exports = { findPackage }; 73 | -------------------------------------------------------------------------------- /getDistTag.js: -------------------------------------------------------------------------------- 1 | /* Get npm dist-tag by comparing local version and latest version. 2 | * 3 | * Npm server set dist-tag to "latest" for the last published package. And prevent 4 | * package publish if local version is less than or equal to latest version. To 5 | * publish a package at lower version (a patch or mis-ordered publish), a dist-tag is 6 | * required. 7 | */ 8 | const compareVersions = require("compare-versions"); 9 | 10 | // Get dist tag, if localVer >= latestVer return latest, otherwise patch@localVer. 11 | const getDistTag = function(localVer, latestVer) { 12 | try { 13 | let ret = compareVersions(localVer, latestVer); 14 | if (ret == 0 || ret == 1) return "latest"; 15 | else return `patch@${localVer}`; 16 | } catch (err) { 17 | // Not valid semver, always return latest. 18 | return "latest"; 19 | } 20 | }; 21 | 22 | if (require.main === module) { 23 | if (process.argv.length < 4) { 24 | console.log("Usage: node getDistTag.js "); 25 | process.exit(1); 26 | } else { 27 | console.log(getDistTag(process.argv[2], process.argv[3])); 28 | process.exit(0); 29 | } 30 | } 31 | 32 | module.exports = { getDistTag }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openupm-pipelines", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "OpenUPM Pipelines", 6 | "scripts": { 7 | "test": "cross-env NODE_ENV=test mocha" 8 | }, 9 | "dependencies": { 10 | "compare-versions": "^3.6.0", 11 | "findit2": "^2.2.3", 12 | "relative": "^3.0.2" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^7.0.3", 16 | "fs-extra": "^11.2.0", 17 | "mocha": "^10.2.0", 18 | "prettier": "^3.2.5", 19 | "should": "^13.2.3" 20 | }, 21 | "volta": { 22 | "node": "18.19.0", 23 | "npm": "10.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/test-findPackage.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable no-undef */ 3 | const should = require("should"); 4 | const path = require("path"); 5 | const fse = require("fs-extra"); 6 | const { getTmpDir, createTmpDir, removeTmpDir } = require("./utils"); 7 | const { findPackage } = require("../findPackage"); 8 | 9 | describe("findPackage.js", function() { 10 | const root = path.relative(__dirname, ".."); 11 | const tmpDir = getTmpDir("test-find-package"); 12 | beforeEach(function() { 13 | removeTmpDir("test-find-package"); 14 | createTmpDir("test-find-package"); 15 | }); 16 | afterEach(function() { 17 | removeTmpDir("test-openupm-cli"); 18 | }); 19 | describe("findPackage()", function() { 20 | it("find success", async function() { 21 | fse.writeJsonSync(path.resolve(tmpDir, "package.json"), { 22 | name: "package-a" 23 | }); 24 | const result = await findPackage("package-a", tmpDir); 25 | result.pkg.name.should.equal("package-a"); 26 | }); 27 | it("find failed", async function() { 28 | const result = await findPackage("package-a", tmpDir); 29 | (result == null).should.be.ok(); 30 | }); 31 | it("search folder not exist", function() { 32 | findPackage( 33 | "package-a", 34 | path.join(tmpDir, "folder-not-exist") 35 | ).should.be.rejected(); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/test-getDistTag.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable no-undef */ 3 | const should = require("should"); 4 | 5 | const { getDistTag } = require("../getDistTag"); 6 | 7 | describe("getDistTag.js", function() { 8 | describe("getDistTag()", function() { 9 | it("same version", function() { 10 | getDistTag("1.0.0", "1.0.0").should.equal("latest"); 11 | }); 12 | it("non semver", function() { 13 | getDistTag("1.0.0.0.0", "1.0.0").should.equal("latest"); 14 | }); 15 | it("lower local version", function() { 16 | getDistTag("0.9.0", "1.0.0").should.equal("patch@0.9.0"); 17 | getDistTag("1.0.0", "1.0.1").should.equal("patch@1.0.0"); 18 | getDistTag("1.0.0-preview-5", "1.0.0").should.equal( 19 | "patch@1.0.0-preview-5" 20 | ); 21 | }); 22 | it("higher local version", function() { 23 | getDistTag("1.1.0", "1.0.0").should.equal("latest"); 24 | getDistTag("1.0.1", "1.0.0").should.equal("latest"); 25 | getDistTag("1.0.1-preview-0", "1.0.0").should.equal("latest"); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const fse = require("fs-extra"); 2 | const path = require("path"); 3 | const os = require("os"); 4 | 5 | const getTmpDir = function(pathToTmp) { 6 | return path.join(os.tmpdir(), pathToTmp); 7 | }; 8 | 9 | const createTmpDir = function(pathToTmp) { 10 | const workDir = getTmpDir(pathToTmp); 11 | fse.mkdirpSync(workDir); 12 | }; 13 | 14 | const removeTmpDir = function(pathToTmp) { 15 | const cwd = getTmpDir(pathToTmp); 16 | fse.removeSync(cwd); 17 | }; 18 | 19 | module.exports = { 20 | getTmpDir, 21 | createTmpDir, 22 | removeTmpDir 23 | }; 24 | --------------------------------------------------------------------------------