@canva/cli 0.0.1-beta.9 → 1.0.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 (149) hide show
  1. package/README.md +131 -31
  2. package/cli.js +841 -437
  3. package/lib/cjs/index.cjs +11 -0
  4. package/lib/esm/index.mjs +11 -0
  5. package/package.json +19 -4
  6. package/templates/base/backend/routers/auth.ts +4 -4
  7. package/templates/base/backend/routers/oauth.ts +3 -3
  8. package/templates/base/eslint.config.mjs +2 -25
  9. package/templates/base/package.json +26 -23
  10. package/templates/base/scripts/ssl/ssl.ts +1 -1
  11. package/templates/base/scripts/start/app_runner.ts +42 -5
  12. package/templates/base/scripts/start/context.ts +12 -6
  13. package/templates/base/scripts/start/start.ts +12 -1
  14. package/templates/base/scripts/start/tests/start.tests.ts +61 -0
  15. package/templates/base/utils/backend/base_backend/create.ts +3 -3
  16. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
  17. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
  18. package/templates/base/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
  19. package/templates/base/utils/use_add_element.ts +1 -1
  20. package/templates/base/utils/use_feature_support.ts +1 -1
  21. package/templates/{gen_ai/webpack.config.cjs → base/webpack.config.ts} +50 -68
  22. package/templates/common/.env.template +3 -3
  23. package/templates/common/README.md +16 -17
  24. package/templates/common/jest.config.mjs +1 -1
  25. package/templates/common/jest.setup.ts +1 -0
  26. package/templates/common/utils/backend/base_backend/create.ts +3 -3
  27. package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
  28. package/templates/common/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
  29. package/templates/common/utils/tests/table_wrapper.tests.ts +1 -1
  30. package/templates/common/utils/use_add_element.ts +1 -1
  31. package/templates/common/utils/use_feature_support.ts +1 -1
  32. package/templates/dam/backend/routers/dam.ts +24 -20
  33. package/templates/dam/backend/server.ts +1 -1
  34. package/templates/dam/canva-app.json +16 -0
  35. package/templates/dam/eslint.config.mjs +2 -28
  36. package/templates/dam/package.json +38 -41
  37. package/templates/dam/scripts/ssl/ssl.ts +1 -1
  38. package/templates/dam/scripts/start/app_runner.ts +42 -5
  39. package/templates/dam/scripts/start/context.ts +12 -6
  40. package/templates/dam/scripts/start/start.ts +12 -1
  41. package/templates/dam/scripts/start/tests/start.tests.ts +61 -0
  42. package/templates/dam/src/app.tsx +24 -3
  43. package/templates/dam/src/config.ts +212 -87
  44. package/templates/dam/src/index.tsx +1 -1
  45. package/templates/dam/utils/backend/base_backend/create.ts +3 -3
  46. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
  47. package/templates/dam/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
  48. package/templates/{base/webpack.config.cjs → dam/webpack.config.ts} +50 -68
  49. package/templates/data_connector/README.md +84 -0
  50. package/templates/data_connector/canva-app.json +21 -0
  51. package/templates/data_connector/declarations/declarations.d.ts +29 -0
  52. package/templates/data_connector/eslint.config.mjs +14 -0
  53. package/templates/data_connector/package.json +91 -0
  54. package/templates/data_connector/scripts/ssl/ssl.ts +131 -0
  55. package/templates/data_connector/scripts/start/app_runner.ts +201 -0
  56. package/templates/data_connector/scripts/start/context.ts +171 -0
  57. package/templates/data_connector/scripts/start/start.ts +46 -0
  58. package/templates/data_connector/scripts/start/tests/start.tests.ts +61 -0
  59. package/templates/data_connector/src/api/connect_client.ts +6 -0
  60. package/templates/data_connector/src/api/data_source.ts +96 -0
  61. package/templates/data_connector/src/api/data_sources/designs.tsx +296 -0
  62. package/templates/data_connector/src/api/data_sources/index.ts +4 -0
  63. package/templates/data_connector/src/api/data_sources/templates.tsx +329 -0
  64. package/templates/data_connector/src/api/fetch_data_table.ts +55 -0
  65. package/templates/data_connector/src/api/index.ts +4 -0
  66. package/templates/data_connector/src/api/oauth.ts +8 -0
  67. package/templates/data_connector/src/api/tests/data_source.test.tsx +99 -0
  68. package/templates/data_connector/src/app.tsx +20 -0
  69. package/templates/data_connector/src/components/app_error.tsx +15 -0
  70. package/templates/data_connector/src/components/footer.tsx +26 -0
  71. package/templates/data_connector/src/components/header.tsx +40 -0
  72. package/templates/data_connector/src/components/index.ts +3 -0
  73. package/templates/data_connector/src/components/inputs/messages.tsx +99 -0
  74. package/templates/data_connector/src/components/inputs/search_filter.tsx +108 -0
  75. package/templates/data_connector/src/components/inputs/select_field.tsx +26 -0
  76. package/templates/data_connector/src/context/app_context.tsx +124 -0
  77. package/templates/data_connector/src/context/index.ts +2 -0
  78. package/templates/data_connector/src/context/use_app_context.ts +17 -0
  79. package/templates/data_connector/src/entrypoint.tsx +70 -0
  80. package/templates/data_connector/src/home.tsx +21 -0
  81. package/templates/data_connector/src/index.tsx +68 -0
  82. package/templates/data_connector/src/pages/data_source_config.tsx +9 -0
  83. package/templates/data_connector/src/pages/error.tsx +37 -0
  84. package/templates/data_connector/src/pages/index.ts +4 -0
  85. package/templates/data_connector/src/pages/login.tsx +145 -0
  86. package/templates/data_connector/src/pages/select_source.tsx +24 -0
  87. package/templates/data_connector/src/routes/index.ts +2 -0
  88. package/templates/data_connector/src/routes/protected_route.tsx +25 -0
  89. package/templates/data_connector/src/routes/routes.tsx +46 -0
  90. package/templates/data_connector/src/utils/data_params.ts +17 -0
  91. package/templates/data_connector/src/utils/data_table.ts +115 -0
  92. package/templates/data_connector/src/utils/fetch_result.ts +36 -0
  93. package/templates/data_connector/src/utils/index.ts +2 -0
  94. package/templates/data_connector/src/utils/tests/data_table.test.ts +133 -0
  95. package/templates/data_connector/styles/components.css +38 -0
  96. package/templates/data_connector/tsconfig.json +54 -0
  97. package/templates/{dam/webpack.config.cjs → data_connector/webpack.config.ts} +50 -68
  98. package/templates/gen_ai/backend/routers/image.ts +8 -8
  99. package/templates/gen_ai/backend/server.ts +1 -1
  100. package/templates/gen_ai/canva-app.json +20 -0
  101. package/templates/gen_ai/eslint.config.mjs +2 -25
  102. package/templates/gen_ai/package.json +42 -43
  103. package/templates/gen_ai/scripts/ssl/ssl.ts +1 -1
  104. package/templates/gen_ai/scripts/start/app_runner.ts +42 -5
  105. package/templates/gen_ai/scripts/start/context.ts +12 -6
  106. package/templates/gen_ai/scripts/start/start.ts +12 -1
  107. package/templates/gen_ai/scripts/start/tests/start.tests.ts +61 -0
  108. package/templates/gen_ai/src/app.tsx +3 -3
  109. package/templates/gen_ai/src/components/footer.tsx +4 -4
  110. package/templates/gen_ai/src/components/image_grid.tsx +1 -1
  111. package/templates/gen_ai/src/components/loading_results.tsx +8 -8
  112. package/templates/gen_ai/src/components/prompt_input.tsx +3 -3
  113. package/templates/gen_ai/src/components/remaining_credits.tsx +1 -1
  114. package/templates/gen_ai/src/components/report_box.tsx +1 -1
  115. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +2 -2
  116. package/templates/gen_ai/src/home.tsx +2 -2
  117. package/templates/gen_ai/src/pages/error.tsx +2 -2
  118. package/templates/gen_ai/src/pages/results.tsx +1 -1
  119. package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
  120. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
  121. package/templates/{hello_world/webpack.config.cjs → gen_ai/webpack.config.ts} +50 -68
  122. package/templates/hello_world/canva-app.json +16 -0
  123. package/templates/hello_world/eslint.config.mjs +2 -25
  124. package/templates/hello_world/package.json +35 -38
  125. package/templates/hello_world/scripts/copy_env.ts +10 -0
  126. package/templates/hello_world/scripts/ssl/ssl.ts +1 -1
  127. package/templates/hello_world/scripts/start/app_runner.ts +42 -5
  128. package/templates/hello_world/scripts/start/context.ts +12 -6
  129. package/templates/hello_world/scripts/start/start.ts +12 -1
  130. package/templates/hello_world/scripts/start/tests/start.tests.ts +61 -0
  131. package/templates/hello_world/src/app.tsx +1 -1
  132. package/templates/hello_world/src/index.tsx +1 -1
  133. package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +5 -5
  134. package/templates/hello_world/src/tests/app.tests.tsx +3 -3
  135. package/templates/hello_world/utils/use_add_element.ts +1 -1
  136. package/templates/hello_world/utils/use_feature_support.ts +1 -1
  137. package/templates/hello_world/webpack.config.ts +254 -0
  138. package/templates/optional/.cursor/mcp.json +8 -0
  139. package/templates/optional/.vscode/mcp.json +9 -0
  140. package/templates/optional/AGENTS.md +76 -0
  141. package/templates/optional/CLAUDE.md +76 -0
  142. package/templates/common/conf/eslint-general.mjs +0 -303
  143. package/templates/common/conf/eslint-i18n.mjs +0 -41
  144. package/templates/common/conf/eslint-local-i18n-rules/index.mjs +0 -181
  145. /package/templates/base/scripts/{copy-env.ts → copy_env.ts} +0 -0
  146. /package/templates/dam/scripts/{copy-env.ts → copy_env.ts} +0 -0
  147. /package/templates/{gen_ai/scripts/copy-env.ts → data_connector/scripts/copy_env.ts} +0 -0
  148. /package/templates/{hello_world/scripts/copy-env.ts → gen_ai/scripts/copy_env.ts} +0 -0
  149. /package/templates/{common → optional}/.vscode/extensions.json +0 -0
