@decocms/start 0.36.1 → 0.36.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.
package/package.json
CHANGED
|
@@ -60,7 +60,7 @@ export function generatePackageJson(ctx: MigrationContext): string {
|
|
|
60
60
|
|
|
61
61
|
// Fetch latest versions from npm registry
|
|
62
62
|
const startVersion = getLatestVersion("@decocms/start", "0.34.0");
|
|
63
|
-
const appsVersion = getLatestVersion("@decocms/apps", "0.
|
|
63
|
+
const appsVersion = getLatestVersion("@decocms/apps", "0.27.0");
|
|
64
64
|
|
|
65
65
|
const pkg = {
|
|
66
66
|
name: ctx.siteName,
|
package/src/apps/autoconfig.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-configures known apps from CMS blocks.
|
|
3
3
|
*
|
|
4
|
-
* Scans the decofile for block keys
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - `handlers` → record of invoke handler keys → handler functions
|
|
8
|
-
*
|
|
9
|
-
* Zero hardcoded app logic in the framework — all app-specific code lives in
|
|
10
|
-
* @decocms/apps/{app}/mod.ts.
|
|
4
|
+
* Scans the decofile for known app block keys (e.g. "deco-resend") and:
|
|
5
|
+
* 1. Configures the app client with CMS-provided credentials
|
|
6
|
+
* 2. Registers invoke handlers so `invoke.app.actions.*` works via the proxy
|
|
11
7
|
*
|
|
12
8
|
* Usage in setup.ts:
|
|
13
9
|
* import { autoconfigApps } from "@decocms/start/apps/autoconfig";
|
|
@@ -20,82 +16,58 @@ import { onChange } from "../cms/loader";
|
|
|
20
16
|
import { resolveSecret } from "../sdk/crypto";
|
|
21
17
|
|
|
22
18
|
// ---------------------------------------------------------------------------
|
|
23
|
-
//
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Maps CMS block keys (e.g. "deco-resend") to their @decocms/apps module path.
|
|
28
|
-
* To add a new app, just add an entry here — no other code changes needed.
|
|
29
|
-
*/
|
|
30
|
-
const BLOCK_TO_APP: Record<string, string> = {
|
|
31
|
-
"deco-resend": "resend",
|
|
32
|
-
// "deco-analytics": "analytics",
|
|
33
|
-
// "deco-shopify": "shopify",
|
|
34
|
-
// "deco-vtex": "vtex",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Generic app loader
|
|
19
|
+
// Known app block keys → dynamic import + configure
|
|
39
20
|
// ---------------------------------------------------------------------------
|
|
40
21
|
|
|
41
|
-
interface
|
|
42
|
-
configure
|
|
43
|
-
|
|
44
|
-
resolveSecret: (value: unknown, envKey: string) => Promise<string | null>,
|
|
45
|
-
) => Promise<boolean>;
|
|
46
|
-
handlers: Record<string, (props: any, request: Request) => Promise<any>>;
|
|
22
|
+
interface AppAutoconfigurator {
|
|
23
|
+
/** Try to import, configure, and return invoke actions for this app */
|
|
24
|
+
(blockData: unknown): Promise<Record<string, InvokeAction>>;
|
|
47
25
|
}
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
27
|
+
const KNOWN_APPS: Record<string, AppAutoconfigurator> = {
|
|
28
|
+
"deco-resend": async (block: any) => {
|
|
29
|
+
try {
|
|
30
|
+
const [resendClient, resendActions] = await Promise.all([
|
|
31
|
+
import("@decocms/apps/resend/client" as string),
|
|
32
|
+
import("@decocms/apps/resend/actions/send" as string),
|
|
33
|
+
]);
|
|
34
|
+
const { configureResend } = resendClient as { configureResend: (cfg: any) => void };
|
|
35
|
+
const { sendEmail } = resendActions as { sendEmail: (props: any) => Promise<any> };
|
|
36
|
+
|
|
37
|
+
const apiKey = await resolveSecret(block.apiKey, "RESEND_API_KEY");
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
console.warn(
|
|
40
|
+
"[autoconfig] deco-resend: no API key found." +
|
|
41
|
+
" Set DECO_CRYPTO_KEY to decrypt CMS secrets, or set RESEND_API_KEY as fallback.",
|
|
42
|
+
);
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
configureResend({
|
|
47
|
+
apiKey,
|
|
48
|
+
emailFrom: block.emailFrom
|
|
49
|
+
? `${block.emailFrom.name || "Contact"} ${block.emailFrom.domain || "<onboarding@resend.dev>"}`
|
|
50
|
+
: undefined,
|
|
51
|
+
emailTo: block.emailTo,
|
|
52
|
+
subject: block.subject,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const handler: InvokeAction = async (props: any) => sendEmail(props);
|
|
56
|
+
return {
|
|
57
|
+
"resend/actions/emails/send": handler,
|
|
58
|
+
"resend/actions/emails/send.ts": handler,
|
|
59
|
+
};
|
|
60
|
+
} catch {
|
|
61
|
+
// @decocms/apps not installed or doesn't have resend — skip
|
|
74
62
|
return {};
|
|
75
63
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
80
66
|
|
|
81
67
|
// ---------------------------------------------------------------------------
|
|
82
68
|
// Main
|
|
83
69
|
// ---------------------------------------------------------------------------
|
|
84
70
|
|
|
85
|
-
async function configureAll(blocks: Record<string, unknown>): Promise<Record<string, InvokeAction>> {
|
|
86
|
-
const actions: Record<string, InvokeAction> = {};
|
|
87
|
-
|
|
88
|
-
for (const [blockKey, appName] of Object.entries(BLOCK_TO_APP)) {
|
|
89
|
-
const block = blocks[blockKey];
|
|
90
|
-
if (!block) continue;
|
|
91
|
-
|
|
92
|
-
const appActions = await loadAndConfigureApp(blockKey, appName, block);
|
|
93
|
-
Object.assign(actions, appActions);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return actions;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
71
|
/**
|
|
100
72
|
* Auto-configure apps from CMS blocks.
|
|
101
73
|
* Call in setup.ts after setBlocks(). Also re-runs on admin hot-reload.
|
|
@@ -103,7 +75,19 @@ async function configureAll(blocks: Record<string, unknown>): Promise<Record<str
|
|
|
103
75
|
export async function autoconfigApps(blocks: Record<string, unknown>) {
|
|
104
76
|
if (typeof document !== "undefined") return; // server-only
|
|
105
77
|
|
|
106
|
-
const actions =
|
|
78
|
+
const actions: Record<string, InvokeAction> = {};
|
|
79
|
+
|
|
80
|
+
for (const [blockKey, configurator] of Object.entries(KNOWN_APPS)) {
|
|
81
|
+
const block = blocks[blockKey];
|
|
82
|
+
if (!block) continue;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const appActions = await configurator(block);
|
|
86
|
+
Object.assign(actions, appActions);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.warn(`[autoconfig] ${blockKey}:`, e);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
107
91
|
|
|
108
92
|
if (Object.keys(actions).length > 0) {
|
|
109
93
|
setInvokeActions(() => ({ ...actions }));
|
|
@@ -112,7 +96,14 @@ export async function autoconfigApps(blocks: Record<string, unknown>) {
|
|
|
112
96
|
// Re-configure on admin hot-reload
|
|
113
97
|
onChange(async (newBlocks) => {
|
|
114
98
|
if (typeof document !== "undefined") return;
|
|
115
|
-
const updatedActions =
|
|
99
|
+
const updatedActions: Record<string, InvokeAction> = {};
|
|
100
|
+
for (const [blockKey, configurator] of Object.entries(KNOWN_APPS)) {
|
|
101
|
+
const block = newBlocks[blockKey];
|
|
102
|
+
if (!block) continue;
|
|
103
|
+
try {
|
|
104
|
+
Object.assign(updatedActions, await configurator(block));
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
116
107
|
if (Object.keys(updatedActions).length > 0) {
|
|
117
108
|
setInvokeActions(() => ({ ...updatedActions }));
|
|
118
109
|
}
|
package/src/sdk/crypto.ts
CHANGED
|
@@ -34,7 +34,7 @@ function hexToBytes(hex: string): Uint8Array {
|
|
|
34
34
|
* Returns null if not set.
|
|
35
35
|
*/
|
|
36
36
|
function getKeyFromEnv(): Promise<{ key: CryptoKey; iv: Uint8Array }> | null {
|
|
37
|
-
const envKey =
|
|
37
|
+
const envKey = getEnvVar("DECO_CRYPTO_KEY");
|
|
38
38
|
if (!envKey) return null;
|
|
39
39
|
|
|
40
40
|
return cachedKey ??= (async () => {
|
|
@@ -62,7 +62,7 @@ function getKeyFromEnv(): Promise<{ key: CryptoKey; iv: Uint8Array }> | null {
|
|
|
62
62
|
* Check if the crypto key is available.
|
|
63
63
|
*/
|
|
64
64
|
export function hasCryptoKey(): boolean {
|
|
65
|
-
return !!
|
|
65
|
+
return !!getEnvVar("DECO_CRYPTO_KEY");
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -142,9 +142,44 @@ export async function resolveSecret(
|
|
|
142
142
|
|
|
143
143
|
// 4. Environment variable fallback
|
|
144
144
|
if (envVarName) {
|
|
145
|
-
const envValue =
|
|
145
|
+
const envValue = getEnvVar(envVarName);
|
|
146
146
|
if (envValue) return envValue;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
return null;
|
|
150
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get an environment variable, checking process.env first, then .dev.vars file.
|
|
154
|
+
* Cloudflare Workers dev mode stores env vars in .dev.vars but they're only
|
|
155
|
+
* accessible via the `env` binding inside request handlers. During setup.ts
|
|
156
|
+
* (module-level init), we need to read the file directly.
|
|
157
|
+
*/
|
|
158
|
+
function getEnvVar(name: string): string | undefined {
|
|
159
|
+
// 1. process.env (works in Node, may work in Workers with nodejs_compat)
|
|
160
|
+
if (typeof process !== "undefined" && process.env?.[name]) {
|
|
161
|
+
return process.env[name];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 2. Read .dev.vars file (Cloudflare Workers dev mode)
|
|
165
|
+
try {
|
|
166
|
+
const fs = require("node:fs");
|
|
167
|
+
const path = require("node:path");
|
|
168
|
+
const devVarsPath = path.resolve(".dev.vars");
|
|
169
|
+
if (fs.existsSync(devVarsPath)) {
|
|
170
|
+
const content = fs.readFileSync(devVarsPath, "utf-8");
|
|
171
|
+
for (const line of content.split("\n")) {
|
|
172
|
+
const trimmed = line.trim();
|
|
173
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
174
|
+
const eqIdx = trimmed.indexOf("=");
|
|
175
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
176
|
+
const val = trimmed.slice(eqIdx + 1).trim();
|
|
177
|
+
if (key === name) return val;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// fs not available (e.g., pure Worker runtime) — ignore
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|