├── .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 |
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 |
--------------------------------------------------------------------------------