@termix-ssh/plugin-sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +8 -1
  3. package/cli/commands/changelog.mjs +25 -0
  4. package/cli/commands/dev.mjs +158 -0
  5. package/cli/commands/validate.mjs +16 -24
  6. package/cli/index.mjs +8 -0
  7. package/cli/lib/tmxplug.mjs +1 -1
  8. package/dist/backend.d.ts +6 -2
  9. package/dist/changelog.d.ts +62 -0
  10. package/dist/changelog.js +187 -0
  11. package/dist/frontend.d.ts +67 -1
  12. package/dist/host/{chunk-F3SDZDDG.js → chunk-LKYAVM4T.js} +17 -2
  13. package/dist/host/{chunk-XH5NXL6Y.js → chunk-SS43S6VI.js} +7816 -7344
  14. package/dist/host/{chunk-XGWE7BTJ.js → chunk-XKMRKQFN.js} +6 -1
  15. package/dist/host/{linked-server-FZGWQW32.js → linked-server-DW5P3NLZ.js} +1 -1
  16. package/dist/host/{rbac-api-6KDMBVDZ.js → rbac-api-JKISXZEP.js} +1 -1
  17. package/dist/host/testing-host.js +38 -5
  18. package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +12 -1
  19. package/dist/host/types/src/types/host-sidebar-preferences.d.ts +18 -1
  20. package/dist/host/types/src/types/onboarding.d.ts +29 -0
  21. package/dist/host/types/src/types/plugin-onboarding.d.ts +39 -0
  22. package/dist/host/types/src/types/ui-preferences.d.ts +13 -4
  23. package/dist/host/types/src/types/ui-types.d.ts +1 -1
  24. package/dist/host/types/src/ui/api/plugins-api.d.ts +211 -0
  25. package/dist/host/types/src/ui/api/system-status-api.d.ts +24 -0
  26. package/dist/host/types/src/ui/components/connection/ConnectionLogPanel.d.ts +2 -1
  27. package/dist/host/types/src/ui/components/connection/ConnectionScreen.d.ts +5 -5
  28. package/dist/host/types/src/ui/components/list-kit.d.ts +102 -0
  29. package/dist/host/types/src/ui/components/panel-layout.d.ts +6 -1
  30. package/dist/host/types/src/ui/components/surface/surface-scope.d.ts +5 -1
  31. package/dist/host/types/src/ui/contexts/UiPreferencesContext.d.ts +15 -2
  32. package/dist/host/types/src/ui/hooks/use-installed-plugins.d.ts +6 -0
  33. package/dist/host/types/src/ui/hooks/use-permissions.d.ts +2 -0
  34. package/dist/host/types/src/ui/lib/embedded-frame.d.ts +14 -0
  35. package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +49 -0
  36. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
  37. package/dist/host/types/src/ui/plugins/open-plugins.d.ts +5 -0
  38. package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +10 -0
  39. package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +2 -0
  40. package/dist/host/types/src/ui/sidebar/rail-items.d.ts +10 -0
  41. package/dist/host/ui.js +1097 -787
  42. package/dist/host/{use-permissions-NULZL43J.js → use-permissions-5CZEA3P4.js} +3 -1
  43. package/dist/manifest.d.ts +19 -2
  44. package/dist/manifest.js +82 -1
  45. package/dist/testing/setup.js +8 -0
  46. package/dist/testing.d.ts +12 -0
  47. package/dist/vitest-preset.d.ts +3 -0
  48. package/dist/vitest-preset.js +6 -0
  49. package/package.json +7 -3
  50. package/schema/manifest.schema.json +12 -1
  51. package/cli/lib/changelog.mjs +0 -126
  52. package/schema/changelog.schema.json +0 -67
@@ -1,15 +1,17 @@
1
1
  const __termixNoGlob = () => ({});
2
2
  import {
3
3
  hasPermission,
4
+ loadPermissionChecker,
4
5
  notifyPermissionsChanged,
5
6
  preloadPermissions,
6
7
  resetPermissionsCache,
7
8
  setPermissionsForTesting,
8
9
  usePermissions
9
- } from "./chunk-XGWE7BTJ.js";
10
+ } from "./chunk-XKMRKQFN.js";
10
11
  import "./chunk-YC2MAKSP.js";
11
12
  export {
12
13
  hasPermission,
14
+ loadPermissionChecker,
13
15
  notifyPermissionsChanged,
14
16
  preloadPermissions,
15
17
  resetPermissionsCache,
@@ -17,7 +17,7 @@
17
17
  * "^1.2" needs 1.2 or later). engine.termix is checked against the core
