@designsystemsinternational/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.
Files changed (42) hide show
  1. package/README.md +60 -0
  2. package/cli/index.ts +196 -0
  3. package/package.json +32 -0
  4. package/templates/dist/auth/assets/index-BqXcY2TH.css +1 -0
  5. package/templates/dist/auth/assets/index-TTeq04ep.js +9 -0
  6. package/templates/dist/auth/fonts/UniversNextPro-Medium.woff2 +0 -0
  7. package/templates/dist/auth/fonts/UniversNextPro-Regular.woff2 +0 -0
  8. package/templates/dist/auth/fonts/UniversNextTypewrtrPro-Rg.woff2 +0 -0
  9. package/templates/dist/auth/index.html +14 -0
  10. package/templates/dist/index.html +6 -0
  11. package/templates/eslint.config.js +23 -0
  12. package/templates/index.html +12 -0
  13. package/templates/package-lock.json +5394 -0
  14. package/templates/package.json +33 -0
  15. package/templates/postcss.config.cjs +19 -0
  16. package/templates/public/fonts/UniversNextPro-Medium.woff2 +0 -0
  17. package/templates/public/fonts/UniversNextPro-Regular.woff2 +0 -0
  18. package/templates/public/fonts/UniversNextTypewrtrPro-Rg.woff2 +0 -0
  19. package/templates/src/App.module.css +116 -0
  20. package/templates/src/App.tsx +116 -0
  21. package/templates/src/assets/Cloudflare_Logo.svg +51 -0
  22. package/templates/src/assets/react.svg +1 -0
  23. package/templates/src/main.tsx +16 -0
  24. package/templates/src/styles/breakpoints.css +9 -0
  25. package/templates/src/styles/fonts.css +17 -0
  26. package/templates/src/styles/globals.css +14 -0
  27. package/templates/src/styles/reset.css +438 -0
  28. package/templates/src/styles/textStyles.css +19 -0
  29. package/templates/src/styles/utilities.css +5 -0
  30. package/templates/src/styles/variables.css +195 -0
  31. package/templates/src/vite-env.d.ts +1 -0
  32. package/templates/tsconfig.app.json +28 -0
  33. package/templates/tsconfig.json +7 -0
  34. package/templates/tsconfig.node.json +26 -0
  35. package/templates/vite.config.ts +15 -0
  36. package/tsconfig.json +10 -0
  37. package/worker-configuration.d.ts +10851 -0
  38. package/workers/password/index.ts +67 -0
  39. package/workers/password/lib/constants.ts +1 -0
  40. package/workers/password/lib/schema.ts +8 -0
  41. package/workers/password/lib/util.ts +28 -0
  42. package/wrangler.jsonc +16 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # DSI Cloudflare Package
