chore: configurable SDK scope (@droposs cutover) and .dropplugin release workflow

This commit is contained in:
BillyOutlast
2026-09-17 14:48:44 -04:00
parent 785ecd16e2
commit a4add52f5b
5 changed files with 208 additions and 11 deletions
+60
View File
@@ -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/[email protected]
- name: Set up Node.js 22
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # actions/[email protected]
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
+5
View File
@@ -0,0 +1,5 @@
{
"sdk": "@droposs",
"sdkVersion": "^0.5.9",
"cliVersion": "^0.5.9"
}
+9 -9
View File
@@ -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": {
+2 -2
View File
@@ -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",
+132
View File
@@ -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");
}