18
18
  * version by the server.
19
19
  */
20
- export declare const PLUGIN_API_VERSION = "1.0.0";
20
+ export declare const PLUGIN_API_VERSION = "1.1.0";
21
21
  /** The API major, the default engine.api for a new plugin. */
22
22
  export declare const SUPPORTED_PLUGIN_API_VERSION: string;
23
23
  /** Whether this build's plugin API satisfies a manifest's engine.api. */
@@ -27,6 +27,18 @@ export declare function isApiCompatible(range: string): boolean;
27
27
  * core (2.9.0-beta.1) counts as that release.
28
28
  */
29
29
  export declare function isTermixCompatible(range: string, coreVersion: string | null): boolean;
30
+ export declare const MAX_FEATURES = 20;
31
+ export declare const MAX_FEATURE_LENGTH = 160;
32
+ /**
33
+ * The usable feature lines of a manifest or registry entry. Anything that is
34
+ * not a short non-empty string is dropped, so an index cannot flood the page.
35
+ */
36
+ export declare function pluginFeatures(value: unknown): string[];
37
+ /**
38
+ * The video id of a YouTube link, or null for anything else. Only the id is
39
+ * ever used, so a manifest can never point the embed at another site.
40
+ */
41
+ export declare function youtubeVideoId(value: unknown): string | null;
30
42
  export declare const PLUGIN_CATEGORIES: readonly ["Terminal", "Files & Transfer", "Infrastructure", "Monitoring", "Networking", "Access & Security", "Productivity"];
31
43
  export declare const PLUGIN_PLATFORMS: readonly ["linux", "win32", "darwin"];
32
44
  export declare const ACTION_CONTRIBUTION_KINDS: readonly ["button", "component"];
@@ -327,7 +339,8 @@ export interface PluginProtocolContribution {
327
339
  defaultPort?: number;
328
340
  /**
329
341
  * What the owner's login falls back to from the host's own SSH login
330
- * when the protocol login leaves it empty.
342
+ * when the protocol login leaves it empty. The password only falls back
343
+ * while the host signs in to SSH with a password.
331
344
  */
332
345
  hostLoginFallback?: Array<"username" | "password">;
333
346
  }
@@ -408,6 +421,10 @@ export interface PluginManifest {
408
421
  repository?: string;
409
422
  category: string;
410
423
  icon?: string;
424
+ /** A YouTube link shown at the top of the plugin's page. */
425
+ video?: string;
426
+ /** Short lines listing what the plugin does, shown on its page. */
427
+ features?: string[];
411
428
  engine: PluginEngine;
412
429
  /** Catalog capability ids. */
413
430
  capabilities: string[];
package/dist/manifest.js CHANGED
@@ -19,7 +19,7 @@ import { isKnownCapability } from "./capabilities.js";
19
19
  * "^1.2" needs 1.2 or later). engine.termix is checked against the core
20
20
  * version by the server.
21
21
  */
22
- export const PLUGIN_API_VERSION = "1.0.0";
22
+ export const PLUGIN_API_VERSION = "1.1.0";
23
23
  /** The API major, the default engine.api for a new plugin. */
24
24
  export const SUPPORTED_PLUGIN_API_VERSION = String(semver.major(PLUGIN_API_VERSION));
25
25
  /** Whether this build's plugin API satisfies a manifest's engine.api. */
@@ -38,6 +38,62 @@ export function isTermixCompatible(range, coreVersion) {
38
38
  return false;
39
39
  return semver.satisfies(semver.coerce(coreVersion).version, range);
40
40
  }
