@canva/cli 0.0.1-beta.1

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 (109) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +206 -0
  3. package/cli.js +566 -0
  4. package/package.json +30 -0
  5. package/templates/base/backend/database/database.ts +42 -0
  6. package/templates/base/backend/routers/auth.ts +285 -0
  7. package/templates/base/declarations/declarations.d.ts +29 -0
  8. package/templates/base/eslint.config.mjs +309 -0
  9. package/templates/base/package.json +83 -0
  10. package/templates/base/scripts/ssl/ssl.ts +131 -0
  11. package/templates/base/scripts/start/app_runner.ts +164 -0
  12. package/templates/base/scripts/start/context.ts +165 -0
  13. package/templates/base/scripts/start/start.ts +35 -0
  14. package/templates/base/styles/components.css +38 -0
  15. package/templates/base/tsconfig.json +54 -0
  16. package/templates/base/utils/backend/base_backend/create.ts +104 -0
  17. package/templates/base/utils/backend/jwt_middleware/index.ts +1 -0
  18. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  19. package/templates/base/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  20. package/templates/base/webpack.config.cjs +270 -0
  21. package/templates/common/.env.template +6 -0
  22. package/templates/common/.gitignore.template +9 -0
  23. package/templates/common/LICENSE.md +48 -0
  24. package/templates/common/README.md +250 -0
  25. package/templates/common/jest.config.mjs +8 -0
  26. package/templates/dam/backend/database/database.ts +42 -0
  27. package/templates/dam/backend/routers/auth.ts +285 -0
  28. package/templates/dam/backend/routers/dam.ts +86 -0
  29. package/templates/dam/backend/server.ts +65 -0
  30. package/templates/dam/declarations/declarations.d.ts +29 -0
  31. package/templates/dam/eslint.config.mjs +309 -0
  32. package/templates/dam/package.json +90 -0
  33. package/templates/dam/scripts/ssl/ssl.ts +131 -0
  34. package/templates/dam/scripts/start/app_runner.ts +164 -0
  35. package/templates/dam/scripts/start/context.ts +165 -0
  36. package/templates/dam/scripts/start/start.ts +35 -0
  37. package/templates/dam/src/adapter.ts +44 -0
  38. package/templates/dam/src/app.tsx +147 -0
  39. package/templates/dam/src/config.ts +95 -0
  40. package/templates/dam/src/index.css +10 -0
  41. package/templates/dam/src/index.tsx +22 -0
  42. package/templates/dam/tsconfig.json +54 -0
  43. package/templates/dam/utils/backend/base_backend/create.ts +104 -0
  44. package/templates/dam/utils/backend/jwt_middleware/index.ts +1 -0
  45. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  46. package/templates/dam/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  47. package/templates/dam/webpack.config.cjs +270 -0
  48. package/templates/gen_ai/README.md +27 -0
  49. package/templates/gen_ai/backend/database/database.ts +42 -0
  50. package/templates/gen_ai/backend/routers/auth.ts +285 -0
  51. package/templates/gen_ai/backend/routers/image.ts +234 -0
  52. package/templates/gen_ai/backend/server.ts +56 -0
  53. package/templates/gen_ai/declarations/declarations.d.ts +29 -0
  54. package/templates/gen_ai/eslint.config.mjs +309 -0
  55. package/templates/gen_ai/package.json +92 -0
  56. package/templates/gen_ai/scripts/ssl/ssl.ts +131 -0
  57. package/templates/gen_ai/scripts/start/app_runner.ts +164 -0
  58. package/templates/gen_ai/scripts/start/context.ts +165 -0
  59. package/templates/gen_ai/scripts/start/start.ts +35 -0
  60. package/templates/gen_ai/src/api/api.ts +228 -0
  61. package/templates/gen_ai/src/api/index.ts +1 -0
  62. package/templates/gen_ai/src/app.tsx +13 -0
  63. package/templates/gen_ai/src/components/app_error.tsx +18 -0
  64. package/templates/gen_ai/src/components/footer.messages.ts +53 -0
  65. package/templates/gen_ai/src/components/footer.tsx +157 -0
  66. package/templates/gen_ai/src/components/image_grid.tsx +96 -0
  67. package/templates/gen_ai/src/components/index.ts +8 -0
  68. package/templates/gen_ai/src/components/loading_results.tsx +169 -0
  69. package/templates/gen_ai/src/components/logged_in_status.tsx +44 -0
  70. package/templates/gen_ai/src/components/prompt_input.messages.ts +14 -0
  71. package/templates/gen_ai/src/components/prompt_input.tsx +149 -0
  72. package/templates/gen_ai/src/components/remaining_credits.tsx +75 -0
  73. package/templates/gen_ai/src/components/report_box.tsx +53 -0
  74. package/templates/gen_ai/src/config.ts +21 -0
  75. package/templates/gen_ai/src/context/app_context.tsx +174 -0
  76. package/templates/gen_ai/src/context/context.messages.ts +41 -0
  77. package/templates/gen_ai/src/context/index.ts +2 -0
  78. package/templates/gen_ai/src/context/use_app_context.ts +17 -0
  79. package/templates/gen_ai/src/index.tsx +31 -0
  80. package/templates/gen_ai/src/pages/error.tsx +41 -0
  81. package/templates/gen_ai/src/pages/generate.tsx +9 -0
  82. package/templates/gen_ai/src/pages/index.ts +3 -0
  83. package/templates/gen_ai/src/pages/results.tsx +31 -0
  84. package/templates/gen_ai/src/routes/index.ts +1 -0
  85. package/templates/gen_ai/src/routes/routes.tsx +26 -0
  86. package/templates/gen_ai/src/services/auth.tsx +31 -0
  87. package/templates/gen_ai/src/services/index.ts +1 -0
  88. package/templates/gen_ai/src/utils/index.ts +1 -0
  89. package/templates/gen_ai/src/utils/obscenity_filter.ts +33 -0
  90. package/templates/gen_ai/styles/components.css +38 -0
  91. package/templates/gen_ai/styles/utils.css +3 -0
  92. package/templates/gen_ai/tsconfig.json +54 -0
  93. package/templates/gen_ai/utils/backend/base_backend/create.ts +104 -0
  94. package/templates/gen_ai/utils/backend/jwt_middleware/index.ts +1 -0
  95. package/templates/gen_ai/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  96. package/templates/gen_ai/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  97. package/templates/gen_ai/webpack.config.cjs +270 -0
  98. package/templates/hello_world/declarations/declarations.d.ts +29 -0
  99. package/templates/hello_world/eslint.config.mjs +309 -0
  100. package/templates/hello_world/package.json +73 -0
  101. package/templates/hello_world/scripts/ssl/ssl.ts +131 -0
  102. package/templates/hello_world/scripts/start/app_runner.ts +164 -0
  103. package/templates/hello_world/scripts/start/context.ts +165 -0
  104. package/templates/hello_world/scripts/start/start.ts +35 -0
  105. package/templates/hello_world/src/app.tsx +41 -0
  106. package/templates/hello_world/src/index.tsx +22 -0
  107. package/templates/hello_world/styles/components.css +38 -0
  108. package/templates/hello_world/tsconfig.json +54 -0
  109. package/templates/hello_world/webpack.config.cjs +270 -0
