@coywolf/emdash 0.0.0-stage → 0.35.14
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/LICENSE +21 -0
- package/README.md +203 -2
- package/docs/README.md +45 -0
- package/docs/ai.md +39 -0
- package/docs/backups.md +88 -0
- package/docs/breadcrumbs.md +59 -0
- package/docs/code-blocks.md +21 -0
- package/docs/content-urls.md +41 -0
- package/docs/custom-blocks.md +27 -0
- package/docs/discovery.md +32 -0
- package/docs/files.md +44 -0
- package/docs/form-uploads.md +59 -0
- package/docs/headings.md +37 -0
- package/docs/images.md +141 -0
- package/docs/internals.md +13 -0
- package/docs/links.md +34 -0
- package/docs/performance.md +72 -0
- package/docs/redirects.md +84 -0
- package/docs/releasing.md +21 -0
- package/docs/reviews.md +89 -0
- package/docs/robots.md +81 -0
- package/docs/schema.md +79 -0
- package/docs/search.md +114 -0
- package/docs/videos.md +48 -0
- package/docs/wordpress-import.md +198 -0
- package/package.json +79 -5
- package/src/admin/admin-css.ts +57 -0
- package/src/admin/ai.tsx +915 -0
- package/src/admin/backups.tsx +613 -0
- package/src/admin/breadcrumbs.tsx +542 -0
- package/src/admin/codeBlocks.tsx +204 -0
- package/src/admin/customBlocks.tsx +321 -0
- package/src/admin/dirty.ts +20 -0
- package/src/admin/discovery.tsx +463 -0
- package/src/admin/download.ts +28 -0
- package/src/admin/feature-switches.tsx +124 -0
- package/src/admin/features.tsx +142 -0
- package/src/admin/files.tsx +791 -0
- package/src/admin/formUploads.tsx +445 -0
- package/src/admin/guides.tsx +18 -0
- package/src/admin/headings.tsx +262 -0
- package/src/admin/images.tsx +825 -0
- package/src/admin/index.tsx +62 -0
- package/src/admin/links.tsx +980 -0
- package/src/admin/performance.tsx +330 -0
- package/src/admin/preview.tsx +63 -0
- package/src/admin/redirects-removed.tsx +266 -0
- package/src/admin/redirects.tsx +470 -0
- package/src/admin/reviews.tsx +232 -0
- package/src/admin/robots-bots.tsx +647 -0
- package/src/admin/robots-shared.tsx +109 -0
- package/src/admin/robots-wizard.tsx +748 -0
- package/src/admin/robots.tsx +911 -0
- package/src/admin/save-bar.tsx +134 -0
- package/src/admin/schema.tsx +1187 -0
- package/src/admin/search.tsx +355 -0
- package/src/admin/settings-ui.tsx +87 -0
- package/src/admin/trash-prompt-core.ts +135 -0
- package/src/admin/trash-prompt.tsx +156 -0
- package/src/admin/usage.tsx +301 -0
- package/src/admin/videos-display.tsx +248 -0
- package/src/admin/videos.tsx +1202 -0
- package/src/admin/wpImport.tsx +1516 -0
- package/src/ai/entities.ts +59 -0
- package/src/ai/export.ts +61 -0
- package/src/ai/logic.ts +606 -0
- package/src/ai/pack.ts +121 -0
- package/src/ai/prompts.ts +87 -0
- package/src/ai/providers.ts +276 -0
- package/src/ai/routes.ts +359 -0
- package/src/ai/store.ts +301 -0
- package/src/ai/wikidata.ts +87 -0
- package/src/ai/worker.ts +530 -0
- package/src/astro/astro-modules.d.ts +6 -0
- package/src/astro/breadcrumbs/Breadcrumbs.astro +129 -0
- package/src/astro/breadcrumbs/BreadcrumbsBlock.astro +12 -0
- package/src/astro/codeBlocks/CodeBlock.astro +62 -0
- package/src/astro/codeBlocks/EmDashCode.astro +61 -0
- package/src/astro/customBlocks/CustomBlock.astro +64 -0
- package/src/astro/customBlocks/Details.astro +8 -0
- package/src/astro/customBlocks/Disclosure.astro +8 -0
- package/src/astro/customBlocks/Note.astro +8 -0
- package/src/astro/customBlocks/Podcast.astro +8 -0
- package/src/astro/customBlocks/Quote.astro +8 -0
- package/src/astro/customBlocks/Testimonial.astro +8 -0
- package/src/astro/files/FileCard.astro +103 -0
- package/src/astro/headings/HeadingBlock.astro +129 -0
- package/src/astro/headings/TableOfContents.astro +45 -0
- package/src/astro/headings/request.ts +19 -0
- package/src/astro/images/ResponsiveImage.astro +60 -0
- package/src/astro/index.ts +84 -0
- package/src/astro/reviews/CoywolfReview.astro +27 -0
- package/src/astro/reviews/Review.astro +49 -0
- package/src/astro/search/SearchBox.astro +61 -0
- package/src/astro/search/SearchBoxForm.astro +509 -0
- package/src/astro/tables/CaptionedTable.astro +72 -0
- package/src/astro/videos/CoywolfVideo.astro +448 -0
- package/src/backups/download.ts +112 -0
- package/src/backups/dump.ts +288 -0
- package/src/backups/module.ts +276 -0
- package/src/backups/pack.ts +33 -0
- package/src/backups/restore.ts +209 -0
- package/src/backups/store.ts +409 -0
- package/src/breadcrumbs/pack.ts +113 -0
- package/src/breadcrumbs/settings.ts +102 -0
- package/src/breadcrumbs/stamp.ts +40 -0
- package/src/breadcrumbs/trail.ts +123 -0
- package/src/codeBlocks/contrast.ts +179 -0
- package/src/codeBlocks/hljs.d.ts +5 -0
- package/src/codeBlocks/pack.ts +110 -0
- package/src/codeBlocks/render.ts +304 -0
- package/src/codeBlocks/settings.ts +18 -0
- package/src/codeBlocks/themes.generated.ts +304 -0
- package/src/codeBlocks/themes.ts +114 -0
- package/src/core/compose.ts +244 -0
- package/src/core/content-url.ts +888 -0
- package/src/core/csv.ts +119 -0
- package/src/core/d1-batch.ts +65 -0
- package/src/core/entry-docs.ts +175 -0
- package/src/core/features-module.ts +48 -0
- package/src/core/features.ts +392 -0
- package/src/core/generation.ts +88 -0
- package/src/core/guide-content.ts +166 -0
- package/src/core/module.ts +104 -0
- package/src/core/read-failures.ts +57 -0
- package/src/core/request-memo.ts +71 -0
- package/src/core/secrets.ts +83 -0
- package/src/core/settings-enhance.ts +74 -0
- package/src/core/site.ts +36 -0
- package/src/core/storage.ts +27 -0
- package/src/customBlocks/pack.ts +260 -0
- package/src/customBlocks/render.ts +424 -0
- package/src/customBlocks/rich.ts +313 -0
- package/src/customBlocks/settings.ts +46 -0
- package/src/discovery/build.ts +252 -0
- package/src/discovery/feed-build.ts +217 -0
- package/src/discovery/feed.ts +172 -0
- package/src/discovery/indexnow.ts +114 -0
- package/src/discovery/llms.ts +79 -0
- package/src/discovery/markdown.ts +294 -0
- package/src/discovery/memory-cache.ts +97 -0
- package/src/discovery/middleware.ts +186 -0
- package/src/discovery/module.ts +353 -0
- package/src/discovery/news.ts +99 -0
- package/src/discovery/pack.ts +58 -0
- package/src/discovery/settings.ts +91 -0
- package/src/discovery/urls.ts +35 -0
- package/src/env.d.ts +10 -0
- package/src/files/card.ts +231 -0
- package/src/files/counts.ts +60 -0
- package/src/files/export.ts +44 -0
- package/src/files/format.ts +163 -0
- package/src/files/module.ts +596 -0
- package/src/files/pack.ts +92 -0
- package/src/files/r2-rules.ts +41 -0
- package/src/files/range.ts +50 -0
- package/src/files/s3.ts +114 -0
- package/src/files/serve.ts +141 -0
- package/src/files/sigv4.ts +160 -0
- package/src/files/site.ts +137 -0
- package/src/files/walker.ts +58 -0
- package/src/formUploads/lib.ts +266 -0
- package/src/formUploads/pack.ts +55 -0
- package/src/formUploads/wrap.ts +592 -0
- package/src/forms.ts +16 -0
- package/src/headings/pack.ts +154 -0
- package/src/headings/render.ts +139 -0
- package/src/headings/settings.ts +117 -0
- package/src/headings/slug.ts +68 -0
- package/src/headings/stamp.ts +142 -0
- package/src/headings/test-hooks.mjs +14 -0
- package/src/headings/toc.ts +48 -0
- package/src/images/cloudflare.ts +329 -0
- package/src/images/lib.ts +221 -0
- package/src/images/module.ts +372 -0
- package/src/images/pack.ts +804 -0
- package/src/images/pending.ts +37 -0
- package/src/images/settings.ts +48 -0
- package/src/images/setup.ts +192 -0
- package/src/images/variants-estimate.ts +162 -0
- package/src/images/variants-store.ts +475 -0
- package/src/images/variants.ts +311 -0
- package/src/index.ts +143 -0
- package/src/links/check.ts +101 -0
- package/src/links/classify.ts +270 -0
- package/src/links/export.ts +59 -0
- package/src/links/module.ts +652 -0
- package/src/links/pack.ts +43 -0
- package/src/links/pt.ts +325 -0
- package/src/links/store.ts +704 -0
- package/src/middleware.ts +287 -0
- package/src/modules.ts +53 -0
- package/src/options.ts +66 -0
- package/src/pageCache/lib.ts +274 -0
- package/src/pageCache/pack.ts +240 -0
- package/src/pageCache/usage-core.ts +152 -0
- package/src/pageCache/usage.ts +262 -0
- package/src/pageCache/warm.ts +722 -0
- package/src/redirects/middleware.ts +169 -0
- package/src/redirects/module.ts +124 -0
- package/src/redirects/pack.ts +89 -0
- package/src/redirects/removed-core.ts +151 -0
- package/src/redirects/removed.ts +293 -0
- package/src/redirects/rules.ts +355 -0
- package/src/redirects/slug-changes.ts +207 -0
- package/src/redirects/slug-core.ts +49 -0
- package/src/redirects/transfer.ts +92 -0
- package/src/reviews/lib.ts +624 -0
- package/src/reviews/pack.ts +135 -0
- package/src/reviews/schema.ts +128 -0
- package/src/reviews/settings.ts +26 -0
- package/src/robots/bots.ts +321 -0
- package/src/robots/data/bots.json +696 -0
- package/src/robots/data/verified.json +582 -0
- package/src/robots/directory.ts +26 -0
- package/src/robots/explain.ts +191 -0
- package/src/robots/guided.ts +300 -0
- package/src/robots/importer.ts +263 -0
- package/src/robots/middleware.ts +151 -0
- package/src/robots/module.ts +446 -0
- package/src/robots/pack.ts +69 -0
- package/src/robots/radar.ts +129 -0
- package/src/robots/rep.ts +362 -0
- package/src/robots/rules.ts +883 -0
- package/src/robots/transfer.ts +45 -0
- package/src/robots/validate.ts +524 -0
- package/src/schema/catalog.ts +146 -0
- package/src/schema/graph.ts +791 -0
- package/src/schema/module.ts +1138 -0
- package/src/schema/og-image.ts +100 -0
- package/src/schema/pack.ts +58 -0
- package/src/search/engine.ts +578 -0
- package/src/search/fallback.ts +80 -0
- package/src/search/live-cache.ts +116 -0
- package/src/search/live-client.ts +770 -0
- package/src/search/live-serve.ts +282 -0
- package/src/search/module.ts +261 -0
- package/src/search/pack.ts +57 -0
- package/src/search/query.ts +132 -0
- package/src/search/ratelimit-core.ts +118 -0
- package/src/search/ratelimit.ts +79 -0
- package/src/search/snippet.ts +135 -0
- package/src/search/title-index.ts +139 -0
- package/src/search/title-match.ts +192 -0
- package/src/shared.ts +71 -0
- package/src/version.ts +2 -0
- package/src/videos/date.ts +40 -0
- package/src/videos/engagement.ts +75 -0
- package/src/videos/lib.ts +679 -0
- package/src/videos/module.ts +1160 -0
- package/src/videos/pack.ts +187 -0
- package/src/videos/pause.ts +42 -0
- package/src/videos/poster.ts +324 -0
- package/src/videos/render.ts +336 -0
- package/src/videos/store.ts +551 -0
- package/src/videos/stream.ts +134 -0
- package/src/wpImport/convert.ts +702 -0
- package/src/wpImport/guests.ts +329 -0
- package/src/wpImport/gutenberg.ts +157 -0
- package/src/wpImport/markers.ts +66 -0
- package/src/wpImport/pack.ts +451 -0
- package/src/wpImport/parents.ts +202 -0
- package/src/wpImport/prepare.ts +592 -0
- package/src/wpImport/redirects.ts +358 -0
- package/src/wpImport/sources.ts +67 -0
- package/src/wpImport/stream.ts +160 -0
- package/src/wpImport/urls.ts +354 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combine Coywolf Pack modules into one EmDash plugin definition.
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginContext } from "emdash";
|
|
5
|
+
|
|
6
|
+
import { type FeatureMap, cachedCtxFeatures, ctxFeatures, isOn } from "./features.js";
|
|
7
|
+
import type { PackModule, TaskDef } from "./module.js";
|
|
8
|
+
import { noteReadFailure } from "./read-failures.js";
|
|
9
|
+
import { requestStore } from "./request-memo.js";
|
|
10
|
+
import { afterResponse } from "../shared.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Capability EmDash requires before it registers each hook
|
|
14
|
+
* (emdash/src/plugins/hooks.ts HOOK_REQUIRED_CAPABILITY). A hook without its
|
|
15
|
+
* capability is silently skipped, so the pack declares these automatically.
|
|
16
|
+
*/
|
|
17
|
+
const HOOK_CAPABILITY: Record<string, string> = {
|
|
18
|
+
"email:beforeSend": "hooks.email-events:register",
|
|
19
|
+
"email:afterSend": "hooks.email-events:register",
|
|
20
|
+
"email:deliver": "hooks.email-transport:register",
|
|
21
|
+
"content:beforeSave": "content:write",
|
|
22
|
+
"content:afterSave": "content:read",
|
|
23
|
+
"content:beforeDelete": "content:read",
|
|
24
|
+
"content:afterDelete": "content:read",
|
|
25
|
+
"content:beforePublish": "hooks.content-policy:register",
|
|
26
|
+
"content:beforeSchedule": "hooks.content-policy:register",
|
|
27
|
+
"content:beforeUnpublish": "hooks.content-policy:register",
|
|
28
|
+
"content:afterPublish": "content:read",
|
|
29
|
+
"content:afterUnpublish": "content:read",
|
|
30
|
+
"content:afterRestore": "content:read",
|
|
31
|
+
"content:afterSchedule": "content:read",
|
|
32
|
+
"content:afterUnschedule": "content:read",
|
|
33
|
+
"media:beforeUpload": "media:write",
|
|
34
|
+
"media:afterUpload": "media:read",
|
|
35
|
+
"comment:beforeCreate": "users:read",
|
|
36
|
+
"comment:moderate": "users:read",
|
|
37
|
+
"comment:afterCreate": "users:read",
|
|
38
|
+
"comment:afterModerate": "users:read",
|
|
39
|
+
"byline:afterSave": "bylines:read",
|
|
40
|
+
"byline:afterDelete": "bylines:read",
|
|
41
|
+
"page:fragments": "hooks.page-fragments:register",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Capabilities the modules' hooks need. */
|
|
45
|
+
export function hookCapabilities(modules: PackModule[]): string[] {
|
|
46
|
+
const caps = new Set<string>();
|
|
47
|
+
for (const module of modules) {
|
|
48
|
+
for (const name of Object.keys(module.hooks ?? {})) {
|
|
49
|
+
const cap = HOOK_CAPABILITY[name];
|
|
50
|
+
if (cap) caps.add(cap);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [...caps];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Hooks whose results are collected from every module. */
|
|
57
|
+
const COLLECTING = new Set(["page:metadata", "page:fragments"]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* EmDash gives a hook 5 seconds (define-plugin.ts), then renders the page
|
|
61
|
+
* without its part and only logs it. A page hook still running just before
|
|
62
|
+
* that (D1 slow, not failing) has its page kept out of the edge cache, like
|
|
63
|
+
* one whose read failed (src/core/read-failures.ts).
|
|
64
|
+
*/
|
|
65
|
+
const PAGE_HOOK_DEADLINE_MS = 4_500;
|
|
66
|
+
function noteIfLate(run: Handler): Handler {
|
|
67
|
+
return async (event, ctx) => {
|
|
68
|
+
const late = setTimeout(() => noteReadFailure("a page hook near EmDash's timeout"), PAGE_HOOK_DEADLINE_MS);
|
|
69
|
+
try {
|
|
70
|
+
return await run(event, ctx);
|
|
71
|
+
} finally {
|
|
72
|
+
clearTimeout(late);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Hooks EmDash runs without waiting for them. media:afterUpload is fired and
|
|
78
|
+
* forgotten right before the upload's response goes out (emdash-runtime
|
|
79
|
+
* handleMediaCreate), so the composed hook hands the whole job (the feature
|
|
80
|
+
* check, then each participant) to waitUntil at once, before anything async:
|
|
81
|
+
* awaiting anything first (the other hooks start with a settings read) let the
|
|
82
|
+
* request finish, which dropped the work without a trace. Participants are
|
|
83
|
+
* gated by hookFeature like everywhere else, inside the job.
|
|
84
|
+
*/
|
|
85
|
+
const BACKGROUND = new Set(["media:afterUpload"]);
|
|
86
|
+
|
|
87
|
+
/** EmDash's deferred-task tracker for the current request (its request context, found through the pack's request memo). */
|
|
88
|
+
function deferredTasks(): { track<T>(promise: Promise<T>): Promise<T> } | undefined {
|
|
89
|
+
const store = requestStore() as { deferredTasks?: { track?: <T>(promise: Promise<T>) => Promise<T> } } | undefined;
|
|
90
|
+
return typeof store?.deferredTasks?.track === "function" ? (store.deferredTasks as { track<T>(promise: Promise<T>): Promise<T> }) : undefined;
|
|
91
|
+
}
|
|
92
|
+
/** Scheduled jobs can take far longer than EmDash's 5-second hook default (Workers cron allows 15 minutes). */
|
|
93
|
+
const CRON_TIMEOUT_MS = 14 * 60_000;
|
|
94
|
+
/** plugin:activate schedules every task (one D1 write each); a boot-time failure would disable the plugin. */
|
|
95
|
+
const ACTIVATE_TIMEOUT_MS = 60_000;
|
|
96
|
+
|
|
97
|
+
const mainFeature = (module: PackModule) => module.features[0]?.id ?? module.id;
|
|
98
|
+
|
|
99
|
+
function hookFeature(module: PackModule, hook: string): string {
|
|
100
|
+
return module.hookFeature?.[hook as keyof typeof module.hookFeature] ?? mainFeature(module);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// biome-ignore lint/suspicious/noExplicitAny: events differ per hook.
|
|
104
|
+
type Handler = (event: any, ctx: PluginContext) => Promise<unknown>;
|
|
105
|
+
|
|
106
|
+
export function composeHooks(modules: PackModule[], extra: { tasks: TaskDef[] }) {
|
|
107
|
+
const names = new Set<string>();
|
|
108
|
+
for (const module of modules) for (const name of Object.keys(module.hooks ?? {})) names.add(name);
|
|
109
|
+
|
|
110
|
+
const hooks: Record<string, Handler | { handler: Handler; timeout?: number; priority?: number }> = {};
|
|
111
|
+
|
|
112
|
+
for (const name of names) {
|
|
113
|
+
if (name === "cron" || name === "plugin:activate") continue; // Composed below.
|
|
114
|
+
const participants = modules.filter((m) => m.hooks?.[name as keyof NonNullable<PackModule["hooks"]>]);
|
|
115
|
+
if (BACKGROUND.has(name)) {
|
|
116
|
+
// Priority 0 (EmDash's default is 100, lower runs first): the pipeline runs plugins' handlers one
|
|
117
|
+
// after another, so this registration comes before another plugin's handler can keep EmDash waiting.
|
|
118
|
+
hooks[name] = { priority: 0, handler: (event, ctx) => {
|
|
119
|
+
const job = (async () => {
|
|
120
|
+
try {
|
|
121
|
+
const features = await cachedCtxFeatures(ctx);
|
|
122
|
+
for (const module of participants) {
|
|
123
|
+
if (!isOn(features, hookFeature(module, name))) continue;
|
|
124
|
+
try {
|
|
125
|
+
await module.hooks?.[name as "media:afterUpload"]?.(event, ctx);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`coywolf-pack: ${module.id}: ${name} failed`, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`coywolf-pack: ${name} failed`, error);
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
// Synchronous: nothing has been awaited yet. Tracked as one of EmDash's deferred tasks too (as its
|
|
135
|
+
// own after() does), so a request-scoped database with a close() stays open until the job is done.
|
|
136
|
+
afterResponse(deferredTasks()?.track(job) ?? job);
|
|
137
|
+
return Promise.resolve();
|
|
138
|
+
} };
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const run: Handler = async (event, ctx) => {
|
|
142
|
+
const features = await cachedCtxFeatures(ctx);
|
|
143
|
+
if (COLLECTING.has(name)) {
|
|
144
|
+
const out: unknown[] = [];
|
|
145
|
+
for (const module of participants) {
|
|
146
|
+
if (!isOn(features, hookFeature(module, name))) continue;
|
|
147
|
+
try {
|
|
148
|
+
const result = await module.hooks?.[name as "page:metadata"]?.(event, ctx);
|
|
149
|
+
if (Array.isArray(result)) out.push(...result);
|
|
150
|
+
else if (result) out.push(result);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
ctx.log.error(`${module.id}: ${name} failed`, { error: String(error) });
|
|
153
|
+
// The page goes out without this module's part (its schema, its video player…): not into the edge cache.
|
|
154
|
+
noteReadFailure(`${module.id}: ${name}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
if (name === "content:beforeSave") {
|
|
160
|
+
let content = event.content as Record<string, unknown>;
|
|
161
|
+
let changed = false;
|
|
162
|
+
for (const module of participants) {
|
|
163
|
+
if (!isOn(features, hookFeature(module, name))) continue;
|
|
164
|
+
const result = (await module.hooks?.["content:beforeSave"]?.({ ...event, content }, ctx)) as Record<string, unknown> | undefined;
|
|
165
|
+
if (result) {
|
|
166
|
+
content = result;
|
|
167
|
+
changed = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return changed ? content : undefined;
|
|
171
|
+
}
|
|
172
|
+
for (const module of participants) {
|
|
173
|
+
if (!isOn(features, hookFeature(module, name))) continue;
|
|
174
|
+
try {
|
|
175
|
+
await module.hooks?.[name as "content:afterSave"]?.(event, ctx);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
ctx.log.error(`${module.id}: ${name} failed`, { error: String(error) });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
};
|
|
182
|
+
hooks[name] = COLLECTING.has(name) ? noteIfLate(run) : run;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const tasks = [...extra.tasks, ...modules.flatMap((m) => (m.tasks ?? []).map((t) => ({ ...t, feature: t.feature ?? mainFeature(m) })))];
|
|
186
|
+
|
|
187
|
+
// EmDash 1.2.0 runs install → activate once at boot for a config plugin with no _plugin_state row, and a
|
|
188
|
+
// hook that fails (its 5-second default timeout included) disables the plugin until an admin re-enables
|
|
189
|
+
// it. Scheduling a dozen tasks is a dozen D1 writes: give it room.
|
|
190
|
+
hooks["plugin:activate"] = {
|
|
191
|
+
timeout: ACTIVATE_TIMEOUT_MS,
|
|
192
|
+
// Never throws: EmDash leaves a plugin whose activation fails turned off, and a brief D1 error here
|
|
193
|
+
// shouldn't switch off the whole pack. Each step is logged; tasks are scheduled again whenever the
|
|
194
|
+
// Features page loads (features/list), and module steps run again on the next activation.
|
|
195
|
+
handler: async (event, ctx) => {
|
|
196
|
+
try {
|
|
197
|
+
await ensureTasks(ctx, tasks);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
ctx.log.error("plugin:activate: scheduling tasks failed; they're scheduled again when the Features page opens", { error: String(error) });
|
|
200
|
+
}
|
|
201
|
+
for (const module of modules) {
|
|
202
|
+
try {
|
|
203
|
+
await module.hooks?.["plugin:activate"]?.(event, ctx);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
ctx.log.error(`${module.id}: plugin:activate failed`, { error: String(error) });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
hooks.cron = {
|
|
212
|
+
timeout: CRON_TIMEOUT_MS,
|
|
213
|
+
handler: async (event: { name: string }, ctx) => {
|
|
214
|
+
const task = tasks.find((t) => t.name === event.name);
|
|
215
|
+
if (!task) return;
|
|
216
|
+
const features: FeatureMap = await ctxFeatures(ctx);
|
|
217
|
+
if (task.feature && !isOn(features, task.feature)) return;
|
|
218
|
+
await task.handler(ctx);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return { hooks, tasks };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Config-registered native plugins don't get plugin:activate at boot, so
|
|
227
|
+
* routes call this too. schedule() is an upsert.
|
|
228
|
+
*/
|
|
229
|
+
export async function ensureTasks(
|
|
230
|
+
ctx: { cron?: { schedule(name: string, opts: { schedule: string }): Promise<void> }; log?: { error(msg: string, data?: unknown): void } },
|
|
231
|
+
tasks: TaskDef[],
|
|
232
|
+
) {
|
|
233
|
+
// One bad task must not break the routes that call this (e.g. the Features page).
|
|
234
|
+
for (const task of tasks) {
|
|
235
|
+
try {
|
|
236
|
+
await ctx.cron?.schedule(task.name, { schedule: task.schedule });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
ctx.log?.error(`coywolf-pack: could not schedule task ${task.name}`, { error: String(error) });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** EmDash's task-name rule (emdash/src/plugins/cron.ts validateTaskName). */
|
|
244
|
+
export const TASK_NAME = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|