41
+ const YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
42
+ const YOUTUBE_HOSTS = new Set([
43
+ "youtube.com",
44
+ "www.youtube.com",
45
+ "m.youtube.com",
46
+ "youtu.be",
47
+ "youtube-nocookie.com",
48
+ "www.youtube-nocookie.com",
49
+ ]);
50
+ export const MAX_FEATURES = 20;
51
+ export const MAX_FEATURE_LENGTH = 160;
52
+ /**
53
+ * The usable feature lines of a manifest or registry entry. Anything that is
54
+ * not a short non-empty string is dropped, so an index cannot flood the page.
55
+ */
56
+ export function pluginFeatures(value) {
57
+ if (!Array.isArray(value))
58
+ return [];
59
+ return value
60
+ .filter((f) => typeof f === "string" &&
61
+ f.trim().length > 0 &&
62
+ f.length <= MAX_FEATURE_LENGTH)
63
+ .map((f) => f.trim())
64
+ .slice(0, MAX_FEATURES);
65
+ }
66
+ /**
67
+ * The video id of a YouTube link, or null for anything else. Only the id is
68
+ * ever used, so a manifest can never point the embed at another site.
69
+ */
70
+ export function youtubeVideoId(value) {
71
+ if (typeof value !== "string")
72
+ return null;
73
+ let url;
74
+ try {
75
+ url = new URL(value.trim());
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ if (url.protocol !== "https:" || url.username || url.password)
81
+ return null;
82
+ const host = url.hostname.toLowerCase();
83
+ if (!YOUTUBE_HOSTS.has(host))
84
+ return null;
85
+ let id;
86
+ if (host === "youtu.be") {
87
+ id = url.pathname.slice(1);
88
+ }
89
+ else if (url.pathname === "/watch") {
90
+ id = url.searchParams.get("v");
91
+ }
92
+ else {
93
+ id = /^\/(?:embed|shorts|live)\/([^/]+)\/?$/.exec(url.pathname)?.[1];
94
+ }
95
+ return id && YOUTUBE_ID.test(id) ? id : null;
96
+ }
41
97
  export const PLUGIN_CATEGORIES = [
42
98
  "Terminal",
43
99
  "Files & Transfer",
@@ -64,6 +120,7 @@ export const RESERVED_PLUGIN_IDS = [
64
120
  "termix",
65
121
  "plugin",
66
122
  "plugins",
123
+ "onboarding",
67
124
  "users",
68
125
  "system",
69
126
  "sdk",
@@ -148,6 +205,8 @@ const ALLOWED_TOP_LEVEL = new Set([
148
205
  ...REQUIRED_TOP_LEVEL,
149
206
  "repository",
150
207
  "icon",
208
+ "video",
209
+ "features",
151
210
  "dependencies",
152
211
  "optionalDependencies",
153
212
  "provides",
@@ -274,6 +333,10 @@ export function validateManifest(manifest) {
274
333
  requireString(m.repository, "repository", errors);
275
334
  if ("icon" in m)
276
335
  requireString(m.icon, "icon", errors);
336
+ if ("video" in m && youtubeVideoId(m.video) === null) {
337
+ errors.push(`Field "video" must be a YouTube link such as https://www.youtube.com/watch?v=..., got: ${JSON.stringify(m.video)}`);
338
+ }
339
+ validateFeatures(m.features, errors);
277
340
  for (const field of ["backend", "frontend", "locales"]) {
278
341
  if (field in m && requireString(m[field], field, errors)) {
279
342
  requireRelativePath(m[field], field, errors);
@@ -298,6 +361,24 @@ export function validateManifest(manifest) {
298
361
  validateContributes(m.contributes, typeof m.id === "string" ? m.id : undefined, errors);
299
362
  return errors;
300
363
  }
364
+ function validateFeatures(features, errors) {
365
+ if (features === undefined)
366
+ return;
367
+ if (!Array.isArray(features)) {
368
+ errors.push('Field "features" must be an array of strings');
369
+ return;
370
+ }
371
+ if (features.length > MAX_FEATURES) {
372
+ errors.push(`Field "features" may list at most ${MAX_FEATURES} items`);
373
+ }
374
+ features.forEach((feature, i) => {
375
+ if (typeof feature !== "string" ||
376
+ feature.trim().length === 0 ||
377
+ feature.length > MAX_FEATURE_LENGTH) {
378
+ errors.push(`Field "features[${i}]" must be a non-empty string of at most ${MAX_FEATURE_LENGTH} characters`);
379
+ }
380
+ });
381
+ }
301
382
  function validateAuthor(author, errors) {
302
383
  if (author === undefined)
303
384
  return;
@@ -7,6 +7,14 @@
7
7
  import { afterEach, vi } from "vitest";
8
8
  import { installSafePorts } from "./safe-ports.js";
9
9
  installSafePorts();
10
+ // Radix measures some controls (Checkbox, Switch) and jsdom has no observer.
11
+ if (typeof globalThis.ResizeObserver === "undefined") {
12
+ globalThis.ResizeObserver = class {
13
+ observe() { }
14
+ unobserve() { }
15
+ disconnect() { }
16
+ };
17
+ }
10
18
  if (typeof window !== "undefined" && !window.matchMedia) {
11
19
  window.matchMedia = vi.fn().mockImplementation((query) => ({
12
20
  matches: false,
package/dist/testing.d.ts CHANGED
@@ -492,6 +492,8 @@ export interface RenderedPluginApp {
492
492
  paletteGroups: () => string[];
493
493
  keybindingActions: () => string[];
494
494
  keybindingDefaults: () => string[];
495
+ /** Onboarding step ids, without the plugin prefix. */
496
+ onboardingSteps: () => string[];
495
497
  };
496
498
  /** A registered palette group's items, as the palette loads them on open. */
497
499
  loadPaletteGroup: (id: string) => Promise<import("./frontend.js").PaletteItem[]>;
@@ -515,6 +517,16 @@ export interface RenderedPluginApp {
515
517
  renderTab: (type: string, props?: Record<string, unknown>) => HTMLElement;
516
518
  renderPanel: (id: string, props?: Record<string, unknown>) => HTMLElement;
517
519
  renderDashboardCard: (id: string) => HTMLElement;
520
+ /**
521
+ * Renders an onboarding step with OnboardingStepProps for a first run,
522
+ * overridable. Read what the step set with the returned `canContinue()`
523
+ * and `next()` (runs its beforeNext, true when it has none).
524
+ */
525
+ renderOnboardingStep: (id: string, props?: Record<string, unknown>) => {
526
+ element: HTMLElement;
527
+ canContinue: () => boolean;
528
+ next: () => Promise<boolean>;
529
+ };
518
530
  /** Renders one component of a contributed extension, "view" by default. */
519
531
  renderExtension: (pointId: string, id: string, component?: string, props?: Record<string, unknown>) => HTMLElement;
520
532
  renderHostEditorSection: (id: string, props?: Record<string, unknown>) => HTMLElement;
@@ -19,6 +19,7 @@ export declare function pluginVitestConfig(configUrl: string, options?: PluginVi
19
19
  test: {
20
20
  globals: boolean;
21
21
  setupFiles: string[];
22
+ pool: "threads";
22
23
  projects: ({
23
24
  resolve: {
24
25
  alias: Record<string, string>;
@@ -27,6 +28,7 @@ export declare function pluginVitestConfig(configUrl: string, options?: PluginVi
27
28
  name: string;
28
29
  root: string;
29
30
  environment: string;
31
+ pool: "threads";
30
32
  globals: boolean;
31
33
  setupFiles: string[];
32
34
  include: string[];
@@ -40,6 +42,7 @@ export declare function pluginVitestConfig(configUrl: string, options?: PluginVi
40
42
  name: string;
41
43
  root: string;
42
44
  environment: string;
45
+ pool: "threads";
43
46
  globals: boolean;
44
47
  setupFiles: string[];
45
48
  server: {
@@ -58,11 +58,15 @@ export function pluginVitestConfig(configUrl, options = {}) {
58
58
  }),
59
59
  ...options.alias,
60
60
  };
61
+ // Forks workers on Windows abort now and then with "Worker exited
62
+ // unexpectedly". Threads don't.
63
+ const pool = "threads";
61
64
  return {
62
65
  resolve: { alias },
63
66
  test: {
64
67
  globals: true,
65
68
  setupFiles: [setupFile],
69
+ pool,
66
70
  projects: [
67
71
  {
68
72
  resolve: { alias },
@@ -70,6 +74,7 @@ export function pluginVitestConfig(configUrl, options = {}) {
70
74
  name: `${pluginId}:backend`,
71
75
  root: pluginDir,
72
76
  environment: "node",
77
+ pool,
73
78
  globals: true,
74
79
  setupFiles: [setupFile],
75
80
  include: ["tests/backend/**/*.test.ts"],
@@ -81,6 +86,7 @@ export function pluginVitestConfig(configUrl, options = {}) {
81
86
  name: `${pluginId}:frontend`,
82
87
  root: pluginDir,
83
88
  environment: "jsdom",
89
+ pool,
84
90
  globals: true,
85
91
  setupFiles: [setupFile],
86
92
  // Run the SDK through Vite rather than plain Node, so its import
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termix-ssh/plugin-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "node": ">=22"
15
15
  },
16
16
  "bin": {
17
- "termix-plugin": "./cli/index.mjs"
17
+ "termix-plugin": "cli/index.mjs"
18
18
  },
19
19
  "exports": {
20
20
  "./backend": {
@@ -49,6 +49,10 @@
49
49
  "types": "./dist/capabilities.d.ts",
50
50
  "default": "./dist/capabilities.js"
51
51
  },
52
+ "./changelog": {
53
+ "types": "./dist/changelog.d.ts",
54
+ "default": "./dist/changelog.js"
55
+ },
52
56
  "./settings": {
53
57
  "types": "./dist/settings.d.ts",
54
58
  "default": "./dist/settings.js"
@@ -75,7 +79,6 @@
75
79
  },
76
80
  "./tsconfig.plugin.json": "./tsconfig.plugin.json",
77
81
  "./schema/manifest.schema.json": "./schema/manifest.schema.json",
78
- "./schema/changelog.schema.json": "./schema/changelog.schema.json",
79
82
  "./ui": {
80
83
  "types": "./dist/host/ui.d.ts",
81
84
  "default": "./dist/host/ui.js"
@@ -91,6 +94,7 @@
91
94
  "schema",
92
95
  "tsconfig.plugin.json",
93
96
  "README.md",
97
+ "CHANGELOG.md",
94
98
  "client.d.ts"
95
99
  ],
96
100
  "scripts": {
@@ -72,7 +72,18 @@
72
72
  "icon": {
73
73
  "type": "string",
74
74
  "minLength": 1,
75
- "description": "A Lucide icon name, or a relative path to an icon.svg in the plugin root."
75
+ "description": "A Lucide icon name, like \"Play\"."
76
+ },
77
+ "video": {
78
+ "type": "string",
79
+ "pattern": "^https://(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/",
80
+ "description": "A YouTube link shown at the top of the plugin's page."
81
+ },
82
+ "features": {
83
+ "type": "array",
84
+ "maxItems": 20,
85
+ "items": { "type": "string", "minLength": 1, "maxLength": 160 },
86
+ "description": "Short lines listing what the plugin does, shown on its page."
76
87
  },
77
88
  "engine": {
78
89
  "type": "object",
@@ -1,126 +0,0 @@
1
- export const CHANGE_TYPES = [
2
- "added",
3
- "changed",
4
- "fixed",
5
- "removed",
6
- "deprecated",
7
- "security",
8
- ];
9
-
10
- const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
11
- const DATE = /^\d{4}-\d{2}-\d{2}$/;
12
- const TOP_KEYS = ["$schema", "unreleased", "releases"];
13
- const RELEASE_KEYS = ["version", "date", "notes", "changes"];
14
- const CHANGE_KEYS = ["type", "text"];
15
-
16
- function compareVersions(a, b) {
17
- const pa = SEMVER.exec(a);
18
- const pb = SEMVER.exec(b);
19
- for (let i = 1; i <= 3; i++) {
20
- const diff = Number(pa[i]) - Number(pb[i]);
21
- if (diff !== 0) return diff;
22
- }
23
- if (pa[4] === pb[4]) return 0;
24
- if (!pa[4]) return 1;
25
- if (!pb[4]) return -1;
26
- return pa[4] < pb[4] ? -1 : 1;
27
- }
28
-
29
- function unknownKeys(value, allowed, where, problems) {
30
- for (const key of Object.keys(value)) {
31
- if (!allowed.includes(key))
32
- problems.push(`${where} has unknown key "${key}"`);
33
- }
34
- }
35
-
36
- function checkChanges(changes, where, problems) {
37
- if (!Array.isArray(changes)) {
38
- problems.push(`${where} must be an array`);
39
- return;
40
- }
41
- changes.forEach((change, i) => {
42
- const at = `${where}[${i}]`;
43
- if (!change || typeof change !== "object" || Array.isArray(change)) {
44
- problems.push(`${at} must be an object`);
45
- return;
46
- }
47
- unknownKeys(change, CHANGE_KEYS, at, problems);
48
- if (!CHANGE_TYPES.includes(change.type)) {
49
- problems.push(`${at}.type must be one of ${CHANGE_TYPES.join(", ")}`);
50
- }
51
- if (typeof change.text !== "string" || change.text.trim() === "") {
52
- problems.push(`${at}.text must be a non-empty string`);
53
- }
54
- });
55
- }
56
-
57
- /**
58
- * Checks a parsed CHANGELOG.json. The newest release has to be the version
59
- * in the manifest, so a release never ships without its notes.
60
- */
61
- export function validateChangelog(changelog, manifestVersion) {
62
- const problems = [];
63
- if (!changelog || typeof changelog !== "object" || Array.isArray(changelog)) {
64
- return ["CHANGELOG.json must be an object"];
65
- }
66
- unknownKeys(changelog, TOP_KEYS, "CHANGELOG.json", problems);
67
-
68
- if (changelog.unreleased !== undefined) {
69
- checkChanges(changelog.unreleased, "unreleased", problems);
70
- }
71
-
72
- const releases = changelog.releases;
73
- if (!Array.isArray(releases) || releases.length === 0) {
74
- problems.push("releases must be a non-empty array");
75
- return problems;
76
- }
77
-
78
- const seen = new Set();
79
- releases.forEach((release, i) => {
80
- const at = `releases[${i}]`;
81
- if (!release || typeof release !== "object" || Array.isArray(release)) {
82
- problems.push(`${at} must be an object`);
83
- return;
84
- }
85
- unknownKeys(release, RELEASE_KEYS, at, problems);
86
- if (typeof release.version !== "string" || !SEMVER.test(release.version)) {
87
- problems.push(`${at}.version must be a version like 1.2.3`);
88
- } else if (seen.has(release.version)) {
89
- problems.push(`${at}.version ${release.version} is listed twice`);
90
- } else {
91
- seen.add(release.version);
92
- }
93
- if (
94
- release.date !== undefined &&
95
- (typeof release.date !== "string" || !DATE.test(release.date))
96
- ) {
97
- problems.push(`${at}.date must be a date like 2026-10-01`);
98
- }
99
- if (release.notes !== undefined && typeof release.notes !== "string") {
100
- problems.push(`${at}.notes must be a string`);
101
- }
102
- checkChanges(release.changes, `${at}.changes`, problems);
103
- if (Array.isArray(release.changes) && release.changes.length === 0) {
104
- problems.push(`${at}.changes must list at least one change`);
105
- }
106
- });
107
-
108
- const versions = releases
109
- .map((release) => release?.version)
110
- .filter((v) => typeof v === "string" && SEMVER.test(v));
111
- for (let i = 1; i < versions.length; i++) {
112
- if (compareVersions(versions[i - 1], versions[i]) < 0) {
113
- problems.push("releases must be listed newest first");
114
- break;
115
- }
116
- }
117
-
118
- const newest = releases[0]?.version;
119
- if (manifestVersion && newest !== manifestVersion) {
120
- problems.push(
121
- `the newest release is ${newest}, but manifest version is ${manifestVersion}`,
122
- );
123
- }
124
-
125
- return problems;
126
- }
@@ -1,67 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://raw.githubusercontent.com/Termix-SSH/Termix/main/packages/plugin-sdk/schema/changelog.schema.json",
4
- "title": "Termix plugin changelog",
5
- "description": "Every release of a plugin, newest first. The newest release must match the manifest version.",
6
- "type": "object",
7
- "additionalProperties": false,
8
- "required": ["releases"],
9
- "properties": {
10
- "$schema": { "type": "string" },
11
- "unreleased": {
12
- "description": "Changes that are merged but not released yet.",
13
- "type": "array",
14
- "items": { "$ref": "#/$defs/change" }
15
- },
16
- "releases": {
17
- "type": "array",
18
- "minItems": 1,
19
- "items": { "$ref": "#/$defs/release" }
20
- }
21
- },
22
- "$defs": {
23
- "release": {
24
- "type": "object",
25
- "additionalProperties": false,
26
- "required": ["version", "changes"],
27
- "properties": {
28
- "version": {
29
- "type": "string",
30
- "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$"
31
- },
32
- "date": {
33
- "description": "When the version was released. Leave it out until it ships.",
34
- "type": "string",
35
- "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
36
- },
37
- "notes": {
38
- "description": "A short line shown above the list of changes.",
39
- "type": "string"
40
- },
41
- "changes": {
42
- "type": "array",
43
- "minItems": 1,
44
- "items": { "$ref": "#/$defs/change" }
45
- }
46
- }
47
- },
48
- "change": {
49
- "type": "object",
50
- "additionalProperties": false,
51
- "required": ["type", "text"],
52
- "properties": {
53
- "type": {
54
- "enum": [
55
- "added",
56
- "changed",
57
- "fixed",
58
- "removed",
59
- "deprecated",
60
- "security"
61
- ]
62
- },
63
- "text": { "type": "string", "minLength": 1 }
64
- }
65
- }
66
- }
67
- }