From aef6be4339ee916735715b6c5dd44e3458a80b18 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:13:43 -0700 Subject: [PATCH 1/5] Update Dependencies and Configs --- .github/workflows/labeler.yaml | 47 +++++++++ .github/workflows/lint.yaml | 4 +- .prettierignore | 1 + .prettierrc.json | 3 +- dist/index.js | 177 +++++++++++++++------------------ eslint.config.mjs | 36 +++---- package-lock.json | 138 ++++--------------------- package.json | 16 +-- src/index.js | 129 +++++++++++------------- src/portainer.js | 12 +-- 10 files changed, 235 insertions(+), 328 deletions(-) create mode 100644 .github/workflows/labeler.yaml diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml new file mode 100644 index 0000000..e2212d8 --- /dev/null +++ b/.github/workflows/labeler.yaml @@ -0,0 +1,47 @@ +name: "PR Labeler" + +on: + pull_request_target: + +permissions: + pull-requests: write + +jobs: + labeler: + name: "Labeler" + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: "Checkout Configs" + uses: actions/checkout@v5 + with: + repository: cssnr/configs + ref: master + path: .configs + sparse-checkout-cone-mode: false + sparse-checkout: | + labels/** + + - name: "Debug" + continue-on-error: true + run: | + echo "::group::labels.yaml" + cat .configs/labels/labels.yaml + echo "::endgroup::" + + echo "::group::labeler.yaml" + cat .configs/labels/labeler.yaml + echo "::endgroup::" + + - name: "Label Creator" + continue-on-error: true + uses: cssnr/label-creator-action@master + with: + file: .configs/labels/labels.yaml + + - name: "Labeler" + uses: actions/labeler@v6 + with: + sync-labels: true + configuration-path: .configs/labels/labeler.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index bfd0732..e87af07 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,10 +20,10 @@ jobs: - name: "Checkout" uses: actions/checkout@v5 - - name: "Setup Node 22" + - name: "Setup Node 24" uses: actions/setup-node@v5 with: - node-version: 22 + node-version: 24 - name: "Install" id: install diff --git a/.prettierignore b/.prettierignore index 3d8988d..cd26c30 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,7 @@ eslint_report.json # Build dist/ node_modules/ +.github/disabled/ # Files .github/pull_request_template.md diff --git a/.prettierrc.json b/.prettierrc.json index 34dae84..4541eb0 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,6 +2,7 @@ "trailingComma": "es5", "semi": false, "singleQuote": true, + "printWidth": 90, "overrides": [ { "files": ["**/*.html", "**/*.yaml", "**/*.yml"], @@ -10,7 +11,7 @@ } }, { - "files": ["**/*.js", "**/*.css", "**/*.scss"], + "files": ["**/*.js", "**/*.mjs", "**/*.css", "**/*.scss"], "options": { "tabWidth": 4 } diff --git a/dist/index.js b/dist/index.js index 94743a5..d401f97 100644 --- a/dist/index.js +++ b/dist/index.js @@ -30740,9 +30740,7 @@ class Portainer { * @return {Promise} */ async getSwarm(endpointId) { - const response = await this.client.get( - `/endpoints/${endpointId}/docker/swarm` - ) + const response = await this.client.get(`/endpoints/${endpointId}/docker/swarm`) return response.data } @@ -30763,11 +30761,9 @@ class Portainer { * @return {Promise} */ async updateStackRepo(stackID, endpointId, body) { - const response = await this.client.put( - `/stacks/${stackID}/git/redeploy`, - body, - { params: { endpointId } } - ) + const response = await this.client.put(`/stacks/${stackID}/git/redeploy`, body, { + params: { endpointId }, + }) return response.data } @@ -32733,7 +32729,7 @@ module.exports = parseParams /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -/*! Axios v1.12.0 Copyright (c) 2025 Matt Zabriskie and contributors */ +/*! Axios v1.12.2 Copyright (c) 2025 Matt Zabriskie and contributors */ const FormData$1 = __nccwpck_require__(6454); @@ -33116,10 +33112,8 @@ function merge(/* obj1, obj2, obj3, ... */) { result[targetKey] = merge({}, val); } else if (isArray(val)) { result[targetKey] = val.slice(); - } else { - if (!skipUndefined || !isUndefined(val)) { - result[targetKey] = val; - } + } else if (!skipUndefined || !isUndefined(val)) { + result[targetKey] = val; } }; @@ -34887,7 +34881,7 @@ function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { return requestedURL; } -const VERSION = "1.12.0"; +const VERSION = "1.12.2"; function parseProtocol(url) { const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); @@ -36667,9 +36661,9 @@ const DEFAULT_CHUNK_SIZE = 64 * 1024; const {isFunction} = utils$1; -const globalFetchAPI = (({fetch, Request, Response}) => ({ - fetch, Request, Response - }))(utils$1.global); +const globalFetchAPI = (({Request, Response}) => ({ + Request, Response +}))(utils$1.global); const { ReadableStream: ReadableStream$1, TextEncoder: TextEncoder$1 @@ -36685,8 +36679,12 @@ const test = (fn, ...args) => { }; const factory = (env) => { - const {fetch, Request, Response} = Object.assign({}, globalFetchAPI, env); - const isFetchSupported = isFunction(fetch); + env = utils$1.merge.call({ + skipUndefined: true + }, globalFetchAPI, env); + + const {fetch: envFetch, Request, Response} = env; + const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === 'function'; const isRequestSupported = isFunction(Request); const isResponseSupported = isFunction(Response); @@ -36789,6 +36787,8 @@ const factory = (env) => { fetchOptions } = resolveConfig(config); + let _fetch = envFetch || fetch; + responseType = responseType ? (responseType + '').toLowerCase() : 'text'; let composedSignal = composeSignals$1([signal, cancelToken && cancelToken.toAbortSignal()], timeout); @@ -36848,7 +36848,7 @@ const factory = (env) => { request = isRequestSupported && new Request(url, resolvedOptions); - let response = await (isRequestSupported ? fetch(request, fetchOptions) : fetch(url, resolvedOptions)); + let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions)); const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); @@ -36911,12 +36911,8 @@ const factory = (env) => { const seedCache = new Map(); const getFetch = (config) => { - let env = utils$1.merge.call({ - skipUndefined: true - }, globalFetchAPI, config ? config.env : null); - + let env = config ? config.env : {}; const {fetch, Request, Response} = env; - const seeds = [ Request, Response, fetch ]; @@ -37341,8 +37337,6 @@ class Axios { let newConfig = config; - i = 0; - while (i < len) { const onFulfilled = requestInterceptorChain[i++]; const onRejected = requestInterceptorChain[i++]; @@ -37792,23 +37786,19 @@ const Portainer = __nccwpck_require__(1055) try { core.info('🏳️ Portainer Stack Deploy Action') - // Parse Config - const config = getConfig() - core.startGroup('Parsed Config') - console.log('config:', config) - core.endGroup() // Config + // Parse Inputs + const inputs = getInputs() + core.startGroup('Parsed Inputs') + console.log('inputs:', inputs) + core.endGroup() // Inputs - if (!['repo', 'file'].includes(config.type)) { - core.setFailed(`Unknown type: ${config.type}. Values: [repo, file]`) + if (!['repo', 'file'].includes(inputs.type)) { + core.setFailed(`Unknown type: ${inputs.type}. Values: [repo, file]`) return } // Check Portainer - const portainer = new Portainer( - config.url, - config.token, - config.headers - ) + const portainer = new Portainer(inputs.url, inputs.token, inputs.headers) const version = await portainer.getVersion() const versionString = `${version.ServerVersion} ${version.VersionSupport} ${version.ServerEdition}` core.startGroup(`Portainer Version: \u001b[34m${versionString}`) @@ -37816,7 +37806,7 @@ const Portainer = __nccwpck_require__(1055) console.log(version) core.endGroup() // Portainer Version - if (config.fs_path) { + if (inputs.fs_path) { if (version.ServerEdition !== 'EE') { core.setFailed('Relative path only supported in Portainer EE!') return @@ -37824,7 +37814,7 @@ const Portainer = __nccwpck_require__(1055) } // Set Variables - let endpointID = parseInt(config.endpoint) + let endpointID = parseInt(inputs.endpoint) if (!endpointID) { const endpoints = await portainer.getEndpoints() // console.log('endpoints:', endpoints) @@ -37836,7 +37826,7 @@ const Portainer = __nccwpck_require__(1055) core.info(`endpointID: \u001b[36m${endpointID}`) let swarmID = null - if (!config.standalone) { + if (!inputs.standalone) { const swarm = await portainer.getSwarm(endpointID) // console.log('swarm:', swarm) swarmID = swarm.ID @@ -37847,57 +37837,50 @@ const Portainer = __nccwpck_require__(1055) const stacks = await portainer.getStacks() // console.log('stacks:', stacks) let stack = stacks.find( - (item) => - item.Name === config.name && item.EndpointId === endpointID + (item) => item.Name === inputs.name && item.EndpointId === endpointID ) // console.log('stack:', stack) let stackID = stack?.Id core.info(`stackID: \u001b[36m${stackID}`) // Update Environment - const env = getEnv(config, stack) + const env = getEnv(inputs, stack) // Perform Deploy - if (config.type === 'repo') { + if (inputs.type === 'repo') { core.info('🌐 Performing Repository Deployment') - const repositoryAuthentication = !!( - config.username || config.password - ) + const repositoryAuthentication = !!(inputs.username || inputs.password) if (stackID) { core.info(`Stack Found - Updating Stack ID: ${stack.Id}`) const body = { env, - prune: config.prune, - pullImage: config.pull, - repositoryReferenceName: config.ref, + prune: inputs.prune, + pullImage: inputs.pull, + repositoryReferenceName: inputs.ref, repositoryAuthentication, - repositoryPassword: config.password, - repositoryUsername: config.username, + repositoryPassword: inputs.password, + repositoryUsername: inputs.username, } // console.log('body:', body) - stack = await portainer.updateStackRepo( - stackID, - endpointID, - body - ) + stack = await portainer.updateStackRepo(stackID, endpointID, body) // console.log('stack:', stack) core.info(`Updated Stack ${stack.Id}: ${stack.Name}`) } else { core.info('Stack NOT Found - Deploying NEW Stack') const body = { - name: config.name, + name: inputs.name, swarmID, - repositoryURL: config.repo, - composeFile: config.file, + repositoryURL: inputs.repo, + composeFile: inputs.file, env, - tlsskipVerify: config.tlsskip, - repositoryReferenceName: config.ref, + tlsskipVerify: inputs.tlsskip, + repositoryReferenceName: inputs.ref, repositoryAuthentication, - repositoryPassword: config.password, - repositoryUsername: config.username, - ...(config.fs_path && { + repositoryPassword: inputs.password, + repositoryUsername: inputs.username, + ...(inputs.fs_path && { supportRelativePath: true, - fileSystemPath: config.fs_path, + fileSystemPath: inputs.fs_path, }), } // console.log('body:', body) @@ -37905,29 +37888,25 @@ const Portainer = __nccwpck_require__(1055) // console.log('stack:', stack) core.info(`Deployed Stack: ${stack.Id}: ${stack.Name}`) } - } else if (config.type === 'file') { + } else if (inputs.type === 'file') { core.info('📄 Performing Stack File Deployment') - const stackFileContent = fs.readFileSync(config.file, 'utf-8') + const stackFileContent = fs.readFileSync(inputs.file, 'utf-8') if (stackID) { core.info(`Stack Found - Updating Stack ID: ${stackID}`) const body = { env, - prune: config.prune, - pullImage: config.pull, + prune: inputs.prune, + pullImage: inputs.pull, stackFileContent, } // console.log('body:', body) - stack = await portainer.updateStackString( - stackID, - endpointID, - body - ) + stack = await portainer.updateStackString(stackID, endpointID, body) // console.log('stack:', stack) core.info(`Updated Stack ${stack.Id}: ${stack.Name}`) } else { core.info(' Stack NOT Found - Deploying NEW Stack') const body = { - name: config.name, + name: inputs.name, swarmID, stackFileContent, env, @@ -37946,10 +37925,10 @@ const Portainer = __nccwpck_require__(1055) core.setOutput('endpointID', endpointID) // Summary - if (config.summary) { + if (inputs.summary) { core.info('📝 Writing Job Summary') try { - await addSummary(config, stack) + await addSummary(inputs, stack) } catch (e) { console.log(e) core.error(`Error writing Job Summary ${e.message}`) @@ -37966,30 +37945,30 @@ const Portainer = __nccwpck_require__(1055) /** * @function getEnv - * @param {Config} config + * @param {Inputs} inputs * @param {Object} stack * @return {Object[]} Portainer formatted environment */ -function getEnv(config, stack) { - if (!config.env_json && !config.env_file) { +function getEnv(inputs, stack) { + if (!inputs.env_json && !inputs.env_file) { return stack?.env ? stack.env : [] } const env = {} - if (config.merge_env && stack?.Env?.length) { + if (inputs.merge_env && stack?.Env?.length) { console.log('🔁 Merging Environment with Current') const current = Object.fromEntries( stack.Env.map(({ name, value }) => [name, value]) ) Object.assign(env, current) } - if (config.env_json) { - let data = JSON.parse(config.env_json) + if (inputs.env_json) { + let data = JSON.parse(inputs.env_json) for (const [name, value] of Object.entries(data)) { env[name] = value } } - if (config.env_file) { - let data = dotenv.config({ path: config.env_file }) + if (inputs.env_file) { + let data = dotenv.config({ path: inputs.env_file }) for (const [name, value] of Object.entries(data.parsed)) { env[name] = value } @@ -38003,11 +37982,11 @@ function getEnv(config, stack) { /** * Add Job Summary - * @param {Config} config + * @param {Inputs} inputs * @param {Object} stack * @return {Promise} */ -async function addSummary(config, stack) { +async function addSummary(inputs, stack) { core.summary.addRaw(`## Portainer Stack Deploy Action\n`) const action = stack.UpdateDate ? '**Updated** Existing' : '**Created** New' core.summary.addRaw(`🎉 ${action} Stack ${stack.Id}: \`${stack.Name}\`\n\n`) @@ -38041,12 +38020,12 @@ async function addSummary(config, stack) { ]) core.summary.addRaw('\n') - delete config.token - delete config.env_json - const yaml = Object.entries(config) + delete inputs.token + delete inputs.env_json + const yaml = Object.entries(inputs) .map(([k, v]) => `${k}: ${JSON.stringify(v)}`) .join('\n') - core.summary.addRaw('
Config') + core.summary.addRaw('
Inputs') core.summary.addCodeBlock(yaml, 'yaml') core.summary.addRaw('
\n') @@ -38059,13 +38038,13 @@ async function addSummary(config, stack) { } /** - * Get Config - * @typedef {Object} Config + * Get Inputs + * @typedef {object} Inputs * @property {string} token * @property {string} url * @property {string} name * @property {string} file - * @property {string | undefined} endpoint + * @property {string|undefined} endpoint * @property {string} ref * @property {string} repo * @property {boolean} tlsskip @@ -38081,9 +38060,9 @@ async function addSummary(config, stack) { * @property {string|undefined} fs_path * @property {object} headers * @property {boolean} summary - * @return {Config} + * @return {Inputs} */ -function getConfig() { +function getInputs() { return { token: core.getInput('token', { required: true }), url: core.getInput('url', { required: true }), diff --git a/eslint.config.mjs b/eslint.config.mjs index e0092c0..74cf080 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,25 +1,21 @@ import js from '@eslint/js' -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' export default [ - js.configs.recommended, - eslintPluginPrettierRecommended, - { - languageOptions: { - ecmaVersion: 'latest', - sourceType: 'module', + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + settings: { + env: { + node: true, + es2021: true, + }, + }, + rules: { + 'no-undef': 'off', + 'no-extra-semi': 'off', + }, }, - settings: { - env: { - browser: true, - es2021: true, - jquery: true, - webextensions: true, - }, - }, - rules: { - 'no-undef': 'off', - 'no-extra-semi': 'off', - }, - }, ] diff --git a/package-lock.json b/package-lock.json index 4dd388a..6984c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,13 @@ "name": "portainer-stack-deploy-action", "dependencies": { "@actions/core": "^1.11.1", - "axios": "^1.12.0", + "axios": "^1.12.2", "dotenv": "^17.2.2" }, "devDependencies": { - "@eslint/js": "^9.35.0", - "@vercel/ncc": "^0.38.3", - "eslint": "^9.35.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", + "@eslint/js": "^9.36.0", + "@vercel/ncc": "^0.38.4", + "eslint": "^9.36.0", "prettier": "^3.6.2" } }, @@ -159,9 +157,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", "dev": true, "license": "MIT", "engines": { @@ -256,19 +254,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -284,9 +269,9 @@ "license": "MIT" }, "node_modules/@vercel/ncc": { - "version": "0.38.3", - "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", - "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz", + "integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==", "dev": true, "license": "MIT", "bin": { @@ -363,9 +348,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz", - "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -486,9 +471,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -604,9 +589,9 @@ } }, "node_modules/eslint": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -616,7 +601,7 @@ "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.35.0", + "@eslint/js": "9.36.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -664,53 +649,6 @@ } } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -812,13 +750,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1367,19 +1298,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1455,22 +1373,6 @@ "node": ">=8" } }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", diff --git a/package.json b/package.json index 89e3e59..2a6a92d 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ { "name": "portainer-stack-deploy-action", + "private": true, + "main": "dist/index.js", "scripts": { "build": "ncc build src/index.js", "build:watch": "npm run build -- --watch", "lint": "npx eslint src", "lint:report": "npm run lint -- --output-file eslint_report.json --format json", - "prettier": "npx prettier --write .", - "prettier:check": "npx prettier --check ." + "prettier:check": "npx prettier --check .", + "prettier:write": "npx prettier --write ." }, "dependencies": { "@actions/core": "^1.11.1", - "axios": "^1.12.0", + "axios": "^1.12.2", "dotenv": "^17.2.2" }, "devDependencies": { - "@eslint/js": "^9.35.0", - "@vercel/ncc": "^0.38.3", - "eslint": "^9.35.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", + "@eslint/js": "^9.36.0", + "@vercel/ncc": "^0.38.4", + "eslint": "^9.36.0", "prettier": "^3.6.2" } } diff --git a/src/index.js b/src/index.js index ef188a3..d82201f 100644 --- a/src/index.js +++ b/src/index.js @@ -8,23 +8,19 @@ const Portainer = require('./portainer') try { core.info('🏳️ Portainer Stack Deploy Action') - // Parse Config - const config = getConfig() - core.startGroup('Parsed Config') - console.log('config:', config) - core.endGroup() // Config + // Parse Inputs + const inputs = getInputs() + core.startGroup('Parsed Inputs') + console.log('inputs:', inputs) + core.endGroup() // Inputs - if (!['repo', 'file'].includes(config.type)) { - core.setFailed(`Unknown type: ${config.type}. Values: [repo, file]`) + if (!['repo', 'file'].includes(inputs.type)) { + core.setFailed(`Unknown type: ${inputs.type}. Values: [repo, file]`) return } // Check Portainer - const portainer = new Portainer( - config.url, - config.token, - config.headers - ) + const portainer = new Portainer(inputs.url, inputs.token, inputs.headers) const version = await portainer.getVersion() const versionString = `${version.ServerVersion} ${version.VersionSupport} ${version.ServerEdition}` core.startGroup(`Portainer Version: \u001b[34m${versionString}`) @@ -32,7 +28,7 @@ const Portainer = require('./portainer') console.log(version) core.endGroup() // Portainer Version - if (config.fs_path) { + if (inputs.fs_path) { if (version.ServerEdition !== 'EE') { core.setFailed('Relative path only supported in Portainer EE!') return @@ -40,7 +36,7 @@ const Portainer = require('./portainer') } // Set Variables - let endpointID = parseInt(config.endpoint) + let endpointID = parseInt(inputs.endpoint) if (!endpointID) { const endpoints = await portainer.getEndpoints() // console.log('endpoints:', endpoints) @@ -52,7 +48,7 @@ const Portainer = require('./portainer') core.info(`endpointID: \u001b[36m${endpointID}`) let swarmID = null - if (!config.standalone) { + if (!inputs.standalone) { const swarm = await portainer.getSwarm(endpointID) // console.log('swarm:', swarm) swarmID = swarm.ID @@ -63,57 +59,50 @@ const Portainer = require('./portainer') const stacks = await portainer.getStacks() // console.log('stacks:', stacks) let stack = stacks.find( - (item) => - item.Name === config.name && item.EndpointId === endpointID + (item) => item.Name === inputs.name && item.EndpointId === endpointID ) // console.log('stack:', stack) let stackID = stack?.Id core.info(`stackID: \u001b[36m${stackID}`) // Update Environment - const env = getEnv(config, stack) + const env = getEnv(inputs, stack) // Perform Deploy - if (config.type === 'repo') { + if (inputs.type === 'repo') { core.info('🌐 Performing Repository Deployment') - const repositoryAuthentication = !!( - config.username || config.password - ) + const repositoryAuthentication = !!(inputs.username || inputs.password) if (stackID) { core.info(`Stack Found - Updating Stack ID: ${stack.Id}`) const body = { env, - prune: config.prune, - pullImage: config.pull, - repositoryReferenceName: config.ref, + prune: inputs.prune, + pullImage: inputs.pull, + repositoryReferenceName: inputs.ref, repositoryAuthentication, - repositoryPassword: config.password, - repositoryUsername: config.username, + repositoryPassword: inputs.password, + repositoryUsername: inputs.username, } // console.log('body:', body) - stack = await portainer.updateStackRepo( - stackID, - endpointID, - body - ) + stack = await portainer.updateStackRepo(stackID, endpointID, body) // console.log('stack:', stack) core.info(`Updated Stack ${stack.Id}: ${stack.Name}`) } else { core.info('Stack NOT Found - Deploying NEW Stack') const body = { - name: config.name, + name: inputs.name, swarmID, - repositoryURL: config.repo, - composeFile: config.file, + repositoryURL: inputs.repo, + composeFile: inputs.file, env, - tlsskipVerify: config.tlsskip, - repositoryReferenceName: config.ref, + tlsskipVerify: inputs.tlsskip, + repositoryReferenceName: inputs.ref, repositoryAuthentication, - repositoryPassword: config.password, - repositoryUsername: config.username, - ...(config.fs_path && { + repositoryPassword: inputs.password, + repositoryUsername: inputs.username, + ...(inputs.fs_path && { supportRelativePath: true, - fileSystemPath: config.fs_path, + fileSystemPath: inputs.fs_path, }), } // console.log('body:', body) @@ -121,29 +110,25 @@ const Portainer = require('./portainer') // console.log('stack:', stack) core.info(`Deployed Stack: ${stack.Id}: ${stack.Name}`) } - } else if (config.type === 'file') { + } else if (inputs.type === 'file') { core.info('📄 Performing Stack File Deployment') - const stackFileContent = fs.readFileSync(config.file, 'utf-8') + const stackFileContent = fs.readFileSync(inputs.file, 'utf-8') if (stackID) { core.info(`Stack Found - Updating Stack ID: ${stackID}`) const body = { env, - prune: config.prune, - pullImage: config.pull, + prune: inputs.prune, + pullImage: inputs.pull, stackFileContent, } // console.log('body:', body) - stack = await portainer.updateStackString( - stackID, - endpointID, - body - ) + stack = await portainer.updateStackString(stackID, endpointID, body) // console.log('stack:', stack) core.info(`Updated Stack ${stack.Id}: ${stack.Name}`) } else { core.info(' Stack NOT Found - Deploying NEW Stack') const body = { - name: config.name, + name: inputs.name, swarmID, stackFileContent, env, @@ -162,10 +147,10 @@ const Portainer = require('./portainer') core.setOutput('endpointID', endpointID) // Summary - if (config.summary) { + if (inputs.summary) { core.info('📝 Writing Job Summary') try { - await addSummary(config, stack) + await addSummary(inputs, stack) } catch (e) { console.log(e) core.error(`Error writing Job Summary ${e.message}`) @@ -182,30 +167,30 @@ const Portainer = require('./portainer') /** * @function getEnv - * @param {Config} config + * @param {Inputs} inputs * @param {Object} stack * @return {Object[]} Portainer formatted environment */ -function getEnv(config, stack) { - if (!config.env_json && !config.env_file) { +function getEnv(inputs, stack) { + if (!inputs.env_json && !inputs.env_file) { return stack?.env ? stack.env : [] } const env = {} - if (config.merge_env && stack?.Env?.length) { + if (inputs.merge_env && stack?.Env?.length) { console.log('🔁 Merging Environment with Current') const current = Object.fromEntries( stack.Env.map(({ name, value }) => [name, value]) ) Object.assign(env, current) } - if (config.env_json) { - let data = JSON.parse(config.env_json) + if (inputs.env_json) { + let data = JSON.parse(inputs.env_json) for (const [name, value] of Object.entries(data)) { env[name] = value } } - if (config.env_file) { - let data = dotenv.config({ path: config.env_file }) + if (inputs.env_file) { + let data = dotenv.config({ path: inputs.env_file }) for (const [name, value] of Object.entries(data.parsed)) { env[name] = value } @@ -219,11 +204,11 @@ function getEnv(config, stack) { /** * Add Job Summary - * @param {Config} config + * @param {Inputs} inputs * @param {Object} stack * @return {Promise} */ -async function addSummary(config, stack) { +async function addSummary(inputs, stack) { core.summary.addRaw(`## Portainer Stack Deploy Action\n`) const action = stack.UpdateDate ? '**Updated** Existing' : '**Created** New' core.summary.addRaw(`🎉 ${action} Stack ${stack.Id}: \`${stack.Name}\`\n\n`) @@ -257,12 +242,12 @@ async function addSummary(config, stack) { ]) core.summary.addRaw('
\n') - delete config.token - delete config.env_json - const yaml = Object.entries(config) + delete inputs.token + delete inputs.env_json + const yaml = Object.entries(inputs) .map(([k, v]) => `${k}: ${JSON.stringify(v)}`) .join('\n') - core.summary.addRaw('
Config') + core.summary.addRaw('
Inputs') core.summary.addCodeBlock(yaml, 'yaml') core.summary.addRaw('
\n') @@ -275,13 +260,13 @@ async function addSummary(config, stack) { } /** - * Get Config - * @typedef {Object} Config + * Get Inputs + * @typedef {object} Inputs * @property {string} token * @property {string} url * @property {string} name * @property {string} file - * @property {string | undefined} endpoint + * @property {string|undefined} endpoint * @property {string} ref * @property {string} repo * @property {boolean} tlsskip @@ -297,9 +282,9 @@ async function addSummary(config, stack) { * @property {string|undefined} fs_path * @property {object} headers * @property {boolean} summary - * @return {Config} + * @return {Inputs} */ -function getConfig() { +function getInputs() { return { token: core.getInput('token', { required: true }), url: core.getInput('url', { required: true }), diff --git a/src/portainer.js b/src/portainer.js index 7a21f12..be8444a 100644 --- a/src/portainer.js +++ b/src/portainer.js @@ -50,9 +50,7 @@ class Portainer { * @return {Promise} */ async getSwarm(endpointId) { - const response = await this.client.get( - `/endpoints/${endpointId}/docker/swarm` - ) + const response = await this.client.get(`/endpoints/${endpointId}/docker/swarm`) return response.data } @@ -73,11 +71,9 @@ class Portainer { * @return {Promise} */ async updateStackRepo(stackID, endpointId, body) { - const response = await this.client.put( - `/stacks/${stackID}/git/redeploy`, - body, - { params: { endpointId } } - ) + const response = await this.client.put(`/stacks/${stackID}/git/redeploy`, body, { + params: { endpointId }, + }) return response.data } From e3f2cfed246f8d68817b2a745c3024068f989fb0 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:26:51 -0700 Subject: [PATCH 2/5] Update README.md --- .github/pull_request_template.md | 1 - README.md | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 034d22b..8ee3a8a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,6 @@ - ## Checklist - [ ] Verify the Required Checks are Passing diff --git a/README.md b/README.md index 00cce06..3e258b6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitHub Tag Major](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*&logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/tags) -[![GitHub Tag Minor](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*.*&logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/tags) +[![GitHub Tag Minor](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*.*&logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/releases) [![GitHub Release Version](https://img.shields.io/github/v/release/cssnr/portainer-stack-deploy-action?logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) -[![GitHub Dist Size](https://img.shields.io/github/size/cssnr/portainer-stack-deploy-action/dist%2Findex.js?logo=github&label=dist%20size)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src/index.js) +[![GitHub Dist Size](https://img.shields.io/github/size/cssnr/portainer-stack-deploy-action/dist%2Findex.js?logo=github&label=dist%20size)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src) [![Workflow Release](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/release.yaml?logo=cachet&label=release)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/release.yaml) [![Workflow Test](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/test.yaml?logo=cachet&label=test)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/test.yaml) [![Workflow Lint](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/lint.yaml?logo=cachet&label=lint)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/lint.yaml) @@ -526,6 +526,8 @@ Additionally, you can support other GitHub Actions I have published: - [Docker Tags Action](https://github.com/cssnr/docker-tags-action?tab=readme-ov-file#readme) - [Package Changelog Action](https://github.com/cssnr/package-changelog-action?tab=readme-ov-file#readme) - [NPM Outdated Check Action](https://github.com/cssnr/npm-outdated-action?tab=readme-ov-file#readme) +- [Label Creator Action](https://github.com/cssnr/label-creator-action?tab=readme-ov-file#readme) - [Algolia Crawler Action](https://github.com/cssnr/algolia-crawler-action?tab=readme-ov-file#readme) +- [Upload Release Action](https://github.com/cssnr/upload-release-action?tab=readme-ov-file#readme) For a full list of current projects visit: [https://cssnr.github.io/](https://cssnr.github.io/) From 3af3d0a70e4ba21e5bb928191388835af7482f3d Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:31:19 -0700 Subject: [PATCH 3/5] Lint --- dist/index.js | 22 +++++++++++++++++++--- src/index.js | 4 ++-- src/portainer.js | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/dist/index.js b/dist/index.js index d401f97..14c2199 100644 --- a/dist/index.js +++ b/dist/index.js @@ -30689,7 +30689,7 @@ module.exports = { /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { const axios = __nccwpck_require__(7269) -const https = __nccwpck_require__(5692) +const https = __nccwpck_require__(4708) class Portainer { /** @@ -30964,6 +30964,22 @@ module.exports = require("node:events"); /***/ }), +/***/ 3024: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:fs"); + +/***/ }), + +/***/ 4708: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:https"); + +/***/ }), + /***/ 7075: /***/ ((module) => { @@ -37777,7 +37793,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec /************************************************************************/ var __webpack_exports__ = {}; const core = __nccwpck_require__(7484) -const fs = __nccwpck_require__(9896) +const fs = __nccwpck_require__(3024) const dotenv = __nccwpck_require__(8889) const Portainer = __nccwpck_require__(1055) @@ -37814,7 +37830,7 @@ const Portainer = __nccwpck_require__(1055) } // Set Variables - let endpointID = parseInt(inputs.endpoint) + let endpointID = Number.parseInt(inputs.endpoint) if (!endpointID) { const endpoints = await portainer.getEndpoints() // console.log('endpoints:', endpoints) diff --git a/src/index.js b/src/index.js index d82201f..52b3ed8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ const core = require('@actions/core') -const fs = require('fs') +const fs = require('node:fs') const dotenv = require('dotenv') const Portainer = require('./portainer') @@ -36,7 +36,7 @@ const Portainer = require('./portainer') } // Set Variables - let endpointID = parseInt(inputs.endpoint) + let endpointID = Number.parseInt(inputs.endpoint) if (!endpointID) { const endpoints = await portainer.getEndpoints() // console.log('endpoints:', endpoints) diff --git a/src/portainer.js b/src/portainer.js index be8444a..24ec486 100644 --- a/src/portainer.js +++ b/src/portainer.js @@ -1,5 +1,5 @@ const axios = require('axios') -const https = require('https') +const https = require('node:https') class Portainer { /** From bc766b8ade464cac8465d3eee67db6bf50bcef11 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 25 Sep 2025 01:00:55 -0700 Subject: [PATCH 4/5] Update README.md --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3e258b6..df1b531 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Docs Last Commit](https://img.shields.io/github/last-commit/cssnr/portainer-stack-deploy-docs?logo=vitepress&logoColor=white&label=docs)](https://portainer-deploy.cssnr.com/) [![GitHub Contributors](https://img.shields.io/github/contributors/cssnr/portainer-stack-deploy-action?logo=github)](https://github.com/cssnr/portainer-stack-deploy-action/graphs/contributors) [![GitHub Repo Size](https://img.shields.io/github/repo-size/cssnr/portainer-stack-deploy-action?logo=bookstack&logoColor=white&label=repo%20size)](https://github.com/cssnr/portainer-stack-deploy-action?tab=readme-ov-file#readme) -[![GitHub Top Language](https://img.shields.io/github/languages/top/cssnr/portainer-stack-deploy-action?logo=htmx)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src/index.js) +[![GitHub Top Language](https://img.shields.io/github/languages/top/cssnr/portainer-stack-deploy-action?logo=htmx)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src) [![GitHub Forks](https://img.shields.io/github/forks/cssnr/portainer-stack-deploy-action?style=flat&logo=github)](https://github.com/cssnr/portainer-stack-deploy-action/forks) [![GitHub Discussions](https://img.shields.io/github/discussions/cssnr/portainer-stack-deploy-action?logo=github)](https://github.com/cssnr/portainer-stack-deploy-action/discussions) [![GitHub Repo Stars](https://img.shields.io/github/stars/cssnr/portainer-stack-deploy-action?style=flat&logo=github)](https://github.com/cssnr/portainer-stack-deploy-action/stargazers) @@ -25,8 +25,8 @@ - [Inputs](#Inputs) - [Outputs](#Outputs) - [Examples](#Examples) -- [Tags](#Tags) - [Troubleshooting](#Troubleshooting) +- [Tags](#Tags) - [Support](#Support) - [Contributing](#Contributing) @@ -448,21 +448,6 @@ jobs: For more examples, you can check out other projects using this action: https://github.com/cssnr/portainer-stack-deploy-action/network/dependents -## Tags - -The following rolling [tags](https://github.com/cssnr/portainer-stack-deploy-action/tags) are maintained. - -| Version Tag | Rolling | Bugs | Feat. | Name | Target | Example | -| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----: | :--: | :---: | :-------: | :------: | :------- | -| [![GitHub Tag Major](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*&style=for-the-badge&label=%20&color=44cc10)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ✅ | ✅ | ✅ | **Major** | `vN.x.x` | `vN` | -| [![GitHub Tag Minor](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*.*&style=for-the-badge&label=%20&color=blue)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ✅ | ✅ | ❌ | **Minor** | `vN.N.x` | `vN.N` | -| [![GitHub Release](https://img.shields.io/github/v/release/cssnr/portainer-stack-deploy-action?style=for-the-badge&label=%20&color=red)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ❌ | ❌ | ❌ | **Micro** | `vN.N.N` | `vN.N.N` | - -You can view the release notes for each version on the [releases](https://github.com/cssnr/portainer-stack-deploy-action/releases) page. - -The **Major** tag is recommended. It is the most up-to-date and always backwards compatible. -Breaking changes would result in a **Major** version bump. At a minimum you should use a **Minor** tag. - ## Troubleshooting - No such image: ghcr.io/user/repo-name:tag @@ -485,6 +470,21 @@ Permissions documentation for [Workflows](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token) and [Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication). +## Tags + +The following rolling [tags](https://github.com/cssnr/portainer-stack-deploy-action/tags) are maintained. + +| Version Tag | Rolling | Bugs | Feat. | Name | Target | Example | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----: | :--: | :---: | :-------: | :------: | :------- | +| [![GitHub Tag Major](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*&style=for-the-badge&label=%20&color=44cc10)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ✅ | ✅ | ✅ | **Major** | `vN.x.x` | `vN` | +| [![GitHub Tag Minor](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*.*&style=for-the-badge&label=%20&color=blue)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ✅ | ✅ | ❌ | **Minor** | `vN.N.x` | `vN.N` | +| [![GitHub Release](https://img.shields.io/github/v/release/cssnr/portainer-stack-deploy-action?style=for-the-badge&label=%20&color=red)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) | ❌ | ❌ | ❌ | **Micro** | `vN.N.N` | `vN.N.N` | + +You can view the release notes for each version on the [releases](https://github.com/cssnr/portainer-stack-deploy-action/releases) page. + +The **Major** tag is recommended. It is the most up-to-date and always backwards compatible. +Breaking changes would result in a **Major** version bump. At a minimum you should use a **Minor** tag. + # Support For general help or to request a feature, see: From efe395aad43e8775fcbc1f21fbedb2a01fdc1235 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 25 Sep 2025 01:16:05 -0700 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df1b531..6c279b9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitHub Tag Major](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*&logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/tags) [![GitHub Tag Minor](https://img.shields.io/github/v/tag/cssnr/portainer-stack-deploy-action?sort=semver&filter=!v*.*.*&logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/releases) [![GitHub Release Version](https://img.shields.io/github/v/release/cssnr/portainer-stack-deploy-action?logo=git&logoColor=white&labelColor=585858&label=%20)](https://github.com/cssnr/portainer-stack-deploy-action/releases/latest) -[![GitHub Dist Size](https://img.shields.io/github/size/cssnr/portainer-stack-deploy-action/dist%2Findex.js?logo=github&label=dist%20size)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src) +[![GitHub Dist Size](https://img.shields.io/github/size/cssnr/portainer-stack-deploy-action/dist%2Findex.js?logo=bookstack&logoColor=white&label=dist%20size)](https://github.com/cssnr/portainer-stack-deploy-action/blob/master/src) [![Workflow Release](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/release.yaml?logo=cachet&label=release)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/release.yaml) [![Workflow Test](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/test.yaml?logo=cachet&label=test)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/test.yaml) [![Workflow Lint](https://img.shields.io/github/actions/workflow/status/cssnr/portainer-stack-deploy-action/lint.yaml?logo=cachet&label=lint)](https://github.com/cssnr/portainer-stack-deploy-action/actions/workflows/lint.yaml)