| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- #!/usr/bin/env node
- /**
- * Nightly publish script for VS Code extension
- * Converts package.json to testing version, packages, publishes, and restores
- *
- * This script:
- * 1. Backs up the original package.json
- * 2. Updates package.json with:
- * - New version (major.minor.timestamp format)
- * - Changes name to "cline-nightly"
- * - Changes displayName to "Cline (Nightly)"
- * 3. Packages the extension as a .vsix file
- * 4. Publishes to VS Code Marketplace (if VSCE_PAT is set)
- * 5. Publishes to OpenVSX Registry (if OVSX_PAT is set)
- * 6. Restores the original package.json
- *
- * Usage:
- * npm run publish:marketplace:nightly
- * npm run publish:marketplace:nightly -- --dry-run
- *
- * Environment variables:
- * VSCE_PAT - Personal Access Token for VS Code Marketplace
- * OVSX_PAT - Personal Access Token for OpenVSX Registry
- *
- * Dependencies:
- * - vsce (VS Code Extension Manager)
- * - ovsx (OpenVSX CLI)
- */
- import { execFileSync, execSync } from "node:child_process"
- import fs from "node:fs"
- import path from "node:path"
- import { fileURLToPath } from "node:url"
- // Get __dirname equivalent in ES modules
- const __filename = fileURLToPath(import.meta.url)
- const __dirname = path.dirname(__filename)
- // ANSI color codes for console output
- const colors = {
- reset: "\x1b[0m",
- red: "\x1b[31m",
- green: "\x1b[32m",
- yellow: "\x1b[33m",
- }
- // Logging utilities
- const log = {
- info: (msg) => console.log(`${colors.green}[INFO]${colors.reset} ${msg}`),
- warn: (msg) => console.log(`${colors.yellow}[WARN]${colors.reset} ${msg}`),
- error: (msg) => console.error(`${colors.red}[ERROR]${colors.reset} ${msg}`),
- }
- // Configuration
- const config = {
- // The name and display name for the nightly version
- nightlyName: "cline-nightly",
- nightlyDisplayName: "Cline (Nightly)",
- projectRoot: path.join(__dirname, ".."),
- get packageJsonPath() {
- return path.join(this.projectRoot, "package.json")
- },
- get packageBackupPath() {
- return path.join(this.projectRoot, "package.json.backup")
- },
- get distDir() {
- return path.join(this.projectRoot, "dist")
- },
- get vsixPath() {
- return path.join(this.distDir, "cline-nightly.vsix")
- },
- }
- // Utility class for managing the publish process
- class NightlyPublisher {
- constructor() {
- this.originalPackageJson = null
- this.hasBackup = false
- }
- /**
- * Check if required dependencies are installed
- */
- checkDependencies() {
- const dependencies = [
- { name: "vsce", check: "vsce --version" },
- { name: "npx", check: "npx --version" },
- ]
- const missing = []
- for (const dep of dependencies) {
- try {
- execSync(dep.check, { stdio: "ignore" })
- } catch {
- missing.push(dep.name)
- }
- }
- if (missing.length > 0) {
- throw new Error(
- `Missing required dependencies: ${missing.join(", ")}. Please install them before running this script.`,
- )
- }
- log.info("All dependencies are installed")
- }
- /**
- * Check if a command exists
- */
- commandExists(command) {
- try {
- execSync(`which ${command}`, { stdio: "ignore" })
- return true
- } catch {
- return false
- }
- }
- /**
- * Create backup of package.json
- */
- backupPackageJson() {
- if (!fs.existsSync(config.packageJsonPath)) {
- throw new Error(`package.json not found at ${config.packageJsonPath}`)
- }
- log.info("Backing up original package.json")
- this.originalPackageJson = fs.readFileSync(config.packageJsonPath, "utf-8")
- fs.writeFileSync(config.packageBackupPath, this.originalPackageJson)
- this.hasBackup = true
- }
- /**
- * Restore original package.json
- */
- restorePackageJson() {
- if (this.hasBackup && fs.existsSync(config.packageBackupPath)) {
- log.info("Restoring original package.json")
- fs.writeFileSync(config.packageJsonPath, this.originalPackageJson)
- fs.unlinkSync(config.packageBackupPath)
- this.hasBackup = false
- }
- }
- /**
- * Generate new version with timestamp
- * Format: major.minor.timestamp
- */
- generateVersion(currentVersion) {
- // Extract major.minor from current version (e.g., "3.27.1" -> "3.27")
- const versionParts = currentVersion.split(".")
- if (versionParts.length < 2) {
- throw new Error(`Invalid version format: ${currentVersion}`)
- }
- const major = versionParts[0]
- const minor = versionParts[1]
- const timestamp = Math.floor(Date.now() / 1000)
- return `${major}.${minor}.${timestamp}`
- }
- /**
- * Update package.json with nightly configuration
- */
- updatePackageJson() {
- // Replace any occurrences cline. or claude-dev with nightly name
- const rawContent = fs.readFileSync(config.packageJsonPath, "utf-8")
- const content = rawContent.replaceAll("claude-dev", config.nightlyName).replaceAll('"cline.', `"${config.nightlyName}.`)
- const pkg = JSON.parse(content)
- const currentVersion = pkg.version
- if (!currentVersion) {
- throw new Error("Could not read version from package.json")
- }
- log.info(`Current version: ${currentVersion}`)
- const newVersion = this.generateVersion(currentVersion)
- log.info(`New version: ${newVersion}`)
- // Update package.json fields
- pkg.version = newVersion
- pkg.name = config.nightlyName
- pkg.displayName = config.nightlyDisplayName
- pkg.contributes.viewsContainers.activitybar.title = config.nightlyDisplayName
- // Save updated package.json
- log.info("Updating package.json for nightly build")
- fs.writeFileSync(config.packageJsonPath, JSON.stringify(pkg, null, "\t"))
- return newVersion
- }
- /**
- * Package the extension
- */
- packageExtension() {
- // Ensure dist directory exists
- if (!fs.existsSync(config.distDir)) {
- fs.mkdirSync(config.distDir, { recursive: true })
- }
- log.info("Packaging extension")
- const args = [
- "package",
- "--pre-release",
- "--no-update-package-json",
- "--no-git-tag-version",
- "--allow-package-secrets",
- "sendgrid",
- "--out",
- config.vsixPath,
- ]
- try {
- execFileSync("vsce", args, {
- stdio: "inherit",
- cwd: config.projectRoot,
- })
- log.info(`Package created: ${config.vsixPath}`)
- } catch (error) {
- throw new Error(`Failed to package extension: ${error.message}`)
- }
- }
- /**
- * Publish to VS Code Marketplace
- */
- publishToVSCodeMarketplace() {
- const token = process.env.VSCE_PAT
- if (!token) {
- log.warn("VSCE_PAT not set, skipping VS Code Marketplace publish")
- return false
- }
- log.info("Publishing to VS Code Marketplace")
- const args = ["publish", "--pre-release", "--no-git-tag-version", "--packagePath", config.vsixPath]
- try {
- execFileSync("vsce", args, {
- env: { ...process.env, VSCE_PAT: token },
- stdio: "inherit",
- cwd: config.projectRoot,
- })
- log.info("Successfully published to VS Code Marketplace")
- return true
- } catch (error) {
- throw new Error(`Failed to publish to VS Code Marketplace: ${error.message}`)
- }
- }
- /**
- * Publish to OpenVSX Registry
- */
- publishToOpenVSX() {
- const token = process.env.OVSX_PAT
- if (!token) {
- log.warn("OVSX_PAT not set, skipping OpenVSX Registry publish")
- return false
- }
- log.info("Publishing to OpenVSX Registry")
- const args = ["ovsx", "publish", "--pre-release", "--packagePath", config.vsixPath, "--pat", token]
- try {
- execFileSync("npx", args, {
- stdio: "inherit",
- cwd: config.projectRoot,
- })
- log.info("Successfully published to OpenVSX Registry")
- return true
- } catch (error) {
- throw new Error(`Failed to publish to OpenVSX Registry: ${error.message}`)
- }
- }
- /**
- * Main execution flow
- */
- async run(isDryRun = false) {
- try {
- log.info(`Starting nightly publish process${isDryRun ? " (dry run)" : ""}`)
- // Step 1: Check dependencies
- this.checkDependencies()
- // Step 2: Backup package.json
- this.backupPackageJson()
- // Step 3: Update package.json
- const newVersion = this.updatePackageJson()
- // Step 4: Package extension
- this.packageExtension()
- // Step 5: Publish to marketplaces (skip if dry run)
- let vsCodePublished = false
- let openVSXPublished = false
- if (isDryRun) {
- log.info("Dry run mode: Skipping marketplace publishing")
- } else {
- vsCodePublished = this.publishToVSCodeMarketplace()
- openVSXPublished = this.publishToOpenVSX()
- }
- // Summary
- log.info(`Nightly publish process completed successfully${isDryRun ? " (dry run)" : ""}`)
- log.info(`Package created for v${newVersion}: ${config.vsixPath}`)
- if (!isDryRun && !vsCodePublished && !openVSXPublished) {
- log.warn("Extension was packaged but not published to any marketplace")
- log.warn("Set VSCE_PAT and/or OVSX_PAT environment variables to enable publishing")
- }
- } catch (error) {
- log.error(`Publish failed: ${error.message}`)
- process.exit(1)
- } finally {
- // Always restore package.json
- this.restorePackageJson()
- }
- }
- }
- // Handle cleanup on process exit
- const publisher = new NightlyPublisher()
- process.on("exit", () => {
- publisher.restorePackageJson()
- })
- process.on("SIGINT", () => {
- log.info("\nInterrupted, cleaning up...")
- publisher.restorePackageJson()
- process.exit(130)
- })
- process.on("SIGTERM", () => {
- log.info("\nTerminated, cleaning up...")
- publisher.restorePackageJson()
- process.exit(143)
- })
- // Parse command line arguments
- const args = process.argv.slice(2)
- const isDryRun = args.includes("--dry-run") || args.includes("-n")
- const showHelp = args.includes("--help") || args.includes("-h")
- if (showHelp) {
- console.log(`
- Nightly publish script for VS Code extension
- Usage:
- npm run publish:marketplace:nightly [options]
- Options:
- --dry-run, -n Run without actually publishing (package only)
- --help, -h Show this help message
- Environment variables:
- VSCE_PAT Personal Access Token for VS Code Marketplace
- OVSX_PAT Personal Access Token for OpenVSX Registry
- Examples:
- npm run publish:marketplace:nightly # Full publish
- npm run publish:marketplace:nightly -- --dry-run # Package only
- VSCE_PAT="token" npm run publish:marketplace:nightly # Publish to VS Code only
- `)
- process.exit(0)
- }
- // Run the publisher
- publisher.run(isDryRun).catch((error) => {
- log.error(error.message)
- process.exit(1)
- })
|