@synoikia/create-plugin 0.1.0

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/build.d.ts +16 -0
  4. package/dist/build.js +32 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/check.d.ts +7 -0
  7. package/dist/check.js +49 -0
  8. package/dist/check.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +104 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/create.d.ts +2 -0
  13. package/dist/create.js +56 -0
  14. package/dist/create.js.map +1 -0
  15. package/dist/files.d.ts +12 -0
  16. package/dist/files.js +42 -0
  17. package/dist/files.js.map +1 -0
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.js +8 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/prompts.d.ts +46 -0
  22. package/dist/prompts.js +112 -0
  23. package/dist/prompts.js.map +1 -0
  24. package/dist/repo-root.d.ts +10 -0
  25. package/dist/repo-root.js +47 -0
  26. package/dist/repo-root.js.map +1 -0
  27. package/dist/repo.d.ts +68 -0
  28. package/dist/repo.js +222 -0
  29. package/dist/repo.js.map +1 -0
  30. package/dist/scaffold.d.ts +68 -0
  31. package/dist/scaffold.js +296 -0
  32. package/dist/scaffold.js.map +1 -0
  33. package/dist/vitest.d.ts +12 -0
  34. package/dist/vitest.js +17 -0
  35. package/dist/vitest.js.map +1 -0
  36. package/package.json +56 -0
  37. package/templates/plugin/blank/plugin.yaml +22 -0
  38. package/templates/plugin/blank/src/plugin.ts +74 -0
  39. package/templates/plugin/blank/test/plugin.test.ts +33 -0
  40. package/templates/plugin/common/README.md +23 -0
  41. package/templates/plugin/common/RUNBOOK.md +10 -0
  42. package/templates/plugin/common/src/index.ts +4 -0
  43. package/templates/plugin/common/tsconfig.build.json +9 -0
  44. package/templates/plugin/common/tsconfig.json +7 -0
  45. package/templates/plugin/common/vitest.config.ts +1 -0
  46. package/templates/plugin/openapi-rest/plugin.yaml +49 -0
  47. package/templates/plugin/openapi-rest/src/plugin.ts +89 -0
  48. package/templates/plugin/openapi-rest/test/e2e.test.ts +49 -0
  49. package/templates/plugin/openapi-rest/test/fake-upstream.ts +63 -0
  50. package/templates/plugin/openapi-rest/test/plugin.test.ts +66 -0
  51. package/templates/plugin/static-rest/plugin.yaml +61 -0
  52. package/templates/plugin/static-rest/src/plugin.ts +70 -0
  53. package/templates/plugin/static-rest/test/e2e.test.ts +46 -0
  54. package/templates/plugin/static-rest/test/fake-upstream.ts +39 -0
  55. package/templates/plugin/static-rest/test/plugin.test.ts +67 -0
  56. package/templates/plugin/websocket-rpc/plugin.yaml +45 -0
  57. package/templates/plugin/websocket-rpc/src/client.ts +96 -0
  58. package/templates/plugin/websocket-rpc/src/plugin.ts +98 -0
  59. package/templates/plugin/websocket-rpc/test/e2e.test.ts +46 -0
  60. package/templates/plugin/websocket-rpc/test/fake-upstream.ts +63 -0
  61. package/templates/plugin/websocket-rpc/test/plugin.test.ts +67 -0
  62. package/templates/repo/base/README.md +27 -0
  63. package/templates/repo/base/_github/workflows/ci.yml +27 -0
  64. package/templates/repo/base/_gitignore +6 -0
  65. package/templates/repo/base/_nvmrc +1 -0
  66. package/templates/repo/base/_prettierignore +3 -0
  67. package/templates/repo/base/_prettierrc +5 -0
  68. package/templates/repo/base/eslint.config.js +17 -0
  69. package/templates/repo/base/package.json +34 -0
  70. package/templates/repo/base/plugin-files.d.ts +10 -0
  71. package/templates/repo/base/pnpm-workspace.yaml +11 -0
  72. package/templates/repo/base/tsconfig.base.json +21 -0
  73. package/templates/repo/base/vitest.shared.ts +9 -0
  74. package/templates/repo/claude/CLAUDE.md +38 -0
  75. package/templates/repo/claude/_claude/agents/security-reviewer.md +33 -0
  76. package/templates/repo/claude/_claude/hooks/format.mjs +20 -0
  77. package/templates/repo/claude/_claude/hooks/guard-paths.mjs +43 -0
  78. package/templates/repo/claude/_claude/hooks/session-start.sh +13 -0
  79. package/templates/repo/claude/_claude/settings.json +21 -0
  80. package/templates/repo/claude/_claude/skills/add-plugin/SKILL.md +39 -0
  81. package/templates/repo/claude/_claude/skills/release-plugin/SKILL.md +18 -0
  82. package/templates/repo/release/RELEASING.md +27 -0
  83. package/templates/repo/release/_github/workflows/release.yml +89 -0
