@freema/drobek-modules 0.3.3

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 (33) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +53 -0
  3. package/dist/chunk-WHKPW5MP.js +6611 -0
  4. package/dist/index.d.ts +1604 -0
  5. package/dist/index.js +373 -0
  6. package/dist/mail-guard.d-BKJhsVA2.d.ts +2097 -0
  7. package/dist/migrations/core/0000_dusty_scarecrow.sql +84 -0
  8. package/dist/migrations/core/0001_busy_orphan.sql +69 -0
  9. package/dist/migrations/core/0002_odd_alex_power.sql +17 -0
  10. package/dist/migrations/core/0003_nosy_shatterstar.sql +26 -0
  11. package/dist/migrations/core/0004_nice_ben_parker.sql +27 -0
  12. package/dist/migrations/core/0005_panoramic_bill_hollister.sql +4 -0
  13. package/dist/migrations/core/0006_outgoing_ricochet.sql +29 -0
  14. package/dist/migrations/core/0007_app_versions.sql +95 -0
  15. package/dist/migrations/core/0008_user_bound_tokens.sql +42 -0
  16. package/dist/migrations/core/0009_app_name.sql +1 -0
  17. package/dist/migrations/core/0010_apps_origin.sql +12 -0
  18. package/dist/migrations/core/0011_modules.sql +32 -0
  19. package/dist/migrations/core/0012_data_module_tables.sql +9 -0
  20. package/dist/migrations/core/0014_get_logs.sql +26 -0
  21. package/dist/migrations/core/0016_apps_slug_release.sql +8 -0
  22. package/dist/migrations/core/0018_custom_domains.sql +22 -0
  23. package/dist/migrations/core/0021_abuse_reports.sql +23 -0
  24. package/dist/migrations/core/0022_gallery.sql +20 -0
  25. package/dist/migrations/core/0023_workspace_modules.sql +13 -0
  26. package/dist/migrations/core/0024_app_assets.sql +18 -0
  27. package/dist/migrations/core/0025_app_asset_snapshots.sql +28 -0
  28. package/dist/migrations/core/0026_publish_approval.sql +13 -0
  29. package/dist/migrations/core/0027_publish_block.sql +6 -0
  30. package/dist/migrations/core/meta/_journal.json +167 -0
  31. package/dist/testing.d.ts +269 -0
  32. package/dist/testing.js +1569 -0
  33. package/package.json +63 -0
