@crowi/plugin-api 0.1.0-alpha.2 → 1.0.0-alpha.4

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/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @crowi/plugin-api
2
+
3
+ Type-only contract for Crowi 2.0 plugins. A Crowi plugin is an ordinary
4
+ npm package that default-exports an object satisfying the `CrowiPlugin`
5
+ type from this package; the runtime loads it via `await
6
+ import('<plugin-name>')` at boot and calls each `register*` callback it
7
+ implements. See
8
+ [RFC-0001](https://github.com/crowi/crowi/blob/main/docs/rfcs/0001-plugin-architecture.md)
9
+ for the full design and the
10
+ [plugin development guide](https://crowi.wiki/docs/plugins/developing)
11
+ for a walkthrough.
12
+
13
+ > **Alpha notice**: Crowi v2 is under active alpha development and this
14
+ > package is at `0.x`. The API stability guarantee (working across every
15
+ > v2 minor) takes effect once it reaches `2.x`; the contract may still
16
+ > change during the alpha period.
17
+
18
+ ## Minimal plugin
19
+
20
+ ```ts
21
+ import type { CrowiPlugin } from '@crowi/plugin-api';
22
+
23
+ const myPlugin: CrowiPlugin = {
24
+ name: '@example/crowi-plugin-mystorage',
25
+ version: '0.1.0',
26
+
27
+ registerStorage: (registry, ctx) => {
28
+ registry.register('mystorage', {
29
+ put: async (key, body, meta) => ({ key }),
30
+ get: async (key) => { throw new Error('not implemented'); },
31
+ delete: async (key) => {},
32
+ });
33
+ },
34
+ };
35
+
36
+ export default myPlugin;
37
+ ```
38
+
39
+ `name` must match the npm package name — it doubles as the namespace
40
+ prefix for this plugin's config rows (`plugin:<name>:*`) and per-page
41
+ metadata (`page.metadata['<name>']`).
42
+
43
+ ## Authoring a config schema
44
+
45
+ A plugin declares its configurable values with `configSchema`, a Zod
46
+ object schema. The admin UI at `/admin/plugins` walks this schema to
47
+ auto-generate a config form, encrypt `@sensitive`-marked fields at
48
+ rest, and render `@action`-marked fields with a button.
49
+
50
+ This package's `peerDependencies` declares `zod: "^4"` — that is the
51
+ correct **npm package** to install, because the zod v4 package is the
52
+ one that ships a `zod/v3` compat subpath. What `peerDependencies`
53
+ cannot express is *which entry point* to import from, and that part
54
+ matters:
55
+
56
+ ```ts
57
+ // Correct — the v3 compat shim that the v4 package ships.
58
+ import { z } from 'zod/v3';
59
+
60
+ // Wrong — compiles and type-checks, but fails at plugin boot.
61
+ import { z } from 'zod';
62
+ ```
63
+
64
+ `CrowiPlugin.configSchema` is typed against `zod/v3`'s `z.ZodObject`,
65
+ and every runtime helper that reads a plugin's config schema
66
+ (`@sensitive` / `@action` marker detection, the admin form field
67
+ serializer, `listSensitiveKeys()`) introspects the `zod/v3` internal
68
+ shape (`_def.typeName`, `_def.values`, `.description`, …). A schema
69
+ built from the top-level `zod` (v4) API has a different internal shape
70
+ and none of that introspection can see through it — most importantly,
71
+ `@sensitive` fields stop being detected, and the value they guard would
72
+ be written to and read from storage as plaintext.
73
+
74
+ To turn that silent failure mode into a loud one, `PluginManager`
75
+ validates every plugin's `configSchema` at boot and throws if it wasn't
76
+ built from `zod/v3`:
77
+
78
+ ```
79
+ Plugin '<name>' declares configSchema built with the top-level 'zod' (v4) API.
80
+ Import from 'zod/v3' instead — @crowi/plugin-api's config-schema introspection
81
+ requires the zod v3 compat shape (see @crowi/plugin-api README).
82
+ ```
83
+
84
+ If you hit this error, the fix is always the same: change the `zod`
85
+ import for the file that builds `configSchema` (and `pageMetadataSchema`,
86
+ if you use it) to `import { z } from 'zod/v3'`. No other code needs to
87
+ change — `zod/v3`'s `z.object()` / `z.string()` / etc. API surface is
88
+ what every first-party Crowi plugin already uses.
89
+
90
+ ```ts
91
+ import { z } from 'zod/v3';
92
+ import type { CrowiPlugin } from '@crowi/plugin-api';
93
+
94
+ const myPlugin: CrowiPlugin = {
95
+ name: '@example/crowi-plugin-mystorage',
96
+ version: '0.1.0',
97
+
98
+ configSchema: z.object({
99
+ endpoint: z.string().url().describe('Storage endpoint URL'),
100
+ accessKey: z.string().describe('@sensitive Access key'),
101
+ }),
102
+
103
+ registerStorage: (registry, ctx) => {
104
+ const config = ctx.config<{ endpoint: string; accessKey: string }>();
105
+ // ... build the driver using config
106
+ },
107
+ };
108
+
109
+ export default myPlugin;
110
+ ```
111
+
112
+ ## See also
113
+
114
+ - [Plugin development guide](https://crowi.wiki/docs/plugins/developing) —
115
+ the full walkthrough (markers, `adminPlacement`, `configI18n`,
116
+ `PluginContext`, dependency plugins, renderer plugins).
117
+ - [RFC-0001: Plugin architecture](https://github.com/crowi/crowi/blob/main/docs/rfcs/0001-plugin-architecture.md)