diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9fac983 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +name: Release Drop Plugin + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: true + +jobs: + release: + name: Build, validate, and publish .dropplugin + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # actions/checkout@v7.0.1 + + - name: Set up Node.js 22 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # actions/setup-node@v7.0.0 + with: + node-version: "22" + cache: npm + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Build + run: npm run build + + - name: Test + run: npm test + + - name: Validate manifest + run: npx --no-install drop-plugin validate . + + - name: Pack plugin archive + run: npx --no-install drop-plugin pack . ./dist-package + + - name: Generate checksums + id: pkg + run: | + FILE=$(find dist-package -name "*.dropplugin" -print -quit) + echo "path=$FILE" >> "$GITHUB_OUTPUT" + echo "name=$(basename "$FILE")" >> "$GITHUB_OUTPUT" + sha256sum "$FILE" > "$FILE.sha256" + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + files: | + ${{ steps.pkg.outputs.path }} + ${{ steps.pkg.outputs.path }}.sha256 + name: ${{ steps.pkg.outputs.name }} + prerelease: ${{ contains(github.ref_name, '-') }} + generate_release_notes: true diff --git a/.sdk-scope.json b/.sdk-scope.json new file mode 100644 index 0000000..654e37d --- /dev/null +++ b/.sdk-scope.json @@ -0,0 +1,5 @@ +{ + "sdk": "@droposs", + "sdkVersion": "^0.5.9", + "cliVersion": "^0.5.9" +} diff --git a/package-lock.json b/package-lock.json index fe842f4..b1327be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@droposs/plugin-sdk": "^0.4.0" + "@droposs/plugin-sdk": "^0.5.9" }, "devDependencies": { - "@droposs/plugin-cli": "^0.4.0", + "@droposs/plugin-cli": "^0.5.9", "@eslint/js": "^10.0.1", "@types/node": "^22.0.0", "eslint": "^10.9.1", @@ -45,13 +45,13 @@ } }, "node_modules/@droposs/plugin-cli": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@droposs/plugin-cli/-/plugin-cli-0.4.0.tgz", - "integrity": "sha512-3gF/Z3Klf3CLBCtLpPdrzX46G/R+tkMQ9EYtUODD8aRm99jh/dkfalIxVCPQXsTGOph/06UbmJQa2gw9mZ/lag==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@droposs/plugin-cli/-/plugin-cli-0.5.9.tgz", + "integrity": "sha512-EMUaeAkJhw+8Qz5mpQx0jf3hBBtGOlqMPziCnTQm1ZyvgC7DYt1VpqgzHz8O/wCK8UHQHJAFi7kKv2Tk+CSCEg==", "dev": true, "license": "MIT", "dependencies": { - "@droposs/plugin-sdk": "0.4.0", + "@droposs/plugin-sdk": "0.5.9", "ajv": "^8.20.0", "esbuild": "^0.28.2" }, @@ -60,9 +60,9 @@ } }, "node_modules/@droposs/plugin-sdk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@droposs/plugin-sdk/-/plugin-sdk-0.4.0.tgz", - "integrity": "sha512-BN9qhONnZPURAzvK0qCnO++5Ie5VenIY6G0kJvP141WfJI/72+a/2ojuKQsGKchSIYo9qnVIHrdmkMB3+GnzhQ==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@droposs/plugin-sdk/-/plugin-sdk-0.5.9.tgz", + "integrity": "sha512-HK3sF964yHtssE0B3vbV+9p4D0L8mg6BJ3cFO7uadYCKkpWw582DVTM0FAJ+CPPSn3xYwZVDxomXTjc/Zmuomg==", "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { diff --git a/package.json b/package.json index bc16977..399e2cd 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "lint": "eslint ." }, "dependencies": { - "@droposs/plugin-sdk": "file:../drop-plugin-sdk/packages/plugin-sdk" + "@droposs/plugin-sdk": "^0.5.9" }, "devDependencies": { - "@droposs/plugin-cli": "file:../drop-plugin-sdk/packages/plugin-cli", + "@droposs/plugin-cli": "^0.5.9", "typescript": "^7.0.2", "@types/node": "^22.0.0", "@eslint/js": "^10.0.1", diff --git a/scripts/switch-sdk-scope.mjs b/scripts/switch-sdk-scope.mjs new file mode 100644 index 0000000..4b6e8a8 --- /dev/null +++ b/scripts/switch-sdk-scope.mjs @@ -0,0 +1,132 @@ +#!/usr/bin/env node + +import { readdirSync, readFileSync, statSync, writeFileSync, existsSync } from "node:fs"; +import path from "node:path"; +import { execSync } from "node:child_process"; + +const LEGACY_SCOPE = "@droposs"; +const NEW_SCOPE = "@drop-oss"; +const SKIP_DIRS = new Set(["node_modules", ".git", "dist-package", "dist-packages"]); +const LOCKFILES = new Set(["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "deno.lock"]); +const TEXT_EXT = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".json", ".md", ".yml", ".yaml"]); + +const configPath = path.resolve(process.cwd(), ".sdk-scope.json"); +if (!existsSync(configPath)) { + console.error("Missing .sdk-scope.json in repo root"); + process.exit(1); +} +const config = JSON.parse(readFileSync(configPath, "utf-8")); +const scope = config.sdk; +if (scope !== NEW_SCOPE && scope !== LEGACY_SCOPE) { + console.error(`Unknown scope '${scope}'; .sdk-scope.json sdk must be '${NEW_SCOPE}' or '${LEGACY_SCOPE}'`); + process.exit(1); +} +const sdkVersion = config["sdkVersion"]; +const cliVersion = config["cliVersion"]; +if (typeof sdkVersion !== "string" || typeof cliVersion !== "string") { + console.error(".sdk-scope.json must define sdkVersion and cliVersion"); + process.exit(1); +} + +const OTHER_SCOPES = scope === NEW_SCOPE ? [LEGACY_SCOPE] : [NEW_SCOPE]; + +function walk(dir, files = []) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (SKIP_DIRS.has(entry.name)) continue; + if (statSync(full).isDirectory()) walk(full, files); + else files.push(full); + } + return files; +} + +function normalizeSpecifiers(text) { + let out = text; + for (const other of OTHER_SCOPES) { + out = out + .replaceAll(`${other}/plugin-sdk`, `${scope}/plugin-sdk`) + .replaceAll(`${other}/plugin-cli`, `${scope}/plugin-cli`); + } + return out; +} + +function rewritePackageJson(file, text) { + const indent = detectIndent(text); + let pkg; + try { + pkg = JSON.parse(text); + } catch { + return text; + } + for (const sectionName of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) { + const section = pkg[sectionName]; + if (!section) continue; + const rebuilt = {}; + for (const dep of Object.keys(section)) { + const kind = parseSdkDep(dep); + if (!kind) { + rebuilt[dep] = section[dep]; + continue; + } + const target = `${scope}/${kind}`; + if (section[dep].startsWith("file:") || section[dep].startsWith("workspace:") || section[dep].startsWith("npm:")) { + rebuilt[target] = kind === "plugin-sdk" ? sdkVersion : cliVersion; + } else { + rebuilt[target] = section[dep]; + } + } + pkg[sectionName] = rebuilt; + } + return JSON.stringify(pkg, null, indent) + (text.endsWith("\n") ? "\n" : ""); +} + +function parseSdkDep(dep) { + for (const scopeName of [LEGACY_SCOPE, NEW_SCOPE, "@drop"]) { + if (dep === `${scopeName}/plugin-sdk`) return "plugin-sdk"; + if (dep === `${scopeName}/plugin-cli`) return "plugin-cli"; + } + return null; +} + +function detectIndent(text) { + const match = text.match(/\n([ \t]+)"/); + return match ? match[1] : "\t"; +} + +const edited = []; + +const files = walk(process.cwd()); +for (const file of files) { + if (LOCKFILES.has(path.basename(file))) continue; + if (!TEXT_EXT.has(path.extname(file))) continue; + const before = readFileSync(file, "utf-8"); + let after = normalizeSpecifiers(before); + if (path.basename(file) === "package.json") { + after = rewritePackageJson(file, after); + } + if (after !== before) { + writeFileSync(file, after); + edited.push(path.relative(process.cwd(), file)); + } +} + +console.log(`Scope set to '${scope}' (sdk ${sdkVersion}, cli ${cliVersion})`); +if (edited.length) { + for (const f of edited) console.log(` rewrote ${f}`); +} else { + console.log(" no specifier changes needed"); +} + +const installDir = process.cwd(); + +if (existsSync(path.join(installDir, "package-lock.json"))) { + try { + execSync("npm install --no-audit --no-fund", { cwd: installDir, stdio: "inherit" }); + } catch { + execSync("npm install --no-audit --no-fund --legacy-peer-deps", { cwd: installDir, stdio: "inherit" }); + } +} else if (existsSync(path.join(installDir, "pnpm-lock.yaml"))) { + execSync("pnpm install", { cwd: installDir, stdio: "inherit" }); +} else { + console.warn("no lockfile found; skipping dependency install"); +}