@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.
- package/README.md +131 -31
- package/cli.js +841 -437
- package/lib/cjs/index.cjs +11 -0
- package/lib/esm/index.mjs +11 -0
- package/package.json +19 -4
- package/templates/base/backend/routers/auth.ts +4 -4
- package/templates/base/backend/routers/oauth.ts +3 -3
- package/templates/base/eslint.config.mjs +2 -25
- package/templates/base/package.json +26 -23
- package/templates/base/scripts/ssl/ssl.ts +1 -1
- package/templates/base/scripts/start/app_runner.ts +42 -5
- package/templates/base/scripts/start/context.ts +12 -6
- package/templates/base/scripts/start/start.ts +12 -1
- package/templates/base/scripts/start/tests/start.tests.ts +61 -0
- package/templates/base/utils/backend/base_backend/create.ts +3 -3
- package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
- package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/base/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/base/utils/use_add_element.ts +1 -1
- package/templates/base/utils/use_feature_support.ts +1 -1
- package/templates/{gen_ai/webpack.config.cjs → base/webpack.config.ts} +50 -68
- package/templates/common/.env.template +3 -3
- package/templates/common/README.md +16 -17
- package/templates/common/jest.config.mjs +1 -1
- package/templates/common/jest.setup.ts +1 -0
- package/templates/common/utils/backend/base_backend/create.ts +3 -3
- package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/common/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/common/utils/tests/table_wrapper.tests.ts +1 -1
- package/templates/common/utils/use_add_element.ts +1 -1
- package/templates/common/utils/use_feature_support.ts +1 -1
- package/templates/dam/backend/routers/dam.ts +24 -20
- package/templates/dam/backend/server.ts +1 -1
- package/templates/dam/canva-app.json +16 -0
- package/templates/dam/eslint.config.mjs +2 -28
- package/templates/dam/package.json +38 -41
- package/templates/dam/scripts/ssl/ssl.ts +1 -1
- package/templates/dam/scripts/start/app_runner.ts +42 -5
- package/templates/dam/scripts/start/context.ts +12 -6
- package/templates/dam/scripts/start/start.ts +12 -1
- package/templates/dam/scripts/start/tests/start.tests.ts +61 -0
- package/templates/dam/src/app.tsx +24 -3
- package/templates/dam/src/config.ts +212 -87
- package/templates/dam/src/index.tsx +1 -1
- package/templates/dam/utils/backend/base_backend/create.ts +3 -3
- package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/dam/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/{base/webpack.config.cjs → dam/webpack.config.ts} +50 -68
- package/templates/data_connector/README.md +84 -0
- package/templates/data_connector/canva-app.json +21 -0
- package/templates/data_connector/declarations/declarations.d.ts +29 -0
- package/templates/data_connector/eslint.config.mjs +14 -0
- package/templates/data_connector/package.json +91 -0
- package/templates/data_connector/scripts/ssl/ssl.ts +131 -0
- package/templates/data_connector/scripts/start/app_runner.ts +201 -0
- package/templates/data_connector/scripts/start/context.ts +171 -0
- package/templates/data_connector/scripts/start/start.ts +46 -0
- package/templates/data_connector/scripts/start/tests/start.tests.ts +61 -0
- package/templates/data_connector/src/api/connect_client.ts +6 -0
- package/templates/data_connector/src/api/data_source.ts +96 -0
- package/templates/data_connector/src/api/data_sources/designs.tsx +296 -0
- package/templates/data_connector/src/api/data_sources/index.ts +4 -0
- package/templates/data_connector/src/api/data_sources/templates.tsx +329 -0
- package/templates/data_connector/src/api/fetch_data_table.ts +55 -0
- package/templates/data_connector/src/api/index.ts +4 -0
- package/templates/data_connector/src/api/oauth.ts +8 -0
- package/templates/data_connector/src/api/tests/data_source.test.tsx +99 -0
- package/templates/data_connector/src/app.tsx +20 -0
- package/templates/data_connector/src/components/app_error.tsx +15 -0
- package/templates/data_connector/src/components/footer.tsx +26 -0
- package/templates/data_connector/src/components/header.tsx +40 -0
- package/templates/data_connector/src/components/index.ts +3 -0
- package/templates/data_connector/src/components/inputs/messages.tsx +99 -0
- package/templates/data_connector/src/components/inputs/search_filter.tsx +108 -0
- package/templates/data_connector/src/components/inputs/select_field.tsx +26 -0
- package/templates/data_connector/src/context/app_context.tsx +124 -0
- package/templates/data_connector/src/context/index.ts +2 -0
- package/templates/data_connector/src/context/use_app_context.ts +17 -0
- package/templates/data_connector/src/entrypoint.tsx +70 -0
- package/templates/data_connector/src/home.tsx +21 -0
- package/templates/data_connector/src/index.tsx +68 -0
- package/templates/data_connector/src/pages/data_source_config.tsx +9 -0
- package/templates/data_connector/src/pages/error.tsx +37 -0
- package/templates/data_connector/src/pages/index.ts +4 -0
- package/templates/data_connector/src/pages/login.tsx +145 -0
- package/templates/data_connector/src/pages/select_source.tsx +24 -0
- package/templates/data_connector/src/routes/index.ts +2 -0
- package/templates/data_connector/src/routes/protected_route.tsx +25 -0
- package/templates/data_connector/src/routes/routes.tsx +46 -0
- package/templates/data_connector/src/utils/data_params.ts +17 -0
- package/templates/data_connector/src/utils/data_table.ts +115 -0
- package/templates/data_connector/src/utils/fetch_result.ts +36 -0
- package/templates/data_connector/src/utils/index.ts +2 -0
- package/templates/data_connector/src/utils/tests/data_table.test.ts +133 -0
- package/templates/data_connector/styles/components.css +38 -0
- package/templates/data_connector/tsconfig.json +54 -0
- package/templates/{dam/webpack.config.cjs → data_connector/webpack.config.ts} +50 -68
- package/templates/gen_ai/backend/routers/image.ts +8 -8
- package/templates/gen_ai/backend/server.ts +1 -1
- package/templates/gen_ai/canva-app.json +20 -0
- package/templates/gen_ai/eslint.config.mjs +2 -25
- package/templates/gen_ai/package.json +42 -43
- package/templates/gen_ai/scripts/ssl/ssl.ts +1 -1
- package/templates/gen_ai/scripts/start/app_runner.ts +42 -5
- package/templates/gen_ai/scripts/start/context.ts +12 -6
- package/templates/gen_ai/scripts/start/start.ts +12 -1
- package/templates/gen_ai/scripts/start/tests/start.tests.ts +61 -0
- package/templates/gen_ai/src/app.tsx +3 -3
- package/templates/gen_ai/src/components/footer.tsx +4 -4
- package/templates/gen_ai/src/components/image_grid.tsx +1 -1
- package/templates/gen_ai/src/components/loading_results.tsx +8 -8
- package/templates/gen_ai/src/components/prompt_input.tsx +3 -3
- package/templates/gen_ai/src/components/remaining_credits.tsx +1 -1
- package/templates/gen_ai/src/components/report_box.tsx +1 -1
- package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +2 -2
- package/templates/gen_ai/src/home.tsx +2 -2
- package/templates/gen_ai/src/pages/error.tsx +2 -2
- package/templates/gen_ai/src/pages/results.tsx +1 -1
- package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
- package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
- package/templates/{hello_world/webpack.config.cjs → gen_ai/webpack.config.ts} +50 -68
- package/templates/hello_world/canva-app.json +16 -0
- package/templates/hello_world/eslint.config.mjs +2 -25
- package/templates/hello_world/package.json +35 -38
- package/templates/hello_world/scripts/copy_env.ts +10 -0
- package/templates/hello_world/scripts/ssl/ssl.ts +1 -1
- package/templates/hello_world/scripts/start/app_runner.ts +42 -5
- package/templates/hello_world/scripts/start/context.ts +12 -6
- package/templates/hello_world/scripts/start/start.ts +12 -1
- package/templates/hello_world/scripts/start/tests/start.tests.ts +61 -0
- package/templates/hello_world/src/app.tsx +1 -1
- package/templates/hello_world/src/index.tsx +1 -1
- package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +5 -5
- package/templates/hello_world/src/tests/app.tests.tsx +3 -3
- package/templates/hello_world/utils/use_add_element.ts +1 -1
- package/templates/hello_world/utils/use_feature_support.ts +1 -1
- package/templates/hello_world/webpack.config.ts +254 -0
- package/templates/optional/.cursor/mcp.json +8 -0
- package/templates/optional/.vscode/mcp.json +9 -0
- package/templates/optional/AGENTS.md +76 -0
- package/templates/optional/CLAUDE.md +76 -0
- package/templates/common/conf/eslint-general.mjs +0 -303
- package/templates/common/conf/eslint-i18n.mjs +0 -41
- package/templates/common/conf/eslint-local-i18n-rules/index.mjs +0 -181
- /package/templates/base/scripts/{copy-env.ts → copy_env.ts} +0 -0
- /package/templates/dam/scripts/{copy-env.ts → copy_env.ts} +0 -0
- /package/templates/{gen_ai/scripts/copy-env.ts → data_connector/scripts/copy_env.ts} +0 -0
- /package/templates/{hello_world/scripts/copy-env.ts → gen_ai/scripts/copy_env.ts} +0 -0
- /package/templates/{common → optional}/.vscode/extensions.json +0 -0
|
@@ -6,62 +6,65 @@
|
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
7
|
"author": "Canva Pty Ltd.",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@canva/app-ui-kit": "^4.
|
|
10
|
-
"@canva/asset": "^2.1
|
|
11
|
-
"@canva/design": "^2.
|
|
9
|
+
"@canva/app-ui-kit": "^4.10.0",
|
|
10
|
+
"@canva/asset": "^2.2.1",
|
|
11
|
+
"@canva/design": "^2.7.2",
|
|
12
12
|
"@canva/error": "^2.1.0",
|
|
13
|
-
"@canva/platform": "^2.
|
|
14
|
-
"@canva/user": "^2.1.
|
|
13
|
+
"@canva/platform": "^2.2.0",
|
|
14
|
+
"@canva/user": "^2.1.1",
|
|
15
15
|
"cookie-parser": "1.4.7",
|
|
16
16
|
"react": "18.3.1",
|
|
17
17
|
"react-dom": "18.3.1"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@
|
|
20
|
+
"@canva/cli": ">= 0.0.1-beta.13",
|
|
21
|
+
"@ngrok/ngrok": "1.5.1",
|
|
21
22
|
"@svgr/webpack": "8.1.0",
|
|
22
23
|
"@types/debug": "4.1.12",
|
|
23
24
|
"@types/express": "4.17.21",
|
|
24
|
-
"@types/express-serve-static-core": "
|
|
25
|
+
"@types/express-serve-static-core": "5.0.6",
|
|
25
26
|
"@types/jest": "29.5.14",
|
|
26
|
-
"@types/jsonwebtoken": "9.0.
|
|
27
|
-
"@types/node": "20.
|
|
27
|
+
"@types/jsonwebtoken": "9.0.10",
|
|
28
|
+
"@types/node": "20.19.2",
|
|
28
29
|
"@types/node-fetch": "2.6.12",
|
|
29
30
|
"@types/node-forge": "1.3.11",
|
|
30
31
|
"@types/nodemon": "1.19.6",
|
|
31
32
|
"@types/prompts": "2.4.9",
|
|
32
33
|
"@types/react": "18.3.12",
|
|
33
34
|
"@types/react-dom": "18.3.1",
|
|
34
|
-
"@types/webpack-env": "1.18.
|
|
35
|
+
"@types/webpack-env": "1.18.8",
|
|
35
36
|
"chalk": "4.1.2",
|
|
36
37
|
"cli-table3": "0.6.5",
|
|
37
38
|
"css-loader": "7.1.2",
|
|
38
39
|
"css-modules-typescript-loader": "4.0.1",
|
|
39
|
-
"cssnano": "7.0.
|
|
40
|
-
"debug": "4.4.
|
|
41
|
-
"dotenv": "16.
|
|
42
|
-
"exponential-backoff": "3.1.
|
|
40
|
+
"cssnano": "7.0.7",
|
|
41
|
+
"debug": "4.4.1",
|
|
42
|
+
"dotenv": "16.6.0",
|
|
43
|
+
"exponential-backoff": "3.1.2",
|
|
43
44
|
"express": "4.21.2",
|
|
44
45
|
"express-basic-auth": "1.2.1",
|
|
45
46
|
"jest": "29.7.0",
|
|
46
47
|
"jsonwebtoken": "9.0.2",
|
|
47
|
-
"jwks-rsa": "3.
|
|
48
|
+
"jwks-rsa": "3.2.0",
|
|
48
49
|
"mini-css-extract-plugin": "2.9.2",
|
|
49
50
|
"node-fetch": "3.3.2",
|
|
50
51
|
"node-forge": "1.3.1",
|
|
51
52
|
"nodemon": "3.0.1",
|
|
53
|
+
"open": "8.4.2",
|
|
52
54
|
"postcss-loader": "8.1.1",
|
|
53
|
-
"prettier": "3.
|
|
55
|
+
"prettier": "3.6.2",
|
|
54
56
|
"prompts": "2.4.2",
|
|
55
57
|
"style-loader": "4.0.0",
|
|
56
|
-
"terser-webpack-plugin": "5.3.
|
|
57
|
-
"
|
|
58
|
-
"ts-
|
|
58
|
+
"terser-webpack-plugin": "5.3.14",
|
|
59
|
+
"tree-kill": "1.2.2",
|
|
60
|
+
"ts-jest": "29.4.0",
|
|
61
|
+
"ts-loader": "9.5.2",
|
|
59
62
|
"ts-node": "10.9.2",
|
|
60
|
-
"typescript": "5.
|
|
63
|
+
"typescript": "5.8.2",
|
|
61
64
|
"url-loader": "4.1.1",
|
|
62
|
-
"webpack": "5.
|
|
63
|
-
"webpack-cli": "
|
|
64
|
-
"webpack-dev-server": "5.
|
|
65
|
+
"webpack": "5.99.9",
|
|
66
|
+
"webpack-cli": "6.0.1",
|
|
67
|
+
"webpack-dev-server": "5.2.2",
|
|
65
68
|
"yargs": "17.7.2"
|
|
66
69
|
},
|
|
67
70
|
"keywords": [
|
|
@@ -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");
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import
|
|
3
|
-
import * as chalk from "chalk";
|
|
4
|
-
import { buildConfig } from "../../webpack.config.cjs";
|
|
2
|
+
import { generatePreviewUrl } from "@canva/cli";
|
|
5
3
|
import * as ngrok from "@ngrok/ngrok";
|
|
6
|
-
import * as
|
|
4
|
+
import * as chalk from "chalk";
|
|
7
5
|
import * as Table from "cli-table3";
|
|
6
|
+
import * as nodemon from "nodemon";
|
|
7
|
+
import * as open from "open";
|
|
8
8
|
import * as webpack from "webpack";
|
|
9
9
|
import * as WebpackDevServer from "webpack-dev-server";
|
|
10
|
+
import { buildConfig } from "../../webpack.config";
|
|
10
11
|
import type { Certificate } from "../ssl/ssl";
|
|
11
12
|
import { createOrRetrieveCertificate } from "../ssl/ssl";
|
|
13
|
+
import type { Context } from "./context";
|
|
12
14
|
|
|
13
15
|
export const infoChalk = chalk.blue.bold;
|
|
14
16
|
export const warnChalk = chalk.bgYellow.bold;
|
|
@@ -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
|
|
7
|
-
ngrok
|
|
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
|
|
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
|
|
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.
|
|
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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as yargs from "yargs";
|
|
3
|
-
import { AppRunner } from "./app_runner";
|
|
4
3
|
import { hideBin } from "yargs/helpers";
|
|
4
|
+
import { AppRunner } from "./app_runner";
|
|
5
5
|
import { Context } from "./context";
|
|
6
6
|
|
|
7
7
|
const appRunner = new AppRunner();
|
|
@@ -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,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
|
|
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
|
|
48
|
+
req["user_id"] = user;
|
|
49
49
|
|
|
50
50
|
next();
|
|
51
51
|
} catch (e) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import * as chalk from "chalk";
|
|
3
3
|
import * as debug from "debug";
|
|
4
|
-
import type { Request, Response
|
|
5
|
-
import * as jwt from "jsonwebtoken";
|
|
6
|
-
import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
|
|
4
|
+
import type { NextFunction, Request, Response } from "express";
|
|
7
5
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
6
|
import Express from "express-serve-static-core";
|
|
7
|
+
import * as jwt from "jsonwebtoken";
|
|
8
|
+
import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Prefix your start command with `DEBUG=express:middleware:jwt` to enable debug logging
|
|
@@ -110,7 +110,7 @@ export function createJwtMiddleware(
|
|
|
110
110
|
return sendUnauthorizedResponse(res);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
req
|
|
113
|
+
req["canva"] = {
|
|
114
114
|
appId: payload.aud,
|
|
115
115
|
brandId: payload.brandId,
|
|
116
116
|
userId: payload.userId,
|
|
@@ -446,7 +446,7 @@ describe("createJwtMiddleware", () => {
|
|
|
446
446
|
expect(res.status).not.toHaveBeenCalled();
|
|
447
447
|
expect(res.json).not.toHaveBeenCalled();
|
|
448
448
|
|
|
449
|
-
expect(req
|
|
449
|
+
expect(req["canva"]).toEqual({
|
|
450
450
|
userId: FAKE_USER_ID,
|
|
451
451
|
brandId: FAKE_BRAND_ID,
|
|
452
452
|
appId: FAKE_APP_ID,
|
|
@@ -7,9 +7,9 @@ import type {
|
|
|
7
7
|
VideoElement,
|
|
8
8
|
} from "@canva/design";
|
|
9
9
|
import { addElementAtCursor, addElementAtPoint } from "@canva/design";
|
|
10
|
-
import { useFeatureSupport } from "./use_feature_support";
|
|
11
10
|
import { features } from "@canva/platform";
|
|
12
11
|
import { useEffect, useState } from "react";
|
|
12
|
+
import { useFeatureSupport } from "./use_feature_support";
|
|
13
13
|
|
|
14
14
|
type AddElementParams =
|
|
15
15
|
| ImageElement
|
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
186
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
module.exports.buildConfig = buildConfig;
|
|
254
|
+
export default buildConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
CANVA_FRONTEND_PORT=8080
|
|
2
2
|
CANVA_BACKEND_PORT=3001
|
|
3
3
|
CANVA_BACKEND_HOST=http://localhost:3001 # TODO: replace this with your production URL before submitting your app
|
|
4
|
-
CANVA_APP_ID
|
|
5
|
-
CANVA_APP_ORIGIN=# TODO: Add your app's origin here from the "Developer Portal ->
|
|
6
|
-
CANVA_HMR_ENABLED=FALSE # TODO: set to TRUE to enable HMR
|
|
4
|
+
CANVA_APP_ID=# TODO: Add your app's ID here to configure your backend for JWT verification
|
|
5
|
+
CANVA_APP_ORIGIN=# TODO: Add your app's origin here from the "Developer Portal -> Configuration -> Credentials" to enable HMR
|
|
6
|
+
CANVA_HMR_ENABLED=FALSE # TODO: set to TRUE to enable HMR
|
|
@@ -77,7 +77,7 @@ You need to bypass the invalid security certificate warning every time you start
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
-
### (Optional)
|
|
80
|
+
### Step 3 (Optional): Enable Hot Module Replacement
|
|
81
81
|
|
|
82
82
|
By default, every time you make a change to an app, you have to reload the entire app to see the results of those changes. If you enable [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) (HMR), changes will be reflected without a full reload, which significantly speeds up the development loop.
|
|
83
83
|
|
|
@@ -86,23 +86,21 @@ By default, every time you make a change to an app, you have to reload the entir
|
|
|
86
86
|
To enable HMR:
|
|
87
87
|
|
|
88
88
|
1. Navigate to an app via the [Your apps](https://www.canva.com/developers/apps).
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
5. Set the `CANVA_APP_ORIGIN` environment variable to the value copied from the **App origin** field:
|
|
89
|
+
1. Select **Configuration** -> **Credentials** -> **.env file**.
|
|
90
|
+
1. Copy the `.env` file contents.
|
|
91
|
+
1. Paste the contents into the starter kit's `.env` file. For example:
|
|
93
92
|
|
|
94
93
|
```bash
|
|
95
|
-
CANVA_APP_ORIGIN
|
|
94
|
+
CANVA_APP_ORIGIN=https://app-aabbccddeeff.canva-apps.com
|
|
95
|
+
CANVA_HMR_ENABLED=true
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
1. Restart the local development server.
|
|
99
|
+
1. Reload the app manually to ensure that HMR takes effect.
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
CANVA_HMR_ENABLED=true
|
|
102
|
-
```
|
|
101
|
+
### Step 4 (Optional): Setup the Canva Dev MCP Server
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
8. Reload the app manually to ensure that HMR takes effect.
|
|
103
|
+
If you're using AI coding tools, such as Cursor or Claude Code, you can connect to the Canva Dev MCP Server to supercharge your development workflow. See this [setup guide](https://www.canva.dev/docs/apps/mcp-server/) to get started.
|
|
106
104
|
|
|
107
105
|
## Running an app's backend
|
|
108
106
|
|
|
@@ -111,21 +109,22 @@ Some templates provide an example backend. This backend is defined in the templa
|
|
|
111
109
|
To run templates that have a backend:
|
|
112
110
|
|
|
113
111
|
1. Navigate to the [Your apps](https://www.canva.com/developers/apps) page.
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
1. Select the app you want to run the example with.
|
|
113
|
+
1. Copy your environment variables from **Configuration** -> **Credentials** -> **.env file**.
|
|
114
|
+
1. Paste the contents into the starter kit's `.env` file.
|
|
116
115
|
|
|
117
116
|
For example:
|
|
118
117
|
|
|
119
118
|
```bash
|
|
120
119
|
CANVA_APP_ID=AABBccddeeff
|
|
121
|
-
CANVA_APP_ORIGIN
|
|
120
|
+
CANVA_APP_ORIGIN=https://app-aabbccddeeff.canva-apps.com
|
|
122
121
|
CANVA_BACKEND_PORT=3001
|
|
123
122
|
CANVA_FRONTEND_PORT=8080
|
|
124
123
|
CANVA_BACKEND_HOST=http://localhost:3001
|
|
125
|
-
CANVA_HMR_ENABLED=
|
|
124
|
+
CANVA_HMR_ENABLED=TRUE
|
|
126
125
|
```
|
|
127
126
|
|
|
128
|
-
|
|
127
|
+
1. Start the app:
|
|
129
128
|
|
|
130
129
|
```bash
|
|
131
130
|
npm start
|