@@ -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,10 +1,10 @@
1
- import { createHashRouter, RouterProvider } from "react-router-dom";
2
- import { ContextProvider } from "./context";
3
- import { routes } from "./routes";
4
1
  import { AppI18nProvider } from "@canva/app-i18n-kit";
5
2
  import { AppUiProvider } from "@canva/app-ui-kit";
6
3
  import { ErrorBoundary } from "react-error-boundary";
4
+ import { createHashRouter, RouterProvider } from "react-router-dom";
5
+ import { ContextProvider } from "./context";
7
6
  import { ErrorPage } from "./pages";
7
+ import { routes } from "./routes";
8
8
 
9
9
  export const App = () => (
10
10
  <AppI18nProvider>
@@ -1,12 +1,12 @@
1
- import { useNavigate, useLocation } from "react-router-dom";
2
- import { Rows, Button } from "@canva/app-ui-kit";
3
- import { queueImageGeneration, purchaseCredits } from "src/api";
1
+ import { Button, Rows } from "@canva/app-ui-kit";
2
+ import { useIntl } from "react-intl";
3
+ import { useLocation, useNavigate } from "react-router-dom";
4
+ import { purchaseCredits, queueImageGeneration } from "src/api";
4
5
  import { RemainingCredits } from "src/components";
5
6
  import { NUMBER_OF_IMAGES_TO_GENERATE } from "src/config";
6
7
  import { useAppContext } from "src/context";
7
8
  import { Paths } from "src/routes";
8
9
  import { getObsceneWords } from "src/utils";
9
- import { useIntl } from "react-intl";
10
10
  import { FooterMessages as Messages } from "./footer.messages";
11
11
 
12
12
  export const Footer = () => {
@@ -2,10 +2,10 @@ import { Grid, ImageCard, Rows, Text } from "@canva/app-ui-kit";
2
2
  import type { QueuedImage } from "@canva/asset";
3
3
  import { upload } from "@canva/asset";
4
4
  import { addElementAtPoint, ui } from "@canva/design";
5
+ import { FormattedMessage, useIntl } from "react-intl";
5
6
  import type { ImageType } from "src/api";
6
7
  import { useAppContext } from "src/context";
7
8
  import * as styles from "styles/utils.css";
8
- import { FormattedMessage, useIntl } from "react-intl";
9
9
 
10
10
  const THUMBNAIL_HEIGHT = 150;
11
11
 
@@ -1,18 +1,18 @@
1
- import { useEffect, useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
1
  import {
4
- Rows,
2
+ Box,
3
+ Button,
5
4
  Grid,
6
5
  Placeholder,
7
6
  ProgressBar,
7
+ Rows,
8
8
  Text,
9
- Button,
10
- Box,
11
9
  } from "@canva/app-ui-kit";
12
- import { Paths } from "src/routes";
13
- import { useAppContext } from "src/context";
14
- import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
10
+ import { useEffect, useState } from "react";
15
11
  import { FormattedMessage, useIntl } from "react-intl";
12
+ import { useNavigate } from "react-router-dom";
13
+ import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
14
+ import { useAppContext } from "src/context";
15
+ import { Paths } from "src/routes";
16
16
 
17
17
  const INTERVAL_DURATION_IN_MS = 100;
18
18
  const TOTAL_PROGRESS_PERCENTAGE = 100;
@@ -1,5 +1,3 @@
1
- import { useState } from "react";
2
- import { useLocation } from "react-router-dom";
3
1
  import {
4
2
  Box,
5
3
  Button,
@@ -7,10 +5,12 @@ import {
7
5
  LightBulbIcon,
8
6
  MultilineInput,
9
7
  } from "@canva/app-ui-kit";
8
+ import { useState } from "react";
9
+ import { useIntl } from "react-intl";
10
+ import { useLocation } from "react-router-dom";
10
11
  import { useAppContext } from "src/context";
11
12
  import { Paths } from "src/routes";
12
13
  import { PromptInputMessages as Messages } from "./prompt_input.messages";
13
- import { useIntl } from "react-intl";
14
14
 
15
15
  // @TODO: Adjust according to your specific requirements.
16
16
  const MAX_INPUT_LENGTH = 280;
@@ -1,7 +1,7 @@
1
1
  import { Link, Rows, Text, TextPlaceholder } from "@canva/app-ui-kit";
2
2
  import { requestOpenExternalUrl } from "@canva/platform";
3
- import { useAppContext } from "src/context";
4
3
  import { FormattedMessage, useIntl } from "react-intl";
4
+ import { useAppContext } from "src/context";
5
5
 
6
6
  // @TODO: Replace this URL with your custom upselling link.
7
7
  const PURCHASE_URL = "https://example.com";
@@ -1,4 +1,4 @@
1
- import { Box, Columns, Column, FlagIcon, Text, Link } from "@canva/app-ui-kit";
1
+ import { Box, Column, Columns, FlagIcon, Link, Text } from "@canva/app-ui-kit";
2
2
  import { requestOpenExternalUrl } from "@canva/platform";
3
3
  import { FormattedMessage, useIntl } from "react-intl";
4
4
  import { APP_NAME } from "src/config";
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable formatjs/no-literal-string-in-jsx */
2
- import { TestAppUiProvider } from "@canva/app-ui-kit";
3
2
  import { TestAppI18nProvider } from "@canva/app-i18n-kit";
3
+ import { TestAppUiProvider } from "@canva/app-ui-kit";
4
+ import { requestOpenExternalUrl } from "@canva/platform";
4
5
  import type { RenderResult } from "@testing-library/react";
5
6
  import { fireEvent, render } from "@testing-library/react";
6
7
  import React from "react";
7
8
  import { RemainingCredits } from "../remaining_credits";
8
- import { requestOpenExternalUrl } from "@canva/platform";
9
9
 
10
10
  function renderInTestProvider(node: React.ReactNode): RenderResult {
11
11
  return render(
@@ -1,7 +1,7 @@
1
- import { Outlet } from "react-router-dom";
2
1
  import { Rows } from "@canva/app-ui-kit";
3
- import { Footer } from "./components";
2
+ import { Outlet } from "react-router-dom";
4
3
  import * as styles from "styles/components.css";
4
+ import { Footer } from "./components";
5
5
 
6
6
  export const Home = () => (
7
7
  <div className={styles.scrollContainer}>
@@ -1,9 +1,9 @@
1
- import { useNavigate } from "react-router-dom";
2
1
  import { Button, Rows, Text } from "@canva/app-ui-kit";
2
+ import { FormattedMessage, useIntl } from "react-intl";
3
+ import { useNavigate } from "react-router-dom";
3
4
  import { useAppContext } from "src/context";
4
5
  import { Paths } from "src/routes";
5
6
  import * as styles from "styles/components.css";
6
- import { FormattedMessage, useIntl } from "react-intl";
7
7
 
8
8
  /**
9
9
  * Bare bones Error Page, please add relevant information and behavior that your app requires.
@@ -6,8 +6,8 @@ import {
6
6
  PromptInput,
7
7
  ReportBox,
8
8
  } from "src/components";
9
- import { useAppContext } from "src/context";
10
9
  import { EXPECTED_LOADING_TIME_IN_SECONDS } from "src/config";
10
+ import { useAppContext } from "src/context";
11
11
 
12
12
  export const ResultsPage = () => {
13
13
  const { isLoadingImages } = useAppContext();
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable no-console */
2
+ import debug from "debug";
2
3
  import * as express from "express";
4
+ import type { NextFunction, Request, Response } from "express";
5
+ import * as fs from "fs";
3
6
  import * as http from "http";
4
7
  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
8
 
9
9
  const serverDebug = debug("server");
10
10
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-console */
2
2
  import * as debug from "debug";
3
- import type { Request, Response, NextFunction } from "express";
3
+ import type { NextFunction, Request, Response } from "express";
4
4
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
5
  import Express from "express-serve-static-core";
6
6
 
@@ -45,7 +45,7 @@ export function createBearerMiddleware(
45
45
  throw new AuthorizationError("Token is invalid");
46
46
  }
47
47
 
48
- req.user_id = user;
48
+ req["user_id"] = user;
49
49
 
50
50
  next();
51
51
  } catch (e) {
@@ -1,31 +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");
7
- const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
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";
8
9
 
9
- /**
10
- *
11
- * @param {Object} [options]
12
- * @param {string} [options.appEntry=./src/index.tsx]
13
- * @param {string} [options.backendHost]
14
- * @param {Object} [options.devConfig]
15
- * @param {number} [options.devConfig.port]
16
- * @param {boolean} [options.devConfig.enableHmr]
17
- * @param {boolean} [options.devConfig.enableHttps]
18
- * @param {string} [options.devConfig.appOrigin]
19
- * @param {string} [options.devConfig.appId] - Deprecated in favour of appOrigin
20
- * @param {string} [options.devConfig.certFile]
21
- * @param {string} [options.devConfig.keyFile]
22
- * @returns {Object}
23
- */
24
- 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({
25
23
  devConfig,
26
24
  appEntry = path.join(process.cwd(), "src", "index.tsx"),
27
25
  backendHost = process.env.CANVA_BACKEND_HOST,
28
- } = {}) {
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?.toLowerCase() === "true",
28
+ }: {
29
+ devConfig?: DevConfig;
30
+ appEntry?: string;
31
+ backendHost?: string;
32
+ inHarness?: boolean;
33
+ } = {}): Configuration & DevServerConfiguration {
29
34
  const mode = devConfig ? "development" : "production";
30
35
 
31
36
  if (!backendHost) {
@@ -46,9 +51,14 @@ function buildConfig({
46
51
  return {
47
52
  mode,
48
53
  context: path.resolve(process.cwd(), "./"),
49
- entry: {
50
- app: appEntry,
51
- },
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
+ },
52
62
  target: "web",
53
63
  resolve: {
54
64
  alias: {
@@ -60,7 +70,7 @@ function buildConfig({
60
70
  extensions: [".ts", ".tsx", ".js", ".css", ".svg", ".woff", ".woff2"],
61
71
  },
62
72
  infrastructureLogging: {
63
- level: "none",
73
+ level: inHarness ? "info" : "none",
64
74
  },
65
75
  module: {
66
76
  rules: [
@@ -172,35 +182,26 @@ function buildConfig({
172
182
  new DefinePlugin({
173
183
  BACKEND_HOST: JSON.stringify(backendHost),
174
184
  }),
175
- // Apps can only submit a single JS file via the developer portal
185
+ // Apps can only submit a single JS file via the Developer Portal
176
186
  new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
177
- mode === "development" && new ReactRefreshWebpackPlugin(),
178
187
  ].filter(Boolean),
179
188
  ...buildDevConfig(devConfig),
180
189
  };
181
190
  }
182
191
 
183
- /**
184
- *
185
- * @param {Object} [options]
186
- * @param {number} [options.port]
187
- * @param {boolean} [options.enableHmr]
188
- * @param {boolean} [options.enableHttps]
189
- * @param {string} [options.appOrigin]
190
- * @param {string} [options.appId] - Deprecated in favour of appOrigin
191
- * @param {string} [options.certFile]
192
- * @param {string} [options.keyFile]
193
- * @returns {Object|null}
194
- */
195
- function buildDevConfig(options) {
192
+ function buildDevConfig(options?: DevConfig): {
193
+ devtool?: string;
194
+ devServer?: DevServerConfiguration;
195
+ } {
196
196
  if (!options) {
197
- return null;
197
+ return {};
198
198
  }
199
199
 
200
200
  const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
201
201
  options;
202
+ const host = "localhost";
202
203
 
203
- let devServer = {
204
+ let devServer: DevServerConfiguration = {
204
205
  server: enableHttps
205
206
  ? {
206
207
  type: "https",
@@ -210,7 +211,8 @@ function buildDevConfig(options) {
210
211
  },
211
212
  }
212
213
  : "http",
213
- host: "localhost",
214
+ host,
215
+ allowedHosts: [host],
214
216
  historyApiFallback: {
215
217
  rewrites: [{ from: /^\/$/, to: "/app.js" }],
216
218
  },
@@ -227,31 +229,13 @@ function buildDevConfig(options) {
227
229
  if (enableHmr && appOrigin) {
228
230
  devServer = {
229
231
  ...devServer,
230
- allowedHosts: new URL(appOrigin).hostname,
232
+ allowedHosts: [host, new URL(appOrigin).hostname],
231
233
  headers: {
232
234
  "Access-Control-Allow-Origin": appOrigin,
233
235
  "Access-Control-Allow-Credentials": "true",
234
236
  "Access-Control-Allow-Private-Network": "true",
235
237
  },
236
238
  };
237
- } else if (enableHmr && appId) {
238
- // Deprecated - App ID should not be used to configure HMR in the future and can be safely removed
239
- // after a few months.
240
-
241
- console.warn(
242
- "Enabling Hot Module Replacement (HMR) with an App ID is deprecated, please see the README.md on how to update.",
243
- );
244
-
245
- const appDomain = `app-${appId.toLowerCase().trim()}.canva-apps.com`;
246
- devServer = {
247
- ...devServer,
248
- allowedHosts: appDomain,
249
- headers: {
250
- "Access-Control-Allow-Origin": `https://${appDomain}`,
251
- "Access-Control-Allow-Credentials": "true",
252
- "Access-Control-Allow-Private-Network": "true",
253
- },
254
- };
255
239
  } else {
256
240
  if (enableHmr && !appOrigin) {
257
241
  console.warn(
@@ -267,6 +251,4 @@ function buildDevConfig(options) {
267
251
  };
268
252
  }
269
253
 
270
- module.exports = () => buildConfig();
271
-
272
- module.exports.buildConfig = buildConfig;
254
+ export default buildConfig;
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://www.canva.dev/schemas/app/v1/manifest-schema.json",
3
+ "manifest_schema_version": 1,
4
+ "runtime": {
5
+ "permissions": [
6
+ {
7
+ "name": "canva:design:content:read",
8
+ "type": "mandatory"
9
+ },
10
+ {
11
+ "name": "canva:design:content:write",
12
+ "type": "mandatory"
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -1,17 +1,4 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import js from "@eslint/js";
4
- import { FlatCompat } from "@eslint/eslintrc";
5
- import general from "./conf/eslint-general.mjs";
6
- import i18n from "./conf/eslint-i18n.mjs";
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- const compat = new FlatCompat({
11
- baseDirectory: __dirname,
12
- recommendedConfig: js.configs.recommended,
13
- allConfig: js.configs.all,
14
- });
1
+ import canvaPlugin from "@canva/app-eslint-plugin";
15
2
 
16
3
  export default [
17
4
  {
@@ -23,15 +10,5 @@ export default [
23
10
  "**/*.config.*",
24
11
  ],
25
12
  },
26
- ...compat.extends(
27
- "eslint:recommended",
28
- "plugin:@typescript-eslint/recommended",
29
- "plugin:@typescript-eslint/eslint-recommended",
30
- "plugin:@typescript-eslint/strict",
31
- "plugin:@typescript-eslint/stylistic",
32
- "plugin:react/recommended",
33
- "plugin:jest/recommended",
34
- ),
35
- ...general,
36
- ...i18n,
13
+ ...canvaPlugin.configs.apps,
37
14
  ];
@@ -4,7 +4,7 @@
4
4
  "description": "An empty Canva App",
5
5
  "scripts": {
6
6
  "extract": "formatjs extract \"src/**/*.{ts,tsx}\" --out-file dist/messages_en.json",
7
- "build": "webpack --config webpack.config.cjs --mode production && npm run extract",
7
+ "build": "webpack --config webpack.config.ts --mode production && npm run extract",
8
8
  "format": "prettier '**/*.{css,ts,tsx}' --no-config --write",
9
9
  "format:check": "prettier '**/*.{css,ts,tsx}' --no-config --check --ignore-path",
10
10
  "format:file": "prettier $1 --no-config --write",
@@ -12,80 +12,77 @@
12
12
  "lint:fix": "eslint . --fix",
13
13
  "lint:types": "tsc",
14
14
  "start": "ts-node ./scripts/start/start.ts",
15
+ "start:preview": "npm run start -- --preview",
15
16
  "test": "jest --no-cache",
16
17
  "test:watch": "jest --watchAll",
17
18
  "test:update": "npm run test -- -u",
18
- "postinstall": "ts-node ./scripts/copy-env.ts"
19
+ "postinstall": "ts-node ./scripts/copy_env.ts"
19
20
  },
20
21
  "dependencies": {
21
- "@canva/app-i18n-kit": "^1.0.2",
22
- "@canva/app-ui-kit": "^4.4.0",
23
- "@canva/asset": "^2.1.0",
24
- "@canva/design": "^2.3.0",
22
+ "@canva/app-i18n-kit": "^1.0.3",
23
+ "@canva/app-ui-kit": "^4.10.0",
24
+ "@canva/asset": "^2.2.1",
25
+ "@canva/design": "^2.7.2",
25
26
  "@canva/error": "^2.1.0",
26
- "@canva/platform": "^2.1.0",
27
- "@canva/user": "^2.1.0",
27
+ "@canva/platform": "^2.2.0",
28
+ "@canva/user": "^2.1.1",
28
29
  "react": "18.3.1",
29
30
  "react-dom": "18.3.1",
30
31
  "react-intl": "6.8.7"
31
32
  },
32
33
  "devDependencies": {
33
- "@eslint/eslintrc": "3.2.0",
34
- "@eslint/js": "9.16.0",
35
- "@formatjs/cli": "6.3.14",
36
- "@formatjs/ts-transformer": "3.13.26",
37
- "@ngrok/ngrok": "1.4.1",
38
- "@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
34
+ "@canva/app-eslint-plugin": "^1.0.0-beta.3",
35
+ "@canva/cli": ">= 0.0.1-beta.13",
36
+ "@formatjs/cli": "6.7.2",
37
+ "@formatjs/ts-transformer": "3.14.0",
38
+ "@ngrok/ngrok": "1.5.1",
39
+ "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
39
40
  "@svgr/webpack": "8.1.0",
40
- "@testing-library/react": "16.1.0",
41
+ "@testing-library/react": "16.3.0",
41
42
  "@types/express": "4.17.21",
42
- "@types/express-serve-static-core": "4.19.6",
43
+ "@types/express-serve-static-core": "5.0.6",
43
44
  "@types/jest": "29.5.14",
44
- "@types/jsonwebtoken": "9.0.7",
45
- "@types/node": "20.10.0",
45
+ "@types/jsonwebtoken": "9.0.10",
46
+ "@types/node": "20.19.2",
46
47
  "@types/node-fetch": "2.6.12",
47
48
  "@types/node-forge": "1.3.11",
48
49
  "@types/nodemon": "1.19.6",
49
50
  "@types/react": "18.3.12",
50
51
  "@types/react-dom": "18.3.1",
51
- "@types/webpack-env": "1.18.5",
52
- "@typescript-eslint/eslint-plugin": "8.18.0",
53
- "@typescript-eslint/parser": "8.18.0",
52
+ "@types/webpack-env": "1.18.8",
54
53
  "chalk": "4.1.2",
55
54
  "cli-table3": "0.6.5",
56
55
  "css-loader": "7.1.2",
57
56
  "css-modules-typescript-loader": "4.0.1",
58
- "cssnano": "7.0.6",
59
- "debug": "4.4.0",
60
- "dotenv": "16.4.7",
61
- "eslint": "9.16.0",
62
- "eslint-plugin-formatjs": "5.2.8",
63
- "eslint-plugin-jest": "28.9.0",
64
- "eslint-plugin-react": "7.37.2",
57
+ "cssnano": "7.0.7",
58
+ "debug": "4.4.1",
59
+ "dotenv": "16.6.0",
65
60
  "express": "4.21.2",
66
61
  "express-basic-auth": "1.2.1",
67
62
  "jest": "29.7.0",
68
63
  "jest-css-modules-transform": "4.4.2",
69
64
  "jest-environment-jsdom": "29.7.0",
70
65
  "jsonwebtoken": "9.0.2",
71
- "jwks-rsa": "3.1.0",
66
+ "jwks-rsa": "3.2.0",
72
67
  "mini-css-extract-plugin": "2.9.2",
73
68
  "node-fetch": "3.3.2",
74
69
  "node-forge": "1.3.1",
75
70
  "nodemon": "3.0.1",
71
+ "open": "8.4.2",
76
72
  "postcss-loader": "8.1.1",
77
- "prettier": "3.4.2",
78
- "react-refresh": "0.16.0",
73
+ "prettier": "3.6.2",
74
+ "react-refresh": "0.17.0",
79
75
  "style-loader": "4.0.0",
80
- "terser-webpack-plugin": "5.3.10",
81
- "ts-jest": "29.2.5",
82
- "ts-loader": "9.5.1",
76
+ "terser-webpack-plugin": "5.3.14",
77
+ "tree-kill": "1.2.2",
78
+ "ts-jest": "29.4.0",
79
+ "ts-loader": "9.5.2",
83
80
  "ts-node": "10.9.2",
84
- "typescript": "5.5.4",
81
+ "typescript": "5.8.2",
85
82
  "url-loader": "4.1.1",
86
- "webpack": "5.97.1",
87
- "webpack-cli": "5.1.4",
88
- "webpack-dev-server": "5.1.0",
83
+ "webpack": "5.99.9",
84
+ "webpack-cli": "6.0.1",
85
+ "webpack-dev-server": "5.2.2",
89
86
  "yargs": "17.7.2"
90
87
  }
91
88
  }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ const envPath = path.resolve(__dirname, "..", ".env");
6
+ const templatePath = path.resolve(__dirname, "..", ".env.template");
7
+
8
+ if (!fs.existsSync(envPath)) {
9
+ fs.copyFileSync(templatePath, envPath);
10
+ }
@@ -1,7 +1,7 @@
1
1
  import * as crypto from "crypto";
2
+ import * as fs from "fs/promises";
2
3
  import { pki } from "node-forge";
3
4
  import * as path from "path";
4
- import * as fs from "fs/promises";
5
5
 
6
6
  const SSL_CERT_DIR = path.resolve(process.cwd(), "..", "..", ".ssl");
7
7
  const CERT_FILE = path.resolve(SSL_CERT_DIR, "certificate.pem");