2
+
3
+ This package provides a set of helpers to deploy both static and dynamic
4
+ websites to Cloudflare Workers. It comes with a nice set of tools:
5
+
6
+ - A CLI to set up the deployment config (including `wrangler.jsonc`) for a repo
7
+ - A Cloudflare Worker to password protect a static website
8
+ - A Vite application that builds the auth templates needed for the password
9
+ worker
10
+
11
+ ## Usage
12
+
13
+ ### Setting up a new deployment
14
+
15
+ In order to deploy a new site on Cloudflare Worker, run the following:
16
+
17
+ ```bash
18
+ npx @designsystemsinternational/cloudflare
19
+ ```
20
+
21
+ The CLI will guide you through setting up the deployment, including with
22
+ password protection.
23
+
24
+ ## Development
25
+
26
+ ### File structure
27
+
28
+ The repo is divided into these main folders:
29
+
30
+ - `templates/` holds a Vite app that can be used to make frontend templates. It
31
+ currently just renders the auth templates.
32
+ - `workers/` holds all Cloudflare Workers code. It currently just has a
33
+ `password` worker that renders the auth templates in front of a static site.
34
+ - `cli` holds the code for the npx command
35
+
36
+ ### Local development
37
+
38
+ Because of the way Cloudflare workers operate, it's hard to have a single local
39
+ development command to test the entire flow while preserving the Vite hot module
40
+ reloading. So, the development will need to happen in two ways:
41
+
42
+ - To design the auth templates, run `cd templates && npm run dev` to run the
43
+ Vite dev server inside the `/templates` folder. This will enable hot module
44
+ reloading, but the worker auth script won't run, so the actual login process
45
+ won't work.
46
+ - To test the whole flow, run `npm run dev` in the root folder. This runs the
47
+ cloudflare worker in a local environment that matches the production
48
+ environment. With this, you can test the entire login flow, but the auth
49
+ templates will use `vite build` and not run via hot module reloading.
50
+
51
+ All you need is to add a `.env.local` file with the following environment
52
+ variables:
53
+
54
+ ```
55
+ SECRET=secret
56
+ PASSWORD=yiah
57
+ ```
58
+
59
+ For testing the CLI, I currently just create a new test package alongside this
60
+ folder and run `npx ../cloudflare`.
package/cli/index.ts ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { confirm, input, select } from "@inquirer/prompts";
4
+ import { existsSync, readFileSync, writeFileSync } from "fs";
5
+ import { exec } from "child_process";
6
+ import { promisify } from "util";
7
+
8
+ /**
9
+ * Base setup
10
+ */
11
+
12
+ const execAsync = promisify(exec);
13
+ const hasWrangler = existsSync("wrangler.jsonc");
14
+ const packageJson = JSON.parse(readFileSync("./package.json", "utf-8"));
15
+
16
+ /**
17
+ * Prompts
18
+ */
19
+
20
+ const isStaticSite = await confirm({
21
+ message: "This CLI currently only supports static sites. Continue?",
22
+ });
23
+
24
+ if (!isStaticSite) {
25
+ process.exit(1);
26
+ }
27
+
28
+ const name = await input({
29
+ message: "Name of the Cloudflare Workers site",
30
+ default: packageJson?.name ?? "my-site",
31
+ });
32
+
33
+ const buildCmd = await input({
34
+ message: "Build command",
35
+ default: "npm run build",
36
+ });
37
+
38
+ const buildDir = await input({
39
+ message: "Build directory",
40
+ default: "./dist",
41
+ });
42
+
43
+ const notFoundHandling = await select({
44
+ message: "Not found handling",
45
+ choices: [
46
+ {
47
+ name: "Single Page Application",
48
+ value: "single-page-application",
49
+ description: "Redirect to the index page when the asset is not found",
50
+ },
51
+ {
52
+ name: "404 Page",
53
+ value: "404-page",
54
+ description: "Render a 404 page when the asset is not found",
55
+ },
56
+ {
57
+ name: "None",
58
+ value: "none",
59
+ description: "Return a 404 status code when the asset is not found",
60
+ },
61
+ ],
62
+ });
63
+
64
+ const hasCustomDomain = await confirm({
65
+ message: "Add a custom domain?",
66
+ default: false,
67
+ });
68
+
69
+ let customDomain;
70
+ if (hasCustomDomain) {
71
+ customDomain = await input({
72
+ message: "Custom domain",
73
+ default: "xxx.ionaldesignsystemsinternat.com",
74
+ });
75
+ }
76
+
77
+ const passwordProtection = await confirm({
78
+ message: "Enable password protection?",
79
+ default: false,
80
+ });
81
+
82
+ /**
83
+ * Create wrangler.jsonc
84
+ */
85
+
86
+ const json: any = {
87
+ name,
88
+ compatibility_date: new Date().toISOString().split("T")[0],
89
+ assets: {
90
+ directory: buildDir,
91
+ not_found_handling: notFoundHandling,
92
+ binding: "ASSETS",
93
+ },
94
+ keep_vars: true,
95
+ workers_dev: true,
96
+ observability: {
97
+ enabled: true,
98
+ },
99
+ };
100
+
101
+ if (hasCustomDomain && customDomain) {
102
+ json.routes = [
103
+ {
104
+ pattern: customDomain,
105
+ custom_domain: true,
106
+ },
107
+ ];
108
+ }
109
+
110
+ if (passwordProtection) {
111
+ json.main =
112
+ "./node_modules/@designsystemsinternational/cloudflare/workers/password/index.ts";
113
+ json.assets.run_worker_first = true;
114
+ }
115
+
116
+ /**
117
+ * Write
118
+ */
119
+
120
+ const wranglerString = JSON.stringify(json, null, 2);
121
+ const buildCmdSplit = buildCmd.split(" ");
122
+ const buildCmdName = buildCmdSplit[2];
123
+ const shouldWriteBuildCmd =
124
+ buildCmdSplit[0] === "npm" &&
125
+ buildCmdSplit[1] === "run" &&
126
+ packageJson?.scripts?.[buildCmdName];
127
+ const copyCmd =
128
+ "cp -r node_modules/@designsystemsinternational/cloudflare/templates/dist/auth dist/auth/";
129
+
130
+ console.log("");
131
+ console.log(
132
+ hasWrangler
133
+ ? "The existing wrangler.jsonc file will be overwritten with this content:"
134
+ : "A wrangler.jsonc file will be created with this content:"
135
+ );
136
+ console.log(wranglerString);
137
+ console.log("");
138
+ if (shouldWriteBuildCmd) {
139
+ console.log("The following command will be added to the build command:");
140
+ console.log(copyCmd);
141
+ console.log("");
142
+ }
143
+ if (passwordProtection) {
144
+ console.log(
145
+ "The @designsystemsinternational/cloudflare package will be installed automatically."
146
+ );
147
+ console.log("");
148
+ }
149
+
150
+ const confirmWrite = await confirm({
151
+ message: "Continue?",
152
+ });
153
+ if (!confirmWrite) {
154
+ process.exit(1);
155
+ }
156
+
157
+ writeFileSync("./wrangler.jsonc", wranglerString, "utf-8");
158
+
159
+ if (shouldWriteBuildCmd) {
160
+ packageJson.scripts[buildCmdName] =
161
+ packageJson.scripts[buildCmdName] + " && " + copyCmd;
162
+ writeFileSync(
163
+ "./package.json",
164
+ JSON.stringify(packageJson, null, 2),
165
+ "utf-8"
166
+ );
167
+ }
168
+
169
+ if (passwordProtection) {
170
+ console.log("Installing @designsystemsinternational/cloudflare...");
171
+ try {
172
+ await execAsync("npm install @designsystemsinternational/cloudflare");
173
+ } catch (error) {
174
+ console.error(
175
+ "Failed to install @designsystemsinternational/cloudflare package. Please install yourself."
176
+ );
177
+ }
178
+ }
179
+
180
+ console.log(`Project setup complete!
181
+
182
+ Next steps:
183
+ - Create a new Cloudflare Workers site with this name: ${name}
184
+ - Add this build command to the site config: ${buildCmd}
185
+ `);
186
+
187
+ if (passwordProtection) {
188
+ console.log(`- Add the following environment variables under Settings > Variables and Secrets
189
+ - SECRET: A random string to encrypt the password
190
+ - PASSWORD: The password you want to use`);
191
+ }
192
+
193
+ if (!shouldWriteBuildCmd) {
194
+ console.log(`- Add the following command to the build command in package.json:
195
+ ${copyCmd}`);
196
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@designsystemsinternational/cloudflare",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/designsystemsinternational/cloudflare.git"
6
+ },
7
+ "version": "0.1.0",
8
+ "type": "module",
9
+ "bin": {
10
+ "cloudflare": "cli/index.ts"
11
+ },
12
+ "scripts": {
13
+ "dev": "cd templates && npm run build && cd .. && wrangler dev",
14
+ "build": "tsc -b && vite build",
15
+ "lint": "eslint .",
16
+ "deploy": "cd templates && npm run build && cd .. && wrangler deploy",
17
+ "types": "wrangler types"
18
+ },
19
+ "dependencies": {
20
+ "@tsndr/cloudflare-worker-jwt": "^3.2.0",
21
+ "worktop": "^0.7.3",
22
+ "zod": "^4.1.13"
23
+ },
24
+ "devDependencies": {
25
+ "@inquirer/prompts": "^8.1.0",
26
+ "@types/node": "^24.10.1",
27
+ "eslint": "^9.33.0",
28
+ "typescript": "~5.8.3",
29
+ "typescript-eslint": "^8.39.1",
30
+ "wrangler": "^4.53.0"
31
+ }
32
+ }
@@ -0,0 +1 @@
1
+ ._root_biwuk_1{display:grid;grid-template-columns:repeat(3,1fr);justify-content:center;align-items:end;min-height:100vh;width:100vw;padding-bottom:var(--spacing-layout-gutter)}@media(min-width:56rem){._root_biwuk_1{align-items:center}}._logo_biwuk_17{position:absolute;top:0;left:0;right:0;display:grid;grid-template-columns:repeat(3,1fr);font-size:var(--text-sm);font-weight:var(--font-weight-medium)}._logo_biwuk_17>div{padding-top:var(--spacing-layout-gutter);padding-right:var(--spacing-layout-gutter);padding-left:var(--spacing-layout-gutter)}._logo_biwuk_17>div:last-child{text-align:right}._container_biwuk_38{padding-right:var(--spacing-layout-gutter);padding-left:var(--spacing-layout-gutter);grid-column:1 / -1}@media(min-width:56rem){._container_biwuk_38{grid-column:2 / 3}}._box_biwuk_50{--input-padding: calc(1.2 * var(--spacing-sm)) var(--spacing-sm) var(--spacing-sm);display:flex;flex-direction:column;justify-content:space-between;gap:var(--spacing-xl);padding:var(--spacing-sm);background-color:var(--color-overlay);border-radius:var(--radius-sm);-webkit-backdrop-filter:var(--backdrop-filter);backdrop-filter:var(--backdrop-filter)}._box_biwuk_50 form{display:flex;flex-direction:column;gap:var(--spacing-2xs)}._box_biwuk_50 [aria-busy]{opacity:.5;cursor:wait}._box_biwuk_50 input{border:1px solid var(--color-border-default);padding:var(--input-padding);font-size:var(--text-sm);border-radius:var(--radius-xs);outline:none;font-family:var(--font-mono)}._box_biwuk_50 input:not([aria-busy]):focus{border-color:var(--color-border-focus)}._box_biwuk_50 button{background:var(--color-bg-inverted-primary);color:var(--color-text-inverted-primary);font-size:var(--text-sm);padding:var(--input-padding);border-radius:var(--radius-xs)}._box_biwuk_50 button:not([aria-busy]):hover{background:var(--color-bg-inverted-secondary);cursor:pointer}._errorBox_biwuk_99[aria-hidden]{visibility:hidden}._errorBox_biwuk_99{padding:var(--spacing-sm);background:var(--color-bg-inverted-tertiary);line-height:var(--leading-loose);border-radius:var(--radius-xs)}._errorBox_biwuk_99 a{font-weight:var(--font-weight-medium)}._errorBox_biwuk_99 a:hover{-webkit-text-decoration:underline;text-decoration:underline}@font-face{font-family:Univers;font-weight:400;src:url(/auth/fonts/UniversNextPro-Regular.woff2) format("woff2")}@font-face{font-family:Univers;font-weight:600;src:url(/auth/fonts/UniversNextPro-Medium.woff2) format("woff2")}@font-face{font-family:Typewriter;font-weight:400;src:url(/auth/fonts/UniversNextTypewrtrPro-Rg.woff2) format("woff2")}@layer reset{*,:after,:before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:--theme(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:--theme(--default-font-feature-settings,normal);font-variation-settings:--theme(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline;text-decoration:underline}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:--theme(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:--theme(--default-mono-font-feature-settings,normal);font-variation-settings:--theme(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:transparent;opacity:1}:where(select[multiple]) optgroup{font-weight:bolder}:where(select[size]) optgroup{font-weight:bolder}:where(select[multiple]) optgroup option{padding-left:20px}:where(select[size]) optgroup option{padding-left:20px}::file-selector-button{margin-right:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}:root{--base-color-black: 0, 0, 0;--base-color-white: 255, 255, 255;--base-color-grey-100: 240, 240, 240;--base-color-grey-300: 204, 204, 204;--base-color-grey-500: 136, 136, 136;--base-color-grey-800: 35, 35, 35;--base-color-grey-900: 15, 15, 15;--color-text-primary: rgb(var(--base-color-black));--color-text-secondary: rgb(var(--base-color-grey-500));--color-text-tertiary: rgb(var(--base-color-grey-300));--color-text-inverted-primary: rgb(var(--base-color-white));--color-text-inverted-secondary: rgba(var(--base-color-white), 50%);--color-bg-primary: rgb(var(--base-color-white));--color-bg-secondary: rgb(var(--base-color-grey-100));--color-bg-inverted-primary: rgb(var(--base-color-black));--color-bg-inverted-secondary: rgb(var(--base-color-grey-800));--color-bg-inverted-tertiary: rgba(var(--base-color-black), 7.5%);--color-overlay: rgba(var(--base-color-white), 40%);--color-overlay-inverted: rgba(var(--base-color-black), 80%);--color-border-default: rgba(var(--base-color-black), 10%);--color-border-focus: rgb(var(--base-color-grey-800));--spacing-4xs: clamp(2px, 1.3077px + .1923vw, 4px);--spacing-3xs: clamp(4px, 3.3077px + .1923vw, 6px);--spacing-2xs: clamp(6px, 5.3077px + .1923vw, 8px);--spacing-xs: clamp(8px, 6.6154px + .3846vw, 12px);--spacing-sm: clamp(12px, 10.6154px + .3846vw, 16px);--spacing-md: clamp(16px, 13.2308px + .7692vw, 24px);--spacing-lg: clamp(24px, 21.2308px + .7692vw, 32px);--spacing-xl: clamp(32px, 26.4615px + 1.5385vw, 48px);--spacing-2xl: clamp(48px, 42.4615px + 1.5385vw, 64px);--spacing-3xl: clamp(64px, 52.9231px + 3.0769vw, 96px);--spacing-4xl: clamp(80px, 57.8462px + 6.1538vw, 144px);--spacing-5xl: clamp(144px, 117.6923px + 7.3077vw, 220px);--spacing-layout-gutter: clamp(8px, 7.3077px + .1923vw, 10px);--spacing-grid-gap: calc(2 * var(--spacing-layout-gutter));--text-xs: clamp(13px, 12.6538px + .0962vw, 14px);--text-sm: clamp(14px, 12.9615px + .2885vw, 17px);--leading-none: 1;--leading-tight: 1.05;--leading-normal: 1.25;--leading-loose: 1.5;--tracking-widest: .05em;--tracking-wider: .025em;--tracking-wide: .001em;--tracking-none: 0;--tracking-tight: -.01em;--tracking-tighter: -.02em;--tracking-tightest: -.04em;--font-sans: "Univers", sans-serif;--font-mono: "Typewriter", monospace;--measure-xs: 30ch;--measure-s: 40ch;--measure-m: 55ch;--measure-l: 75ch;--measure-xl: 80ch;--measure-2xl: 100ch;--font-weight-regular: 400;--font-weight-medium: 600;--ease-in-out: cubic-bezier(.4, 0, .2, 1);--duration-fast: .15s;--duration-slow: .4s;--aspect-square: 1 / 1;--aspect-wide: 16 / 9;--radius-xs: 1px;--radius-sm: 2px;--radius-full: 9999px;--backdrop-filter: blur(10px);--z-index-tooltip-hover-target: 90;--z-index-tooltip: 100;--z-index-control-elements: 200}@media(prefers-color-scheme:dark){:root{--color-text-primary: rgb(var(--base-color-white));--color-text-secondary: rgba(var(--base-color-white), 50%);--color-text-inverted-primary: rgb(var(--base-color-black));--color-text-inverted-secondary: rgba(var(--base-color-black), 50%);--color-bg-primary: rgb(var(--base-color-black));--color-bg-secondary: rgb(var(--base-color-grey-900));--color-bg-inverted-primary: rgb(var(--base-color-white));--color-bg-inverted-secondary: rgb(var(--base-color-grey-100));--color-bg-inverted-tertiary: rgba(var(--base-color-white), 20%);--color-overlay: rgba(var(--base-color-white), 8%);--color-overlay-inverted: rgba(var(--base-color-white), 80%);--color-border-default: rgba(var(--base-color-white), 20%);--color-border-focus: rgba(var(--base-color-white), 70%)}}body{padding:0;overflow-x:hidden;background:var(--color-bg-secondary);color:var(--color-text-primary);font-family:var(--font-sans);font-size:var(--text-md);line-height:var(--leading-normal);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:where(.text-xs){font-size:var(--text-xs);letter-spacing:var(--tracking-wider);line-height:var(--leading-normal)}:where(.text-sm){font-size:var(--text-sm);letter-spacing:var(--tracking-wide);line-height:var(--leading-normal)}:where(.text-primary){color:var(--color-text-primary)}:where(.text-secondary){color:var(--color-text-secondary)}