@termix-ssh/plugin-sdk 1.0.8 → 1.0.10
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.
- package/CHANGELOG.md +17 -0
- package/PLUGIN-GUIDE.md +37 -18
- package/cli/commands/build.mjs +54 -3
- package/cli/lib/tmxplug.mjs +3 -0
- package/dist/vitest-preset.d.ts +24 -0
- package/dist/vitest-preset.js +22 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.10
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `termix-plugin pack` includes the plugin's `LICENSE` and `NOTICE` files, so third-party notices ship with it
|
|
8
|
+
|
|
9
|
+
## 1.0.9
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Worker entries in `package.json` `termix.workers`, each bundled on its own into `dist/workers/<name>.js`
|
|
14
|
+
- A `.wasm`, `.ttf` or `.otf` import gives the file's bytes, in `termix-plugin build` and in plugin tests
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- The plugin guide listed jszip, guacamole-lite and @anthropic-ai/sdk as shared packages, but they are bundled
|
|
19
|
+
|
|
3
20
|
## 1.0.8
|
|
4
21
|
|
|
5
22
|
### Added
|
package/PLUGIN-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termix plugin guide
|
|
2
2
|
|
|
3
|
-
SDK 1.0.
|
|
3
|
+
SDK 1.0.10 | Plugin API 1.2.0
|
|
4
4
|
|
|
5
5
|
This guide is for anyone, person or AI coding tool, building a Termix plugin. It ships inside
|
|
6
6
|
`@termix-ssh/plugin-sdk`, so the copy in your `node_modules` always matches the SDK you have installed.
|
|
@@ -90,7 +90,7 @@ npm test # termix-plugin test: Vitest
|
|
|
90
90
|
npm run typecheck # tsc
|
|
91
91
|
npm run validate # termix-plugin validate: manifest, files, migrations, changelog, docs
|
|
92
92
|
npm run migrations # termix-plugin migrations: write migrations after changing tables.ts
|
|
93
|
-
npm run pack # termix-plugin pack: write <id>-<version>.tmxplug
|
|
93
|
+
npm run pack # termix-plugin pack: write <id>-<version>.tmxplug (with LICENSE and NOTICE)
|
|
94
94
|
npm run format # Prettier
|
|
95
95
|
npm run format:check
|
|
96
96
|
```
|
|
@@ -334,8 +334,27 @@ ctx.schedule.every(60_000, () => refresh(), { jitterMs: 5_000 });
|
|
|
334
334
|
### Shared packages
|
|
335
335
|
|
|
336
336
|
These come from Termix and are never bundled: `@termix-ssh/plugin-sdk`, `express`, `ssh2`, `ws`, `multer`,
|
|
337
|
-
`cookie-parser`, `axios`, `
|
|
338
|
-
|
|
337
|
+
`cookie-parser`, `axios`, `drizzle-orm`, `sharp` and every Node builtin. List the ones you use in
|
|
338
|
+
`peerDependencies`. Any other npm package is bundled.
|
|
339
|
+
|
|
340
|
+
### Workers and WebAssembly
|
|
341
|
+
|
|
342
|
+
Heavy work (decoding, encoding, parsing) belongs in a worker thread so it never blocks the server. Name
|
|
343
|
+
each worker entry in `package.json`:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
"termix": { "workers": { "engine": "src/backend/engine/worker.ts" } }
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`build` bundles it alone into `dist/workers/engine.js`; start it with
|
|
350
|
+
`new Worker(new URL("./workers/engine.js", import.meta.url))`. A worker bundles everything it imports and
|
|
351
|
+
can't use the shared packages above, so it must not import `@termix-ssh/plugin-sdk`. Talk to it with
|
|
352
|
+
`postMessage`.
|
|
353
|
+
|
|
354
|
+
Importing a `.wasm`, `.ttf` or `.otf` file gives its bytes as a `Uint8Array`, in `build` and in tests, so a module ships
|
|
355
|
+
inside the bundle with no file to find. Compile it once on the main thread with `new WebAssembly.Module`
|
|
356
|
+
and post that to each worker. Add `declare module "*.wasm" { const bytes: Uint8Array; export default bytes; }`
|
|
357
|
+
to a `.d.ts` in the plugin.
|
|
339
358
|
|
|
340
359
|
## Database
|
|
341
360
|
|
|
@@ -683,20 +702,20 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
683
702
|
|
|
684
703
|
## termix-plugin CLI
|
|
685
704
|
|
|
686
|
-
| Command | What it does
|
|
687
|
-
| ------------------------------ |
|
|
688
|
-
| `build` | Bundle `dist/backend.js
|
|
689
|
-
| `validate` | Check the manifest, its files, migrations for every database, the changelog and `package.json`
|
|
690
|
-
| `test` | Apply patches, run Vitest
|
|
691
|
-
| `migrations` | Write a migration per database from `tables.ts`. `--check` fails when one is missing
|
|
692
|
-
| `openapi` | Write `dist/openapi.json` from `@openapi` blocks
|
|
693
|
-
| `pack` | Validate, then write `<id>-<version>.tmxplug`. `--out <dir>`
|
|
694
|
-
| `sign <file>` | Sign a `.tmxplug` with `TERMIX_PLUGIN_SIGNING_KEY`
|
|
695
|
-
| `verify <file> --key <base64>` | Check a `.tmxplug` against its `.sig`
|
|
696
|
-
| `keygen` | Write a new signing key pair. `--out <dir>`
|
|
697
|
-
| `changelog` | Print one version's notes. `--version x.y.z`
|
|
698
|
-
| `patch` | Apply the plugin's dependency patches (`build` and `test` do this too)
|
|
699
|
-
| `dev` | Build, pack and install on a server, again on every change. `--server`, `--key`, `--once`
|
|
705
|
+
| Command | What it does |
|
|
706
|
+
| ------------------------------ | ----------------------------------------------------------------------------------------------- |
|
|
707
|
+
| `build` | Bundle `dist/backend.js`, `dist/frontend.js`, workers, CSS, locales, migrations, `openapi.json` |
|
|
708
|
+
| `validate` | Check the manifest, its files, migrations for every database, the changelog and `package.json` |
|
|
709
|
+
| `test` | Apply patches, run Vitest |
|
|
710
|
+
| `migrations` | Write a migration per database from `tables.ts`. `--check` fails when one is missing |
|
|
711
|
+
| `openapi` | Write `dist/openapi.json` from `@openapi` blocks |
|
|
712
|
+
| `pack` | Validate, then write `<id>-<version>.tmxplug`. `--out <dir>` |
|
|
713
|
+
| `sign <file>` | Sign a `.tmxplug` with `TERMIX_PLUGIN_SIGNING_KEY` |
|
|
714
|
+
| `verify <file> --key <base64>` | Check a `.tmxplug` against its `.sig` |
|
|
715
|
+
| `keygen` | Write a new signing key pair. `--out <dir>` |
|
|
716
|
+
| `changelog` | Print one version's notes. `--version x.y.z` |
|
|
717
|
+
| `patch` | Apply the plugin's dependency patches (`build` and `test` do this too) |
|
|
718
|
+
| `dev` | Build, pack and install on a server, again on every change. `--server`, `--key`, `--once` |
|
|
700
719
|
|
|
701
720
|
## SDK entry points
|
|
702
721
|
|
package/cli/commands/build.mjs
CHANGED
|
@@ -52,6 +52,43 @@ export function externalRequireInterop(externals) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// A .wasm or font import becomes its bytes (a Uint8Array) inside the bundle,
|
|
56
|
+
// so it ships in backend.js or a worker with no separate file to find.
|
|
57
|
+
export const BINARY_EXTENSIONS = [".wasm", ".ttf", ".otf"];
|
|
58
|
+
const BINARY_LOADERS = Object.fromEntries(
|
|
59
|
+
BINARY_EXTENSIONS.map((ext) => [ext, "binary"]),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const REQUIRE_BANNER =
|
|
63
|
+
"import { createRequire as __termixCreateRequire } from 'node:module'; const require = __termixCreateRequire(import.meta.url);";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* package.json "termix": { "workers": { "engine": "src/backend/engine/worker.ts" } }
|
|
67
|
+
* bundles each entry on its own into dist/workers/<name>.js, for
|
|
68
|
+
* new Worker(new URL("./workers/<name>.js", import.meta.url)). Nothing is left
|
|
69
|
+
* external: core's import fallback only runs on the main thread, so a worker
|
|
70
|
+
* can't rely on host packages and must not import the SDK.
|
|
71
|
+
*/
|
|
72
|
+
export function readWorkerEntries(cwd, pluginId) {
|
|
73
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
74
|
+
const pkg = fs.existsSync(pkgPath)
|
|
75
|
+
? JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
76
|
+
: {};
|
|
77
|
+
const workers = pkg.termix?.workers ?? {};
|
|
78
|
+
return Object.entries(workers).map(([name, entry]) => {
|
|
79
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
80
|
+
throw new Error(`${pluginId}: worker name "${name}" must be lowercase`);
|
|
81
|
+
}
|
|
82
|
+
const file = path.resolve(cwd, String(entry));
|
|
83
|
+
if (!fs.existsSync(file)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`${pluginId}: worker "${name}" entry ${entry} is missing`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return { name, file };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
55
92
|
const SDK_PATCHES = path.join(
|
|
56
93
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
57
94
|
"..",
|
|
@@ -216,13 +253,27 @@ export async function build({ cwd }) {
|
|
|
216
253
|
// node_modules import instead, resolved at runtime like a host-provided
|
|
217
254
|
// package.
|
|
218
255
|
external: [...BACKEND_EXTERNALS, ...(manifest.nativeDependencies ?? [])],
|
|
256
|
+
loader: BINARY_LOADERS,
|
|
219
257
|
// Bundled CJS deps still call require() for Node builtins at runtime.
|
|
220
258
|
// ESM has no ambient require, so give esbuild's require shim a real one.
|
|
221
|
-
banner: {
|
|
222
|
-
js: "import { createRequire as __termixCreateRequire } from 'node:module'; const require = __termixCreateRequire(import.meta.url);",
|
|
223
|
-
},
|
|
259
|
+
banner: { js: REQUIRE_BANNER },
|
|
224
260
|
});
|
|
225
261
|
|
|
262
|
+
for (const worker of readWorkerEntries(cwd, pluginId)) {
|
|
263
|
+
await esbuild.build({
|
|
264
|
+
entryPoints: [worker.file],
|
|
265
|
+
outfile: path.join(outDir, "workers", `${worker.name}.js`),
|
|
266
|
+
bundle: true,
|
|
267
|
+
format: "esm",
|
|
268
|
+
platform: "node",
|
|
269
|
+
target: "node22",
|
|
270
|
+
sourcemap: true,
|
|
271
|
+
logLevel: "warning",
|
|
272
|
+
loader: BINARY_LOADERS,
|
|
273
|
+
banner: { js: REQUIRE_BANNER },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
226
277
|
// Without this Node finds the host's typeless package.json, tries backend.js
|
|
227
278
|
// as CommonJS first and warns before reparsing it as ESM.
|
|
228
279
|
fs.writeFileSync(
|
package/cli/lib/tmxplug.mjs
CHANGED
package/dist/vitest-preset.d.ts
CHANGED
|
@@ -8,11 +8,25 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Two projects: tests/backend runs in node, tests/frontend in jsdom.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* termix-plugin build turns a .wasm or font import into its bytes. Tests get the same,
|
|
13
|
+
* so code that imports a module compiles it the same way under vitest.
|
|
14
|
+
*/
|
|
15
|
+
export declare function wasmBinaryPlugin(): {
|
|
16
|
+
name: string;
|
|
17
|
+
enforce: "pre";
|
|
18
|
+
load(id: string): string;
|
|
19
|
+
};
|
|
11
20
|
export interface PluginVitestOptions {
|
|
12
21
|
/** Extra aliases merged over the defaults. */
|
|
13
22
|
alias?: Record<string, string>;
|
|
14
23
|
}
|
|
15
24
|
export declare function pluginVitestConfig(configUrl: string, options?: PluginVitestOptions): {
|
|
25
|
+
plugins: {
|
|
26
|
+
name: string;
|
|
27
|
+
enforce: "pre";
|
|
28
|
+
load(id: string): string;
|
|
29
|
+
}[];
|
|
16
30
|
resolve: {
|
|
17
31
|
alias: Record<string, string>;
|
|
18
32
|
};
|
|
@@ -21,6 +35,11 @@ export declare function pluginVitestConfig(configUrl: string, options?: PluginVi
|
|
|
21
35
|
setupFiles: string[];
|
|
22
36
|
pool: "threads";
|
|
23
37
|
projects: ({
|
|
38
|
+
plugins: {
|
|
39
|
+
name: string;
|
|
40
|
+
enforce: "pre";
|
|
41
|
+
load(id: string): string;
|
|
42
|
+
}[];
|
|
24
43
|
resolve: {
|
|
25
44
|
alias: Record<string, string>;
|
|
26
45
|
};
|
|
@@ -35,6 +54,11 @@ export declare function pluginVitestConfig(configUrl: string, options?: PluginVi
|
|
|
35
54
|
server?: undefined;
|
|
36
55
|
};
|
|
37
56
|
} | {
|
|
57
|
+
plugins: {
|
|
58
|
+
name: string;
|
|
59
|
+
enforce: "pre";
|
|
60
|
+
load(id: string): string;
|
|
61
|
+
}[];
|
|
38
62
|
resolve: {
|
|
39
63
|
alias: Record<string, string>;
|
|
40
64
|
};
|
package/dist/vitest-preset.js
CHANGED
|
@@ -29,6 +29,24 @@ function findRepoRoot(startDir) {
|
|
|
29
29
|
dir = parent;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* termix-plugin build turns a .wasm or font import into its bytes. Tests get the same,
|
|
34
|
+
* so code that imports a module compiles it the same way under vitest.
|
|
35
|
+
*/
|
|
36
|
+
export function wasmBinaryPlugin() {
|
|
37
|
+
return {
|
|
38
|
+
name: "termix-wasm-binary",
|
|
39
|
+
enforce: "pre",
|
|
40
|
+
load(id) {
|
|
41
|
+
const file = id.split("?")[0];
|
|
42
|
+
if (![".wasm", ".ttf", ".otf"].some((ext) => file.endsWith(ext))) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const base64 = fs.readFileSync(file).toString("base64");
|
|
46
|
+
return `export default new Uint8Array(Buffer.from(${JSON.stringify(base64)}, "base64"));`;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
32
50
|
export function pluginVitestConfig(configUrl, options = {}) {
|
|
33
51
|
const pluginDir = path.dirname(fileURLToPath(configUrl));
|
|
34
52
|
const manifestPath = path.join(pluginDir, "manifest.json");
|
|
@@ -61,7 +79,9 @@ export function pluginVitestConfig(configUrl, options = {}) {
|
|
|
61
79
|
// Forks workers on Windows abort now and then with "Worker exited
|
|
62
80
|
// unexpectedly". Threads don't.
|
|
63
81
|
const pool = "threads";
|
|
82
|
+
const plugins = [wasmBinaryPlugin()];
|
|
64
83
|
return {
|
|
84
|
+
plugins,
|
|
65
85
|
resolve: { alias },
|
|
66
86
|
test: {
|
|
67
87
|
globals: true,
|
|
@@ -69,6 +89,7 @@ export function pluginVitestConfig(configUrl, options = {}) {
|
|
|
69
89
|
pool,
|
|
70
90
|
projects: [
|
|
71
91
|
{
|
|
92
|
+
plugins,
|
|
72
93
|
resolve: { alias },
|
|
73
94
|
test: {
|
|
74
95
|
name: `${pluginId}:backend`,
|
|
@@ -81,6 +102,7 @@ export function pluginVitestConfig(configUrl, options = {}) {
|
|
|
81
102
|
},
|
|
82
103
|
},
|
|
83
104
|
{
|
|
105
|
+
plugins,
|
|
84
106
|
resolve: { alias },
|
|
85
107
|
test: {
|
|
86
108
|
name: `${pluginId}:frontend`,
|
package/package.json
CHANGED