@@ -0,0 +1,296 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { RESERVED_NAMESPACES, SDK_VERSION } from '@synoikia/plugin-sdk';
5
+ import { dependencyRange, TEMPLATES_DIR } from './repo-root.js';
6
+ /**
7
+ * Scaffolding: a new plugin repository (`pnpm create @synoikia/plugin`) and a new plugin inside one
8
+ * (`pnpm new`). Templates are plain files with `{{name}}` placeholders; only known names are replaced
9
+ * and nothing in a template runs. Files whose name starts with `_` are written with a leading `.`
10
+ * (npm drops some dotfiles from published packages). Generated code uses only the published SDK and
11
+ * core APIs, and the generated manifest already follows the secret rules `synoikia-plugin check`
12
+ * enforces.
13
+ */
14
+ export const ARCHETYPES = {
15
+ 'openapi-rest': 'REST API that serves an OpenAPI spec: the catalog is the spec, rules lock and describe',
16
+ 'static-rest': 'REST API without a spec: operations are declared in plugin.yaml',
17
+ 'websocket-rpc': 'JSON-RPC 2.0 over WebSocket: methods declared in plugin.yaml',
18
+ blank: 'A minimal plugin to fill in by hand',
19
+ };
20
+ export const AUTH_KINDS = {
21
+ bearer: 'Bearer token (Authorization: Bearer …)',
22
+ 'api-key': 'API key header (e.g. X-Api-Key)',
23
+ basic: 'Username and password (HTTP Basic)',
24
+ none: 'No authentication',
25
+ };
26
+ export const ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}$/;
27
+ const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
28
+ export const camelCase = (id) => id.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
29
+ export const pascalCase = (id) => camelCase(id).replace(/^./, (c) => c.toUpperCase());
30
+ /** Problems with a plugin id or namespace, or undefined. */
31
+ export function validateId(id, root) {
32
+ if (!ID_PATTERN.test(id))
33
+ return 'Use 2–63 lowercase letters, digits and dashes, starting with a letter or digit';
34
+ if (root && existsSync(path.join(root, 'plugins', id)))
35
+ return `plugins/${id} already exists`;
36
+ return undefined;
37
+ }
38
+ /**
39
+ * Display names and descriptions land in generated code (`const SERVICE = '…'`), YAML and Markdown:
40
+ * plain text only, so no quote, backslash or newline can break out of them.
41
+ */
42
+ const SAFE_TEXT = /^[\p{L}\p{N}][\p{L}\p{N} .,:()&+_/-]*$/u;
43
+ export function validateName(name) {
44
+ if (name.length > 64 || !SAFE_TEXT.test(name))
45
+ return 'Use letters, digits, spaces and . , : ( ) & + _ / - (up to 64 characters)';
46
+ return undefined;
47
+ }
48
+ export function validateDescription(text) {
49
+ if (text.length > 200 || !SAFE_TEXT.test(text))
50
+ return 'Use plain text without quotes or newlines (up to 200 characters)';
51
+ return undefined;
52
+ }
53
+ export function validateNamespace(ns) {
54
+ if (!JS_IDENTIFIER.test(ns))
55
+ return 'Must be a JavaScript identifier';
56
+ if (RESERVED_NAMESPACES.includes(ns))
57
+ return `"${ns}" is reserved by core`;
58
+ return undefined;
59
+ }
60
+ const PLACEHOLDER = /\{\{([a-zA-Z]+)\}\}/g;
61
+ /** Replaces `{{name}}` for the names in `vars`; anything else (e.g. `{{connection.baseUrl}}`) stays. */
62
+ export function fill(text, vars) {
63
+ return text.replace(PLACEHOLDER, (whole, name) => (Object.hasOwn(vars, name) ? vars[name] : whole));
64
+ }
65
+ const outName = (name) => (/^_[a-zA-Z]/.test(name) ? `.${name.slice(1)}` : name);
66
+ /** Copies a template directory, filling placeholders. Refuses to overwrite an existing file. */
67
+ export function renderTemplate(from, to, vars, written = []) {
68
+ for (const entry of readdirSync(from).sort()) {
69
+ const src = path.join(from, entry);
70
+ const dest = path.join(to, outName(entry));
71
+ if (statSync(src).isDirectory()) {
72
+ renderTemplate(src, dest, vars, written);
73
+ continue;
74
+ }
75
+ if (existsSync(dest))
76
+ throw new Error(`${dest} already exists`);
77
+ mkdirSync(path.dirname(dest), { recursive: true });
78
+ writeFileSync(dest, fill(readFileSync(src, 'utf8'), vars));
79
+ written.push(dest);
80
+ }
81
+ return written;
82
+ }
83
+ const write = (file, text, written) => {
84
+ if (existsSync(file))
85
+ throw new Error(`${file} already exists`);
86
+ mkdirSync(path.dirname(file), { recursive: true });
87
+ writeFileSync(file, text);
88
+ written.push(file);
89
+ };
90
+ const AUTH = {
91
+ bearer: {
92
+ secret: {
93
+ name: 'token',
94
+ title: 'API token',
95
+ help: 'A long-lived API token for a dedicated, least-privileged user.',
96
+ },
97
+ source: () => `/** Authenticates every request with the connection's API token. */
98
+ export function authHeaders({ secrets }: InitParams): () => Record<string, string> {
99
+ const token = requireString(secrets, 'token');
100
+ return () => ({ authorization: \`Bearer \${token}\` });
101
+ }
102
+ `,
103
+ fake: () => `export const CREDENTIALS = { token: 'fake-api-token-abcdef' };
104
+
105
+ export const authorized = (headers: IncomingHttpHeaders) => headers.authorization === \`Bearer \${CREDENTIALS.token}\`;
106
+ `,
107
+ },
108
+ 'api-key': {
109
+ secret: { name: 'apiKey', title: 'API key', help: 'An API key for a dedicated, least-privileged user.' },
110
+ source: (header) => `/** Authenticates every request with the connection's API key. */
111
+ export function authHeaders({ secrets }: InitParams): () => Record<string, string> {
112
+ const apiKey = requireString(secrets, 'apiKey');
113
+ return () => ({ '${header}': apiKey });
114
+ }
115
+ `,
116
+ fake: (header) => `export const CREDENTIALS = { apiKey: 'fake-api-key-abcdef' };
117
+
118
+ export const authorized = (headers: IncomingHttpHeaders) => headers['${header}'] === CREDENTIALS.apiKey;
119
+ `,
120
+ },
121
+ basic: {
122
+ secret: { name: 'password', title: 'Password', help: 'The password of a dedicated, least-privileged user.' },
123
+ user: true,
124
+ source: () => `/** Authenticates every request with the connection's username and password (HTTP Basic). */
125
+ export function authHeaders({ config, secrets }: InitParams): () => Record<string, string> {
126
+ const credentials = Buffer.from(\`\${requireString(config, 'username')}:\${requireString(secrets, 'password')}\`).toString('base64');
127
+ return () => ({ authorization: \`Basic \${credentials}\` });
128
+ }
129
+ `,
130
+ fake: () => `export const CREDENTIALS = { username: 'synoikia', password: 'fake-password-abcdef' };
131
+
132
+ export const authorized = (headers: IncomingHttpHeaders) =>
133
+ headers.authorization === \`Basic \${Buffer.from(\`\${CREDENTIALS.username}:\${CREDENTIALS.password}\`).toString('base64')}\`;
134
+ `,
135
+ },
136
+ none: {
137
+ source: () => `/** The upstream needs no authentication. */
138
+ export function authHeaders(_init: InitParams): () => Record<string, string> {
139
+ return () => ({});
140
+ }
141
+ `,
142
+ fake: () => `export const CREDENTIALS = {};
143
+
144
+ export const authorized = (_headers: IncomingHttpHeaders) => true;
145
+ `,
146
+ },
147
+ };
148
+ function authModule(auth, header) {
149
+ const spec = AUTH[auth];
150
+ const usesRequire = auth !== 'none';
151
+ return `${usesRequire ? "import { requireString } from '@synoikia/plugin-sdk';\n" : ''}import type { InitParams } from '@synoikia/plugin-sdk';
152
+
153
+ ${spec.source(header)}`;
154
+ }
155
+ function fakeAuthModule(auth, header) {
156
+ return `import type { IncomingHttpHeaders } from 'node:http';
157
+
158
+ /** The connection the tests use, and how the fake upstream checks it. */
159
+ ${AUTH[auth].fake(header)}`;
160
+ }
161
+ /** The generated manifest: connection form, secrets, network hosts and binding. */
162
+ export function manifestFor(opts, namespace) {
163
+ const spec = AUTH[opts.auth];
164
+ const properties = {
165
+ baseUrl: { type: 'string', format: 'uri', title: 'Base URL' },
166
+ ...(spec.user ? { username: { type: 'string', title: 'Username' } } : {}),
167
+ ...(spec.secret ? { [spec.secret.name]: { type: 'string', title: spec.secret.title, writeOnly: true } } : {}),
168
+ verifyTls: { type: 'boolean', title: 'Verify TLS certificate', default: true },
169
+ };
170
+ const ui = {
171
+ baseUrl: { widget: 'url', placeholder: `https://${opts.id}.internal.lan` },
172
+ ...(spec.user ? { username: { widget: 'text' } } : {}),
173
+ ...(spec.secret ? { [spec.secret.name]: { widget: 'secret', help: spec.secret.help } } : {}),
174
+ verifyTls: { widget: 'bool' },
175
+ };
176
+ const required = ['baseUrl', ...(spec.user ? ['username'] : []), ...(spec.secret ? [spec.secret.name] : [])];
177
+ const sensitiveKeys = [
178
+ ...new Set([...(spec.secret ? [spec.secret.name] : []), 'apiKey', 'api_key', 'token', 'password', 'secret']),
179
+ ];
180
+ return {
181
+ id: opts.id,
182
+ name: opts.name,
183
+ version: '0.1.0',
184
+ sdk: `^${SDK_VERSION}`,
185
+ description: opts.description ?? `${opts.name} for Synoikia.`,
186
+ entry: 'dist/index.js',
187
+ binding: { namespace, functions: [opts.archetype === 'openapi-rest' ? 'request' : 'call'] },
188
+ connection: {
189
+ schema: { type: 'object', required, properties },
190
+ ui,
191
+ help: `1. In ${opts.name}, create a dedicated user with only the access Synoikia should have${spec.secret ? `, and its ${spec.secret.title.toLowerCase()}` : ''}.\n2. Enter the base URL and credentials, then **Test connection**.\n3. Keep *Verify TLS* on unless ${opts.name} uses a self-signed certificate on a trusted network.\n\nAfter the first sync, raise groups on the Access page as needed.`,
192
+ },
193
+ sensitiveKeys,
194
+ network: { hosts: ['{{connection.baseUrl}}'] },
195
+ };
196
+ }
197
+ function packageFor(opts) {
198
+ const range = (k, name) => opts.ranges?.[k] ?? dependencyRange(name);
199
+ const ws = opts.archetype === 'websocket-rpc';
200
+ return {
201
+ name: `@synoikia/plugin-${opts.id}`,
202
+ version: '0.1.0',
203
+ private: true,
204
+ type: 'module',
205
+ main: 'dist/index.js',
206
+ scripts: {
207
+ build: 'synoikia-plugin build',
208
+ check: 'synoikia-plugin check',
209
+ typecheck: 'tsc -p tsconfig.json --noEmit',
210
+ test: 'synoikia-plugin check && synoikia-plugin build && vitest run',
211
+ },
212
+ dependencies: {
213
+ '@synoikia/plugin-sdk': range('sdk', '@synoikia/plugin-sdk'),
214
+ ...(ws ? { ws: '^8.22.0' } : {}),
215
+ },
216
+ devDependencies: {
217
+ '@synoikia/core': range('core', '@synoikia/core'),
218
+ '@synoikia/create-plugin': range('cli', '@synoikia/create-plugin'),
219
+ ...(ws ? { '@types/ws': '^8.18.1' } : {}),
220
+ },
221
+ };
222
+ }
223
+ /** Writes `plugins/<id>/` from the archetype's template. Returns the files written. */
224
+ export function newPlugin(opts) {
225
+ const idProblem = validateId(opts.id, opts.root);
226
+ if (idProblem)
227
+ throw new Error(`Plugin id: ${idProblem}`);
228
+ const namespace = opts.namespace ?? camelCase(opts.id);
229
+ const nsProblem = validateNamespace(namespace);
230
+ if (nsProblem)
231
+ throw new Error(`Namespace: ${nsProblem}`);
232
+ const nameProblem = validateName(opts.name);
233
+ if (nameProblem)
234
+ throw new Error(`Name: ${nameProblem}`);
235
+ const descProblem = opts.description === undefined ? undefined : validateDescription(opts.description);
236
+ if (descProblem)
237
+ throw new Error(`Description: ${descProblem}`);
238
+ if (!ARCHETYPES[opts.archetype])
239
+ throw new Error(`Unknown archetype ${opts.archetype}`);
240
+ if (!AUTH[opts.auth])
241
+ throw new Error(`Unknown auth ${opts.auth}`);
242
+ const header = (opts.apiKeyHeader ?? 'X-Api-Key').toLowerCase();
243
+ if (!/^[a-z0-9-]+$/.test(header))
244
+ throw new Error('The API key header may only hold letters, digits and dashes');
245
+ const dir = path.join(opts.root, 'plugins', opts.id);
246
+ const vars = {
247
+ id: opts.id,
248
+ name: opts.name,
249
+ namespace,
250
+ Pascal: pascalCase(opts.id),
251
+ description: opts.description ?? `${opts.name} for Synoikia.`,
252
+ fn: opts.archetype === 'openapi-rest' ? 'request' : 'call',
253
+ archetype: opts.archetype,
254
+ };
255
+ const written = [];
256
+ renderTemplate(path.join(TEMPLATES_DIR, 'plugin', 'common'), dir, vars, written);
257
+ renderTemplate(path.join(TEMPLATES_DIR, 'plugin', opts.archetype), dir, vars, written);
258
+ write(path.join(dir, 'manifest.json'), `${JSON.stringify(manifestFor(opts, namespace), null, 2)}\n`, written);
259
+ write(path.join(dir, 'package.json'), `${JSON.stringify(packageFor(opts), null, 2)}\n`, written);
260
+ write(path.join(dir, 'src', 'auth.ts'), authModule(opts.auth, header), written);
261
+ if (opts.archetype !== 'blank')
262
+ write(path.join(dir, 'test', 'fake-auth.ts'), fakeAuthModule(opts.auth, header), written);
263
+ return written;
264
+ }
265
+ /** Writes a new plugin repository. Returns the files written. */
266
+ export function createRepo(opts) {
267
+ const dir = path.resolve(opts.dir);
268
+ if (existsSync(dir) && readdirSync(dir).length)
269
+ throw new Error(`${dir} is not empty`);
270
+ const name = path.basename(dir);
271
+ // The directory name becomes the package name and appears in generated files.
272
+ if (!/^[a-z0-9][a-z0-9._-]{0,213}$/.test(name))
273
+ throw new Error(`${name}: use a lowercase directory name (letters, digits, . _ -), as for an npm package`);
274
+ const repository = opts.repository ?? `OWNER/${name}`;
275
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository))
276
+ throw new Error('repository must look like owner/name');
277
+ const vars = {
278
+ repoName: name,
279
+ repository,
280
+ coreRange: opts.ranges?.core ?? dependencyRange('@synoikia/core'),
281
+ cliRange: opts.ranges?.cli ?? dependencyRange('@synoikia/create-plugin'),
282
+ };
283
+ const written = [];
284
+ renderTemplate(path.join(TEMPLATES_DIR, 'repo', 'base'), dir, vars, written);
285
+ if (opts.release)
286
+ renderTemplate(path.join(TEMPLATES_DIR, 'repo', 'release'), dir, vars, written);
287
+ if (opts.claude)
288
+ renderTemplate(path.join(TEMPLATES_DIR, 'repo', 'claude'), dir, vars, written);
289
+ mkdirSync(path.join(dir, 'plugins'), { recursive: true });
290
+ return written;
291
+ }
292
+ /** `pnpm install` in the repository (links a new plugin, updates the lockfile). */
293
+ export function install(root) {
294
+ execFileSync('pnpm', ['install'], { cwd: root, stdio: 'inherit' });
295
+ }
296
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Vite/Vitest plugin that loads `.yaml` and `.md` imports the way `synoikia-plugin build` bundles
3
+ * them, so unit tests see exactly what the bundle does. Add it to the repository's `vitest.shared.ts`.
4
+ */
5
+ export declare function pluginFiles(): {
6
+ name: string;
7
+ enforce: "pre";
8
+ transform(code: string, id: string): {
9
+ code: string;
10
+ map: null;
11
+ } | undefined;
12
+ };
package/dist/vitest.js ADDED
@@ -0,0 +1,17 @@
1
+ import { moduleSource } from './files.js';
2
+ /**
3
+ * Vite/Vitest plugin that loads `.yaml` and `.md` imports the way `synoikia-plugin build` bundles
4
+ * them, so unit tests see exactly what the bundle does. Add it to the repository's `vitest.shared.ts`.
5
+ */
6
+ export function pluginFiles() {
7
+ return {
8
+ name: 'synoikia-plugin-files',
9
+ enforce: 'pre',
10
+ transform(code, id) {
11
+ const file = id.split('?')[0];
12
+ const source = moduleSource(file, file.endsWith('.md') ? code : undefined);
13
+ return source === undefined ? undefined : { code: source, map: null };
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=vitest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,KAAc;QACvB,SAAS,CAAC,IAAY,EAAE,EAAU;YAChC,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAC3E,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@synoikia/create-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Create Synoikia plugin repositories and plugins, then build, check and publish them",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/via-justa/synoikia-core.git",
9
+ "directory": "packages/create-plugin"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "create-plugin": "./dist/create.js",
14
+ "synoikia-plugin": "./dist/cli.js"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "synoikia-source": "./src/index.ts",
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./vitest": {
23
+ "synoikia-source": "./src/vitest.ts",
24
+ "types": "./dist/vitest.d.ts",
25
+ "import": "./dist/vitest.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "templates"
31
+ ],
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.8.1",
34
+ "esbuild": "^0.28.2",
35
+ "yaml": "^2.9.1",
36
+ "@synoikia/plugin-sdk": "^0.2.1"
37
+ },
38
+ "peerDependencies": {
39
+ "@synoikia/core": "^0.4.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@synoikia/core": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@types/ws": "^8.18.1",
48
+ "ws": "^8.22.0",
49
+ "@synoikia/core": "^0.4.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.build.json",
53
+ "typecheck": "tsc -p tsconfig.json --noEmit",
54
+ "test": "vitest run"
55
+ }
56
+ }
@@ -0,0 +1,22 @@
1
+ # yaml-language-server: $schema=./node_modules/@synoikia/plugin-sdk/plugin-settings.schema.json
2
+ #
3
+ # What {{name}} tells Synoikia about its operations. Declare them here (or discover them in
4
+ # src/plugin.ts and keep only the rules). Core decides what is allowed. Reference: docs/plugin-authoring.md.
5
+
6
+ defaults:
7
+ timeouts: { request: 15000, lookup: 10000 }
8
+
9
+ operations:
10
+ - key: example.read
11
+ group: example
12
+ classification: read
13
+ summary: An example read. Replace it with the upstream's operations.
14
+ - key: example.delete
15
+ group: example
16
+ classification: write
17
+ summary: An example destructive write.
18
+
19
+ rules:
20
+ - match: example.delete
21
+ locked: true
22
+ confirm: { param: /name }
@@ -0,0 +1,74 @@
1
+ import {
2
+ compileRules,
3
+ definePlugin,
4
+ ErrorCodes,
5
+ isPlainObject,
6
+ parsePluginSettings,
7
+ PluginError,
8
+ requireString,
9
+ staticCatalog,
10
+ truncate,
11
+ } from '@synoikia/plugin-sdk';
12
+ import type { PluginHandlers } from '@synoikia/plugin-sdk';
13
+ import raw from '../plugin.yaml';
14
+ import { authHeaders } from './auth.js';
15
+
16
+ /**
17
+ * The {{name}} plugin. The sandbox calls `{{namespace}}.call(operation, params)`. Fill in the client
18
+ * (`connect`), the version, and `invoke`; operations and their rules live in plugin.yaml.
19
+ */
20
+
21
+ const SERVICE = '{{name}}';
22
+
23
+ export const settings = parsePluginSettings(raw);
24
+
25
+ interface Client {
26
+ baseUrl: string;
27
+ headers: () => Record<string, string>;
28
+ }
29
+
30
+ export function create{{Pascal}}Plugin(): PluginHandlers {
31
+ return definePlugin<Client>({
32
+ // TODO: build a real client (HttpJsonClient for HTTP APIs, PendingRequests for socket protocols).
33
+ connect: (init) => ({ baseUrl: requireString(init.config, 'baseUrl'), headers: authHeaders(init) }),
34
+
35
+ // TODO: ask the upstream for its version.
36
+ version: async () => '0.0.0',
37
+
38
+ handlers(kit) {
39
+ const rules = compileRules(settings);
40
+ const operations = staticCatalog(rules);
41
+ const known = new Set(settings.operations.map((o) => o.key));
42
+ return {
43
+ async syncCatalog() {
44
+ return { upstreamVersion: await kit.version(), operations };
45
+ },
46
+
47
+ resolveOperation({ fn, args }) {
48
+ if (fn !== 'call')
49
+ throw new PluginError(ErrorCodes.UnknownOperation, `{{namespace}}.${fn} is not a binding function`);
50
+ const [key, params] = args;
51
+ if (typeof key !== 'string' || !known.has(key))
52
+ throw new PluginError(ErrorCodes.UnknownOperation, `${String(key)} is not a ${SERVICE} operation`);
53
+ if (params !== undefined && !isPlainObject(params))
54
+ throw new PluginError(ErrorCodes.InvalidParams, 'params must be an object');
55
+ // A split twin declared in plugin.yaml is locked; take it unless code here decides the call is safe.
56
+ const split = rules.splits(key)[0];
57
+ return { key: split ? `${key}#${split}` : key, params: params ?? {} };
58
+ },
59
+
60
+ async summarize({ key, params, targets }) {
61
+ const json = JSON.stringify(params ?? {});
62
+ const text = `${SERVICE} ${key}${json === '{}' ? '' : ` ${truncate(json, 400)}`}`;
63
+ const literal = await rules.confirmLiteral({ key, params, targets });
64
+ return literal ? { text, confirmLiteral: literal } : { text };
65
+ },
66
+
67
+ invoke({ key }) {
68
+ // TODO: call the upstream with kit.client(), then return rules.maskResult(key, result).
69
+ throw new PluginError(ErrorCodes.NotImplemented, `${key} is not implemented yet`);
70
+ },
71
+ };
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,33 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { checkConformance } from '@synoikia/plugin-sdk';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { create{{Pascal}}Plugin, settings } from '../src/plugin.js';
5
+
6
+ const manifest: unknown = JSON.parse(readFileSync(new URL('../manifest.json', import.meta.url), 'utf8'));
7
+
8
+ // Fill in a connection for your auth, and point baseUrl at a fake upstream (see @synoikia/core/testing's
9
+ // startFakeHttp) once the plugin talks to one.
10
+ const init = {
11
+ instanceId: '{{id}}',
12
+ config: { baseUrl: 'http://127.0.0.1:1', username: 'synoikia' },
13
+ secrets: { token: 't', apiKey: 'k', password: 'p' },
14
+ };
15
+
16
+ describe('{{name}} plugin', () => {
17
+ it('passes the SDK conformance checks', async () => {
18
+ const plugin = create{{Pascal}}Plugin();
19
+ const issues = await checkConformance({
20
+ manifest,
21
+ settings,
22
+ handlers: plugin,
23
+ init,
24
+ samples: [
25
+ { fn: 'call', args: ['example.read'], expectKey: 'example.read' },
26
+ { fn: 'call', args: ['example.delete', { name: 'thing' }], expectKey: 'example.delete' },
27
+ ],
28
+ rejects: [{ fn: 'call', args: ['example.nope'] }],
29
+ });
30
+ expect(issues).toEqual([]);
31
+ await plugin.shutdown?.();
32
+ });
33
+ });
@@ -0,0 +1,23 @@
1
+ # {{name}}
2
+
3
+ {{description}}
4
+
5
+ The sandbox calls `{{namespace}}.{{fn}}(…)`. Every call goes through Synoikia's permission gate, approvals, redaction and audit log; this plugin only describes {{name}}'s API.
6
+
7
+ ## Files
8
+
9
+ | File | What |
10
+ | --------------- | ---------------------------------------------------------------------------------------- |
11
+ | `manifest.json` | Binding, connection form, `sensitiveKeys` and network hosts |
12
+ | `plugin.yaml` | Locked operations, split twins, sensitive params and results, confirmation literals |
13
+ | `src/plugin.ts` | Discovery and anything that needs logic |
14
+ | `src/auth.ts` | How requests authenticate |
15
+ | `test/` | A fake {{name}}, unit tests, and e2e tests on Synoikia's harness |
16
+
17
+ ## Connection
18
+
19
+ See the manifest's `connection.help`. Use a dedicated {{name}} user with only the access Synoikia should have.
20
+
21
+ ## Locked operations
22
+
23
+ List the operations `plugin.yaml` locks, and why, here.
@@ -0,0 +1,10 @@
1
+ # {{name}} runbook
2
+
3
+ A manual checklist against a real {{name}} before a release.
4
+
5
+ 1. Add an instance with a dedicated user; **Test connection** reports the version.
6
+ 2. Sync: the catalog appears, and every group starts at the default level.
7
+ 3. Run a read at Read. Check that secrets in its result show as `[REDACTED]`.
8
+ 4. A write stays refused until its group is raised.
9
+ 5. A locked operation asks for approval and requires the typed confirmation literal.
10
+ 6. Check the audit log lists each call.
@@ -0,0 +1,4 @@
1
+ import { runPlugin } from '@synoikia/plugin-sdk';
2
+ import { create{{Pascal}}Plugin } from './plugin.js';
3
+
4
+ runPlugin(create{{Pascal}}Plugin());
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "customConditions": []
7
+ },
8
+ "include": ["src", "../../plugin-files.d.ts"]
9
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src", "test", "../../plugin-files.d.ts"]
7
+ }
@@ -0,0 +1 @@
1
+ export { default } from '../../vitest.shared.ts';
@@ -0,0 +1,49 @@
1
+ # yaml-language-server: $schema=./node_modules/@synoikia/plugin-sdk/plugin-settings.schema.json
2
+ #
3
+ # What {{name}} tells Synoikia about its operations. The OpenAPI spec lists them (GET reads, every other
4
+ # verb writes, an action-shaped GET is a write flagged for review); these rules lock, split, classify
5
+ # and describe them on top. Core decides what is allowed. Reference: docs/plugin-authoring.md.
6
+
7
+ defaults:
8
+ # The literal an approver types for a locked operation with no `confirm` of its own.
9
+ confirm: instance
10
+ timeouts: { request: 15000, lookup: 10000 }
11
+
12
+ # Names this instance for `confirm: instance`. Lookups are GETs only.
13
+ instanceName: { op: 'GET /status', field: name, fallback: '{{name}}' }
14
+
15
+ # Operations left out of the catalog entirely (session plumbing, debug endpoints).
16
+ exclude:
17
+ - 'POST /auth/*'
18
+
19
+ rules:
20
+ # Destructive or irreversible: always a human who types the confirmation literal.
21
+ - match: 'DELETE /items/{itemId}'
22
+ locked: true
23
+ # The approver types the item's name, looked up by the id in the call (or the id if that fails).
24
+ confirm:
25
+ lookup:
26
+ op: 'GET /items/{itemId}'
27
+ args: [{ itemId: { $param: /path/itemId } }]
28
+ field: name
29
+
30
+ # A secret the upstream returns under a name too common for the manifest's sensitiveKeys:
31
+ # - match: GET /backups/*
32
+ # sensitiveResult: { keys: [key], deep: true }
33
+
34
+ # A GET the heuristic flagged that was reviewed and only reads:
35
+ # - match: GET /sync/status
36
+ # classification: read
37
+ # reason: reviewed:read
38
+ # needsReview: false
39
+
40
+ plugin:
41
+ # Path of the API under the base URL.
42
+ apiPath: /api
43
+ # Where the instance serves its OpenAPI spec, under the API path.
44
+ specPath: /openapi.json
45
+ # Endpoint and field that report the version.
46
+ versionPath: /status
47
+ versionField: version
48
+ # Refuse a spec with fewer operations: a truncated one must not replace the catalog.
49
+ minOperations: 3