@canva/cli 0.0.1-beta.3 → 0.0.1-beta.31

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 (132) hide show
  1. package/README.md +218 -104
  2. package/cli.js +661 -394
  3. package/lib/cjs/index.cjs +5 -0
  4. package/lib/esm/index.mjs +5 -0
  5. package/package.json +19 -4
  6. package/templates/base/eslint.config.mjs +2 -27
  7. package/templates/base/package.json +31 -25
  8. package/templates/base/scripts/copy_env.ts +10 -0
  9. package/templates/base/scripts/start/app_runner.ts +39 -2
  10. package/templates/base/scripts/start/context.ts +12 -6
  11. package/templates/base/scripts/start/start.ts +11 -0
  12. package/templates/base/scripts/start/tests/start.tests.ts +61 -0
  13. package/templates/base/{webpack.config.cjs → webpack.config.ts} +46 -46
  14. package/templates/common/.cursor/mcp.json +8 -0
  15. package/templates/common/.gitignore.template +5 -6
  16. package/templates/common/.nvmrc +1 -0
  17. package/templates/common/.prettierrc +21 -0
  18. package/templates/common/.vscode/extensions.json +6 -0
  19. package/templates/common/.vscode/mcp.json +9 -0
  20. package/templates/common/README.md +4 -7
  21. package/templates/common/jest.config.mjs +29 -2
  22. package/templates/common/jest.setup.ts +20 -0
  23. package/templates/common/utils/backend/base_backend/create.ts +104 -0
  24. package/templates/common/utils/backend/jwt_middleware/index.ts +1 -0
  25. package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  26. package/templates/common/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  27. package/templates/common/utils/table_wrapper.ts +477 -0
  28. package/templates/common/utils/tests/table_wrapper.tests.ts +252 -0
  29. package/templates/common/utils/use_add_element.ts +48 -0
  30. package/templates/common/utils/use_feature_support.ts +28 -0
  31. package/templates/common/utils/use_overlay_hook.ts +74 -0
  32. package/templates/common/utils/use_selection_hook.ts +37 -0
  33. package/templates/dam/backend/routers/dam.ts +23 -19
  34. package/templates/dam/eslint.config.mjs +2 -28
  35. package/templates/dam/package.json +43 -39
  36. package/templates/dam/scripts/copy_env.ts +10 -0
  37. package/templates/dam/scripts/start/app_runner.ts +39 -2
  38. package/templates/dam/scripts/start/context.ts +12 -6
  39. package/templates/dam/scripts/start/start.ts +11 -0
  40. package/templates/dam/scripts/start/tests/start.tests.ts +61 -0
  41. package/templates/dam/src/app.tsx +24 -3
  42. package/templates/dam/src/config.ts +212 -87
  43. package/templates/{hello_world/webpack.config.cjs → dam/webpack.config.ts} +46 -46
  44. package/templates/data_connector/README.md +84 -0
  45. package/templates/data_connector/declarations/declarations.d.ts +29 -0
  46. package/templates/data_connector/eslint.config.mjs +14 -0
  47. package/templates/data_connector/package.json +91 -0
  48. package/templates/data_connector/scripts/copy_env.ts +10 -0
  49. package/templates/data_connector/scripts/ssl/ssl.ts +131 -0
  50. package/templates/data_connector/scripts/start/app_runner.ts +201 -0
  51. package/templates/data_connector/scripts/start/context.ts +171 -0
  52. package/templates/data_connector/scripts/start/start.ts +46 -0
  53. package/templates/data_connector/scripts/start/tests/start.tests.ts +61 -0
  54. package/templates/data_connector/src/api/connect_client.ts +6 -0
  55. package/templates/data_connector/src/api/data_source.ts +96 -0
  56. package/templates/data_connector/src/api/data_sources/designs.tsx +296 -0
  57. package/templates/data_connector/src/api/data_sources/index.ts +4 -0
  58. package/templates/data_connector/src/api/data_sources/templates.tsx +329 -0
  59. package/templates/data_connector/src/api/fetch_data_table.ts +55 -0
  60. package/templates/data_connector/src/api/index.ts +4 -0
  61. package/templates/data_connector/src/api/oauth.ts +8 -0
  62. package/templates/data_connector/src/api/tests/data_source.test.tsx +99 -0
  63. package/templates/data_connector/src/app.tsx +20 -0
  64. package/templates/data_connector/src/components/app_error.tsx +15 -0
  65. package/templates/data_connector/src/components/footer.tsx +26 -0
  66. package/templates/data_connector/src/components/header.tsx +40 -0
  67. package/templates/data_connector/src/components/index.ts +3 -0
  68. package/templates/data_connector/src/components/inputs/messages.tsx +99 -0
  69. package/templates/data_connector/src/components/inputs/search_filter.tsx +108 -0
  70. package/templates/data_connector/src/components/inputs/select_field.tsx +26 -0
  71. package/templates/data_connector/src/context/app_context.tsx +124 -0
  72. package/templates/data_connector/src/context/index.ts +2 -0
  73. package/templates/data_connector/src/context/use_app_context.ts +17 -0
  74. package/templates/data_connector/src/entrypoint.tsx +70 -0
  75. package/templates/data_connector/src/home.tsx +21 -0
  76. package/templates/data_connector/src/index.tsx +69 -0
  77. package/templates/data_connector/src/pages/data_source_config.tsx +9 -0
  78. package/templates/data_connector/src/pages/error.tsx +37 -0
  79. package/templates/data_connector/src/pages/index.ts +4 -0
  80. package/templates/data_connector/src/pages/login.tsx +145 -0
  81. package/templates/data_connector/src/pages/select_source.tsx +24 -0
  82. package/templates/data_connector/src/routes/index.ts +2 -0
  83. package/templates/data_connector/src/routes/protected_route.tsx +25 -0
  84. package/templates/data_connector/src/routes/routes.tsx +46 -0
  85. package/templates/data_connector/src/utils/data_params.ts +17 -0
  86. package/templates/data_connector/src/utils/data_table.ts +115 -0
  87. package/templates/data_connector/src/utils/fetch_result.ts +36 -0
  88. package/templates/data_connector/src/utils/index.ts +2 -0
  89. package/templates/data_connector/src/utils/tests/data_table.test.ts +133 -0
  90. package/templates/data_connector/styles/components.css +38 -0
  91. package/templates/data_connector/tsconfig.json +54 -0
  92. package/templates/{gen_ai/webpack.config.cjs → data_connector/webpack.config.ts} +46 -46
  93. package/templates/gen_ai/README.md +1 -40
  94. package/templates/gen_ai/backend/routers/image.ts +11 -11
  95. package/templates/gen_ai/backend/server.ts +0 -7
  96. package/templates/gen_ai/eslint.config.mjs +2 -27
  97. package/templates/gen_ai/package.json +48 -42
  98. package/templates/gen_ai/scripts/copy_env.ts +10 -0
  99. package/templates/gen_ai/scripts/start/app_runner.ts +39 -2
  100. package/templates/gen_ai/scripts/start/context.ts +12 -6
  101. package/templates/gen_ai/scripts/start/start.ts +11 -0
  102. package/templates/gen_ai/scripts/start/tests/start.tests.ts +61 -0
  103. package/templates/gen_ai/src/api/api.ts +24 -79
  104. package/templates/gen_ai/src/app.tsx +16 -10
  105. package/templates/gen_ai/src/components/footer.messages.ts +0 -5
  106. package/templates/gen_ai/src/components/footer.tsx +7 -25
  107. package/templates/gen_ai/src/components/index.ts +0 -1
  108. package/templates/gen_ai/src/components/loading_results.tsx +4 -8
  109. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +43 -0
  110. package/templates/gen_ai/src/context/app_context.tsx +5 -33
  111. package/templates/gen_ai/src/context/context.messages.ts +1 -12
  112. package/templates/gen_ai/src/home.tsx +13 -0
  113. package/templates/gen_ai/src/index.tsx +2 -18
  114. package/templates/gen_ai/src/routes/routes.tsx +2 -2
  115. package/templates/{dam/webpack.config.cjs → gen_ai/webpack.config.ts} +46 -46
  116. package/templates/hello_world/eslint.config.mjs +2 -27
  117. package/templates/hello_world/package.json +47 -34
  118. package/templates/hello_world/scripts/copy_env.ts +10 -0
  119. package/templates/hello_world/scripts/start/app_runner.ts +39 -2
  120. package/templates/hello_world/scripts/start/context.ts +12 -6
  121. package/templates/hello_world/scripts/start/start.ts +11 -0
  122. package/templates/hello_world/scripts/start/tests/start.tests.ts +61 -0
  123. package/templates/hello_world/src/app.tsx +20 -0
  124. package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +45 -0
  125. package/templates/hello_world/src/tests/app.tests.tsx +86 -0
  126. package/templates/hello_world/webpack.config.ts +270 -0
  127. package/templates/common/conf/eslint-general.mjs +0 -277
  128. package/templates/common/conf/eslint-i18n.mjs +0 -23
  129. package/templates/gen_ai/backend/routers/oauth.ts +0 -393
  130. package/templates/gen_ai/src/components/logged_in_status.tsx +0 -44
  131. package/templates/gen_ai/src/services/auth.tsx +0 -26
  132. package/templates/gen_ai/src/services/index.ts +0 -1