@@ -0,0 +1,28 @@
1
+ -- NSO-362: assets honour publish. `app_assets` becomes the draft (what the
2
+ -- preview host serves); `app_version_assets` holds the set a publish froze for
3
+ -- a version (what the production host serves), `app_versions.assets_frozen_at`
4
+ -- marks a version that has one. Upgrade: the assets of every published app are
5
+ -- frozen as its live version's set, so nothing a public URL serves today
6
+ -- disappears; they stay the draft too.
7
+ CREATE TABLE "app_version_assets" (
8
+ "version_id" text NOT NULL,
9
+ "app_id" text NOT NULL,
10
+ "name" text NOT NULL,
11
+ "content_type" text NOT NULL,
12
+ "size" bigint NOT NULL,
13
+ "sha256" text NOT NULL,
14
+ "storage_key" text NOT NULL,
15
+ "updated_at" timestamp DEFAULT now() NOT NULL,
16
+ CONSTRAINT "app_version_assets_version_id_name_pk" PRIMARY KEY("version_id","name")
17
+ );
18
+ --> statement-breakpoint
19
+ ALTER TABLE "app_versions" ADD COLUMN "assets_frozen_at" timestamp;--> statement-breakpoint
20
+ ALTER TABLE "app_version_assets" ADD CONSTRAINT "app_version_assets_version_id_app_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "public"."app_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
21
+ ALTER TABLE "app_version_assets" ADD CONSTRAINT "app_version_assets_app_id_apps_id_fk" FOREIGN KEY ("app_id") REFERENCES "public"."apps"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
22
+ CREATE INDEX "app_version_assets_app_idx" ON "app_version_assets" USING btree ("app_id");--> statement-breakpoint
23
+ INSERT INTO "app_version_assets" ("version_id", "app_id", "name", "content_type", "size", "sha256", "storage_key", "updated_at")
24
+ SELECT a."published_version_id", aa."app_id", aa."name", aa."content_type", aa."size", aa."sha256", aa."storage_key", aa."updated_at"
25
+ FROM "app_assets" aa JOIN "apps" a ON a."id" = aa."app_id"
26
+ WHERE a."published_version_id" IS NOT NULL;--> statement-breakpoint
27
+ UPDATE "app_versions" SET "assets_frozen_at" = now()
28
+ WHERE "id" IN (SELECT "published_version_id" FROM "apps" WHERE "published_version_id" IS NOT NULL);
@@ -0,0 +1,13 @@
1
+ -- NSO-366: PUBLISH_APPROVAL=approval — a super-admin decides per workspace
2
+ -- whether it may publish. `publish_approved_at` null = not approved; the
3
+ -- approval request columns dedupe the operator e-mail. Upgrade: every
4
+ -- workspace that already has a published app is approved now, so turning
5
+ -- approval on never takes the next publish of a live app away.
6
+ ALTER TABLE "workspaces" ADD COLUMN "publish_approved_at" timestamp with time zone;--> statement-breakpoint
7
+ ALTER TABLE "workspaces" ADD COLUMN "publish_approved_by" text;--> statement-breakpoint
8
+ ALTER TABLE "workspaces" ADD COLUMN "publish_approval_requested_at" timestamp with time zone;--> statement-breakpoint
9
+ ALTER TABLE "workspaces" ADD COLUMN "publish_approval_requested_by" text;--> statement-breakpoint
10
+ ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_publish_approved_by_users_id_fk" FOREIGN KEY ("publish_approved_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
11
+ ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_publish_approval_requested_by_users_id_fk" FOREIGN KEY ("publish_approval_requested_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
12
+ UPDATE "workspaces" SET "publish_approved_at" = now()
13
+ WHERE "id" IN (SELECT "workspace_id" FROM "apps" WHERE "published_version_id" IS NOT NULL AND "deleted_at" IS NULL);
@@ -0,0 +1,6 @@
1
+ -- NSO-366: a super-admin can turn publishing off for a workspace, in every
2
+ -- PUBLISH_APPROVAL mode. Blocked = publish_blocked_at set; allowed =
3
+ -- publish_approved_at set (0026); neither = the server mode decides.
4
+ ALTER TABLE "workspaces" ADD COLUMN "publish_blocked_at" timestamp with time zone;--> statement-breakpoint
5
+ ALTER TABLE "workspaces" ADD COLUMN "publish_blocked_by" text;--> statement-breakpoint
6
+ ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_publish_blocked_by_users_id_fk" FOREIGN KEY ("publish_blocked_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
@@ -0,0 +1,167 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1782985986575,
9
+ "tag": "0000_dusty_scarecrow",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1783043106616,
16
+ "tag": "0001_busy_orphan",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1783067845061,
23
+ "tag": "0002_odd_alex_power",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "7",
29
+ "when": 1783096383394,
30
+ "tag": "0003_nosy_shatterstar",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "7",
36
+ "when": 1783125113132,
37
+ "tag": "0004_nice_ben_parker",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1783143918577,
44
+ "tag": "0005_panoramic_bill_hollister",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1783151896848,
51
+ "tag": "0006_outgoing_ricochet",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "7",
57
+ "when": 1790132000000,
58
+ "tag": "0007_app_versions",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 8,
63
+ "version": "7",
64
+ "when": 1790146977226,
65
+ "tag": "0008_user_bound_tokens",
66
+ "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 9,
70
+ "version": "7",
71
+ "when": 1790149105340,
72
+ "tag": "0009_app_name",
73
+ "breakpoints": true
74
+ },
75
+ {
76
+ "idx": 10,
77
+ "version": "7",
78
+ "when": 1790152014046,
79
+ "tag": "0010_apps_origin",
80
+ "breakpoints": true
81
+ },
82
+ {
83
+ "idx": 11,
84
+ "version": "7",
85
+ "when": 1790162052842,
86
+ "tag": "0011_modules",
87
+ "breakpoints": true
88
+ },
89
+ {
90
+ "idx": 12,
91
+ "version": "7",
92
+ "when": 1790175801064,
93
+ "tag": "0012_data_module_tables",
94
+ "breakpoints": true
95
+ },
96
+ {
97
+ "idx": 14,
98
+ "version": "7",
99
+ "when": 1790192202963,
100
+ "tag": "0014_get_logs",
101
+ "breakpoints": true
102
+ },
103
+ {
104
+ "idx": 16,
105
+ "version": "7",
106
+ "when": 1790194823940,
107
+ "tag": "0016_apps_slug_release",
108
+ "breakpoints": true
109
+ },
110
+ {
111
+ "idx": 18,
112
+ "version": "7",
113
+ "when": 1790194890971,
114
+ "tag": "0018_custom_domains",
115
+ "breakpoints": true
116
+ },
117
+ {
118
+ "idx": 21,
119
+ "version": "7",
120
+ "when": 1790196335795,
121
+ "tag": "0021_abuse_reports",
122
+ "breakpoints": true
123
+ },
124
+ {
125
+ "idx": 22,
126
+ "version": "7",
127
+ "when": 1790432415711,
128
+ "tag": "0022_gallery",
129
+ "breakpoints": true
130
+ },
131
+ {
132
+ "idx": 23,
133
+ "version": "7",
134
+ "when": 1790432800000,
135
+ "tag": "0023_workspace_modules",
136
+ "breakpoints": true
137
+ },
138
+ {
139
+ "idx": 24,
140
+ "version": "7",
141
+ "when": 1790433136356,
142
+ "tag": "0024_app_assets",
143
+ "breakpoints": true
144
+ },
145
+ {
146
+ "idx": 25,
147
+ "version": "7",
148
+ "when": 1790437501780,
149
+ "tag": "0025_app_asset_snapshots",
150
+ "breakpoints": true
151
+ },
152
+ {
153
+ "idx": 26,
154
+ "version": "7",
155
+ "when": 1790493019726,
156
+ "tag": "0026_publish_approval",
157
+ "breakpoints": true
158
+ },
159
+ {
160
+ "idx": 27,
161
+ "version": "7",
162
+ "when": 1790496410618,
163
+ "tag": "0027_publish_block",
164
+ "breakpoints": true
165
+ }
166
+ ]
167
+ }
@@ -0,0 +1,269 @@
1
+ import { AnyModule, SdkBundle, ModuleContext, MailEnvelope, Principal, EndUserCallbackResult, HookApp, DB, Logger, MailGuard } from './index.js';
2
+ import 'node:stream';
3
+ import 'drizzle-orm/postgres-js';
4
+ import 'zod';
5
+
6
+ /** One skill as the checker sees it (a module's `skill.markdown`, or a general `skills/<name>/SKILL.md`). */
7
+ interface SkillSource {
8
+ name: string;
9
+ kind: 'module' | 'general';
10
+ useWhen: string;
11
+ /** The markdown skill_info returns (a general skill: without its frontmatter). */
12
+ content: string;
13
+ /** The SKILL.md as named in the issues (e.g. `SKILL.md`, `modules/auth/SKILL.md`). */
14
+ file: string;
15
+ /** The file's full text (frontmatter included). */
16
+ fileText: string;
17
+ module?: AnyModule;
18
+ }
19
+ /** One finding of `checkSkill` / `checkSkillSources`. */
20
+ interface SkillIssue {
21
+ skill: string;
22
+ /** The SKILL.md (see SkillSource.file). */
23
+ file: string;
24
+ /** 0-based index of the code block in the file; -1 when the issue is not about one block. */
25
+ block: number;
26
+ /** 1-based line in the SKILL.md (0 when the issue concerns the whole file). */
27
+ line: number;
28
+ message: string;
29
+ }
30
+ /** `SKILL.md:12 (skill "erp", code block #2): message` */
31
+ declare function formatSkillIssue(p: SkillIssue): string;
32
+
33
+ /**
34
+ * `checkSkill(module)` (NSO-349) — the SKILL.md gate the built-in modules
35
+ * pass, for any module: a module author runs it in the module's own tests
36
+ * (`@drobek/modules/testing`), the repo gate (packages/skills-check) runs
37
+ * `checkSkillSources` over the built-in modules + the general skills.
38
+ *
39
+ * - the format: the five sections in order, ≤ 150 lines, a one-sentence
40
+ * "use when", working code first, only real error codes (the core
41
+ * catalogue + the module's own `errors`), present tense (format.ts);
42
+ * - the code: every fenced block compiled with drobek's compiler and
43
+ * typechecked against the sdk.d.ts of the module (+ `modules`), `ts api`
44
+ * blocks mutually assignable to the real SDK, configure_module payloads
45
+ * against the module's schema (examples.ts).
46
+ */
47
+
48
+ interface CheckSkillOptions {
49
+ /**
50
+ * Other modules the skill's examples use next to this one (e.g. the
51
+ * built-in `auth` module for `drobek.auth`): their SDK is composed in and
52
+ * their configure_module payloads are checked. Default: none.
53
+ */
54
+ modules?: AnyModule[];
55
+ /** The SKILL.md as named in the issues (default `SKILL.md`). */
56
+ file?: string;
57
+ /**
58
+ * The directory whose node_modules resolve the examples' bare imports
59
+ * (`react` → @types/react, …); default: the working directory. An import
60
+ * that does not resolve is typed `any`.
61
+ */
62
+ root?: string;
63
+ }
64
+ interface CheckSkillSourcesOptions {
65
+ /** The SDK bundle of `modules` (built when omitted). */
66
+ sdk?: SdkBundle;
67
+ /** See CheckSkillOptions.root. */
68
+ root?: string;
69
+ }
70
+ /** The error codes a skill may name: the core catalogue + its module's `errors` (a general skill: every module's). */
71
+ declare function knownErrorCodes(src: SkillSource, modules: AnyModule[]): Set<string>;
72
+ /** Format + code issues of `skills` on a server running `modules` (every module skill's module must be among them). */
73
+ declare function checkSkillSources(skills: SkillSource[], modules: AnyModule[], opts?: CheckSkillSourcesOptions): Promise<SkillIssue[]>;
74
+ /** The skill of one module as skill_info serves it. */
75
+ declare function moduleSkillSource(module: AnyModule, file?: string): SkillSource;
76
+ /**
77
+ * Check a module's skill (`defineModule({ skill })`) the way drobek checks
78
+ * its built-in modules. `[]` = it passes.
79
+ *
80
+ * const issues = await checkSkill(erp);
81
+ * expect(issues.map(formatSkillIssue)).toEqual([]);
82
+ */
83
+ declare function checkSkill(module: AnyModule, opts?: CheckSkillOptions): Promise<SkillIssue[]>;
84
+
85
+ interface ExamplesOptions {
86
+ /** The SDK bundle of `modules` (built when omitted). */
87
+ sdk?: SdkBundle;
88
+ /** The directory whose node_modules resolve the examples' bare imports (default: the working directory). */
89
+ root?: string;
90
+ }
91
+ interface ExamplesReport {
92
+ problems: SkillIssue[];
93
+ counts: {
94
+ blocks: number;
95
+ compiled: number;
96
+ typechecked: number;
97
+ apiChecked: number;
98
+ configChecked: number;
99
+ };
100
+ }
101
+ /**
102
+ * Compile and typecheck every code block of `skills` against the SDK of
103
+ * `modules` (the server's sdk.d.ts + the inline declarations).
104
+ */
105
+ declare function checkExamples(skills: SkillSource[], modules: AnyModule[], opts?: ExamplesOptions): Promise<ExamplesReport>;
106
+
107
+ declare const SKILL_SECTIONS: readonly ["1. When to use", "2. Minimal working code", "3. API and types", "4. Rules and limits", "5. Errors → fix"];
108
+ declare const SKILL_MAX_LINES = 150;
109
+ /**
110
+ * The format issues of one skill. `knownCodes`: the error codes the skill may
111
+ * name in "Errors → fix" (the core catalogue + the module's own `errors`).
112
+ */
113
+ declare function skillFormatIssues(src: SkillSource, knownCodes: ReadonlySet<string>): SkillIssue[];
114
+
115
+ /**
116
+ * A tiny Markdown reader for the skills: fenced code blocks (with their info
117
+ * string) and `##` headings. Skills are plain CommonMark written by us, so a
118
+ * line scanner is enough — no dependency.
119
+ */
120
+ interface CodeBlock {
121
+ /** First word of the info string (`tsx`, `json`, `html`, …); '' when none. */
122
+ lang: string;
123
+ /** The rest of the info string (`api`, `drobek.json`, …). */
124
+ meta: string;
125
+ code: string;
126
+ /** 1-based line of the opening fence. */
127
+ line: number;
128
+ /** 0-based index among the file's code blocks. */
129
+ index: number;
130
+ /** The `##` heading the block sits under ('' before the first). */
131
+ section: string;
132
+ }
133
+ interface Heading {
134
+ level: number;
135
+ text: string;
136
+ line: number;
137
+ }
138
+ declare function codeBlocks(markdown: string): CodeBlock[];
139
+ /** Every ATX heading outside code blocks. */
140
+ declare function headings(markdown: string): Heading[];
141
+ /** The text between the `##` heading that starts with `prefix` and the next `##` (fences included). */
142
+ declare function sectionText(markdown: string, prefix: string): string;
143
+ /** The markdown without its fenced code blocks (the prose an agent reads). */
144
+ declare function proseOf(markdown: string): string;
145
+
146
+ /**
147
+ * The folder of drobek's core migrations (journal `__drizzle_migrations_core`):
148
+ * apply it before a module's own migrations, whose tables reference
149
+ * `apps(id)`. The published package ships a copy (`dist/migrations/core`);
150
+ * in the drobek repository it is `packages/db/drizzle/migrations`.
151
+ */
152
+ declare function coreMigrationsDir(): string;
153
+ /**
154
+ * A workspace + an app in a database with the core migrations applied — the
155
+ * `app` to pass to createModuleTestContext (module rows reference its id).
156
+ */
157
+ declare function createTestApp(db: unknown, opts?: {
158
+ slug?: string;
159
+ }): Promise<HookApp>;
160
+ interface ModuleTestOptions {
161
+ /** A partial config (merged over configDefaults, then validated). */
162
+ config?: Record<string, unknown>;
163
+ principal?: Principal;
164
+ /** Secret name → plaintext. */
165
+ secrets?: Record<string, string>;
166
+ /** Limit overrides (the rest come from the module's defaults). */
167
+ limits?: Record<string, number>;
168
+ app?: Partial<HookApp>;
169
+ db?: DB;
170
+ log?: Logger;
171
+ /** The app host the requests come from (default `http://test--preview.apps.localhost`). */
172
+ origin?: string;
173
+ now?: () => number;
174
+ /** The app owners' addresses (`{ appOwners: true }` recipients; default none). */
175
+ owners?: string[];
176
+ /**
177
+ * The operator-wide e-mail guard core runs around every `ctx.email.send`
178
+ * (e.g. `memoryMailGuard(...)`): pass one to test how the module behaves
179
+ * while module e-mail is paused. Default: no guard.
180
+ */
181
+ mailGuard?: MailGuard;
182
+ /**
183
+ * Slot → the contributions `ctx.contributions(slot)` returns (as the
184
+ * slot's schema would have parsed them; default: none — `[]`). A slot
185
+ * host's `compose` runs with them first, as at server start.
186
+ */
187
+ contributions?: Record<string, unknown[]>;
188
+ }
189
+ /** One request to the module's `endUsers.callback` (the dashboard-host IdP callback). */
190
+ interface TestCallbackInit {
191
+ provider: string;
192
+ method?: 'GET' | 'POST';
193
+ query?: Record<string, string>;
194
+ body?: Record<string, string> | null;
195
+ /** Default `127.0.0.1`; `null` = no resolved client IP. */
196
+ clientIp?: string | null;
197
+ }
198
+ interface TestRequestInit {
199
+ /** A JSON body. */
200
+ body?: unknown;
201
+ /** A raw body instead (set its `content-type` in `headers`), e.g. multipart/form-data. */
202
+ rawBody?: Buffer | string;
203
+ /** A `bodyTypes: ['file']` route streams `rawBody` in chunks of this many bytes (default 64 KiB). */
204
+ chunkSize?: number;
205
+ query?: Record<string, string>;
206
+ headers?: Record<string, string>;
207
+ /** Default `127.0.0.1`; `null` = no resolved client IP (per-IP limits are skipped). */
208
+ clientIp?: string | null;
209
+ }
210
+ interface TestResponse {
211
+ status: number;
212
+ headers: Record<string, string>;
213
+ /** Parsed JSON (or the raw text when the body is not JSON). */
214
+ body: unknown;
215
+ /** The raw response bytes (a streamed body is collected). */
216
+ bytes: Buffer;
217
+ /** Request body bytes a streaming (`file`) route pulled before it stopped. */
218
+ bodyBytesRead: number;
219
+ }
220
+ interface ModuleTestContext {
221
+ ctx: ModuleContext<any>;
222
+ /**
223
+ * Run `method path` through the production pipeline. SDK header + same
224
+ * Origin are sent by default. Rejects — where production answers
225
+ * `500 internal_error` — when the handler throws something other than a
226
+ * ModuleError, or a ModuleError whose code is neither a core code nor in
227
+ * the module's `errors`.
228
+ */
229
+ request(method: string, path: string, init?: TestRequestInit): Promise<TestResponse>;
230
+ /** Audit rows the module wrote (`<module>.<action>`). */
231
+ audits: {
232
+ action: string;
233
+ meta: Record<string, unknown>;
234
+ }[];
235
+ /**
236
+ * E-mails the module sent (resolved recipients, one entry per send). When
237
+ * the module under test is the mail authority (`mail`), its own `prepare`
238
+ * runs first (limits, envelope) exactly as core runs it.
239
+ */
240
+ emails: ({
241
+ to: string[];
242
+ subject: string;
243
+ text: string;
244
+ kind: 'sign_in' | 'notification';
245
+ } & MailEnvelope)[];
246
+ /** Change who is calling. */
247
+ setPrincipal(principal: Principal): void;
248
+ /**
249
+ * Run the module's `confirmRequired(before, after, context)` the way
250
+ * configure_module does — both configs merged over configDefaults and
251
+ * validated, the context naming the test app and `db` ([] without a
252
+ * confirmRequired).
253
+ */
254
+ confirm(before: Record<string, unknown>, after: Record<string, unknown>): Promise<string[]>;
255
+ /**
256
+ * Run the module's `endUsers.callback` the way core runs it for
257
+ * `/__drobek/auth/callback/:provider` on the dashboard host: the test app
258
+ * is the only live app (`services.app(id)` answers it for its id, null for
259
+ * any other), with the test config, secrets, limits and audits; the rate
260
+ * limiter shares the test clock. Rejects when the module has no callback.
261
+ */
262
+ endUserCallback(init: TestCallbackInit): Promise<EndUserCallbackResult>;
263
+ /** The module as the test runs it (composed from `contributions` when it hosts slots). */
264
+ module: AnyModule;
265
+ }
266
+ declare function createModuleTestContext(declared: AnyModule, opts?: ModuleTestOptions): ModuleTestContext;
267
+
268
+ export { SKILL_MAX_LINES, SKILL_SECTIONS, checkExamples, checkSkill, checkSkillSources, codeBlocks, coreMigrationsDir, createModuleTestContext, createTestApp, formatSkillIssue, headings, knownErrorCodes, moduleSkillSource, proseOf, sectionText, skillFormatIssues };
269
+ export type { CheckSkillOptions, CheckSkillSourcesOptions, CodeBlock, ExamplesOptions, ExamplesReport, Heading, ModuleTestContext, ModuleTestOptions, SkillIssue, SkillSource, TestCallbackInit, TestRequestInit, TestResponse };