feat(release): add FTP deploy script and env vars; add npm run release:push
This commit is contained in:
		
					parent
					
						
							
								77b5f8fb5a
							
						
					
				
			
			
				commit
				
					
						7348316409
					
				
			
		
					 3 changed files with 129 additions and 1 deletions
				
			
		
							
								
								
									
										10
									
								
								.env.example
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								.env.example
									
										
									
									
									
								
							| 
						 | 
					@ -3,3 +3,13 @@
 | 
				
			||||||
AMO_JWT_ISSUER=your-amo-jwt-issuer
 | 
					AMO_JWT_ISSUER=your-amo-jwt-issuer
 | 
				
			||||||
AMO_JWT_SECRET=your-amo-jwt-secret
 | 
					AMO_JWT_SECRET=your-amo-jwt-secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# FTP deploy (used by npm run release:push)
 | 
				
			||||||
 | 
					# Protocol: ftp (default) or ftps
 | 
				
			||||||
 | 
					FTP_PROTOCOL=ftp
 | 
				
			||||||
 | 
					FTP_HOST=your-ftp-host.example.com
 | 
				
			||||||
 | 
					# Port for ftp/ftps (21 for FTP/explicit FTPS, 990 for implicit FTPS)
 | 
				
			||||||
 | 
					FTP_PORT=21
 | 
				
			||||||
 | 
					FTP_USER=your-ftp-username
 | 
				
			||||||
 | 
					FTP_PASS=your-ftp-password
 | 
				
			||||||
 | 
					# Remote directory to upload signed artifacts (e.g., /addons/archive-org-link-grabber/)
 | 
				
			||||||
 | 
					FTP_REMOTE_DIR=/path/on/server
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,8 @@
 | 
				
			||||||
    "release:prepare:patch": "npm version patch && node scripts/sync-version.js",
 | 
					    "release:prepare:patch": "npm version patch && node scripts/sync-version.js",
 | 
				
			||||||
    "release:prepare:minor": "npm version minor && node scripts/sync-version.js",
 | 
					    "release:prepare:minor": "npm version minor && node scripts/sync-version.js",
 | 
				
			||||||
    "release:prepare:major": "npm version major && node scripts/sync-version.js",
 | 
					    "release:prepare:major": "npm version major && node scripts/sync-version.js",
 | 
				
			||||||
    "release:sign": "node scripts/release-sign.js"
 | 
					    "release:sign": "node scripts/release-sign.js",
 | 
				
			||||||
 | 
					    "release:push": "node scripts/release-push.js"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "web-ext": "^8.3.0"
 | 
					    "web-ext": "^8.3.0"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										117
									
								
								scripts/release-push.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								scripts/release-push.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,117 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env node
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Uploads the latest signed release artifacts in releases/<version>/ via FTP/FTPS using curl.
 | 
				
			||||||
 | 
					 * Reads configuration from .env (auto-loaded) or process.env.
 | 
				
			||||||
 | 
					 * Env vars:
 | 
				
			||||||
 | 
					 *   FTP_PROTOCOL=ftp|ftps (default: ftp)
 | 
				
			||||||
 | 
					 *   FTP_HOST=example.com (required)
 | 
				
			||||||
 | 
					 *   FTP_PORT=21 (optional)
 | 
				
			||||||
 | 
					 *   FTP_USER=username (required)
 | 
				
			||||||
 | 
					 *   FTP_PASS=password (required)
 | 
				
			||||||
 | 
					 *   FTP_REMOTE_DIR=/remote/path (required)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					const { execSync } = require('child_process');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const root = path.join(__dirname, '..');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Auto-load .env if present
 | 
				
			||||||
 | 
					(() => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const envPath = path.join(root, '.env');
 | 
				
			||||||
 | 
					    if (fs.existsSync(envPath)) {
 | 
				
			||||||
 | 
					      const content = fs.readFileSync(envPath, 'utf8');
 | 
				
			||||||
 | 
					      for (const rawLine of content.split(/\r?\n/)) {
 | 
				
			||||||
 | 
					        const line = rawLine.trim();
 | 
				
			||||||
 | 
					        if (!line || line.startsWith('#')) continue;
 | 
				
			||||||
 | 
					        const cleaned = line.startsWith('export ')
 | 
				
			||||||
 | 
					          ? line.slice('export '.length).trim()
 | 
				
			||||||
 | 
					          : line;
 | 
				
			||||||
 | 
					        const eq = cleaned.indexOf('=');
 | 
				
			||||||
 | 
					        if (eq === -1) continue;
 | 
				
			||||||
 | 
					        const key = cleaned.slice(0, eq).trim();
 | 
				
			||||||
 | 
					        let val = cleaned.slice(eq + 1).trim();
 | 
				
			||||||
 | 
					        if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
 | 
				
			||||||
 | 
					          val = val.slice(1, -1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!(key in process.env)) process.env[key] = val;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      console.log('Loaded environment from .env');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.warn('Warning: Failed to load .env:', e.message);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function mask(str) {
 | 
				
			||||||
 | 
					  if (!str) return str;
 | 
				
			||||||
 | 
					  return '***';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function run(cmd, secrets = []) {
 | 
				
			||||||
 | 
					  let shown = cmd;
 | 
				
			||||||
 | 
					  for (const s of secrets) {
 | 
				
			||||||
 | 
					    if (s) shown = shown.split(String(s)).join('***');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  console.log('> ' + shown);
 | 
				
			||||||
 | 
					  execSync(cmd, { stdio: 'inherit' });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pkg = require(path.join(root, 'package.json'));
 | 
				
			||||||
 | 
					const version = pkg.version;
 | 
				
			||||||
 | 
					if (!version) {
 | 
				
			||||||
 | 
					  console.error('package.json is missing version');
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const artifactsDir = path.join(root, 'releases', version);
 | 
				
			||||||
 | 
					if (!fs.existsSync(artifactsDir) || !fs.statSync(artifactsDir).isDirectory()) {
 | 
				
			||||||
 | 
					  console.error(`Artifacts directory not found: ${artifactsDir}. Run npm run release:sign first.`);
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const files = fs.readdirSync(artifactsDir).filter(f => fs.statSync(path.join(artifactsDir, f)).isFile());
 | 
				
			||||||
 | 
					if (files.length === 0) {
 | 
				
			||||||
 | 
					  console.error(`No files found in ${artifactsDir}`);
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const protocol = (process.env.FTP_PROTOCOL || 'ftp').toLowerCase();
 | 
				
			||||||
 | 
					const host = process.env.FTP_HOST;
 | 
				
			||||||
 | 
					const port = process.env.FTP_PORT;
 | 
				
			||||||
 | 
					const user = process.env.FTP_USER;
 | 
				
			||||||
 | 
					const pass = process.env.FTP_PASS;
 | 
				
			||||||
 | 
					let remoteDir = process.env.FTP_REMOTE_DIR || '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!host || !user || !pass || !remoteDir) {
 | 
				
			||||||
 | 
					  console.error('Missing FTP config. Required: FTP_HOST, FTP_USER, FTP_PASS, FTP_REMOTE_DIR');
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Normalize remoteDir
 | 
				
			||||||
 | 
					if (!remoteDir.startsWith('/')) remoteDir = '/' + remoteDir;
 | 
				
			||||||
 | 
					if (remoteDir.endsWith('/')) remoteDir = remoteDir.slice(0, -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Construct base URL
 | 
				
			||||||
 | 
					const baseUrl = `${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(`Uploading ${files.length} file(s) from ${artifactsDir} to ${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}/`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (const file of files) {
 | 
				
			||||||
 | 
					  const localPath = path.join(artifactsDir, file);
 | 
				
			||||||
 | 
					  const url = `${baseUrl}/${encodeURIComponent(file)}`;
 | 
				
			||||||
 | 
					  // --ftp-create-dirs ensures remote dirs are created; --fail fails on server errors.
 | 
				
			||||||
 | 
					  const cmd = [
 | 
				
			||||||
 | 
					    'curl',
 | 
				
			||||||
 | 
					    '--fail',
 | 
				
			||||||
 | 
					    '--ftp-create-dirs',
 | 
				
			||||||
 | 
					    `--user`, `${user}:${pass}`,
 | 
				
			||||||
 | 
					    '--upload-file', JSON.stringify(localPath),
 | 
				
			||||||
 | 
					    JSON.stringify(url),
 | 
				
			||||||
 | 
					  ].join(' ');
 | 
				
			||||||
 | 
					  run(cmd, [user, pass]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log('Upload complete.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue