@xcelera/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/devcontainer.json +41 -0
- package/.env.example +61 -0
- package/.gitattributes +3 -0
- package/.github/dependabot.yml +30 -0
- package/.github/workflows/check-dist.yml +72 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/codeql-analysis.yml +48 -0
- package/.github/workflows/licensed.yml +74 -0
- package/.github/workflows/linter.yml +53 -0
- package/.licensed.yml +18 -0
- package/.licenses/npm/@actions/core.dep.yml +20 -0
- package/.licenses/npm/@actions/exec.dep.yml +20 -0
- package/.licenses/npm/@actions/http-client.dep.yml +32 -0
- package/.licenses/npm/@actions/io.dep.yml +20 -0
- package/.licenses/npm/@fastify/busboy.dep.yml +30 -0
- package/.licenses/npm/tunnel.dep.yml +35 -0
- package/.licenses/npm/undici.dep.yml +34 -0
- package/.markdown-lint.yml +24 -0
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc.yml +16 -0
- package/.vscode/launch.json +15 -0
- package/.yaml-lint.yml +14 -0
- package/CODEOWNERS +7 -0
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/action.yml +29 -0
- package/badges/coverage.svg +1 -0
- package/dist/action.js +31290 -0
- package/dist/action.js.map +1 -0
- package/dist/cli.js +95 -0
- package/eslint.config.mjs +81 -0
- package/jest.config.js +40 -0
- package/package.json +76 -0
- package/rollup.config.ts +30 -0
- package/script/release +133 -0
- package/src/action.ts +35 -0
- package/src/api.ts +46 -0
- package/src/cli.ts +84 -0
- package/src/git.ts +66 -0
- package/src/types/git.ts +5 -0
- package/src/types/parse-github-url.d.ts +12 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.eslint.json +17 -0
- package/tsconfig.json +11 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
|
|
4
|
+
async function requestAudit(url, token, github) {
|
|
5
|
+
const apiUrl = `${getApiBaseUrl()}/api/v1/audit`;
|
|
6
|
+
const response = await fetch(apiUrl, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
Authorization: `Bearer ${token}`
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
url,
|
|
14
|
+
github
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const errorText = await response.text();
|
|
19
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
20
|
+
}
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
function getApiBaseUrl() {
|
|
25
|
+
if (process.env.NODE_ENV === 'development' ||
|
|
26
|
+
process.env.GITHUB_ACTIONS !== 'true') {
|
|
27
|
+
return 'http://localhost:3000';
|
|
28
|
+
}
|
|
29
|
+
return 'https://xcelera.dev';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const options = {
|
|
33
|
+
url: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
required: true
|
|
36
|
+
},
|
|
37
|
+
token: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
required: true,
|
|
40
|
+
default: process.env.XCELERA_TOKEN
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const { positionals, values } = parseArgs({
|
|
44
|
+
options,
|
|
45
|
+
allowPositionals: true,
|
|
46
|
+
args: process.argv.slice(2)
|
|
47
|
+
});
|
|
48
|
+
const command = positionals[0];
|
|
49
|
+
if (!command) {
|
|
50
|
+
console.error('A command is required. Only "audit" is currently supported.');
|
|
51
|
+
printHelp();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
if (command === 'help') {
|
|
55
|
+
printHelp();
|
|
56
|
+
}
|
|
57
|
+
if (command !== 'audit') {
|
|
58
|
+
console.error('Invalid command. Only "audit" is currently supported.');
|
|
59
|
+
printHelp();
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const { url, token } = values;
|
|
63
|
+
if (!url) {
|
|
64
|
+
console.error('URL is required. Use --url <url> to specify the URL to audit.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
if (!token) {
|
|
68
|
+
console.error('A token is required. Use --token or set XCELERA_TOKEN environment variable.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// const githubContext = inferGitContext()
|
|
73
|
+
const githubContext = {
|
|
74
|
+
owner: 'xcelera-dev',
|
|
75
|
+
repo: 'app',
|
|
76
|
+
sha: 'fc0cb4434f8275ea67931f78dcd19f0f1c8f2366'
|
|
77
|
+
};
|
|
78
|
+
await requestAudit(url, token, githubContext);
|
|
79
|
+
console.log('✅ Audit scheduled successfully!');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
83
|
+
console.error(`❌ ${errorMessage}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
function printHelp() {
|
|
87
|
+
console.log('Usage: xcelera audit --url <url> [--token <token>]');
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log('Options:');
|
|
90
|
+
console.log(' --token <token> The xcelera API token to use for authentication.');
|
|
91
|
+
console.log('Can also be set with the XCELERA_TOKEN environment variable.');
|
|
92
|
+
console.log(' --url <url> The URL to audit.');
|
|
93
|
+
console.log('');
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// See: https://eslint.org/docs/latest/use/configure/configuration-files
|
|
2
|
+
|
|
3
|
+
import { fixupPluginRules } from '@eslint/compat'
|
|
4
|
+
import { FlatCompat } from '@eslint/eslintrc'
|
|
5
|
+
import js from '@eslint/js'
|
|
6
|
+
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
|
7
|
+
import tsParser from '@typescript-eslint/parser'
|
|
8
|
+
import _import from 'eslint-plugin-import'
|
|
9
|
+
import jest from 'eslint-plugin-jest'
|
|
10
|
+
import prettier from 'eslint-plugin-prettier'
|
|
11
|
+
import globals from 'globals'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
16
|
+
const __dirname = path.dirname(__filename)
|
|
17
|
+
const compat = new FlatCompat({
|
|
18
|
+
baseDirectory: __dirname,
|
|
19
|
+
recommendedConfig: js.configs.recommended,
|
|
20
|
+
allConfig: js.configs.all
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export default [
|
|
24
|
+
{
|
|
25
|
+
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules']
|
|
26
|
+
},
|
|
27
|
+
...compat.extends(
|
|
28
|
+
'eslint:recommended',
|
|
29
|
+
'plugin:@typescript-eslint/eslint-recommended',
|
|
30
|
+
'plugin:@typescript-eslint/recommended',
|
|
31
|
+
'plugin:jest/recommended',
|
|
32
|
+
'plugin:prettier/recommended'
|
|
33
|
+
),
|
|
34
|
+
{
|
|
35
|
+
plugins: {
|
|
36
|
+
import: fixupPluginRules(_import),
|
|
37
|
+
jest,
|
|
38
|
+
prettier,
|
|
39
|
+
'@typescript-eslint': typescriptEslint
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
languageOptions: {
|
|
43
|
+
globals: {
|
|
44
|
+
...globals.node,
|
|
45
|
+
...globals.jest,
|
|
46
|
+
Atomics: 'readonly',
|
|
47
|
+
SharedArrayBuffer: 'readonly'
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
parser: tsParser,
|
|
51
|
+
ecmaVersion: 2023,
|
|
52
|
+
sourceType: 'module',
|
|
53
|
+
|
|
54
|
+
parserOptions: {
|
|
55
|
+
project: ['tsconfig.eslint.json'],
|
|
56
|
+
tsconfigRootDir: '.'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
settings: {
|
|
61
|
+
'import/resolver': {
|
|
62
|
+
typescript: {
|
|
63
|
+
alwaysTryTypes: true,
|
|
64
|
+
project: 'tsconfig.eslint.json'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
rules: {
|
|
70
|
+
camelcase: 'off',
|
|
71
|
+
'eslint-comments/no-use': 'off',
|
|
72
|
+
'eslint-comments/no-unused-disable': 'off',
|
|
73
|
+
'i18n-text/no-en': 'off',
|
|
74
|
+
'import/no-namespace': 'off',
|
|
75
|
+
'no-console': 'off',
|
|
76
|
+
'no-shadow': 'off',
|
|
77
|
+
'no-unused-vars': 'off',
|
|
78
|
+
'prettier/prettier': 'error'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// See: https://jestjs.io/docs/configuration
|
|
2
|
+
|
|
3
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
|
4
|
+
export default {
|
|
5
|
+
clearMocks: true,
|
|
6
|
+
collectCoverage: true,
|
|
7
|
+
collectCoverageFrom: ['./src/**'],
|
|
8
|
+
coverageDirectory: './coverage',
|
|
9
|
+
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
10
|
+
coverageReporters: ['json-summary', 'text', 'lcov'],
|
|
11
|
+
// Uncomment the below lines if you would like to enforce a coverage threshold
|
|
12
|
+
// for your action. This will fail the build if the coverage is below the
|
|
13
|
+
// specified thresholds.
|
|
14
|
+
// coverageThreshold: {
|
|
15
|
+
// global: {
|
|
16
|
+
// branches: 100,
|
|
17
|
+
// functions: 100,
|
|
18
|
+
// lines: 100,
|
|
19
|
+
// statements: 100
|
|
20
|
+
// }
|
|
21
|
+
// },
|
|
22
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
23
|
+
moduleFileExtensions: ['ts', 'js'],
|
|
24
|
+
preset: 'ts-jest',
|
|
25
|
+
reporters: ['default'],
|
|
26
|
+
resolver: 'ts-jest-resolver',
|
|
27
|
+
testEnvironment: 'node',
|
|
28
|
+
testMatch: ['**/*.test.ts'],
|
|
29
|
+
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
|
|
30
|
+
transform: {
|
|
31
|
+
'^.+\\.ts$': [
|
|
32
|
+
'ts-jest',
|
|
33
|
+
{
|
|
34
|
+
tsconfig: 'tsconfig.eslint.json',
|
|
35
|
+
useESM: true
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
verbose: true
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xcelera/cli",
|
|
3
|
+
"description": "CLI for xcelera.dev",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"author": "",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/xcelera-dev/cli.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/xcelera-dev/cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"actions",
|
|
16
|
+
"xcelera",
|
|
17
|
+
"performance",
|
|
18
|
+
"lighthouse"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./dist/action.js"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"xcelera": "./dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"bundle": "npm run format:write && npm run package",
|
|
31
|
+
"ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest --passWithNoTests",
|
|
32
|
+
"coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg",
|
|
33
|
+
"format:write": "npx prettier --write .",
|
|
34
|
+
"format:check": "npx prettier --check .",
|
|
35
|
+
"lint": "npx eslint .",
|
|
36
|
+
"local-action": "npx @github/local-action . src/action.ts .env",
|
|
37
|
+
"package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
|
|
38
|
+
"package:watch": "npm run package -- --watch",
|
|
39
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest --passWithNoTests",
|
|
40
|
+
"all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@actions/core": "^1.11.1",
|
|
45
|
+
"parse-github-url": "^1.0.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/compat": "^1.3.1",
|
|
49
|
+
"@github/local-action": "^3.2.1",
|
|
50
|
+
"@jest/globals": "^30.0.4",
|
|
51
|
+
"@rollup/plugin-commonjs": "^28.0.6",
|
|
52
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
53
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
54
|
+
"@types/jest": "^30.0.0",
|
|
55
|
+
"@types/node": "^20.19.4",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
|
57
|
+
"@typescript-eslint/parser": "^8.32.1",
|
|
58
|
+
"eslint": "^9.30.1",
|
|
59
|
+
"eslint-config-prettier": "^10.1.5",
|
|
60
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
61
|
+
"eslint-plugin-import": "^2.32.0",
|
|
62
|
+
"eslint-plugin-jest": "^29.0.1",
|
|
63
|
+
"eslint-plugin-prettier": "^5.5.1",
|
|
64
|
+
"jest": "^30.0.4",
|
|
65
|
+
"make-coverage-badge": "^1.2.0",
|
|
66
|
+
"prettier": "^3.6.2",
|
|
67
|
+
"prettier-eslint": "^16.4.2",
|
|
68
|
+
"rollup": "^4.44.2",
|
|
69
|
+
"ts-jest": "^29.4.0",
|
|
70
|
+
"ts-jest-resolver": "^2.0.1",
|
|
71
|
+
"typescript": "^5.8.3"
|
|
72
|
+
},
|
|
73
|
+
"optionalDependencies": {
|
|
74
|
+
"@rollup/rollup-linux-x64-gnu": "*"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/rollup.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// See: https://rollupjs.org/introduction/
|
|
2
|
+
|
|
3
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
4
|
+
import nodeResolve from '@rollup/plugin-node-resolve'
|
|
5
|
+
import typescript from '@rollup/plugin-typescript'
|
|
6
|
+
|
|
7
|
+
const config = [
|
|
8
|
+
{
|
|
9
|
+
input: 'src/action.ts',
|
|
10
|
+
output: {
|
|
11
|
+
esModule: true,
|
|
12
|
+
file: 'dist/action.js',
|
|
13
|
+
format: 'es',
|
|
14
|
+
sourcemap: true
|
|
15
|
+
},
|
|
16
|
+
plugins: [typescript(), nodeResolve({ preferBuiltins: true }), commonjs()]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
input: 'src/cli.ts',
|
|
20
|
+
output: {
|
|
21
|
+
esModule: true,
|
|
22
|
+
file: 'dist/cli.js',
|
|
23
|
+
format: 'es',
|
|
24
|
+
sourcemap: true
|
|
25
|
+
},
|
|
26
|
+
plugins: [typescript(), nodeResolve({ preferBuiltins: true }), commonjs()]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export default config
|
package/script/release
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Exit early
|
|
4
|
+
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# About:
|
|
8
|
+
#
|
|
9
|
+
# This is a helper script to tag and push a new release. GitHub Actions use
|
|
10
|
+
# release tags to allow users to select a specific version of the action to use.
|
|
11
|
+
#
|
|
12
|
+
# See: https://github.com/actions/typescript-action#publishing-a-new-release
|
|
13
|
+
# See: https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations
|
|
14
|
+
#
|
|
15
|
+
# This script will do the following:
|
|
16
|
+
#
|
|
17
|
+
# 1. Retrieve the latest release tag
|
|
18
|
+
# 2. Display the latest release tag
|
|
19
|
+
# 3. Prompt the user for a new release tag
|
|
20
|
+
# 4. Validate the new release tag
|
|
21
|
+
# 5. Remind user to update the version field in package.json
|
|
22
|
+
# 6. Tag a new release
|
|
23
|
+
# 7. Set 'is_major_release' variable
|
|
24
|
+
# 8. Point separate major release tag (e.g. v1, v2) to the new release
|
|
25
|
+
# 9. Push the new tags (with commits, if any) to remote
|
|
26
|
+
# 10. If this is a major release, create a 'releases/v#' branch and push
|
|
27
|
+
#
|
|
28
|
+
# Usage:
|
|
29
|
+
#
|
|
30
|
+
# script/release
|
|
31
|
+
|
|
32
|
+
# Variables
|
|
33
|
+
semver_tag_regex='v[0-9]+\.[0-9]+\.[0-9]+$'
|
|
34
|
+
semver_tag_glob='v[0-9].[0-9].[0-9]*'
|
|
35
|
+
git_remote='origin'
|
|
36
|
+
major_semver_tag_regex='\(v[0-9]*\)'
|
|
37
|
+
|
|
38
|
+
# Terminal colors
|
|
39
|
+
OFF='\033[0m'
|
|
40
|
+
BOLD_RED='\033[1;31m'
|
|
41
|
+
BOLD_GREEN='\033[1;32m'
|
|
42
|
+
BOLD_BLUE='\033[1;34m'
|
|
43
|
+
BOLD_PURPLE='\033[1;35m'
|
|
44
|
+
BOLD_UNDERLINED='\033[1;4m'
|
|
45
|
+
BOLD='\033[1m'
|
|
46
|
+
|
|
47
|
+
# 1. Retrieve the latest release tag
|
|
48
|
+
if ! latest_tag=$(git describe --abbrev=0 --match="$semver_tag_glob"); then
|
|
49
|
+
# There are no existing release tags
|
|
50
|
+
echo -e "No tags found (yet) - Continue to create and push your first tag"
|
|
51
|
+
latest_tag="[unknown]"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# 2. Display the latest release tag
|
|
55
|
+
echo -e "The latest release tag is: ${BOLD_BLUE}${latest_tag}${OFF}"
|
|
56
|
+
|
|
57
|
+
# 3. Prompt the user for a new release tag
|
|
58
|
+
read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag
|
|
59
|
+
|
|
60
|
+
# 4. Validate the new release tag
|
|
61
|
+
if echo "$new_tag" | grep -q -E "$semver_tag_regex"; then
|
|
62
|
+
# Release tag is valid
|
|
63
|
+
echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF} is valid syntax"
|
|
64
|
+
else
|
|
65
|
+
# Release tag is not in `vX.X.X` format
|
|
66
|
+
echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF} is ${BOLD_RED}not valid${OFF} (must be in ${BOLD}vX.X.X${OFF} format)"
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# 5. Remind user to update the version field in package.json
|
|
71
|
+
echo -e -n "Make sure the version field in package.json is ${BOLD_BLUE}$new_tag${OFF}. Yes? [Y/${BOLD_UNDERLINED}n${OFF}] "
|
|
72
|
+
read -r YN
|
|
73
|
+
|
|
74
|
+
if [[ ! ($YN == "y" || $YN == "Y") ]]; then
|
|
75
|
+
# Package.json version field is not up to date
|
|
76
|
+
echo -e "Please update the package.json version to ${BOLD_PURPLE}$new_tag${OFF} and commit your changes"
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# 6. Tag a new release
|
|
81
|
+
git tag "$new_tag" --annotate --message "$new_tag Release"
|
|
82
|
+
echo -e "Tagged: ${BOLD_GREEN}$new_tag${OFF}"
|
|
83
|
+
|
|
84
|
+
# 7. Set 'is_major_release' variable
|
|
85
|
+
new_major_release_tag=$(expr "$new_tag" : "$major_semver_tag_regex")
|
|
86
|
+
|
|
87
|
+
if [[ "$latest_tag" = "[unknown]" ]]; then
|
|
88
|
+
# This is the first major release
|
|
89
|
+
is_major_release='yes'
|
|
90
|
+
else
|
|
91
|
+
# Compare the major version of the latest tag with the new tag
|
|
92
|
+
latest_major_release_tag=$(expr "$latest_tag" : "$major_semver_tag_regex")
|
|
93
|
+
|
|
94
|
+
if ! [[ "$new_major_release_tag" = "$latest_major_release_tag" ]]; then
|
|
95
|
+
is_major_release='yes'
|
|
96
|
+
else
|
|
97
|
+
is_major_release='no'
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# 8. Point separate major release tag (e.g. v1, v2) to the new release
|
|
102
|
+
if [ $is_major_release = 'yes' ]; then
|
|
103
|
+
# Create a new major version tag and point it to this release
|
|
104
|
+
git tag "$new_major_release_tag" --annotate --message "$new_major_release_tag Release"
|
|
105
|
+
echo -e "New major version tag: ${BOLD_GREEN}$new_major_release_tag${OFF}"
|
|
106
|
+
else
|
|
107
|
+
# Update the major version tag to point it to this release
|
|
108
|
+
git tag "$latest_major_release_tag" --force --annotate --message "Sync $latest_major_release_tag tag with $new_tag"
|
|
109
|
+
echo -e "Synced ${BOLD_GREEN}$latest_major_release_tag${OFF} with ${BOLD_GREEN}$new_tag${OFF}"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# 9. Push the new tags (with commits, if any) to remote
|
|
113
|
+
git push --follow-tags
|
|
114
|
+
|
|
115
|
+
if [ $is_major_release = 'yes' ]; then
|
|
116
|
+
# New major version tag is pushed with the '--follow-tags' flags
|
|
117
|
+
echo -e "Tags: ${BOLD_GREEN}$new_major_release_tag${OFF} and ${BOLD_GREEN}$new_tag${OFF} pushed to remote"
|
|
118
|
+
else
|
|
119
|
+
# Force push the updated major version tag
|
|
120
|
+
git push $git_remote "$latest_major_release_tag" --force
|
|
121
|
+
echo -e "Tags: ${BOLD_GREEN}$latest_major_release_tag${OFF} and ${BOLD_GREEN}$new_tag${OFF} pushed to remote"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# 10. If this is a major release, create a 'releases/v#' branch and push
|
|
125
|
+
if [ $is_major_release = 'yes' ]; then
|
|
126
|
+
git branch "releases/$new_major_release_tag" "$new_major_release_tag"
|
|
127
|
+
echo -e "Branch: ${BOLD_BLUE}releases/$new_major_release_tag${OFF} created from ${BOLD_BLUE}$new_major_release_tag${OFF} tag"
|
|
128
|
+
git push --set-upstream $git_remote "releases/$new_major_release_tag"
|
|
129
|
+
echo -e "Branch: ${BOLD_GREEN}releases/$new_major_release_tag${OFF} pushed to remote"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Completed
|
|
133
|
+
echo -e "${BOLD_GREEN}Done!${OFF}"
|
package/src/action.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as core from '@actions/core'
|
|
2
|
+
import * as github from '@actions/github'
|
|
3
|
+
import { requestAudit } from './api.js'
|
|
4
|
+
|
|
5
|
+
run()
|
|
6
|
+
|
|
7
|
+
async function run(): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
const url = core.getInput('url', { required: true })
|
|
10
|
+
const token = core.getInput('token', { required: true })
|
|
11
|
+
|
|
12
|
+
core.debug(`Auditing URL: ${url}`)
|
|
13
|
+
|
|
14
|
+
core.debug('Calling Xcelera audit API...')
|
|
15
|
+
const githubContext = {
|
|
16
|
+
owner: github.context.repo.owner,
|
|
17
|
+
repo: github.context.repo.repo,
|
|
18
|
+
sha: github.context.sha
|
|
19
|
+
}
|
|
20
|
+
const response = await requestAudit(url, token, githubContext)
|
|
21
|
+
|
|
22
|
+
// Set outputs
|
|
23
|
+
core.setOutput('auditId', response.auditId)
|
|
24
|
+
core.setOutput('status', response.status)
|
|
25
|
+
|
|
26
|
+
core.info(`✅ Audit scheduled successfully!`)
|
|
27
|
+
core.info(`Audit ID: ${response.auditId}`)
|
|
28
|
+
core.info(`Status: ${response.status}`)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const errorMessage =
|
|
31
|
+
error instanceof Error ? error.message : 'Unknown error occurred'
|
|
32
|
+
core.setFailed(errorMessage)
|
|
33
|
+
core.setOutput('status', 'failed')
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { GitContext } from './types/git.js'
|
|
2
|
+
|
|
3
|
+
export interface AuditResponse {
|
|
4
|
+
auditId: string
|
|
5
|
+
status: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function requestAudit(
|
|
9
|
+
url: string,
|
|
10
|
+
token: string,
|
|
11
|
+
github: GitContext
|
|
12
|
+
): Promise<AuditResponse> {
|
|
13
|
+
const apiUrl = `${getApiBaseUrl()}/api/v1/audit`
|
|
14
|
+
|
|
15
|
+
const response = await fetch(apiUrl, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
Authorization: `Bearer ${token}`
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
url,
|
|
23
|
+
github
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const errorText = await response.text()
|
|
29
|
+
throw new Error(
|
|
30
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await response.json()
|
|
35
|
+
return data as AuditResponse
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getApiBaseUrl(): string {
|
|
39
|
+
if (
|
|
40
|
+
process.env.NODE_ENV === 'development' ||
|
|
41
|
+
process.env.GITHUB_ACTIONS !== 'true'
|
|
42
|
+
) {
|
|
43
|
+
return 'http://localhost:3000'
|
|
44
|
+
}
|
|
45
|
+
return 'https://xcelera.dev'
|
|
46
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from 'node:util'
|
|
4
|
+
|
|
5
|
+
import { requestAudit } from './api.js'
|
|
6
|
+
import { inferGitContext } from './git.js'
|
|
7
|
+
|
|
8
|
+
const options = {
|
|
9
|
+
url: {
|
|
10
|
+
type: 'string' as const,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
token: {
|
|
14
|
+
type: 'string' as const,
|
|
15
|
+
required: true,
|
|
16
|
+
default: process.env.XCELERA_TOKEN
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { positionals, values } = parseArgs({
|
|
21
|
+
options,
|
|
22
|
+
allowPositionals: true,
|
|
23
|
+
args: process.argv.slice(2)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const command = positionals[0]
|
|
27
|
+
|
|
28
|
+
if (!command) {
|
|
29
|
+
console.error('A command is required. Only "audit" is currently supported.')
|
|
30
|
+
printHelp()
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === 'help') {
|
|
35
|
+
printHelp()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (command !== 'audit') {
|
|
39
|
+
console.error('Invalid command. Only "audit" is currently supported.')
|
|
40
|
+
printHelp()
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { url, token } = values
|
|
45
|
+
|
|
46
|
+
if (!url) {
|
|
47
|
+
console.error('URL is required. Use --url <url> to specify the URL to audit.')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!token) {
|
|
52
|
+
console.error(
|
|
53
|
+
'A token is required. Use --token or set XCELERA_TOKEN environment variable.'
|
|
54
|
+
)
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// const githubContext = inferGitContext()
|
|
60
|
+
const githubContext = {
|
|
61
|
+
owner: 'xcelera-dev',
|
|
62
|
+
repo: 'app',
|
|
63
|
+
sha: 'fc0cb4434f8275ea67931f78dcd19f0f1c8f2366'
|
|
64
|
+
}
|
|
65
|
+
await requestAudit(url, token, githubContext)
|
|
66
|
+
console.log('✅ Audit scheduled successfully!')
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage =
|
|
69
|
+
error instanceof Error ? error.message : 'Unknown error occurred'
|
|
70
|
+
console.error(`❌ ${errorMessage}`)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function printHelp() {
|
|
75
|
+
console.log('Usage: xcelera audit --url <url> [--token <token>]')
|
|
76
|
+
console.log('')
|
|
77
|
+
console.log('Options:')
|
|
78
|
+
console.log(
|
|
79
|
+
' --token <token> The xcelera API token to use for authentication.'
|
|
80
|
+
)
|
|
81
|
+
console.log('Can also be set with the XCELERA_TOKEN environment variable.')
|
|
82
|
+
console.log(' --url <url> The URL to audit.')
|
|
83
|
+
console.log('')
|
|
84
|
+
}
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import parseGithubUrl from 'parse-github-url'
|
|
3
|
+
import type { GitContext } from './types/git.js'
|
|
4
|
+
|
|
5
|
+
export function inferGitContext(): GitContext {
|
|
6
|
+
if (!isGitRepository()) {
|
|
7
|
+
throw new Error('Not git repository detected.')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const remoteUrl = getRemoteUrl()
|
|
11
|
+
|
|
12
|
+
const parsed = parseGithubUrl(remoteUrl)
|
|
13
|
+
if (!parsed || !parsed.owner || !parsed.repo) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Could not parse GitHub URL: ${remoteUrl}. Expected format: https://github.com/owner/repo or git@github.com:owner/repo`
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { owner, repo } = parsed
|
|
20
|
+
|
|
21
|
+
const sha = getCurrentSha()
|
|
22
|
+
|
|
23
|
+
// repo is parsed as owner/repo but we want to use just the repo name
|
|
24
|
+
const repoName = repo.replace(`${owner}/`, '')
|
|
25
|
+
|
|
26
|
+
return { owner, repo: repoName, sha }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isGitRepository(): boolean {
|
|
30
|
+
try {
|
|
31
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' })
|
|
32
|
+
return true
|
|
33
|
+
} catch {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getRemoteUrl(): string {
|
|
39
|
+
try {
|
|
40
|
+
const remoteUrl = execSync('git remote get-url origin', {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
stdio: 'pipe'
|
|
43
|
+
}).trim()
|
|
44
|
+
|
|
45
|
+
if (!remoteUrl) {
|
|
46
|
+
throw new Error('No origin remote found')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return remoteUrl
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'Could not determine git remote URL. Please ensure you have an origin remote configured.'
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getCurrentSha(): string {
|
|
58
|
+
try {
|
|
59
|
+
return execSync('git rev-parse HEAD', {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: 'pipe'
|
|
62
|
+
}).trim()
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error('Could not determine current commit SHA')
|
|
65
|
+
}
|
|
66
|
+
}
|