{
23 | const provider = new GoogleAuthProvider();
24 | return signInWithPopup(auth, provider);
25 | }
26 |
--------------------------------------------------------------------------------
/app/global.d.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | interface ImportMetaEnv {
5 | readonly VITE_APP_HOSTNAME: string;
6 | readonly VITE_GOOGLE_CLOUD_PROJECT: string;
7 | readonly VITE_FIREBASE_APP_ID: string;
8 | readonly VITE_FIREBASE_API_KEY: string;
9 | readonly VITE_FIREBASE_AUTH_DOMAIN: string;
10 | readonly VITE_GA_MEASUREMENT_ID: string;
11 | }
12 |
13 | interface Window {
14 | dataLayer: unknown[];
15 | }
16 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cloudflare Starter Kit
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { CssBaseline } from "@mui/material";
5 | import * as React from "react";
6 | import { createRoot } from "react-dom/client";
7 | import { BrowserRouter } from "react-router-dom";
8 | import { RecoilRoot } from "recoil";
9 | import { AppRoutes } from "./routes/index.js";
10 |
11 | const root = createRoot(document.getElementById("root") as HTMLElement);
12 |
13 | root.render(
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
--------------------------------------------------------------------------------
/app/layout/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { Toolbar } from "@mui/material";
5 | import * as React from "react";
6 | import { useOutlet } from "react-router-dom";
7 | import { AppToolbar } from "./components/AppToolbar.js";
8 |
9 | export function AppLayout(): JSX.Element {
10 | const outlet = useOutlet();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 | {outlet}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/app/layout/components/AppToolbar.tsx:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { AppBar, Box, Button, Toolbar, Typography } from "@mui/material";
5 | import { useAuth, useCurrentUser } from "../../state/firebase.js";
6 |
7 | export function AppToolbar(): JSX.Element {
8 | const me = useCurrentUser();
9 | const auth = useAuth();
10 |
11 | return (
12 |
13 |
14 |
15 | App Name
16 |
17 |
18 |
19 |
20 | {me === null && (
21 |
22 | )}
23 |
24 | {me && (
25 |
26 | )}
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "start": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@emotion/react": "^11.10.6",
13 | "@emotion/styled": "^11.10.6",
14 | "@mui/material": "^5.11.9",
15 | "firebase": "^9.17.1",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "react-router-dom": "^6.8.1",
19 | "recoil": "^0.7.6"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.20.12",
23 | "@emotion/babel-plugin": "^11.10.6",
24 | "@types/react": "^18.0.28",
25 | "@types/react-dom": "^18.0.11",
26 | "@vitejs/plugin-react": "^3.1.0",
27 | "dotenv": "^16.0.3",
28 | "typescript": "^4.9.5",
29 | "vite": "^4.1.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kriasoft/cloudflare-starter-kit/c51a50cabe15eec1194895764f8889e4b537d9b1/app/public/favicon.ico
--------------------------------------------------------------------------------
/app/routes/Home.tsx:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { CircularProgress, Container, Typography } from "@mui/material";
5 | import * as React from "react";
6 | import { usePerson } from "../state/example.js";
7 |
8 | export default function Home(): JSX.Element {
9 | return (
10 |
11 |
12 | Welcome to Cloudflare Starter Kit!
13 |
14 |
15 |
16 | Fetching /api/people/1
(as an example):
17 |
18 |
19 | }
21 | fallback={}
22 | />
23 |
24 | );
25 | }
26 |
27 | export function Person(): JSX.Element {
28 | const person = usePerson(1);
29 |
30 | return (
31 |
32 | {person && JSON.stringify(person, null, " ")}
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/app/routes/index.tsx:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { lazy } from "react";
5 | import { Route, Routes } from "react-router-dom";
6 | import { AppLayout } from "../layout/AppLayout.js";
7 |
8 | const Home = lazy(() => import("./Home.js"));
9 |
10 | export function AppRoutes(): JSX.Element {
11 | return (
12 |
13 | }>
14 | } />
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/app/state/example.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { selectorFamily, useRecoilValue } from "recoil";
5 |
6 | export const Person = selectorFamily({
7 | key: "Person",
8 | get(id) {
9 | return async function (): Promise {
10 | // const me = get(CurrentUser);
11 | // const idToken = await me?.getIdToken();
12 | const res = await fetch(`/api/people/${id}`);
13 |
14 | if (!res.ok) {
15 | const err = await res.json();
16 | throw new Error(err.message ?? `${res.status} ${res.statusText}`);
17 | }
18 |
19 | return await res.json();
20 | };
21 | },
22 | });
23 |
24 | export function usePerson(id: number | string) {
25 | return useRecoilValue(Person(id));
26 | }
27 |
28 | type Person = Record;
29 |
--------------------------------------------------------------------------------
/app/state/firebase.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { type User } from "firebase/auth";
5 | import React from "react";
6 | import { atom, selector, useRecoilValue, useRecoilValueLoadable } from "recoil";
7 | import * as firebase from "../core/firebase.js";
8 |
9 | /**
10 | * Firebase Client SDK.
11 | */
12 | export const Firebase = selector({
13 | key: "Firebase",
14 | dangerouslyAllowMutability: true,
15 | get() {
16 | return import("../core/firebase.js");
17 | },
18 | });
19 |
20 | /**
21 | * The currently logged-in (authenticated) user object.
22 | */
23 | export const CurrentUser = atom({
24 | key: "CurrentUser",
25 | dangerouslyAllowMutability: true,
26 | default: undefined,
27 | effects: [
28 | (ctx) => {
29 | if (ctx.trigger === "get") {
30 | // Subscribe to the authenticated state changes
31 | const promise = ctx
32 | .getPromise(Firebase)
33 | .then((fb) =>
34 | fb.auth.onAuthStateChanged((user) => {
35 | ctx.setSelf(user);
36 | })
37 | )
38 | .catch((err) => ctx.setSelf(Promise.reject(err)));
39 | return () => promise.then((unsubscribe) => unsubscribe?.());
40 | }
41 | },
42 | ],
43 | });
44 |
45 | export function useFirebase(): Firebase {
46 | return useRecoilValue(Firebase);
47 | }
48 |
49 | /**
50 | * The currently logged-in (authenticated) user object.
51 | *
52 | * @example
53 | * function Example(): JSX.Element {
54 | * const me = useCurrentUser();
55 | * // => { uid: "xxx", email: "me@example.com", ... }
56 | * // => Or, `null` when not authenticated
57 | * // => Or, `undefined` when not initialized
58 | * }
59 | */
60 | export function useCurrentUser() {
61 | return useRecoilValue(CurrentUser);
62 | }
63 |
64 | /**
65 | * Authentication manager.
66 | *
67 | * @example
68 | * import { useAuth } from "../state/firebase.js";
69 | *
70 | * function Example(): JSX.Element {
71 | * const auth = useAuth();
72 | * return (
73 | *
74 | *
75 | *
76 | *
77 | * );
78 | * }
79 | */
80 | export function useAuth() {
81 | const value = useRecoilValueLoadable(Firebase);
82 | return React.useMemo(
83 | () => ({
84 | signIn() {
85 | return value.toPromise().then((fb) => fb.signIn());
86 | },
87 | signOut() {
88 | return value.toPromise().then((fb) => fb.auth.signOut());
89 | },
90 | }),
91 | [value.toPromise]
92 | );
93 | }
94 |
95 | type Firebase = typeof firebase;
96 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "ESNext"],
5 | "jsx": "react-jsx",
6 | "jsxImportSource": "@emotion/react",
7 | "types": ["vite/client"],
8 | "outDir": "./dist"
9 | },
10 | "include": ["**/*.ts", "**/*.tsx", "**/*.json"],
11 | "exclude": ["dist/**/*", "vite.config.ts"],
12 | "references": [{ "path": "./tsconfig.node.json" }]
13 | }
14 |
--------------------------------------------------------------------------------
/app/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "moduleResolution": "Node",
5 | "outDir": "../.cache/typescript-app"
6 | },
7 | "include": ["vite.config.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/app/vite.config.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import react from "@vitejs/plugin-react";
5 | import * as dotenv from "dotenv";
6 | import { defineConfig } from "vite";
7 |
8 | // Load environment variables from .env file
9 | dotenv.config({ path: "../.env" });
10 |
11 | // Tells Vite which environment variables need to be injected into the app
12 | // https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes
13 | [
14 | "APP_HOSTNAME",
15 | "GOOGLE_CLOUD_PROJECT",
16 | "FIREBASE_APP_ID",
17 | "FIREBASE_API_KEY",
18 | "GA_MEASUREMENT_ID",
19 | ].forEach((key) => (process.env[`VITE_${key}`] = process.env[key]));
20 |
21 | /**
22 | * Vite configuration
23 | * https://vitejs.dev/config/
24 | */
25 | export default defineConfig({
26 | cacheDir: `../.cache/vite-app`,
27 |
28 | build: {
29 | rollupOptions: {
30 | output: {
31 | manualChunks: {
32 | firebase: ["firebase/app", "firebase/auth"],
33 | react: ["react", "react-dom", "react-router-dom", "recoil"],
34 | },
35 | },
36 | },
37 | },
38 |
39 | plugins: [
40 | // https://github.com/vitejs/vite/tree/main/packages/plugin-react
41 | react({
42 | jsxRuntime: "classic",
43 | jsxImportSource: "@emotion/react",
44 | babel: {
45 | plugins: ["@emotion/babel-plugin"],
46 | },
47 | }),
48 | ],
49 |
50 | server: {
51 | proxy: {
52 | "/api": {
53 | target: "https://swapi.dev/",
54 | changeOrigin: true,
55 | },
56 | },
57 | },
58 | });
59 |
--------------------------------------------------------------------------------
/edge/global.d.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | declare module "__STATIC_CONTENT_MANIFEST" {
5 | const JSON: string;
6 | export default JSON;
7 | }
8 |
9 | declare type Bindings = {
10 | APP_ENV: "local" | "test" | "prod";
11 | APP_NAME: string;
12 | APP_HOSTNAME: string;
13 | __STATIC_CONTENT: KVNamespace;
14 | };
15 |
16 | declare type Env = {
17 | Bindings: Bindings;
18 | };
19 |
20 | declare const bindings: Bindings;
21 |
22 | declare function getMiniflareBindings(): T;
23 |
--------------------------------------------------------------------------------
/edge/index.test.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { expect, test } from "vitest";
5 | import app from "./index.js";
6 |
7 | test.skip("GET /", async () => {
8 | const req = new Request(`https://${env.APP_HOSTNAME}/`);
9 | const res = await app.fetch(req, bindings);
10 | const body = await res.text();
11 |
12 | expect(res.status).toEqual(200);
13 | expect(/(.*)<\/title>/.test(body)).toEqual(true);
14 | });
15 |
--------------------------------------------------------------------------------
/edge/index.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { Hono } from "hono";
5 | import { serveStatic } from "hono/cloudflare-workers";
6 | import assetManifest from "__STATIC_CONTENT_MANIFEST";
7 |
8 | const app = new Hono();
9 |
10 | app.get("/echo", ({ json, req }) => {
11 | return json({
12 | headers: Object.fromEntries(req.headers.entries()),
13 | cf: req.raw.cf,
14 | });
15 | });
16 |
17 | // Rewrite HTTP requests starting with "/api/"
18 | // to the Star Wars API as an example
19 | app.use("/api/*", ({ req }) => {
20 | const { pathname, search } = new URL(req.url);
21 | return fetch(`https://swapi.dev${pathname}${search}`, req);
22 | });
23 |
24 | app.use("*", serveStatic({ manifest: assetManifest }));
25 |
26 | export default app;
27 |
--------------------------------------------------------------------------------
/edge/manifest.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | /**
5 | * __STATIC_CONTENT_MANIFEST stub for unit testing
6 | */
7 | export default "{}";
8 |
--------------------------------------------------------------------------------
/edge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "vite build",
8 | "test": "vitest",
9 | "coverage": "vitest run --coverage",
10 | "deploy": "node -r dotenv/config $(yarn bin wrangler) publish --no-bundle dist/index.js"
11 | },
12 | "dependencies": {
13 | "hono": "^3.0.0"
14 | },
15 | "devDependencies": {
16 | "@cloudflare/workers-types": "^4.20230215.0",
17 | "dotenv": "^16.0.3",
18 | "toml": "^3.0.0",
19 | "typescript": "^4.9.5",
20 | "vite": "^4.1.2",
21 | "vitest": "^0.28.5",
22 | "wrangler": "^2.10.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/edge/transform.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | type Page = {
5 | title?: string;
6 | description?: string;
7 | data?: Record;
8 | };
9 |
10 | /**
11 | * Injects HTML page metadata (title, description, etc.) as well as
12 | * the serialized application store.
13 | */
14 | export function transform(res: Response, page: Page): Response {
15 | return (
16 | new HTMLRewriter()
17 | // ...
18 | .on("title:first-of-type", {
19 | element(el) {
20 | if (page.title) {
21 | el.setInnerContent(page.title);
22 | }
23 | },
24 | })
25 |
26 | //
27 | .on('meta[name="description"]:first-of-type', {
28 | element(el) {
29 | if (page.description) {
30 | el.setAttribute("content", page.description);
31 | }
32 | },
33 | })
34 |
35 | //
36 | // https://developer.mozilla.org/docs/Web/HTML/Element/script#embedding_data_in_html
37 | .on("script#data", {
38 | element(el) {
39 | if (page.data) {
40 | const json = JSON.stringify(page.data).replace(
41 | /<\/script/g,
42 | "\\u0073cript"
43 | );
44 | el.setInnerContent(json, { html: true });
45 | }
46 | },
47 | })
48 | .transform(res)
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/edge/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["ESNext"],
5 | "types": ["@cloudflare/workers-types"],
6 | "outDir": "./dist"
7 | },
8 | "include": ["**/*.ts", "**/*.json", "global.d.ts"],
9 | "exclude": ["dist/**/*", "**/*.test.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/edge/vite.config.ts:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { readFileSync } from "node:fs";
5 | import { parse } from "toml";
6 | import { defineConfig } from "vitest/config";
7 |
8 | const config = parse(readFileSync("./wrangler.toml", "utf8"));
9 |
10 | export default defineConfig({
11 | cacheDir: "../.cache/vite-edge",
12 |
13 | // Production build configuration
14 | // https://vitejs.dev/guide/build
15 | build: {
16 | lib: {
17 | name: "edge",
18 | entry: "index.ts",
19 | fileName: "index",
20 | formats: ["es"],
21 | },
22 | rollupOptions: {
23 | external: ["__STATIC_CONTENT_MANIFEST"],
24 | },
25 | },
26 |
27 | resolve: {
28 | alias: {
29 | ["__STATIC_CONTENT_MANIFEST"]: "./manifest.ts",
30 | },
31 | },
32 |
33 | define: {
34 | bindings: JSON.stringify(config.vars),
35 | },
36 |
37 | // Unit testing configuration
38 | // https://vitest.dev/config/
39 | test: {
40 | deps: {
41 | registerNodeLoader: true,
42 | external: ["__STATIC_CONTENT_MANIFEST"],
43 | },
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/edge/wrangler.toml:
--------------------------------------------------------------------------------
1 | # Cloudflare Workers configuration
2 | # https://developers.cloudflare.com/workers/wrangler/configuration/
3 |
4 | name = "example"
5 | main = "index.js"
6 |
7 | # https://developers.cloudflare.com/workers/platform/compatibility-dates/
8 | compatibility_date = "2022-11-30"
9 |
10 | account_id = "xxxxx"
11 |
12 | routes = [
13 | { pattern = "example.com/*", zone_id = "xxxxx" }
14 | ]
15 |
16 | rules = [
17 | { type = "ESModule", globs = ["dist/*.js"] },
18 | { type = "Text", globs = ["dist/*.md"], fallthrough = true }
19 | ]
20 |
21 | [vars]
22 | APP_ENV = "production"
23 | APP_NAME = "example"
24 | APP_HOSTNAME = "example.com"
25 |
26 | # [secrets]
27 | # GOOGLE_CLOUD_CREDENTIALS
28 |
29 | [site]
30 | bucket = "../app/dist"
31 |
32 | [env.test]
33 | name = "example-test"
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "version": "0.0.0",
4 | "packageManager": "yarn@4.0.0-rc.39",
5 | "private": true,
6 | "type": "module",
7 | "workspaces": [
8 | "api",
9 | "app",
10 | "edge"
11 | ],
12 | "scripts": {
13 | "postinstall": "husky install && node ./scripts/postinstall.js",
14 | "start": "yarn workspace app run start",
15 | "lint": "eslint --cache --report-unused-disable-directives .",
16 | "test": "yarn workspaces foreach -tR run test",
17 | "build": "yarn workspaces foreach run build",
18 | "deploy": "yarn workspaces foreach run deploy",
19 | "app:start": "yarn workspace app run start",
20 | "app:build": "yarn workspace app run build",
21 | "app:preview": "yarn workspace app run preview",
22 | "api:build": "yarn workspace api run build",
23 | "api:deploy": "yarn workspace api run build && yarn workspace api run deploy",
24 | "edge:build": "yarn workspace edge run build",
25 | "edge:deploy": "yarn workspaces foreach -tR --from '{app,edge}' run build && yarn workspace edge run deploy",
26 | "g:tsc": "tsc"
27 | },
28 | "devDependencies": {
29 | "@emotion/eslint-plugin": "^11.10.0",
30 | "@types/node": "^18.14.0",
31 | "@typescript-eslint/eslint-plugin": "^5.52.0",
32 | "@typescript-eslint/parser": "^5.52.0",
33 | "dotenv": "^16.0.3",
34 | "eslint": "^8.34.0",
35 | "eslint-config-prettier": "^8.6.0",
36 | "execa": "^7.0.0",
37 | "got": "^12.5.3",
38 | "husky": "^8.0.3",
39 | "miniflare": "^2.12.0",
40 | "prettier": "^2.8.4",
41 | "typescript": "^4.9.5",
42 | "whatwg-fetch": "^3.6.2",
43 | "wrangler": "^2.10.0",
44 | "zx": "^7.1.1"
45 | },
46 | "resolutions": {
47 | "chalk@npm:^5.0.1": "^4.1.2"
48 | },
49 | "eslintConfig": {
50 | "env": {
51 | "es6": true
52 | },
53 | "extends": [
54 | "eslint:recommended",
55 | "prettier"
56 | ],
57 | "parserOptions": {
58 | "ecmaVersion": 2022,
59 | "sourceType": "module"
60 | },
61 | "overrides": [
62 | {
63 | "files": [
64 | "*.ts",
65 | ".tsx"
66 | ],
67 | "parser": "@typescript-eslint/parser",
68 | "extends": [
69 | "plugin:@typescript-eslint/recommended"
70 | ],
71 | "plugins": [
72 | "@typescript-eslint"
73 | ],
74 | "parserOptions": {
75 | "warnOnUnsupportedTypeScriptVersion": true
76 | }
77 | },
78 | {
79 | "files": [
80 | "*/vite.config.ts",
81 | "scripts/**/*.js"
82 | ],
83 | "env": {
84 | "node": true
85 | }
86 | }
87 | ],
88 | "ignorePatterns": [
89 | "/.cache",
90 | "/.git",
91 | "/.husky",
92 | "/.yarn",
93 | "/*/dist"
94 | ]
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/scripts/postinstall.js:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import { existsSync } from "node:fs";
5 | import { writeFile } from "node:fs/promises";
6 | import { EOL } from "node:os";
7 | import { dirname, join } from "node:path";
8 | import { fileURLToPath } from "node:url";
9 |
10 | const __dirname = dirname(fileURLToPath(import.meta.url));
11 |
12 | // Create `.env` file from the template
13 | const filename = join(__dirname, `../.env`);
14 |
15 | if (!existsSync(filename)) {
16 | await writeFile(
17 | filename,
18 | [
19 | "# Environment variables",
20 | "#",
21 | "# CLOUDFLARE_API_TOKEN=xxxxx",
22 | "# GOOGLE_CLOUD_CREDENTIALS=xxxxx",
23 | "",
24 | ].join(EOL),
25 | "utf-8"
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | /* SPDX-FileCopyrightText: 2020-present Kriasoft */
2 | /* SPDX-License-Identifier: MIT */
3 |
4 | import envars from "envars";
5 | import { Miniflare } from "miniflare";
6 | import * as rollup from "rollup";
7 | import { createServer } from "vite";
8 | import { $, argv, cd, chalk } from "zx";
9 |
10 | process.env.TARGET = "api";
11 | const { default: apiConfig } = await import("../rollup.config.mjs");
12 |
13 | // Configure the Cloudflare Workers server
14 | const mf = new Miniflare({
15 | scriptPath: "dist/api/index.js",
16 | modules: true,
17 | bindings: envars.config(),
18 | sourceMap: true,
19 | });
20 |
21 | // Launch the API compiler in "watch" mode
22 | const api = await new Promise((resolve, reject) => {
23 | cd("./api");
24 | let initialized = false;
25 | rollup.watch(apiConfig).on("event", (event) => {
26 | if (event.code === "END") {
27 | if (initialized) {
28 | mf.reload();
29 | } else {
30 | initialized = true;
31 | mf.startServer()
32 | .then(async (server) => {
33 | await mf.startScheduler();
34 | cd("..");
35 | resolve(server);
36 | })
37 | .catch(reject);
38 | }
39 | } else if (event.code === "ERROR") {
40 | if (initialized) {
41 | initialized = true;
42 | reject(event.error);
43 | } else {
44 | console.log(event.error);
45 | }
46 | }
47 | });
48 | });
49 |
50 | console.clear();
51 | console.log("");
52 |
53 | console.log(
54 | [
55 | `${chalk.gray("Application")}: ${chalk.greenBright($.env.APP_NAME)}`,
56 | `${chalk.gray("environment")}: ${chalk.greenBright($.env.APP_ENV)}`,
57 | ].join(", ")
58 | );
59 |
60 | console.log("");
61 |
62 | // Launch the web application (front-end) server
63 | const app = await createServer({
64 | root: "app",
65 | base: argv.base,
66 | logLevel: argv.logLevel ?? argv.l,
67 | clearScreen: argv.clearScreen,
68 | optimizeDeps: {
69 | force: argv.force,
70 | },
71 | server: {
72 | host: argv.host,
73 | port: argv.port,
74 | https: argv.https,
75 | open: argv.open,
76 | cors: argv.cors,
77 | strictPort: argv.strictPort,
78 | proxy: {
79 | "/api": {
80 | target: `http://localhost:${api.address().port}`,
81 | changeOrigin: true,
82 | },
83 | },
84 | },
85 | });
86 |
87 | await app.listen();
88 | app.printUrls();
89 |
90 | console.log("");
91 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "ESNext", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | "moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52 | // "outDir": "./", /* Specify an output folder for all emitted files. */
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77 |
78 | /* Type Checking */
79 | "strict": true, /* Enable all strict type-checking options. */
80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [{ "path": "./api" }, { "path": "./app" }, { "path": "./edge" }]
4 | }
5 |
--------------------------------------------------------------------------------