mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: add a plugin sdk changelog and tag sdk releases, release 1.0.2
This commit is contained in:
1 parent
ea678c5602
commit
64eafa0032
7 files changed
+113
-35
No files matched your search
@@ -13,7 +13,7 @@ on:
|
|||||||
- Dry run
|
- Dry run
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -64,3 +64,21 @@ jobs:
|
|||||||
else
|
else
|
||||||
npm publish --workspace @termix-ssh/plugin-sdk --provenance --access public
|
npm publish --workspace @termix-ssh/plugin-sdk --provenance --access public
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Tag and release
|
||||||
|
if: ${{ inputs.mode == 'Publish' }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
VERSION=$(node -p "require('./packages/plugin-sdk/package.json').version")
|
||||||
|
node -e "
|
||||||
|
const fs = require('fs');
|
||||||
|
const { changelogSection } = require('./packages/plugin-sdk/dist/changelog.js');
|
||||||
|
const notes = changelogSection(fs.readFileSync('packages/plugin-sdk/CHANGELOG.md', 'utf8'), process.argv[1]);
|
||||||
|
fs.writeFileSync('sdk-notes.md', notes);
|
||||||
|
" "$VERSION"
|
||||||
|
gh release create "sdk-v$VERSION" \
|
||||||
|
--target "${{ github.sha }}" \
|
||||||
|
--title "Plugin SDK $VERSION" \
|
||||||
|
--notes-file sdk-notes.md \
|
||||||
|
--latest=false
|
||||||
Generated
+1
-1
@@ -15049,7 +15049,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin-sdk": {
|
"packages/plugin-sdk": {
|
||||||
"name": "@termix-ssh/plugin-sdk",
|
"name": "@termix-ssh/plugin-sdk",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/node": "^4.2.4",
|
"@tailwindcss/node": "^4.2.4",
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `features` manifest field: up to 20 short lines about what the plugin does, shown on its page. `pluginFeatures()` reads them
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `parseChangelog()` and `validateChangelog()` flag a `## Unreleased` section. Notes go under the version they ship in
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `Changelog.unreleased`
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Plugin API 1.1
|
||||||
|
- `app.registerOnboardingStep()` adds a step to onboarding, with `OnboardingStepProps` and `OnboardingStepContribution`. Needs `engine.api` 1.1
|
||||||
|
- `video` manifest field: a YouTube link shown at the top of the plugin's page, read with `youtubeVideoId()`
|
||||||
|
- `@termix-ssh/plugin-sdk/changelog` parses and checks a plugin's `CHANGELOG.md`
|
||||||
|
- `termix-plugin changelog` prints one version's notes from `CHANGELOG.md`
|
||||||
|
- `termix-plugin dev` installs a plugin on a running server and reinstalls it on every change. Needs developer mode and an admin API key
|
||||||
|
- `renderOnboardingStep()` and `registered.onboardingSteps()` in `renderWithApp()`
|
||||||
|
- `AddButton`, `FormFooter`, `ListRow`, `ListRowAction`, `ListRowFolder`, `ListBadge` and `PanelList` in `@termix-ssh/plugin-sdk/ui`
|
||||||
|
- `PanelSearch` takes `disabled`, `title`, `onEnter` and `inputRef`, and `SurfaceScope` takes `onClose`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Release notes moved from `CHANGELOG.json` to `CHANGELOG.md`, which `pack` now ships. `validate` flags a leftover `CHANGELOG.json`
|
||||||
|
- `ConnectionScreen` dropped `errorMessage` and `retryLabel` and gained `onClose`. `ConnectionLogPanel` dropped `position`
|
||||||
|
- Plugin tests run in worker threads, since forked workers crash now and then on Windows
|
||||||
|
- The test setup stubs `ResizeObserver` for Radix controls under jsdom
|
||||||
|
- `onboarding` is a reserved plugin id
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- The `onboarding.*` action slots. Use `registerOnboardingStep()`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `schema/changelog.schema.json`
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Backend, frontend, database, manifest, capability and settings types for plugin API 1.0
|
||||||
|
- Testing helpers and a Vitest preset for plugin tests
|
||||||
|
- The `@termix-ssh/plugin-sdk/ui` component set
|
||||||
|
- The `termix-plugin` CLI to build, validate, test, pack, sign and write migrations
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@termix-ssh/plugin-sdk",
|
"name": "@termix-ssh/plugin-sdk",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
|
"description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
"schema",
|
"schema",
|
||||||
"tsconfig.plugin.json",
|
"tsconfig.plugin.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
|
"CHANGELOG.md",
|
||||||
"client.d.ts"
|
"client.d.ts"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,12 +3,7 @@
|
|||||||
*
|
*
|
||||||
* # Changelog
|
* # Changelog
|
||||||
*
|
*
|
||||||
* ## Unreleased
|
* ## 1.1.0
|
||||||
*
|
|
||||||
* ### Added
|
|
||||||
* - Something merged but not shipped yet
|
|
||||||
*
|
|
||||||
* ## 1.1.0 - 2026-10-06
|
|
||||||
*
|
*
|
||||||
* An optional summary paragraph.
|
* An optional summary paragraph.
|
||||||
*
|
*
|
||||||
@@ -19,7 +14,8 @@
|
|||||||
* - Something broken
|
* - Something broken
|
||||||
*
|
*
|
||||||
* Releases are listed newest first, and the newest one has to be the
|
* Releases are listed newest first, and the newest one has to be the
|
||||||
* manifest version. The date is optional until the version ships.
|
* manifest version. Notes go straight under the version they ship in, there
|
||||||
|
* is no Unreleased section.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
@@ -48,7 +44,6 @@ export interface ChangelogRelease {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Changelog {
|
export interface Changelog {
|
||||||
unreleased: ChangelogChange[];
|
|
||||||
/** Newest first, as written. */
|
/** Newest first, as written. */
|
||||||
releases: ChangelogRelease[];
|
releases: ChangelogRelease[];
|
||||||
}
|
}
|
||||||
@@ -135,7 +130,7 @@ export function parseChangelog(markdown: string): {
|
|||||||
problems: string[];
|
problems: string[];
|
||||||
} {
|
} {
|
||||||
const problems: string[] = [];
|
const problems: string[] = [];
|
||||||
const changelog: Changelog = { unreleased: [], releases: [] };
|
const changelog: Changelog = { releases: [] };
|
||||||
const lines = markdown.replace(/\r/g, "").split("\n");
|
const lines = markdown.replace(/\r/g, "").split("\n");
|
||||||
|
|
||||||
type Block = { heading: string; line: number; body: string[] };
|
type Block = { heading: string; line: number; body: string[] };
|
||||||
@@ -151,12 +146,11 @@ export function parseChangelog(markdown: string): {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
blocks.forEach((block, i) => {
|
blocks.forEach((block) => {
|
||||||
if (UNRELEASED_HEADING.test(block.heading)) {
|
if (UNRELEASED_HEADING.test(block.heading)) {
|
||||||
if (i !== 0) problems.push(`"## Unreleased" has to come first`);
|
problems.push(
|
||||||
const notes = parseReleaseNotes(block.body.join("\n"), "Unreleased");
|
`line ${block.line}: drop "## Unreleased", notes go under the version they ship in`,
|
||||||
problems.push(...notes.problems);
|
);
|
||||||
changelog.unreleased = notes.changes;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,23 @@ if (!fs.existsSync(sdk)) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { parseChangelog } = require(sdk);
|
const { parseChangelog, validateChangelog } = require(sdk);
|
||||||
const { problems } = parseChangelog(
|
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
|
||||||
fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8"),
|
|
||||||
|
const problems = parseChangelog(read("CHANGELOG.md")).problems.map(
|
||||||
|
(problem) => `CHANGELOG.md: ${problem}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The SDK is published on its own, so its newest release has to be the package version.
|
||||||
|
const sdkVersion = JSON.parse(read("packages/plugin-sdk/package.json")).version;
|
||||||
|
problems.push(
|
||||||
|
...validateChangelog(
|
||||||
|
read("packages/plugin-sdk/CHANGELOG.md"),
|
||||||
|
sdkVersion,
|
||||||
|
).map((problem) => `packages/plugin-sdk/CHANGELOG.md: ${problem}`),
|
||||||
|
);
|
||||||
|
|
||||||
if (problems.length > 0) {
|
if (problems.length > 0) {
|
||||||
for (const problem of problems) console.error(` CHANGELOG.md: ${problem}`);
|
for (const problem of problems) console.error(` ${problem}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -9,11 +9,6 @@ import {
|
|||||||
const FILE = [
|
const FILE = [
|
||||||
"# Changelog",
|
"# Changelog",
|
||||||
"",
|
"",
|
||||||
"## Unreleased",
|
|
||||||
"",
|
|
||||||
"### Fixed",
|
|
||||||
"- A bug not shipped yet",
|
|
||||||
"",
|
|
||||||
"## 1.1.0 - 2026-10-06",
|
"## 1.1.0 - 2026-10-06",
|
||||||
"",
|
"",
|
||||||
"Second release.",
|
"Second release.",
|
||||||
@@ -34,12 +29,9 @@ const FILE = [
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
describe("parseChangelog", () => {
|
describe("parseChangelog", () => {
|
||||||
it("reads releases, summaries, sections and unreleased changes", () => {
|
it("reads releases, summaries and sections", () => {
|
||||||
const { changelog, problems } = parseChangelog(FILE);
|
const { changelog, problems } = parseChangelog(FILE);
|
||||||
expect(problems).toEqual([]);
|
expect(problems).toEqual([]);
|
||||||
expect(changelog.unreleased).toEqual([
|
|
||||||
{ type: "fixed", text: "A bug not shipped yet" },
|
|
||||||
]);
|
|
||||||
expect(changelog.releases).toEqual([
|
expect(changelog.releases).toEqual([
|
||||||
{
|
{
|
||||||
version: "1.1.0",
|
version: "1.1.0",
|
||||||
@@ -104,12 +96,19 @@ describe("parseChangelog", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flags a release with no changes and Unreleased out of place", () => {
|
it("flags a release with no changes", () => {
|
||||||
const { problems } = parseChangelog(
|
const { problems } = parseChangelog("## 1.0.0\n\nJust a summary.\n");
|
||||||
"## 1.0.0\n\nJust a summary.\n\n## Unreleased\n### Added\n- x\n",
|
|
||||||
);
|
|
||||||
expect(problems).toContain("1.0.0 has no changes listed");
|
expect(problems).toContain("1.0.0 has no changes listed");
|
||||||
expect(problems).toContain('"## Unreleased" has to come first');
|
});
|
||||||
|
|
||||||
|
it("flags an Unreleased section", () => {
|
||||||
|
const { changelog, problems } = parseChangelog(
|
||||||
|
"## Unreleased\n### Added\n- x\n\n## 1.0.0\n### Added\n- y\n",
|
||||||
|
);
|
||||||
|
expect(problems).toEqual([
|
||||||
|
'line 1: drop "## Unreleased", notes go under the version they ship in',
|
||||||
|
]);
|
||||||
|
expect(changelog.releases.map((r) => r.version)).toEqual(["1.0.0"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores headings inside code fences", () => {
|
it("ignores headings inside code fences", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user