@@ -0,0 +1,165 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ interface CliArgs {
5
+ example?: string;
6
+ useHttps?: boolean;
7
+ ngrok?: boolean;
8
+ }
9
+
10
+ interface EnvVars {
11
+ frontendPort: number;
12
+ backendPort: number;
13
+ hmrEnabled: boolean;
14
+ appId?: string;
15
+ appOrigin?: string;
16
+ backendHost?: string;
17
+ }
18
+
19
+ export class Context {
20
+ private readonly envVars: EnvVars;
21
+
22
+ constructor(
23
+ private env: NodeJS.ProcessEnv = process.env,
24
+ private readonly args: CliArgs,
25
+ ) {
26
+ this.envVars = this.parseAndValidateEnvironmentVariables();
27
+ }
28
+
29
+ static get srcDir() {
30
+ const src = path.join(Context.rootDir, "src");
31
+
32
+ if (!fs.existsSync(src)) {
33
+ throw new Error(`Directory does not exist: ${src}`);
34
+ }
35
+
36
+ return src;
37
+ }
38
+
39
+ static get readmeDir() {
40
+ return path.join(Context.rootDir, "README.md");
41
+ }
42
+
43
+ get entryDir() {
44
+ return Context.srcDir;
45
+ }
46
+
47
+ get ngrokEnabled() {
48
+ return !!this.args.ngrok;
49
+ }
50
+
51
+ get hmrEnabled() {
52
+ return this.envVars.hmrEnabled;
53
+ }
54
+
55
+ get httpsEnabled() {
56
+ return !!this.args.useHttps;
57
+ }
58
+
59
+ get frontendEntryPath() {
60
+ const frontendEntryPath = path.join(this.entryDir, "index.tsx");
61
+
62
+ if (!fs.existsSync(frontendEntryPath)) {
63
+ throw new Error(
64
+ `Entry point for frontend does not exist: ${frontendEntryPath}`,
65
+ );
66
+ }
67
+
68
+ return frontendEntryPath;
69
+ }
70
+
71
+ get frontendUrl() {
72
+ return `${this.protocol}://localhost:${this.envVars.frontendPort}`;
73
+ }
74
+
75
+ get frontendPort() {
76
+ return this.envVars.frontendPort;
77
+ }
78
+
79
+ get developerBackendEntryPath(): string | undefined {
80
+ const developerBackendEntryPath = path.join(
81
+ Context.rootDir,
82
+ "backend",
83
+ "server.ts",
84
+ );
85
+
86
+ if (!fs.existsSync(developerBackendEntryPath)) {
87
+ return undefined;
88
+ }
89
+
90
+ return developerBackendEntryPath;
91
+ }
92
+
93
+ get backendUrl() {
94
+ return `${this.protocol}://localhost:${this.envVars.backendPort}`;
95
+ }
96
+
97
+ get backendHost() {
98
+ let backendHost = this.envVars.backendHost;
99
+
100
+ // if there's no custom URL provided by the developer, we fallback to our localhost backend
101
+ if (!backendHost || backendHost.trim() === "") {
102
+ backendHost = this.backendUrl;
103
+ }
104
+
105
+ return backendHost;
106
+ }
107
+
108
+ get backendPort() {
109
+ return this.envVars.backendPort;
110
+ }
111
+
112
+ get appOrigin(): string | undefined {
113
+ return this.envVars.appOrigin;
114
+ }
115
+
116
+ get appId(): string | undefined {
117
+ return this.envVars.appId;
118
+ }
119
+
120
+ private get protocol(): "https" | "http" {
121
+ return this.httpsEnabled ? "https" : "http";
122
+ }
123
+
124
+ private static get rootDir() {
125
+ return path.join(process.cwd());
126
+ }
127
+
128
+ private parseAndValidateEnvironmentVariables(): EnvVars {
129
+ const {
130
+ CANVA_FRONTEND_PORT,
131
+ CANVA_BACKEND_PORT,
132
+ CANVA_BACKEND_HOST,
133
+ CANVA_APP_ID,
134
+ CANVA_APP_ORIGIN,
135
+ CANVA_HMR_ENABLED,
136
+ } = this.env;
137
+
138
+ if (!CANVA_FRONTEND_PORT) {
139
+ throw new Error(
140
+ "CANVA_FRONTEND_PORT environment variable is not defined",
141
+ );
142
+ }
143
+
144
+ if (!CANVA_BACKEND_PORT) {
145
+ throw new Error("CANVA_BACKEND_PORT environment variable is not defined");
146
+ }
147
+
148
+ const envVars: EnvVars = {
149
+ frontendPort: parseInt(CANVA_FRONTEND_PORT, 10),
150
+ backendPort: parseInt(CANVA_BACKEND_PORT, 10),
151
+ hmrEnabled: CANVA_HMR_ENABLED?.toLowerCase().trim() === "true",
152
+ appId: CANVA_APP_ID,
153
+ appOrigin: CANVA_APP_ORIGIN,
154
+ backendHost: CANVA_BACKEND_HOST,
155
+ };
156
+
157
+ if (envVars.hmrEnabled && envVars.appOrigin == null) {
158
+ throw new Error(
159
+ "CANVA_HMR_ENABLED environment variable is TRUE, but CANVA_APP_ORIGIN is not set. Refer to the instructions in the README.md on configuring HMR.",
160
+ );
161
+ }
162
+
163
+ return envVars;
164
+ }
165
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import * as yargs from "yargs";
3
+ import { AppRunner } from "./app_runner";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { Context } from "./context";
6
+
7
+ const appRunner = new AppRunner();
8
+
9
+ yargs(hideBin(process.argv))
10
+ .version(false)
11
+ .help()
12
+ .option("ngrok", {
13
+ description: "Run backend server via ngrok.",
14
+ type: "boolean",
15
+ // npm swallows command line args instead of forwarding to the script
16
+ default:
17
+ process.env.npm_config_ngrok?.toLocaleLowerCase().trim() === "true",
18
+ })
19
+ .option("use-https", {
20
+ description: "Start local development server on HTTPS.",
21
+ type: "boolean",
22
+ // npm swallows commands line args instead of forwarding to the script
23
+ default:
24
+ process.env.npm_config_use_https?.toLocaleLowerCase().trim() === "true",
25
+ })
26
+ .command(
27
+ "$0",
28
+ "Starts local development",
29
+ () => {},
30
+ (args) => {
31
+ const ctx = new Context(process.env, args);
32
+ appRunner.run(ctx);
33
+ },
34
+ )
35
+ .parse();
@@ -0,0 +1,44 @@
1
+ import type {
2
+ FindResourcesRequest,
3
+ FindResourcesResponse,
4
+ } from "@canva/app-components";
5
+ import { auth } from "@canva/user";
6
+
7
+ export async function findResources(
8
+ request: FindResourcesRequest<"folder">,
9
+ ): Promise<FindResourcesResponse> {
10
+ const userToken = await auth.getCanvaUserToken();
11
+
12
+ // TODO: Update the API path to match your backend
13
+ // If using the backend example, the URL should be updated to `${BACKEND_HOST}/api/resources/find` to ensure requests are authenticated in production
14
+ const url = new URL(`${BACKEND_HOST}/resources/find`);
15
+
16
+ try {
17
+ const response = await fetch(url, {
18
+ method: "POST",
19
+ headers: {
20
+ Authorization: `Bearer ${userToken}`,
21
+ "Content-Type": "application/json",
22
+ },
23
+ body: JSON.stringify(request),
24
+ });
25
+ const body = await response.json();
26
+
27
+ if (body.resources) {
28
+ return {
29
+ type: "SUCCESS",
30
+ resources: body.resources,
31
+ continuation: body.continuation,
32
+ };
33
+ }
34
+ return {
35
+ type: "ERROR",
36
+ errorCode: body.errorCode || "INTERNAL_ERROR",
37
+ };
38
+ } catch {
39
+ return {
40
+ type: "ERROR",
41
+ errorCode: "INTERNAL_ERROR",
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,147 @@
1
+ import { useEffect, useState } from "react";
2
+ import { SearchableListView } from "@canva/app-components";
3
+ import { Alert, Box, Button, LoadingIndicator, Rows } from "@canva/app-ui-kit";
4
+ import type { Authentication } from "@canva/user";
5
+ import { auth } from "@canva/user";
6
+ import { findResources } from "./adapter";
7
+ import { config } from "./config";
8
+ import * as styles from "./index.css";
9
+ import "@canva/app-ui-kit/styles.css";
10
+
11
+ type AuthenticationState =
12
+ | "authenticated"
13
+ | "not_authenticated"
14
+ | "checking"
15
+ | "cancelled"
16
+ | "error";
17
+
18
+ /**
19
+ * This endpoint is defined in the ./backend/server.ts file. You need to
20
+ * register the endpoint in the Developer Portal before sending requests.
21
+ *
22
+ * BACKEND_HOST is configured in the root .env file, for more information,
23
+ * refer to the README.md.
24
+ */
25
+ const AUTHENTICATION_CHECK_URL = `${BACKEND_HOST}/api/authentication/status`;
26
+
27
+ const checkAuthenticationStatus = async (
28
+ auth: Authentication,
29
+ ): Promise<AuthenticationState> => {
30
+ /**
31
+ * Send a request to an endpoint that checks if the user is authenticated.
32
+ * This is example code, intended to convey the basic idea. When implementing this in your app, you might want more advanced checks.
33
+ *
34
+ * Note: You must register the provided endpoint via the Developer Portal.
35
+ */
36
+ try {
37
+ const token = await auth.getCanvaUserToken();
38
+ const res = await fetch(AUTHENTICATION_CHECK_URL, {
39
+ headers: {
40
+ Authorization: `Bearer ${token}`,
41
+ },
42
+ method: "POST",
43
+ });
44
+ const body = await res.json();
45
+
46
+ if (body?.isAuthenticated) {
47
+ return "authenticated";
48
+ } else {
49
+ return "not_authenticated";
50
+ }
51
+ } catch (error) {
52
+ // eslint-disable-next-line no-console
53
+ console.error(error);
54
+ return "error";
55
+ }
56
+ };
57
+
58
+ export function App() {
59
+ // Keep track of the user's authentication status.
60
+ const [authState, setAuthState] = useState<AuthenticationState>("checking");
61
+
62
+ useEffect(() => {
63
+ setAuthState("checking");
64
+ checkAuthenticationStatus(auth).then((status) => {
65
+ setAuthState(status);
66
+ });
67
+ }, []);
68
+
69
+ useEffect(() => {
70
+ if (authState === "not_authenticated") {
71
+ startAuthenticationFlow();
72
+ }
73
+ }, [authState]);
74
+
75
+ const startAuthenticationFlow = async () => {
76
+ try {
77
+ const response = await auth.requestAuthentication();
78
+ switch (response.status) {
79
+ case "COMPLETED":
80
+ setAuthState("authenticated");
81
+ break;
82
+ case "ABORTED":
83
+ // eslint-disable-next-line no-console
84
+ console.warn("Authentication aborted by user.");
85
+ setAuthState("cancelled");
86
+ break;
87
+ case "DENIED":
88
+ // eslint-disable-next-line no-console
89
+ console.warn("Authentication denied by user", response.details);
90
+ setAuthState("cancelled");
91
+ break;
92
+ default:
93
+ // eslint-disable-next-line no-console
94
+ console.log("Unknown auth state");
95
+ break;
96
+ }
97
+ } catch (e) {
98
+ // eslint-disable-next-line no-console
99
+ console.error(e);
100
+ setAuthState("error");
101
+ }
102
+ };
103
+
104
+ if (authState === "error") {
105
+ // eslint-disable-next-line no-console
106
+ console.warn(
107
+ "Warning: authentication not enabled on this app. Please enable auth with the instructions in README",
108
+ );
109
+ // Comment this next line out for production apps
110
+ setAuthState("authenticated");
111
+ }
112
+
113
+ // If user has denied or aborted auth flow
114
+ if (authState === "cancelled") {
115
+ return (
116
+ <Box paddingEnd="2u" height="full" className={styles.centerInPage}>
117
+ <Rows spacing="2u" align="center">
118
+ <Alert tone="critical">
119
+ Something went wrong while authenticating
120
+ </Alert>
121
+ <Button
122
+ variant="primary"
123
+ onClick={startAuthenticationFlow}
124
+ stretch={true}
125
+ >
126
+ Start authentication flow
127
+ </Button>
128
+ </Rows>
129
+ </Box>
130
+ );
131
+ }
132
+
133
+ return authState === "authenticated" ? (
134
+ <Box className={styles.rootWrapper}>
135
+ <SearchableListView config={config} findResources={findResources} />
136
+ </Box>
137
+ ) : (
138
+ <Box
139
+ width="full"
140
+ height="full"
141
+ paddingTop="1u"
142
+ className={styles.centerInPage}
143
+ >
144
+ <LoadingIndicator size="large" />
145
+ </Box>
146
+ );
147
+ }
@@ -0,0 +1,95 @@
1
+ import type { Config } from "@canva/app-components";
2
+
3
+ type ContainerTypes = "folder";
4
+ export const config: Config<ContainerTypes> = {
5
+ serviceName: "Example App",
6
+ search: {
7
+ enabled: true,
8
+ filterFormConfig: {
9
+ containerTypes: ["folder"],
10
+ filters: [
11
+ {
12
+ filterType: "CHECKBOX",
13
+ label: "File Type",
14
+ key: "fileType",
15
+ options: [
16
+ { value: "mp4", label: "MP4" },
17
+ { value: "png", label: "PNG" },
18
+ { value: "jpeg", label: "JPEG" },
19
+ ],
20
+ allowCustomValue: true,
21
+ },
22
+ {
23
+ filterType: "RADIO",
24
+ label: "Size",
25
+ key: "size",
26
+ options: [
27
+ {
28
+ label: "Large (800+ px)",
29
+ value: "large",
30
+ },
31
+ {
32
+ label: "Medium (200-799px)",
33
+ value: "medium",
34
+ },
35
+ ],
36
+ allowCustomValue: true,
37
+ customValueInputType: "SIZE_RANGE",
38
+ },
39
+ {
40
+ filterType: "RADIO",
41
+ label: "Update Date",
42
+ key: "updateDate",
43
+ options: [
44
+ { value: ">now-30m", label: "Last 30 Minutes" },
45
+ { value: ">now-7d", label: "Last 7 days" },
46
+ ],
47
+ allowCustomValue: true,
48
+ customValueInputType: "DATE_RANGE",
49
+ },
50
+ {
51
+ filterType: "RADIO",
52
+ label: "Design Status",
53
+ key: "designStatus",
54
+ options: [
55
+ { value: "approved", label: "Approved" },
56
+ { value: "rejected", label: "Rejected" },
57
+ { value: "draft", label: "Draft" },
58
+ ],
59
+ allowCustomValue: true,
60
+ customValueInputType: "PLAIN_TEXT",
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ containerTypes: [
66
+ {
67
+ value: "folder",
68
+ label: "Folders",
69
+ listingSurfaces: [
70
+ { surface: "HOMEPAGE" },
71
+ {
72
+ surface: "CONTAINER",
73
+ parentContainerTypes: ["folder"],
74
+ },
75
+ { surface: "SEARCH" },
76
+ ],
77
+ searchInsideContainer: {
78
+ enabled: true,
79
+ placeholder: "Search for resources inside this folder",
80
+ },
81
+ },
82
+ ],
83
+ sortOptions: [
84
+ { value: "created_at DESC", label: "Creation date (newest)" },
85
+ { value: "created_at ASC", label: "Creation date (oldest)" },
86
+ { value: "updated_at DESC", label: "Updated (newest)" },
87
+ { value: "updated_at ASC", label: "Updated (oldest)" },
88
+ { value: "name ASC", label: "Name (A-Z)" },
89
+ { value: "name DESC", label: "Name (Z-A)" },
90
+ ],
91
+ layouts: ["MASONRY", "LIST"],
92
+ resourceTypes: ["IMAGE", "VIDEO", "EMBED"],
93
+ moreInfoMessage:
94
+ "At the moment, we only support images and videos. Corrupted and unsupported files will not appear.",
95
+ };
@@ -0,0 +1,10 @@
1
+ .rootWrapper {
2
+ height: 100%;
3
+ overflow-y: hidden;
4
+ }
5
+
6
+ .centerInPage {
7
+ display: flex;
8
+ flex-direction: column;
9
+ justify-content: center;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { AppUiProvider } from "@canva/app-ui-kit";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./app";
4
+ import "@canva/app-ui-kit/styles.css";
5
+ import { AppI18nProvider } from "@canva/app-i18n-kit";
6
+
7
+ const root = createRoot(document.getElementById("root") as Element);
8
+ function render() {
9
+ root.render(
10
+ <AppI18nProvider>
11
+ <AppUiProvider>
12
+ <App />
13
+ </AppUiProvider>
14
+ </AppI18nProvider>,
15
+ );
16
+ }
17
+
18
+ render();
19
+
20
+ if (module.hot) {
21
+ module.hot.accept("./app", render);
22
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "es2018",
8
+ "es2019.array",
9
+ "es2019.object",
10
+ "es2019.string",
11
+ "es2020.promise",
12
+ "es2020.string"
13
+ ],
14
+ "types": ["node", "webpack-env", "jest"],
15
+ "composite": false,
16
+ "declaration": false,
17
+ "declarationMap": false,
18
+ "experimentalDecorators": true,
19
+ "importHelpers": true,
20
+ "noImplicitOverride": true,
21
+ "moduleResolution": "node",
22
+ "rootDir": ".",
23
+ "outDir": "dist",
24
+ "strict": true,
25
+ "skipLibCheck": true,
26
+ "target": "ES2019",
27
+ "sourceMap": true,
28
+ "inlineSources": true,
29
+ "module": "ESNext",
30
+ "noImplicitAny": false,
31
+ "removeComments": true,
32
+ "preserveConstEnums": true,
33
+ "allowSyntheticDefaultImports": true,
34
+ "baseUrl": "./",
35
+ "paths": {
36
+ "assets": ["./assets"],
37
+ "styles": ["./styles"]
38
+ }
39
+ },
40
+ "include": [
41
+ "./src/**/*",
42
+ "./backend/**/*",
43
+ "./utils/**/*",
44
+ "./scripts/**/*",
45
+ "./declarations/declarations.d.ts",
46
+ "./styles/**/*",
47
+ "./node_modules/@types/**/*"
48
+ ],
49
+ "ts-node": {
50
+ "compilerOptions": {
51
+ "module": "commonjs"
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,104 @@
1
+ /* eslint-disable no-console */
2
+ import * as express from "express";
3
+ import * as http from "http";
4
+ import * as https from "https";
5
+ import * as fs from "fs";
6
+ import type { Request, Response, NextFunction } from "express";
7
+ import debug from "debug";
8
+
9
+ const serverDebug = debug("server");
10
+
11
+ interface BaseServer {
12
+ app: express.Express;
13
+
14
+ /**
15
+ * Starts the server on the address or port provided
16
+ * @param address port number or string address or if left undefined express defaults to port 3000
17
+ */
18
+ start: (address: number | string | undefined) => void;
19
+ }
20
+
21
+ /**
22
+ * createBaseServer instantiates a customised express server with:
23
+ * - json body handling
24
+ * - health check endpoint
25
+ * - catchall endpoint
26
+ * - error handler catch route
27
+ * - process termination handling
28
+ * - debug logging - prefix starting your server with `DEBUG=server npm run XXX`
29
+ *
30
+ * @returns BaseServer object containing the express app and a start function
31
+ */
32
+ export function createBaseServer(router: express.Router): BaseServer {
33
+ const SHOULD_ENABLE_HTTPS = process.env?.SHOULD_ENABLE_HTTPS === "true";
34
+ const HTTPS_CERT_FILE = process.env?.HTTPS_CERT_FILE;
35
+ const HTTPS_KEY_FILE = process.env?.HTTPS_KEY_FILE;
36
+
37
+ const app = express();
38
+ app.use(express.json());
39
+
40
+ // It can help to provide an extra layer of obsecurity to reduce server fingerprinting.
41
+ app.disable("x-powered-by");
42
+
43
+ // Health check endpoint
44
+ app.get("/healthz", (req, res: Response) => {
45
+ res.sendStatus(200);
46
+ });
47
+
48
+ // logging middleware
49
+ app.use((req: Request, res: Response, next: NextFunction) => {
50
+ serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
51
+ next();
52
+ });
53
+
54
+ // Custom routes router
55
+ app.use(router);
56
+
57
+ // catch all router
58
+ app.all("*", (req, res) => {
59
+ res.status(404).send({
60
+ error: `unhandled '${req.method}' on '${req.url}'`,
61
+ });
62
+ });
63
+
64
+ // default error handler
65
+ app.use((err, req, res, next) => {
66
+ console.error(err.stack);
67
+ res.status(500).send({
68
+ error: "something went wrong",
69
+ });
70
+ });
71
+
72
+ let server;
73
+ if (SHOULD_ENABLE_HTTPS) {
74
+ if (!HTTPS_CERT_FILE || !HTTPS_KEY_FILE) {
75
+ throw new Error(
76
+ "Looks like you're running the example with --use-https flag, but SSL certificates haven't been generated. Please remove the .ssl/ folder and re-run the command again.",
77
+ );
78
+ }
79
+
80
+ server = https.createServer(
81
+ {
82
+ key: fs.readFileSync(HTTPS_KEY_FILE),
83
+ cert: fs.readFileSync(HTTPS_CERT_FILE),
84
+ },
85
+ app,
86
+ );
87
+ } else {
88
+ server = http.createServer(app);
89
+ }
90
+
91
+ return {
92
+ app,
93
+ start: (address: number | string | undefined) => {
94
+ console.log(`Listening on '${address}'`);
95
+ server.listen(address);
96
+ process.on("SIGTERM", () => {
97
+ serverDebug("SIGTERM signal received: closing HTTP server");
98
+ server.close(() => {
99
+ serverDebug("HTTP server closed");
100
+ });
101
+ });
102
+ },
103
+ };
104
+ }
@@ -0,0 +1 @@
1
+ export { createJwtMiddleware } from "./jwt_middleware";