@we8/cloudflare 0.1.0

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,179 @@
1
+ /**
2
+ * The wrangler config generator. One function turns a scaffolder's answers
3
+ * into the `wrangler.jsonc` a project deploys with, shaped after the CMS
4
+ * package's own config so the two never drift apart in compatibility date,
5
+ * flags, cron, or binding names.
6
+ *
7
+ * It writes JSONC on purpose. The file a stranger opens first should explain
8
+ * itself, and half of what it has to explain (which value is a placeholder,
9
+ * why the secret is not here) has nowhere to live in plain JSON.
10
+ */
11
+ /** The placeholder id a project carries until its D1 database exists. */
12
+ export const D1_ID_PLACEHOLDER = 'REPLACE_WITH_YOUR_D1_DATABASE_ID';
13
+ /**
14
+ * The compatibility date the CMS is written against. Pinned here rather than
15
+ * set to today's date at scaffold time: a date the code has never run under is
16
+ * a guess, and a guess in a compatibility date is a runtime surprise.
17
+ */
18
+ export const COMPATIBILITY_DATE = '2026-07-05';
19
+ /** Where the migrations live in an installed project. */
20
+ export const DEFAULT_MIGRATIONS_DIR = 'node_modules/@we8/cms/migrations';
21
+ /** The daily retention sweep, matching the CMS package config. */
22
+ export const RETENTION_CRON = '0 3 * * *';
23
+ const WORKER_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
24
+ const D1_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25
+ const COMPAT_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
26
+ /**
27
+ * The rules a Worker, database, and bucket name all share on Cloudflare:
28
+ * lowercase, digits, and hyphens, starting with a letter or a digit.
29
+ */
30
+ export function isValidResourceName(name) {
31
+ return WORKER_NAME_PATTERN.test(name);
32
+ }
33
+ /** True for a real D1 id. The placeholder is deliberately not one. */
34
+ export function isRealDatabaseId(id) {
35
+ return typeof id === 'string' && D1_ID_PATTERN.test(id);
36
+ }
37
+ function requireResourceName(label, value) {
38
+ if (!isValidResourceName(value)) {
39
+ throw new Error(`${label} "${value}" is not a valid Cloudflare name: lowercase letters, digits, and hyphens, starting with a letter or digit, at most 63 characters.`);
40
+ }
41
+ }
42
+ function requireOrigin(value) {
43
+ let url;
44
+ try {
45
+ url = new URL(value);
46
+ }
47
+ catch {
48
+ throw new Error(`siteUrl "${value}" is not a URL. Use a full origin, for example https://example.com`);
49
+ }
50
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
51
+ throw new Error(`siteUrl "${value}" must be http or https.`);
52
+ }
53
+ }
54
+ /** A JSON string literal, so a generated value can never break the file. */
55
+ function q(value) {
56
+ return JSON.stringify(value);
57
+ }
58
+ /**
59
+ * Renders the project's `wrangler.jsonc`. Throws on an answer that would
60
+ * produce a config Cloudflare rejects, because failing here is a sentence and
61
+ * failing at deploy time is a support thread.
62
+ */
63
+ export function generateWranglerConfig(answers) {
64
+ requireResourceName('workerName', answers.workerName);
65
+ requireResourceName('databaseName', answers.databaseName);
66
+ requireResourceName('bucketName', answers.bucketName);
67
+ requireOrigin(answers.siteUrl);
68
+ const databaseId = answers.databaseId ?? D1_ID_PLACEHOLDER;
69
+ if (databaseId !== D1_ID_PLACEHOLDER && !isRealDatabaseId(databaseId)) {
70
+ throw new Error(`databaseId "${databaseId}" is not a D1 database id. Run \`wrangler d1 create ${answers.databaseName}\` and paste the uuid it prints.`);
71
+ }
72
+ const compatibilityDate = answers.compatibilityDate ?? COMPATIBILITY_DATE;
73
+ if (!COMPAT_DATE_PATTERN.test(compatibilityDate)) {
74
+ throw new Error(`compatibilityDate "${compatibilityDate}" is not a YYYY-MM-DD date.`);
75
+ }
76
+ const emailMode = answers.emailMode ?? 'off';
77
+ if (emailMode === 'cloudflare' && !answers.emailFrom) {
78
+ throw new Error('emailMode "cloudflare" needs an emailFrom address on a domain onboarded to Cloudflare Email Service.');
79
+ }
80
+ const migrationsDir = answers.migrationsDir ?? DEFAULT_MIGRATIONS_DIR;
81
+ const lines = [];
82
+ const push = (line = '') => {
83
+ lines.push(line);
84
+ };
85
+ push('{');
86
+ push(' "$schema": "node_modules/wrangler/config-schema.json",');
87
+ push(` "name": ${q(answers.workerName)},`);
88
+ push(' // The whole app: this file composes @we8/cms and registers the pack.');
89
+ push(' "main": "src/index.ts",');
90
+ push(` "compatibility_date": ${q(compatibilityDate)},`);
91
+ push(' // better-auth and the CMS both need the Node built-ins. Do not remove.');
92
+ push(' "compatibility_flags": ["nodejs_compat"],');
93
+ push();
94
+ push(' // One site, one database, one bucket.');
95
+ if (databaseId === D1_ID_PLACEHOLDER) {
96
+ push(' // The id below is a placeholder. Create the database and paste the uuid:');
97
+ push(` // wrangler d1 create ${answers.databaseName}`);
98
+ }
99
+ push(' "d1_databases": [');
100
+ push(' {');
101
+ push(' "binding": "DB",');
102
+ push(` "database_name": ${q(answers.databaseName)},`);
103
+ push(` "database_id": ${q(databaseId)},`);
104
+ push(' // The migrations ship inside the @we8/cms package, so an upgrade');
105
+ push(' // brings its schema with it and nothing has to be copied here.');
106
+ push(` "migrations_dir": ${q(migrationsDir)}`);
107
+ push(' }');
108
+ push(' ],');
109
+ push();
110
+ push(' "r2_buckets": [');
111
+ push(' {');
112
+ push(' "binding": "MEDIA",');
113
+ push(` "bucket_name": ${q(answers.bucketName)}`);
114
+ push(' }');
115
+ push(' ],');
116
+ push();
117
+ push(' // The admin portal, served by the worker itself. `npm run admin:bundle`');
118
+ push(' // fills this directory from @we8/admin-app; empty means API-only.');
119
+ push(' "assets": {');
120
+ push(' "binding": "ASSETS",');
121
+ push(' "directory": "./public",');
122
+ push(' },');
123
+ push();
124
+ if (emailMode === 'cloudflare') {
125
+ push(' // Cloudflare Email Service. The sending domain must be onboarded first:');
126
+ push(' // Cloudflare dashboard, Compute, Email Service, Email Sending.');
127
+ push(' "send_email": [');
128
+ push(' {');
129
+ push(' "name": "EMAIL",');
130
+ push(' // Talks to the real service even under `wrangler dev`.');
131
+ push(' "remote": true');
132
+ push(' }');
133
+ push(' ],');
134
+ }
135
+ else {
136
+ push(' // Mail is off. To send over Cloudflare Email Service: onboard the');
137
+ push(' // sending domain, set EMAIL_MODE to "cloudflare" and EMAIL_FROM to an');
138
+ push(' // address on it, and uncomment this binding.');
139
+ push(' // "send_email": [{ "name": "EMAIL", "remote": true }],');
140
+ }
141
+ push();
142
+ push(' "vars": {');
143
+ push(' // The public origin of the site this CMS backs. Generated URLs');
144
+ push(' // (sitemap, llms.txt, JSON-LD) are built from it.');
145
+ push(` "SITE_URL": ${q(answers.siteUrl)},`);
146
+ push(` "CMS_VERSION": ${q(answers.cmsVersion)}`);
147
+ if (emailMode !== 'off') {
148
+ lines[lines.length - 1] += ',';
149
+ if (emailMode === 'console') {
150
+ push(' // Logs mail to the console instead of sending it.');
151
+ }
152
+ push(` "EMAIL_MODE": ${q(emailMode)}`);
153
+ if (answers.emailFrom) {
154
+ lines[lines.length - 1] += ',';
155
+ push(` "EMAIL_FROM": ${q(answers.emailFrom)}`);
156
+ }
157
+ }
158
+ push(' },');
159
+ push();
160
+ push(' // BETTER_AUTH_SECRET is a SECRET, never a var. The admin API refuses');
161
+ push(' // every request without it rather than signing sessions with a');
162
+ push(' // predictable value.');
163
+ push(' // local: put it in .dev.vars');
164
+ push(' // deployed: wrangler secret put BETTER_AUTH_SECRET');
165
+ push(' // TURNSTILE_SECRET is optional, also a secret, and only enforced for');
166
+ push(' // forms that switch Turnstile on.');
167
+ push();
168
+ push(' "observability": {');
169
+ push(' "enabled": true');
170
+ push(' },');
171
+ push();
172
+ push(' // The daily retention sweep, at 03:00 UTC.');
173
+ push(' "triggers": {');
174
+ push(` "crons": [${q(RETENTION_CRON)}]`);
175
+ push(' }');
176
+ push('}');
177
+ return `${lines.join('\n')}\n`;
178
+ }
179
+ //# sourceMappingURL=wrangler-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrangler-config.js","sourceRoot":"","sources":["../src/wrangler-config.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@we8/cloudflare",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/reveriext/we8-package.git",
8
+ "directory": "packages/cloudflare"
9
+ },
10
+ "homepage": "https://github.com/reveriext/we8-package/tree/main/packages/cloudflare#readme",
11
+ "bugs": "https://github.com/reveriext/we8-package/issues",
12
+ "type": "module",
13
+ "description": "The Cloudflare pack for we8: the wrangler config generator, the first-boot doctor, the migrations runner, and the Cloudflare Email Service sender.",
14
+ "keywords": [
15
+ "we8",
16
+ "cms",
17
+ "cloudflare",
18
+ "cloudflare-workers",
19
+ "d1",
20
+ "r2",
21
+ "wrangler"
22
+ ],
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "bin": {
32
+ "we8-cloudflare": "./dist/cli.js"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.build.json",
41
+ "typecheck": "tsc -p tsconfig.json",
42
+ "prepare": "npm run build",
43
+ "test": "vitest run",
44
+ "prepack": "npm run build"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "peerDependencies": {
50
+ "@we8/cms": "^0.1.0",
51
+ "wrangler": "^4.107.0"
52
+ },
53
+ "devDependencies": {
54
+ "vitest": "^4.1.9"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ }
59
+ }