@@ -1,12 +1,14 @@
1
1
  /* eslint-disable no-console */
2
2
  import type { Context } from "./context";
3
3
  import * as chalk from "chalk";
4
- import { buildConfig } from "../../webpack.config.cjs";
4
+ import { buildConfig } from "../../webpack.config";
5
5
  import * as ngrok from "@ngrok/ngrok";
6
6
  import * as nodemon from "nodemon";
7
7
  import * as Table from "cli-table3";
8
8
  import * as webpack from "webpack";
9
9
  import * as WebpackDevServer from "webpack-dev-server";
10
+ import * as open from "open";
11
+ import { generatePreviewUrl } from "@canva/cli";
10
12
  import type { Certificate } from "../ssl/ssl";
11
13
  import { createOrRetrieveCertificate } from "../ssl/ssl";
12
14
 
@@ -46,12 +48,18 @@ export class AppRunner {
46
48
  }
47
49
  }
48
50
 
49
- const table = new Table();
51
+ const table = new Table({
52
+ colWidths: [30, 80],
53
+ wordWrap: true,
54
+ wrapOnWordBoundary: true,
55
+ });
50
56
 
51
57
  const server = await this.runWebpackDevServer(ctx, table, cert);
52
58
 
53
59
  await this.maybeRunBackendServer(ctx, table, cert, server);
