Last active
June 6, 2026 06:58
-
-
Save ArtDepartmentMJ/04da069c9df36a6db8e81a203f8460c5 to your computer and use it in GitHub Desktop.
release script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const VERSION = '1.0.5'; | |
| import { execSync } from 'child_process'; | |
| import { readFileSync } from 'fs'; | |
| import { resolve } from 'path'; | |
| import * as readline from 'readline/promises'; | |
| import { stdin as input, stdout as output } from 'process'; | |
| // Load .env from repo root | |
| try { | |
| const envPath = resolve(execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(), '.env'); | |
| const lines = readFileSync(envPath, 'utf8').split('\n'); | |
| for (const line of lines) { | |
| const trimmed = line.trim(); | |
| if (!trimmed || trimmed.startsWith('#')) continue; | |
| const eq = trimmed.indexOf('='); | |
| if (eq === -1) continue; | |
| const key = trimmed.slice(0, eq).trim(); | |
| const val = trimmed.slice(eq + 1).trim().replace(/^(['"])(.*)\1$/, '$2'); | |
| if (!(key in process.env)) process.env[key] = val; | |
| } | |
| } catch { | |
| // No .env file | |
| } | |
| // Parse CLI args: --message "..." --tag v1.0.5 --no-tag --push --no-push --deploy --no-deploy | |
| const args = process.argv.slice(2); | |
| const cli = {}; | |
| for (let i = 0; i < args.length; i++) { | |
| if (args[i] === '--message' || args[i] === '-m') cli.message = args[++i]; | |
| else if (args[i] === '--tag' || args[i] === '-t') cli.tag = args[++i]; | |
| else if (args[i] === '--no-tag') cli.noTag = true; | |
| else if (args[i] === '--push') cli.push = true; | |
| else if (args[i] === '--no-push') cli.push = false; | |
| else if (args[i] === '--deploy') cli.deploy = true; | |
| else if (args[i] === '--no-deploy') cli.deploy = false; | |
| } | |
| const nonInteractive = Object.keys(cli).length > 0; | |
| const rl = readline.createInterface({ input, output }); | |
| async function ask(question) { | |
| const answer = await rl.question(question); | |
| return answer.trim(); | |
| } | |
| function run(cmd, opts = {}) { | |
| return execSync(cmd, { stdio: opts.silent ? 'pipe' : 'inherit', encoding: 'utf8' }); | |
| } | |
| function runCapture(cmd) { | |
| return execSync(cmd, { stdio: 'pipe', encoding: 'utf8' }).trim(); | |
| } | |
| // Get current branch | |
| const branch = runCapture('git branch --show-current'); | |
| // Get latest tag and suggest next patch version | |
| let latestTag = ''; | |
| let suggestedTag = ''; | |
| try { | |
| latestTag = runCapture('git tag --sort=-v:refname | head -1'); | |
| const match = latestTag.match(/^(v?)(\d+)\.(\d+)\.(\d+)$/); | |
| if (match) { | |
| const [, prefix, major, minor, patch] = match; | |
| suggestedTag = `${prefix}${major}.${minor}.${parseInt(patch) + 1}`; | |
| } else { | |
| suggestedTag = latestTag; | |
| } | |
| } catch { | |
| // No tags yet | |
| } | |
| console.log(`\nBranch: ${branch}`); | |
| console.log(`Latest tag: ${latestTag || '(none)'}`); | |
| console.log('\n→ Building...'); | |
| run('npm run build'); | |
| console.log('\n→ Staging all files...'); | |
| run('git add -A'); | |
| const staged = runCapture('git diff --cached --name-only'); | |
| const hasChanges = !!staged; | |
| if (!hasChanges) { | |
| console.log('\nNothing to commit.'); | |
| } | |
| // Commit message (only if there's something to commit) | |
| let message = cli.message ?? ''; | |
| if (hasChanges && !message) { | |
| message = await ask('\nCommit message: '); | |
| } | |
| if (hasChanges && !message.trim()) { | |
| console.error('Commit message is required.'); | |
| rl.close(); | |
| process.exit(1); | |
| } | |
| // Tag | |
| let finalTag = null; | |
| if (!cli.noTag && cli.tag === undefined) { | |
| const tagYesNo = await ask('\nCreate a tag? [Y/n]: '); | |
| if (tagYesNo.toLowerCase() !== 'n') { | |
| const tagInput = await ask(suggestedTag ? `Tag [${suggestedTag}]: ` : 'Tag: '); | |
| finalTag = tagInput || suggestedTag || null; | |
| } | |
| } else if (!cli.noTag && cli.tag) { | |
| finalTag = cli.tag; | |
| } | |
| // Push | |
| let shouldPush; | |
| if (cli.push !== undefined) { | |
| shouldPush = cli.push; | |
| } else { | |
| const pushYesNo = await ask('\nPush to remote? [Y/n]: '); | |
| shouldPush = pushYesNo.toLowerCase() !== 'n'; | |
| } | |
| // Deploy | |
| const envoyer = process.env.ENVOYER_HOOK; | |
| const forgeToken = process.env.FORGE_API_TOKEN; | |
| const forgeServer = process.env.FORGE_SERVER_ID; | |
| const forgeSite = process.env.FORGE_SITE_ID; | |
| const canDeploy = shouldPush && (envoyer || (forgeToken && forgeServer && forgeSite)); | |
| let shouldDeploy = false; | |
| if (canDeploy) { | |
| if (cli.deploy !== undefined) { | |
| shouldDeploy = cli.deploy; | |
| } else { | |
| const deployYesNo = await ask('\nTrigger deployment? [Y/n]: '); | |
| shouldDeploy = deployYesNo.toLowerCase() !== 'n'; | |
| } | |
| } | |
| rl.close(); | |
| if (hasChanges) { | |
| console.log(`\n→ Committing: "${message}"`); | |
| run(`git commit -m ${JSON.stringify(message)}`); | |
| } | |
| if (finalTag) { | |
| console.log(`\n→ Tagging: ${finalTag}`); | |
| run(`git tag ${finalTag}`); | |
| } | |
| if (shouldPush) { | |
| console.log(`\n→ Pushing branch "${branch}"...`); | |
| try { | |
| run(`git push origin ${branch}`); | |
| } catch { | |
| run(`git push --set-upstream origin ${branch}`); | |
| } | |
| if (finalTag) { | |
| console.log('\n→ Pushing tags...'); | |
| run('git push --tags'); | |
| } | |
| } | |
| if (shouldDeploy) { | |
| if (envoyer) { | |
| console.log('\n→ Triggering deployment via Envoyer...'); | |
| await fetch(`${envoyer}?branch=${encodeURIComponent(branch)}`, { method: 'POST' }); | |
| console.log(' Done.'); | |
| } else { | |
| console.log('\n→ Triggering deployment via Forge...'); | |
| const res = await fetch(`https://forge.laravel.com/api/v1/servers/${forgeServer}/sites/${forgeSite}/deployment/deploy`, { | |
| method: 'POST', | |
| headers: { Authorization: `Bearer ${forgeToken}`, Accept: 'application/json' }, | |
| }); | |
| if (!res.ok) { | |
| const body = await res.text(); | |
| console.error(` Forge API error ${res.status}: ${body}`); | |
| } else { | |
| console.log(' Done.'); | |
| } | |
| } | |
| } | |
| console.log(`\nDone.${finalTag ? ` Released ${finalTag}` : ''}`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment