@50arms/plugin-sdk 0.0.0-stage → 0.2.53

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.
@@ -0,0 +1,246 @@
1
+ /** The lockfiles a project may carry; the archive must hold one so the download installs the exact versions it was built with. */
2
+ declare const LOCKFILE_NAMES: string[];
3
+ /**
4
+ * The files of a plugin project's source archive, sorted. Inside git: tracked plus untracked-unignored. Outside git: everything the top-level
5
+ * `.gitignore` does not ignore. Both drop `node_modules`, `.git`, a top-level `dist` and every `.env*` file.
6
+ */
7
+ declare function sourceFiles(projectDir: string): Promise<string[]>;
8
+ /** The project's source as a gzip-compressed tar archive, ready for `source_archive_base64`. The same files give the same bytes (fixed mtime, `portable`). */
9
+ declare function archiveSource(projectDir: string): Promise<Buffer>;
10
+
11
+ /** The names of the surface-wide settings, one per member of {@link SurfaceWideSettings} (a new member fails `tsc` here). */
12
+ declare const SURFACE_WIDE_SETTING_KEYS: readonly ["date_range", "keyword"];
13
+ type SurfaceWideSettingKey = (typeof SURFACE_WIDE_SETTING_KEYS)[number];
14
+
15
+ /**
16
+ * DX-6233 / DX-6269 (PLAN-96 G-6) - the schema a Widget's config and settings are declared in, and the check of a value against it. Import-free of Node: the
17
+ * app imports this module at runtime (`@backend/widget-surfaces/value-schema.js`) to draw a placement's form and to check what is stored, and the server
18
+ * imports the same module to refuse a write the schema rejects, so the two can never disagree about what a Widget accepts.
19
+ *
20
+ * Every field carries its default, so the schema is also where a Widget's defaults live. A value is checked as a (possibly partial) set of the schema's
21
+ * fields: a key the schema does not name, or a value of the wrong type, is an issue, never read as a default.
22
+ */
23
+ /**
24
+ * DX-6254 - the words that name a field, or one option of a field: a built-in Widget's message id, which the app looks up in the person's language, or the text a
25
+ * plugin's author wrote (a plugin brings no message catalog). The Configure form shows these, never a field's key or an option's stored id.
26
+ */
27
+ type FieldText = {
28
+ readonly messageId: string;
29
+ } | {
30
+ readonly text: string;
31
+ };
32
+ type ValueField = {
33
+ readonly type: "string";
34
+ readonly label: FieldText;
35
+ readonly default: string;
36
+ readonly maxLength?: number;
37
+ readonly enum?: readonly string[];
38
+ /** The words for each option of `enum`, by option id; present exactly when `enum` is, naming every option and no other. The stored value is the id. */
39
+ readonly optionLabels?: Readonly<Record<string, FieldText>>;
40
+ } | {
41
+ readonly type: "number";
42
+ readonly label: FieldText;
43
+ readonly default: number;
44
+ readonly min?: number;
45
+ readonly max?: number;
46
+ readonly integer?: boolean;
47
+ } | {
48
+ readonly type: "boolean";
49
+ readonly label: FieldText;
50
+ readonly default: boolean;
51
+ };
52
+ type ValueSchema = Readonly<Record<string, ValueField>>;
53
+
54
+ /**
55
+ * DX-6269 (PLAN-96 G-6) - the layout rules of a widget surface, as pure functions: snapping to whole cells, a Widget's size limits, collision, clamping and the
56
+ * first free space. Import-free of Node. The app runs them live while a placement is dragged or resized (so what the person sees while dragging is what is
57
+ * saved), and the server runs the same functions to check every layout write and to place a new or changed placement, so the two never disagree.
58
+ *
59
+ * Positions are 0-based cells; the grid is `GRID_COLUMNS` wide with unlimited rows. A layout never has two boxes overlapping: moving, resizing or changing a
60
+ * box pushes whatever it now overlaps down to just below it, and whatever that one now overlaps in turn. The box being edited never moves for another.
61
+ */
62
+
63
+ interface Size {
64
+ w: number;
65
+ h: number;
66
+ }
67
+
68
+ /**
69
+ * DX-6269 (PLAN-96 G-6; DX-6233 finding (d)) - a Widget's DECLARATION: the part of a Widget that is data - its key, the sizes it may take, its config and
70
+ * settings schemas, the surface-wide settings it reads and the host context it needs. The component that draws it, its title and its description are the
71
+ * app's (`frontend/src/lib/widgets/catalog.ts`). Import-free of Node, so the app, the server and DX-6254's plugin SDK all read the one declaration: the app
72
+ * to draw the Widget and its forms, the server to refuse a placement write the declaration rejects.
73
+ */
74
+
75
+ type CellSize = Size;
76
+ interface WidgetDeclaration<C extends ValueSchema = ValueSchema, S extends ValueSchema = ValueSchema> {
77
+ /** Stable identity, stored on every placement: lowercase words joined by `-` for a built-in, `plugin:<plugin key>:<widget key>` for a plugin's (`pluginWidgetKey`). */
78
+ readonly key: string;
79
+ /** The size a new placement gets, and the smallest and largest one may take, in cells. */
80
+ readonly size: {
81
+ readonly default: CellSize;
82
+ readonly min: CellSize;
83
+ readonly max: CellSize;
84
+ };
85
+ readonly config: C;
86
+ readonly settings: S;
87
+ readonly readsSurfaceSettings: readonly SurfaceWideSettingKey[];
88
+ /** The host context keys the Widget cannot render without. */
89
+ readonly needsHostContext: readonly string[];
90
+ }
91
+
92
+ /**
93
+ * DX-5170 / DX-4679 section 2 - the shape of `danx-plugin.json`, the manifest `@50arms/plugin-sdk` writes beside a built plugin and the
94
+ * upload validator (DX-5171) checks before a revision is stored: the entry module and every listed file present with the stated size and
95
+ * sha256, nothing unlisted, and `runtime` naming the majors the plugin was built against (`runtime-contract.ts`).
96
+ *
97
+ * Pure types and constants; the writer is the SDK's, the checks are the server's.
98
+ */
99
+
100
+ /** One file of the built plugin, other than the manifest itself (a file cannot hold its own hash). */
101
+ interface PluginManifestFile {
102
+ /** Relative to the output folder, `/` separated. */
103
+ readonly path: string;
104
+ readonly size: number;
105
+ /** Lowercase hex. */
106
+ readonly sha256: string;
107
+ }
108
+ declare const PLUGIN_VIEW_EXPORTS: readonly ["CardView", "CardListView"];
109
+ type PluginViewExport = (typeof PLUGIN_VIEW_EXPORTS)[number];
110
+ interface PluginManifest {
111
+ /** The ES module the app `import()`s, a `path` in `files`. */
112
+ readonly entry: string;
113
+ /** The stylesheets to add with the module, each a `path` in `files`; empty when the plugin ships none. */
114
+ readonly styles: readonly string[];
115
+ /** DX-5171: a screenshot taken at publish, a listed `.png` or `.webp` shown to the approver; absent when there is none. */
116
+ readonly screenshot?: string;
117
+ /** Each shared library's major the plugin was built against (`PLUGIN_RUNTIME_LIBRARIES`), compared with what the app serves. */
118
+ readonly runtime: Readonly<Record<string, number>>;
119
+ /**
120
+ * DX-6178: the card types the plugin's `CardView` is made for (card type keys, e.g. `["Bug"]`); absent when it is made for none. Each is that type's DEFAULT view
121
+ * while the plugin's active revision is approved and exports `CardView`: the admin's card-view board shows it as the type's "Default" and reverts to it.
122
+ * Where two plugins declare one type, the one activated first wins (`plugins.activated_at`).
123
+ */
124
+ readonly cardTypes?: readonly string[];
125
+ /** DX-5282: which view modules the entry exports (`PLUGIN_VIEW_EXPORTS`), recorded at build from the entry chunk's real exports; empty only for a plugin that declares Widgets. */
126
+ readonly exports: readonly PluginViewExport[];
127
+ /** DX-6254: the Widgets the plugin adds to the Widget Library, each drawn by one of the entry's exports; empty when it adds none. Checked by `manifestWidgetIssues`. */
128
+ readonly widgets: readonly PluginManifestWidget[];
129
+ readonly files: readonly PluginManifestFile[];
130
+ }
131
+ /** DX-6254: one Widget a plugin declares. Its words are the author's own text: a plugin brings no message catalog. */
132
+ interface PluginManifestWidget {
133
+ /** The name of the entry's export that draws the Widget, recorded at build only when the entry chunk really exports it. */
134
+ readonly export: string;
135
+ readonly title: string;
136
+ /** One sentence for the Widget Library: what the Widget shows. */
137
+ readonly description: string;
138
+ /** The built-in Widgets' own declaration, with `key` the Widget's key inside the plugin; a placement stores `plugin:<plugin key>:<key>`. */
139
+ readonly declaration: WidgetDeclaration;
140
+ }
141
+
142
+ /** The project's build output folder: the SDK's Vite preset builds into Vite's default `dist`. */
143
+ declare const PLUGIN_DIST_DIR = "dist";
144
+ /** One built file, as the upload route takes it. */
145
+ interface BuiltFile {
146
+ readonly path: string;
147
+ readonly bytes: Buffer;
148
+ }
149
+ interface BuiltPlugin {
150
+ readonly manifest: PluginManifest;
151
+ readonly files: readonly BuiltFile[];
152
+ }
153
+ /**
154
+ * Runs the project's own `npm run build` in a short-lived child process (the DX-4727 pattern): everything Vite loads belongs to that
155
+ * process and is released when it exits, so the long-lived MCP server never holds a file of the plugin's project. A non-zero exit
156
+ * rejects with the tail of the build's output.
157
+ */
158
+ declare function runProjectBuild(projectDir: string): Promise<void>;
159
+ /**
160
+ * Reads what a build left in `<projectDir>/dist`: the `danx-plugin.json` manifest the `danxPlugin()` preset wrote and the bytes of every
161
+ * file it lists. Whether the manifest is valid is the dashboard's to say (`manifest-validation.ts`); this only refuses a build that left
162
+ * no manifest, because there is nothing to upload then.
163
+ */
164
+ declare function readBuiltPlugin(projectDir: string): Promise<BuiltPlugin>;
165
+ /** Builds the project and reads the result. */
166
+ declare function buildPlugin(projectDir: string): Promise<BuiltPlugin>;
167
+
168
+ /** Where a screenshot lands in the bundle; the manifest's `screenshot` field names it (DX-5171). */
169
+ declare const PLUGIN_SCREENSHOT_FILE = "screenshot.png";
170
+ /**
171
+ * The built plugin with a screenshot added to the upload: the PNG joins the files, and the manifest lists it (size and sha256 like every other
172
+ * file) and names it in `screenshot`. Only the upload carries it; the project's `dist/` is untouched, so a rebuild from the source download
173
+ * yields the same files minus this one.
174
+ */
175
+ declare function withScreenshot(built: BuiltPlugin, png: Buffer): BuiltPlugin;
176
+
177
+ /**
178
+ * DX-5237 - the one seam between the publish flow and the dashboard. The MCP tool passes its authenticated `DashboardHttpClient`; the
179
+ * `danx-plugin` CLI passes {@link fetchTransport}. The flow never builds a URL or a header, so neither caller's credential handling leaks into it.
180
+ */
181
+ /** A 2xx answer or a 4xx refusal, the body parsed and untouched: a refusal's named reason must reach the caller verbatim. 5xx and network failures throw. */
182
+ interface TransportResult {
183
+ readonly ok: boolean;
184
+ readonly status: number;
185
+ readonly body: unknown;
186
+ }
187
+ interface PluginTransport {
188
+ request(call: {
189
+ method: "GET" | "POST";
190
+ path: string;
191
+ query?: Record<string, string | number>;
192
+ body?: unknown;
193
+ }): Promise<TransportResult>;
194
+ }
195
+ /** A transport over `fetch` for a person's own API key (`Authorization: Bearer`, `.claude/rules/dashboard.md` "Scripting the API"). */
196
+ declare function fetchTransport(config: {
197
+ baseUrl: string;
198
+ apiKey: string;
199
+ }, fetchImpl?: typeof fetch): PluginTransport;
200
+
201
+ /** The Settings page that lists a plugin's revisions and approves them (`frontend/src/app/settings-nav.ts`, `plugins`). */
202
+ declare const PLUGINS_SETTINGS_PATH = "/settings/team/plugins";
203
+ interface PublishInput {
204
+ /** The plugin's stable key within the team (lowercase letters, digits, - and _). Created on the first publish. */
205
+ readonly plugin: string;
206
+ /** The display name given to a plugin this publish creates; ignored when the plugin exists. Defaults to the key. */
207
+ readonly name?: string;
208
+ /** What this revision changes, shown to the approver. */
209
+ readonly note: string;
210
+ readonly built: BuiltPlugin;
211
+ /** The project's `source.tar.gz`. */
212
+ readonly source: Buffer;
213
+ /** The dashboard's base URL, the front of the approval link. */
214
+ readonly dashboardUrl: string;
215
+ }
216
+ /** What a published revision answers. */
217
+ interface PublishedRevision {
218
+ readonly plugin: string;
219
+ readonly plugin_id: number;
220
+ readonly revision_id: string;
221
+ readonly revision_no: number;
222
+ /** Where a team admin approves it: Settings > Plugins. */
223
+ readonly approval_url: string;
224
+ }
225
+ /** `ok: true` carries the revision; a refusal is the dashboard's, untouched, with the step it came from (`find`, `create` or `upload`). */
226
+ type PublishResult = {
227
+ readonly ok: true;
228
+ readonly status: number;
229
+ readonly body: PublishedRevision;
230
+ } | {
231
+ readonly ok: false;
232
+ readonly status: number;
233
+ readonly body: unknown;
234
+ readonly step: "find" | "create" | "upload";
235
+ };
236
+ /** The one rule for a blank note. The MCP tool and the CLI call it first, before the build, so a blank note costs nothing; `uploadRevision` calls it too. */
237
+ declare function assertNote(note: string): void;
238
+ /**
239
+ * Uploads a built plugin as a new revision: finds the plugin by key (creating it when the key is new), then posts the bundle and its source in
240
+ * one request. The dashboard is the only validator: a refusal (a manifest rule, a size cap, a runtime-major mismatch, a missing permission)
241
+ * comes back verbatim in {@link PublishResult}, with the step that was refused.
242
+ */
243
+ declare function uploadRevision(transport: PluginTransport, input: PublishInput): Promise<PublishResult>;
244
+
245
+ export { LOCKFILE_NAMES, PLUGINS_SETTINGS_PATH, PLUGIN_DIST_DIR, PLUGIN_SCREENSHOT_FILE, archiveSource, assertNote, buildPlugin, fetchTransport, readBuiltPlugin, runProjectBuild, sourceFiles, uploadRevision, withScreenshot };
246
+ export type { BuiltFile, BuiltPlugin, PluginTransport, PublishInput, PublishResult, PublishedRevision, TransportResult };