54
60
 
61
+ await this.generateAndOpenPreviewUrl(ctx.openPreview, table);
62
+
55
63
  console.log(table.toString(), "\n");
56
64
 
57
65
  console.log(
@@ -161,4 +169,33 @@ export class AppRunner {
161
169
 
162
170
  return server;
163
171
  };
172
+
173
+ /**
174
+ * Calls the Canva CLI to generate a preview URL for the app
175
+ */
176
+ private readonly generateAndOpenPreviewUrl = async (
177
+ openPreview: boolean,
178
+ table: Table.Table,
179
+ ) => {
180
+ const previewCellHeader = { content: "Preview your app in Canva" };
181
+
182
+ const generatePreviewResult = await generatePreviewUrl();
183
+
184
+ if (!generatePreviewResult.success) {
185
+ table.push([
186
+ previewCellHeader,
187
+ { content: warnChalk(generatePreviewResult.message) },
188
+ ]);
189
+ return;
190
+ }
191
+
192
+ table.push([
193
+ previewCellHeader,
194
+ { content: "Preview URL", href: generatePreviewResult.data },
195
+ ]);
196
+
197
+ if (openPreview) {
198
+ open(generatePreviewResult.data);
199
+ }
200
+ };
164
201
  }
@@ -3,8 +3,10 @@ import * as path from "path";
3
3
 
4
4
  interface CliArgs {
5
5
  example?: string;
6
- useHttps?: boolean;
7
- ngrok?: boolean;
6
+ useHttps: boolean;
7
+ ngrok: boolean;
8
+ preview: boolean;
9
+ overrideFrontendPort?: number;
8
10
  }
9
11
 
10
12
  interface EnvVars {
@@ -45,7 +47,7 @@ export class Context {
45
47
  }
46
48
 
47
49
  get ngrokEnabled() {
48
- return !!this.args.ngrok;
50
+ return this.args.ngrok;
49
51
  }
50
52
 
51
53
  get hmrEnabled() {
@@ -53,7 +55,7 @@ export class Context {
53
55
  }
54
56
 
55
57
  get httpsEnabled() {
56
- return !!this.args.useHttps;
58
+ return this.args.useHttps;
57
59
  }
58
60
 
59
61
  get frontendEntryPath() {
@@ -69,11 +71,11 @@ export class Context {
69
71
  }
70
72
 
71
73
  get frontendUrl() {
72
- return `${this.protocol}://localhost:${this.envVars.frontendPort}`;
74
+ return `${this.protocol}://localhost:${this.frontendPort}`;
73
75
  }
74
76
 
75
77
  get frontendPort() {
76
- return this.envVars.frontendPort;
78
+ return this.args.overrideFrontendPort || this.envVars.frontendPort;
77
79
  }
78
80
 
79
81
  get developerBackendEntryPath(): string | undefined {
@@ -117,6 +119,10 @@ export class Context {
117
119
  return this.envVars.appId;
118
120
  }
119
121
 
122
+ get openPreview(): boolean {
123
+ return this.args.preview;
124
+ }
125
+
120
126
  private get protocol(): "https" | "http" {
121
127
  return this.httpsEnabled ? "https" : "http";
122
128
  }
@@ -23,6 +23,17 @@ yargs(hideBin(process.argv))
23
23
  default:
24
24
  process.env.npm_config_use_https?.toLocaleLowerCase().trim() === "true",
25
25
  })
26
+ .option("override-frontend-port", {
27
+ description:
28
+ "Port to run the local development server on. Overrides the frontend port set in the .env file.",
29
+ type: "number",
30
+ alias: "p",
31
+ })
32
+ .option("preview", {
33
+ description: "Open the app in Canva.",
34
+ type: "boolean",
35
+ default: false,
36
+ })
26
37
  .command(
27
38
  "$0",
28
39
  "Starts local development",
@@ -0,0 +1,61 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import type { ChildProcess } from "child_process";
4
+ import { spawn } from "child_process";
5
+ import * as treeKill from "tree-kill";
6
+
7
+ describe("start script", () => {
8
+ let serverProcess: ChildProcess;
9
+
10
+ afterEach(() => {
11
+ if (serverProcess?.pid && !serverProcess.exitCode) {
12
+ treeKill(serverProcess.pid);
13
+ }
14
+ });
15
+
16
+ it("should execute 'npm run start' and start a dev server", async () => {
17
+ const testServerPort = 8089;
18
+ serverProcess = await spawn(
19
+ `npm run start -- -p ${testServerPort} --no-preview`,
20
+ {
21
+ shell: true,
22
+ },
23
+ );
24
+
25
+ if (!serverProcess.pid) {
26
+ throw new Error("Unable to start server");
27
+ }
28
+
29
+ // wait for the server to start and collect output until it reports the running URL
30
+ let output = "";
31
+ await new Promise<void>((resolve, reject) => {
32
+ serverProcess.stdout?.on("data", (data) => {
33
+ output += data.toString();
34
+ if (output.includes("Development URL")) {
35
+ resolve();
36
+ }
37
+ });
38
+
39
+ serverProcess.stderr?.on("data", (data) => {
40
+ console.error("Server process error: ", data.toString());
41
+ reject();
42
+ });
43
+
44
+ // timeout and fail test if the server hasn't correctly started in 10 seconds
45
+ setTimeout(() => {
46
+ if (serverProcess?.pid && !serverProcess.exitCode) {
47
+ treeKill(serverProcess.pid);
48
+ }
49
+ reject(new Error("Test timed out"));
50
+ }, 10000);
51
+ });
52
+
53
+ // check that the server is running and accessible
54
+ const expectedServerURL = `http://localhost:${testServerPort}`;
55
+ expect(output).toContain(expectedServerURL);
56
+ expect(serverProcess.exitCode).toBeNull();
57
+
58
+ // clean up
59
+ treeKill(serverProcess.pid);
60
+ }, 15000); // 15 seconds timeout to allow for the server to start
61
+ });
@@ -1,30 +1,36 @@
1
- require("dotenv").config();
2
- const path = require("path");
3
- const TerserPlugin = require("terser-webpack-plugin");
4
- const { DefinePlugin, optimize } = require("webpack");
5
- const chalk = require("chalk");
6
- const { transform } = require("@formatjs/ts-transformer");
1
+ import type { Configuration } from "webpack";
2
+ import { DefinePlugin, optimize } from "webpack";
3
+ import * as path from "path";
4
+ import * as TerserPlugin from "terser-webpack-plugin";
5
+ import { transform } from "@formatjs/ts-transformer";
6
+ import * as chalk from "chalk";
7
+ import { config } from "dotenv";
8
+ import { Configuration as DevServerConfiguration } from "webpack-dev-server";
7
9
 
8
- /**
9
- *
10
- * @param {Object} [options]
11
- * @param {string} [options.appEntry=./src/index.tsx]
12
- * @param {string} [options.backendHost]
13
- * @param {Object} [options.devConfig]
14
- * @param {number} [options.devConfig.port]
15
- * @param {boolean} [options.devConfig.enableHmr]
16
- * @param {boolean} [options.devConfig.enableHttps]
17
- * @param {string} [options.devConfig.appOrigin]
18
- * @param {string} [options.devConfig.appId] - Deprecated in favour of appOrigin
19
- * @param {string} [options.devConfig.certFile]
20
- * @param {string} [options.devConfig.keyFile]
21
- * @returns {Object}
22
- */
23
- function buildConfig({
10
+ config();
11
+
12
+ type DevConfig = {
13
+ port: number;
14
+ enableHmr: boolean;
15
+ enableHttps: boolean;
16
+ appOrigin?: string;
17
+ appId?: string; // Deprecated in favour of appOrigin
18
+ certFile?: string;
19
+ keyFile?: string;
20
+ };
21
+
22
+ export function buildConfig({
24
23
  devConfig,
25
24
  appEntry = path.join(process.cwd(), "src", "index.tsx"),
26
25
  backendHost = process.env.CANVA_BACKEND_HOST,
27
- } = {}) {
26
+ // For IN_HARNESS, refer to the following docs for more information: https://www.canva.dev/docs/apps/test-harness/
27
+ inHarness = process.env.IN_HARNESS === "true",
28
+ }: {
29
+ devConfig?: DevConfig;
30
+ appEntry?: string;
31
+ backendHost?: string;
32
+ inHarness?: boolean;
33
+ } = {}): Configuration & DevServerConfiguration {
28
34
  const mode = devConfig ? "development" : "production";
29
35
 
30
36
  if (!backendHost) {
@@ -45,9 +51,14 @@ function buildConfig({
45
51
  return {
46
52
  mode,
47
53
  context: path.resolve(process.cwd(), "./"),
48
- entry: {
49
- app: appEntry,
50
- },
54
+ entry: inHarness
55
+ ? {
56
+ harness: path.join(process.cwd(), "harness", "harness.tsx"),
57
+ init: path.join(process.cwd(), "harness", "init.ts"),
58
+ }
59
+ : {
60
+ app: appEntry,
61
+ },
51
62
  target: "web",
52
63
  resolve: {
53
64
  alias: {
@@ -59,7 +70,7 @@ function buildConfig({
59
70
  extensions: [".ts", ".tsx", ".js", ".css", ".svg", ".woff", ".woff2"],
60
71
  },
61
72
  infrastructureLogging: {
62
- level: "none",
73
+ level: inHarness ? "info" : "none",
63
74
  },
64
75
  module: {
65
76
  rules: [
@@ -173,32 +184,23 @@ function buildConfig({
173
184
  }),
174
185
  // Apps can only submit a single JS file via the developer portal
175
186
  new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
176
- ],
187
+ ].filter(Boolean),
177
188
  ...buildDevConfig(devConfig),
178
189
  };
179
190
  }
180
191
 
181
- /**
182
- *
183
- * @param {Object} [options]
184
- * @param {number} [options.port]
185
- * @param {boolean} [options.enableHmr]
186
- * @param {boolean} [options.enableHttps]
187
- * @param {string} [options.appOrigin]
188
- * @param {string} [options.appId] - Deprecated in favour of appOrigin
189
- * @param {string} [options.certFile]
190
- * @param {string} [options.keyFile]
191
- * @returns {Object|null}
192
- */
193
- function buildDevConfig(options) {
192
+ function buildDevConfig(options?: DevConfig): {
193
+ devtool?: string;
194
+ devServer?: DevServerConfiguration;
195
+ } {
194
196
  if (!options) {
195
- return null;
197
+ return {};
196
198
  }
197
199
 
198
200
  const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
199
201
  options;
200
202
 
201
- let devServer = {
203
+ let devServer: DevServerConfiguration = {
202
204
  server: enableHttps
203
205
  ? {
204
206
  type: "https",
@@ -265,6 +267,4 @@ function buildDevConfig(options) {
265
267
  };
266
268
  }
267
269
 
268
- module.exports = () => buildConfig();
269
-
270
- module.exports.buildConfig = buildConfig;
270
+ export default buildConfig;
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "canva": {
4
+ "command": "npx",
5
+ "args": ["-y", "@canva/cli@latest", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -1,9 +1,8 @@
1
- .idea
2
- .ssl
3
- node_modules
4
1
  .DS_Store
5
- yarn-error.log
6
- dist
7
- secrets.json
8
2
  .env
3
+ .idea
4
+ .ssl
5
+ *.log*
9
6
  **/*/db.json
7
+ dist
8
+ node_modules
@@ -0,0 +1 @@
1
+ 20.10.0
@@ -0,0 +1,21 @@
1
+ {
2
+ "arrowParens": "always",
3
+ "bracketSpacing": true,
4
+ "endOfLine": "lf",
5
+ "htmlWhitespaceSensitivity": "css",
6
+ "insertPragma": false,
7
+ "singleAttributePerLine": false,
8
+ "bracketSameLine": false,
9
+ "jsxSingleQuote": false,
10
+ "printWidth": 80,
11
+ "proseWrap": "preserve",
12
+ "quoteProps": "as-needed",
13
+ "requirePragma": false,
14
+ "semi": true,
15
+ "singleQuote": false,
16
+ "tabWidth": 2,
17
+ "trailingComma": "all",
18
+ "useTabs": false,
19
+ "embeddedLanguageFormatting": "auto",
20
+ "experimentalTernaries": false
21
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ // For developers using vscode we recommend the following extensions. Following
3
+ // and listening to the formatting and linting guidelines can help with
4
+ // implementation quality and in some instances reduce app review timelines.
5
+ "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
6
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "servers": {
3
+ "canva": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": ["-y", "@canva/cli@latest", "mcp"]
7
+ }
8
+ }
9
+ }
@@ -63,18 +63,15 @@ To preview apps in Safari:
63
63
  1. Start the development server with HTTPS enabled:
64
64
 
65
65
  ```bash
66
- # Run the main app
67
66
  npm start --use-https
68
-
69
- # Run an example
70
- npm start <example-name> --use-https
71
67
  ```
72
68
 
73
69
  2. Navigate to <https://localhost:8080>.
74
70
  3. Bypass the invalid security certificate warning:
75
- 4. Click **Show details**.
76
- 5. Click **Visit website**.
77
- 6. In the Developer Portal, set the app's **Development URL** to <https://localhost:8080>.
71
+ 1. Click **Show details**.
72
+ 2. Click **Visit website**.
73
+ 4. In the Developer Portal, set the app's **Development URL** to <https://localhost:8080>.
74
+ 5. Click preview (or refresh your app if it's already open).
78
75
 
79
76
  You need to bypass the invalid security certificate warning every time you start the local server. A similar warning will appear in other browsers (and will need to be bypassed) whenever HTTPS is enabled.
80
77
 
@@ -1,8 +1,35 @@
1
+ import { pathsToModuleNameMapper } from "ts-jest";
2
+ import tsconfig from "./tsconfig.json" assert { type: "json" };
3
+
4
+ const { compilerOptions } = tsconfig;
5
+
1
6
  /** @type {import('ts-jest').JestConfigWithTsJest} */
2
7
 
3
8
  export default {
4
9
  preset: "ts-jest",
5
- testEnvironment: "node",
6
- testRegex: "(/tests/.*|(\\.|/)(tests))\\.ts?$",
10
+ testEnvironment: "jsdom",
11
+ testRegex: "(/tests/.*|(\\.|/)(tests))\\.tsx?$",
7
12
  modulePathIgnorePatterns: ["./internal/", "./node_modules/"],
13
+ modulePaths: [compilerOptions.baseUrl],
14
+ moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
15
+ transform: {
16
+ ".+\\.(css)$": "jest-css-modules-transform",
17
+ "^.+\\.tsx?$": [
18
+ "ts-jest",
19
+ {
20
+ astTransformers: {
21
+ before: [
22
+ {
23
+ path: "@formatjs/ts-transformer/ts-jest-integration",
24
+ options: {
25
+ overrideIdFn: "[sha512:contenthash:base64:6]",
26
+ ast: true,
27
+ },
28
+ },
29
+ ],
30
+ },
31
+ },
32
+ ],
33
+ },
34
+ setupFiles: ["<rootDir>/jest.setup.ts"],
8
35
  };
@@ -0,0 +1,20 @@
1
+ // Import testing sub-packages
2
+ import * as asset from "@canva/asset/test";
3
+ import * as design from "@canva/design/test";
4
+ import * as error from "@canva/error/test";
5
+ import * as platform from "@canva/platform/test";
6
+ import * as user from "@canva/user/test";
7
+
8
+ // Initialize the test environments
9
+ asset.initTestEnvironment();
10
+ design.initTestEnvironment();
11
+ error.initTestEnvironment();
12
+ platform.initTestEnvironment();
13
+ user.initTestEnvironment();
14
+
15
+ // Once they're initialized, mock the SDKs
16
+ jest.mock("@canva/asset");
17
+ jest.mock("@canva/design");
18
+ jest.mock("@canva/platform");
19
+ jest.mock("@canva/user");
20
+ // n.b. @canva/error should not be mocked - use it to simulate API error responses from other mocks by throwing CanvaError
@@ -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";