@canva/cli 0.0.1-beta.8 → 0.0.1-beta.9
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/cli.js +298 -277
- package/package.json +1 -1
- package/templates/base/package.json +12 -12
- package/templates/base/webpack.config.cjs +3 -1
- package/templates/common/conf/eslint-i18n.mjs +3 -0
- package/templates/common/conf/eslint-local-i18n-rules/index.mjs +181 -0
- package/templates/common/jest.config.mjs +29 -2
- package/templates/common/jest.setup.ts +19 -0
- package/templates/dam/package.json +26 -20
- package/templates/dam/webpack.config.cjs +3 -1
- package/templates/gen_ai/package.json +28 -22
- package/templates/gen_ai/src/app.tsx +16 -10
- package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +43 -0
- package/templates/gen_ai/src/home.tsx +13 -0
- package/templates/gen_ai/src/index.tsx +2 -18
- package/templates/gen_ai/src/routes/routes.tsx +2 -2
- package/templates/gen_ai/webpack.config.cjs +3 -1
- package/templates/hello_world/package.json +28 -20
- package/templates/hello_world/src/app.tsx +20 -0
- package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +45 -0
- package/templates/hello_world/src/tests/app.tests.tsx +86 -0
- package/templates/hello_world/webpack.config.cjs +3 -1
package/package.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
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.
|
|
11
|
-
"@canva/design": "^2.
|
|
12
|
-
"@canva/error": "^2.
|
|
13
|
-
"@canva/platform": "^2.
|
|
14
|
-
"@canva/user": "^2.
|
|
9
|
+
"@canva/app-ui-kit": "^4.4.0",
|
|
10
|
+
"@canva/asset": "^2.1.0",
|
|
11
|
+
"@canva/design": "^2.3.0",
|
|
12
|
+
"@canva/error": "^2.1.0",
|
|
13
|
+
"@canva/platform": "^2.1.0",
|
|
14
|
+
"@canva/user": "^2.1.0",
|
|
15
15
|
"cookie-parser": "1.4.7",
|
|
16
16
|
"react": "18.3.1",
|
|
17
17
|
"react-dom": "18.3.1"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@types/jest": "29.5.14",
|
|
26
26
|
"@types/jsonwebtoken": "9.0.7",
|
|
27
27
|
"@types/node": "20.10.0",
|
|
28
|
-
"@types/node-fetch": "2.6.
|
|
28
|
+
"@types/node-fetch": "2.6.12",
|
|
29
29
|
"@types/node-forge": "1.3.11",
|
|
30
30
|
"@types/nodemon": "1.19.6",
|
|
31
31
|
"@types/prompts": "2.4.9",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"css-loader": "7.1.2",
|
|
38
38
|
"css-modules-typescript-loader": "4.0.1",
|
|
39
39
|
"cssnano": "7.0.6",
|
|
40
|
-
"debug": "4.
|
|
41
|
-
"dotenv": "16.4.
|
|
40
|
+
"debug": "4.4.0",
|
|
41
|
+
"dotenv": "16.4.7",
|
|
42
42
|
"exponential-backoff": "3.1.1",
|
|
43
|
-
"express": "4.21.
|
|
43
|
+
"express": "4.21.2",
|
|
44
44
|
"express-basic-auth": "1.2.1",
|
|
45
45
|
"jest": "29.7.0",
|
|
46
46
|
"jsonwebtoken": "9.0.2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"node-forge": "1.3.1",
|
|
51
51
|
"nodemon": "3.0.1",
|
|
52
52
|
"postcss-loader": "8.1.1",
|
|
53
|
-
"prettier": "3.
|
|
53
|
+
"prettier": "3.4.2",
|
|
54
54
|
"prompts": "2.4.2",
|
|
55
55
|
"style-loader": "4.0.0",
|
|
56
56
|
"terser-webpack-plugin": "5.3.10",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"ts-node": "10.9.2",
|
|
60
60
|
"typescript": "5.5.4",
|
|
61
61
|
"url-loader": "4.1.1",
|
|
62
|
-
"webpack": "5.
|
|
62
|
+
"webpack": "5.97.1",
|
|
63
63
|
"webpack-cli": "5.1.4",
|
|
64
64
|
"webpack-dev-server": "5.1.0",
|
|
65
65
|
"yargs": "17.7.2"
|
|
@@ -4,6 +4,7 @@ const TerserPlugin = require("terser-webpack-plugin");
|
|
|
4
4
|
const { DefinePlugin, optimize } = require("webpack");
|
|
5
5
|
const chalk = require("chalk");
|
|
6
6
|
const { transform } = require("@formatjs/ts-transformer");
|
|
7
|
+
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
*
|
|
@@ -173,7 +174,8 @@ function buildConfig({
|
|
|
173
174
|
}),
|
|
174
175
|
// Apps can only submit a single JS file via the developer portal
|
|
175
176
|
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
176
|
-
|
|
177
|
+
mode === "development" && new ReactRefreshWebpackPlugin(),
|
|
178
|
+
].filter(Boolean),
|
|
177
179
|
...buildDevConfig(devConfig),
|
|
178
180
|
};
|
|
179
181
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import formatjs from "eslint-plugin-formatjs";
|
|
2
|
+
import eslintLocalI18nRules from "./eslint-local-i18n-rules/index.mjs";
|
|
2
3
|
|
|
3
4
|
export default [
|
|
4
5
|
{
|
|
5
6
|
plugins: {
|
|
6
7
|
formatjs,
|
|
8
|
+
"local-i18n-rules": eslintLocalI18nRules,
|
|
7
9
|
},
|
|
8
10
|
rules: {
|
|
9
11
|
"formatjs/no-invalid-icu": "error",
|
|
@@ -33,6 +35,7 @@ export default [
|
|
|
33
35
|
"formatjs/no-offset": "error",
|
|
34
36
|
"formatjs/blocklist-elements": [2, ["selectordinal"]],
|
|
35
37
|
"formatjs/no-complex-selectors": "error",
|
|
38
|
+
"local-i18n-rules/enforce-object-property-translation": ["warn"],
|
|
36
39
|
},
|
|
37
40
|
},
|
|
38
41
|
];
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule that identifies and flags untranslated user-facing strings in object properties.
|
|
3
|
+
*
|
|
4
|
+
* This rule helps maintain internationalization consistency by detecting untranslated
|
|
5
|
+
* strings in specific object properties (default: 'label'). It suggests using
|
|
6
|
+
* intl.formatMessage for proper translation.
|
|
7
|
+
*
|
|
8
|
+
* Note: The rule is currently implemented as a local rule, with plans to publish as
|
|
9
|
+
* an npm package to make it available to the broader development community.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // ❌ Incorrect - Untranslated strings
|
|
13
|
+
* const options = [
|
|
14
|
+
* { value: "inbox", label: "Inbox" },
|
|
15
|
+
* { value: "starred", label: "Starred messages" },
|
|
16
|
+
* { value: "spam", label: "Spam folder" }
|
|
17
|
+
* ];
|
|
18
|
+
*
|
|
19
|
+
* // ✅ Correct - Using intl.formatMessage with descriptions
|
|
20
|
+
* const options = [
|
|
21
|
+
* {
|
|
22
|
+
* value: "inbox",
|
|
23
|
+
* label: intl.formatMessage({
|
|
24
|
+
* defaultMessage: "Inbox",
|
|
25
|
+
* description: "Label for main message inbox folder option"
|
|
26
|
+
* })
|
|
27
|
+
* },
|
|
28
|
+
* {
|
|
29
|
+
* value: "starred",
|
|
30
|
+
* label: intl.formatMessage({
|
|
31
|
+
* defaultMessage: "Starred messages",
|
|
32
|
+
* description: "Label for folder containing messages marked as important"
|
|
33
|
+
* })
|
|
34
|
+
* },
|
|
35
|
+
* {
|
|
36
|
+
* value: "spam",
|
|
37
|
+
* label: intl.formatMessage({
|
|
38
|
+
* defaultMessage: "Spam folder",
|
|
39
|
+
* description: "Label for folder containing filtered spam messages"
|
|
40
|
+
* })
|
|
41
|
+
* }
|
|
42
|
+
* ];
|
|
43
|
+
*
|
|
44
|
+
* @see https://www.canva.dev/docs/apps/localization/
|
|
45
|
+
*/
|
|
46
|
+
export default {
|
|
47
|
+
rules: {
|
|
48
|
+
"enforce-object-property-translation": {
|
|
49
|
+
meta: {
|
|
50
|
+
type: "problem",
|
|
51
|
+
docs: {
|
|
52
|
+
description:
|
|
53
|
+
"Enforce translation of specific properties using intl.formatMessage",
|
|
54
|
+
category: "Possible Errors",
|
|
55
|
+
recommended: true,
|
|
56
|
+
},
|
|
57
|
+
fixable: "code",
|
|
58
|
+
schema: [
|
|
59
|
+
{
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
properties: {
|
|
63
|
+
type: "array",
|
|
64
|
+
items: { type: "string" },
|
|
65
|
+
default: ["label"],
|
|
66
|
+
},
|
|
67
|
+
intlObjectName: {
|
|
68
|
+
type: "string",
|
|
69
|
+
default: "intl",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
messages: {
|
|
76
|
+
untranslatedProperty: `If "{{ originalMessage }}" is a user-facing string, you should translate it using "intl.formatMessage". See https://www.canva.dev/docs/apps/localization/.`,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
create(context) {
|
|
80
|
+
const config = context.options[0] || {};
|
|
81
|
+
const propertiesToCheck = config.properties || ["label"];
|
|
82
|
+
const intlObjectName = config.intlObjectName || "intl";
|
|
83
|
+
|
|
84
|
+
function getTemplateLiteralString(node) {
|
|
85
|
+
const src = context.getSourceCode();
|
|
86
|
+
return src.getText(node);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Extract string content from different node types
|
|
90
|
+
function extractStringContent(node) {
|
|
91
|
+
if (!node) return [];
|
|
92
|
+
|
|
93
|
+
switch (node.type) {
|
|
94
|
+
// label: "Foo"
|
|
95
|
+
case "Literal":
|
|
96
|
+
return typeof node.value === "string"
|
|
97
|
+
? [{ node, value: node.value }]
|
|
98
|
+
: [];
|
|
99
|
+
|
|
100
|
+
// label: `Foo ${bar}`
|
|
101
|
+
case "TemplateLiteral":
|
|
102
|
+
return [{ node, value: getTemplateLiteralString(node) }];
|
|
103
|
+
// label: foo || "Bar"
|
|
104
|
+
case "LogicalExpression": {
|
|
105
|
+
if (node.operator === "||") {
|
|
106
|
+
return [
|
|
107
|
+
...extractStringContent(node.left),
|
|
108
|
+
...extractStringContent(node.right),
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
// label: "Foo" + "Bar" + "Baz"
|
|
114
|
+
case "BinaryExpression":
|
|
115
|
+
if (node.operator === "+") {
|
|
116
|
+
return [
|
|
117
|
+
...extractStringContent(node.left),
|
|
118
|
+
...extractStringContent(node.right),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
return [];
|
|
122
|
+
// label: foo ? "Foo" : "Bar"
|
|
123
|
+
case "ConditionalExpression":
|
|
124
|
+
return [
|
|
125
|
+
...extractStringContent(node.consequent),
|
|
126
|
+
...extractStringContent(node.alternate),
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
default:
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isTranslated(node) {
|
|
135
|
+
return (
|
|
136
|
+
node.parent.type === "CallExpression" &&
|
|
137
|
+
node.parent.callee.type === "MemberExpression" &&
|
|
138
|
+
node.parent.callee.object.name === intlObjectName &&
|
|
139
|
+
node.parent.callee.property.name === "formatMessage"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
Property(node) {
|
|
145
|
+
const keyName = node.key.name || node.key.value;
|
|
146
|
+
if (propertiesToCheck.includes(keyName)) {
|
|
147
|
+
const results = extractStringContent(node.value);
|
|
148
|
+
if (!results) return;
|
|
149
|
+
results.forEach((result) => {
|
|
150
|
+
const { node: stringNode, value: stringValue } = result;
|
|
151
|
+
|
|
152
|
+
if (!isTranslated(stringNode)) {
|
|
153
|
+
context.report({
|
|
154
|
+
node: stringNode,
|
|
155
|
+
messageId: "untranslatedProperty",
|
|
156
|
+
data: {
|
|
157
|
+
property: keyName,
|
|
158
|
+
originalMessage:
|
|
159
|
+
stringValue.length > 40
|
|
160
|
+
? stringValue.split(" ").slice(0, 4).join(" ") + "..."
|
|
161
|
+
: stringValue,
|
|
162
|
+
intlObjectName,
|
|
163
|
+
},
|
|
164
|
+
fix(fixer) {
|
|
165
|
+
const newText = `${intlObjectName}.formatMessage({
|
|
166
|
+
defaultMessage: ${JSON.stringify(stringValue)},
|
|
167
|
+
// TODO: Provide a meaningful description for translators
|
|
168
|
+
description: ""
|
|
169
|
+
})`;
|
|
170
|
+
return fixer.replaceText(stringNode, newText);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
@@ -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: "
|
|
6
|
-
testRegex: "(/tests/.*|(\\.|/)(tests))\\.
|
|
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,19 @@
|
|
|
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");
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@canva/app-components": "^1.0.0-beta.29",
|
|
21
|
-
"@canva/app-i18n-kit": "^1.0.
|
|
22
|
-
"@canva/app-ui-kit": "^4.
|
|
23
|
-
"@canva/asset": "^2.
|
|
24
|
-
"@canva/design": "^2.
|
|
25
|
-
"@canva/
|
|
26
|
-
"@canva/
|
|
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",
|
|
25
|
+
"@canva/error": "^2.1.0",
|
|
26
|
+
"@canva/platform": "^2.1.0",
|
|
27
|
+
"@canva/user": "^2.1.0",
|
|
27
28
|
"cookie-parser": "1.4.7",
|
|
28
29
|
"cors": "2.8.5",
|
|
29
30
|
"react": "18.3.1",
|
|
@@ -31,12 +32,14 @@
|
|
|
31
32
|
"react-intl": "6.8.7"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@eslint/eslintrc": "3.
|
|
35
|
-
"@eslint/js": "9.
|
|
36
|
-
"@formatjs/cli": "6.3.
|
|
37
|
-
"@formatjs/ts-transformer": "3.13.
|
|
35
|
+
"@eslint/eslintrc": "3.2.0",
|
|
36
|
+
"@eslint/js": "9.16.0",
|
|
37
|
+
"@formatjs/cli": "6.3.14",
|
|
38
|
+
"@formatjs/ts-transformer": "3.13.26",
|
|
38
39
|
"@ngrok/ngrok": "1.4.1",
|
|
40
|
+
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
|
39
41
|
"@svgr/webpack": "8.1.0",
|
|
42
|
+
"@testing-library/react": "16.1.0",
|
|
40
43
|
"@types/cors": "2.8.17",
|
|
41
44
|
"@types/debug": "4.1.12",
|
|
42
45
|
"@types/express": "4.17.21",
|
|
@@ -44,30 +47,32 @@
|
|
|
44
47
|
"@types/jest": "29.5.14",
|
|
45
48
|
"@types/jsonwebtoken": "9.0.7",
|
|
46
49
|
"@types/node": "20.10.0",
|
|
47
|
-
"@types/node-fetch": "2.6.
|
|
50
|
+
"@types/node-fetch": "2.6.12",
|
|
48
51
|
"@types/node-forge": "1.3.11",
|
|
49
52
|
"@types/nodemon": "1.19.6",
|
|
50
53
|
"@types/prompts": "2.4.9",
|
|
51
54
|
"@types/react": "18.3.12",
|
|
52
55
|
"@types/react-dom": "18.3.1",
|
|
53
56
|
"@types/webpack-env": "1.18.5",
|
|
54
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
55
|
-
"@typescript-eslint/parser": "8.
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "8.18.0",
|
|
58
|
+
"@typescript-eslint/parser": "8.18.0",
|
|
56
59
|
"chalk": "4.1.2",
|
|
57
60
|
"cli-table3": "0.6.5",
|
|
58
61
|
"css-loader": "7.1.2",
|
|
59
62
|
"css-modules-typescript-loader": "4.0.1",
|
|
60
63
|
"cssnano": "7.0.6",
|
|
61
|
-
"debug": "4.
|
|
62
|
-
"dotenv": "16.4.
|
|
63
|
-
"eslint": "9.
|
|
64
|
-
"eslint-plugin-formatjs": "5.2.
|
|
64
|
+
"debug": "4.4.0",
|
|
65
|
+
"dotenv": "16.4.7",
|
|
66
|
+
"eslint": "9.16.0",
|
|
67
|
+
"eslint-plugin-formatjs": "5.2.8",
|
|
65
68
|
"eslint-plugin-jest": "28.9.0",
|
|
66
69
|
"eslint-plugin-react": "7.37.2",
|
|
67
70
|
"exponential-backoff": "3.1.1",
|
|
68
|
-
"express": "4.21.
|
|
71
|
+
"express": "4.21.2",
|
|
69
72
|
"express-basic-auth": "1.2.1",
|
|
70
73
|
"jest": "29.7.0",
|
|
74
|
+
"jest-css-modules-transform": "4.4.2",
|
|
75
|
+
"jest-environment-jsdom": "29.7.0",
|
|
71
76
|
"jsonwebtoken": "9.0.2",
|
|
72
77
|
"jwks-rsa": "3.1.0",
|
|
73
78
|
"mini-css-extract-plugin": "2.9.2",
|
|
@@ -75,8 +80,9 @@
|
|
|
75
80
|
"node-forge": "1.3.1",
|
|
76
81
|
"nodemon": "3.0.1",
|
|
77
82
|
"postcss-loader": "8.1.1",
|
|
78
|
-
"prettier": "3.
|
|
83
|
+
"prettier": "3.4.2",
|
|
79
84
|
"prompts": "2.4.2",
|
|
85
|
+
"react-refresh": "0.16.0",
|
|
80
86
|
"style-loader": "4.0.0",
|
|
81
87
|
"terser-webpack-plugin": "5.3.10",
|
|
82
88
|
"ts-jest": "29.2.5",
|
|
@@ -84,7 +90,7 @@
|
|
|
84
90
|
"ts-node": "10.9.2",
|
|
85
91
|
"typescript": "5.5.4",
|
|
86
92
|
"url-loader": "4.1.1",
|
|
87
|
-
"webpack": "5.
|
|
93
|
+
"webpack": "5.97.1",
|
|
88
94
|
"webpack-cli": "5.1.4",
|
|
89
95
|
"webpack-dev-server": "5.1.0",
|
|
90
96
|
"yargs": "17.7.2"
|
|
@@ -4,6 +4,7 @@ const TerserPlugin = require("terser-webpack-plugin");
|
|
|
4
4
|
const { DefinePlugin, optimize } = require("webpack");
|
|
5
5
|
const chalk = require("chalk");
|
|
6
6
|
const { transform } = require("@formatjs/ts-transformer");
|
|
7
|
+
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
*
|
|
@@ -173,7 +174,8 @@ function buildConfig({
|
|
|
173
174
|
}),
|
|
174
175
|
// Apps can only submit a single JS file via the developer portal
|
|
175
176
|
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
176
|
-
|
|
177
|
+
mode === "development" && new ReactRefreshWebpackPlugin(),
|
|
178
|
+
].filter(Boolean),
|
|
177
179
|
...buildDevConfig(devConfig),
|
|
178
180
|
};
|
|
179
181
|
}
|
|
@@ -17,16 +17,17 @@
|
|
|
17
17
|
"postinstall": "ts-node ./scripts/copy-env.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@canva/app-i18n-kit": "^1.0.
|
|
21
|
-
"@canva/app-ui-kit": "^4.
|
|
22
|
-
"@canva/asset": "^2.
|
|
23
|
-
"@canva/design": "^2.
|
|
24
|
-
"@canva/
|
|
25
|
-
"@canva/
|
|
20
|
+
"@canva/app-i18n-kit": "^1.0.2",
|
|
21
|
+
"@canva/app-ui-kit": "^4.4.0",
|
|
22
|
+
"@canva/asset": "^2.1.0",
|
|
23
|
+
"@canva/design": "^2.3.0",
|
|
24
|
+
"@canva/error": "^2.1.0",
|
|
25
|
+
"@canva/platform": "^2.1.0",
|
|
26
|
+
"@canva/user": "^2.1.0",
|
|
26
27
|
"cookie-parser": "1.4.7",
|
|
27
28
|
"cors": "2.8.5",
|
|
28
|
-
"html-react-parser": "5.
|
|
29
|
-
"obscenity": "0.4.
|
|
29
|
+
"html-react-parser": "5.2.0",
|
|
30
|
+
"obscenity": "0.4.1",
|
|
30
31
|
"react": "18.3.1",
|
|
31
32
|
"react-dom": "18.3.1",
|
|
32
33
|
"react-error-boundary": "4.1.2",
|
|
@@ -34,42 +35,46 @@
|
|
|
34
35
|
"react-router-dom": "6.28.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@eslint/eslintrc": "3.
|
|
38
|
-
"@eslint/js": "9.
|
|
39
|
-
"@formatjs/cli": "6.3.
|
|
40
|
-
"@formatjs/ts-transformer": "3.13.
|
|
38
|
+
"@eslint/eslintrc": "3.2.0",
|
|
39
|
+
"@eslint/js": "9.16.0",
|
|
40
|
+
"@formatjs/cli": "6.3.14",
|
|
41
|
+
"@formatjs/ts-transformer": "3.13.26",
|
|
41
42
|
"@ngrok/ngrok": "1.4.1",
|
|
43
|
+
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
|
42
44
|
"@svgr/webpack": "8.1.0",
|
|
45
|
+
"@testing-library/react": "16.1.0",
|
|
43
46
|
"@types/debug": "4.1.12",
|
|
44
47
|
"@types/express": "4.17.21",
|
|
45
48
|
"@types/express-serve-static-core": "4.19.6",
|
|
46
49
|
"@types/jest": "29.5.14",
|
|
47
50
|
"@types/jsonwebtoken": "9.0.7",
|
|
48
51
|
"@types/node": "20.10.0",
|
|
49
|
-
"@types/node-fetch": "2.6.
|
|
52
|
+
"@types/node-fetch": "2.6.12",
|
|
50
53
|
"@types/node-forge": "1.3.11",
|
|
51
54
|
"@types/nodemon": "1.19.6",
|
|
52
55
|
"@types/prompts": "2.4.9",
|
|
53
56
|
"@types/react": "18.3.12",
|
|
54
57
|
"@types/react-dom": "18.3.1",
|
|
55
58
|
"@types/webpack-env": "1.18.5",
|
|
56
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
57
|
-
"@typescript-eslint/parser": "8.
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "8.18.0",
|
|
60
|
+
"@typescript-eslint/parser": "8.18.0",
|
|
58
61
|
"chalk": "4.1.2",
|
|
59
62
|
"cli-table3": "0.6.5",
|
|
60
63
|
"css-loader": "7.1.2",
|
|
61
64
|
"css-modules-typescript-loader": "4.0.1",
|
|
62
65
|
"cssnano": "7.0.6",
|
|
63
|
-
"debug": "4.
|
|
64
|
-
"dotenv": "16.4.
|
|
65
|
-
"eslint": "9.
|
|
66
|
-
"eslint-plugin-formatjs": "5.2.
|
|
66
|
+
"debug": "4.4.0",
|
|
67
|
+
"dotenv": "16.4.7",
|
|
68
|
+
"eslint": "9.16.0",
|
|
69
|
+
"eslint-plugin-formatjs": "5.2.8",
|
|
67
70
|
"eslint-plugin-jest": "28.9.0",
|
|
68
71
|
"eslint-plugin-react": "7.37.2",
|
|
69
72
|
"exponential-backoff": "3.1.1",
|
|
70
|
-
"express": "4.21.
|
|
73
|
+
"express": "4.21.2",
|
|
71
74
|
"express-basic-auth": "1.2.1",
|
|
72
75
|
"jest": "29.7.0",
|
|
76
|
+
"jest-css-modules-transform": "4.4.2",
|
|
77
|
+
"jest-environment-jsdom": "29.7.0",
|
|
73
78
|
"jsonwebtoken": "9.0.2",
|
|
74
79
|
"jwks-rsa": "3.1.0",
|
|
75
80
|
"mini-css-extract-plugin": "2.9.2",
|
|
@@ -77,8 +82,9 @@
|
|
|
77
82
|
"node-forge": "1.3.1",
|
|
78
83
|
"nodemon": "3.0.1",
|
|
79
84
|
"postcss-loader": "8.1.1",
|
|
80
|
-
"prettier": "3.
|
|
85
|
+
"prettier": "3.4.2",
|
|
81
86
|
"prompts": "2.4.2",
|
|
87
|
+
"react-refresh": "0.16.0",
|
|
82
88
|
"style-loader": "4.0.0",
|
|
83
89
|
"terser-webpack-plugin": "5.3.10",
|
|
84
90
|
"ts-jest": "29.2.5",
|
|
@@ -86,7 +92,7 @@
|
|
|
86
92
|
"ts-node": "10.9.2",
|
|
87
93
|
"typescript": "5.5.4",
|
|
88
94
|
"url-loader": "4.1.1",
|
|
89
|
-
"webpack": "5.
|
|
95
|
+
"webpack": "5.97.1",
|
|
90
96
|
"webpack-cli": "5.1.4",
|
|
91
97
|
"webpack-dev-server": "5.1.0",
|
|
92
98
|
"yargs": "17.7.2"
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
2
|
+
import { ContextProvider } from "./context";
|
|
3
|
+
import { routes } from "./routes";
|
|
4
|
+
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
5
|
+
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
6
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
7
|
+
import { ErrorPage } from "./pages";
|
|
5
8
|
|
|
6
9
|
export const App = () => (
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<AppI18nProvider>
|
|
11
|
+
<AppUiProvider>
|
|
12
|
+
<ErrorBoundary fallback={<ErrorPage />}>
|
|
13
|
+
<ContextProvider>
|
|
14
|
+
<RouterProvider router={createHashRouter(routes)} />
|
|
15
|
+
</ContextProvider>
|
|
16
|
+
</ErrorBoundary>
|
|
17
|
+
</AppUiProvider>
|
|
18
|
+
</AppI18nProvider>
|
|
13
19
|
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
|
2
|
+
import { TestAppUiProvider } from "@canva/app-ui-kit";
|
|
3
|
+
import { TestAppI18nProvider } from "@canva/app-i18n-kit";
|
|
4
|
+
import type { RenderResult } from "@testing-library/react";
|
|
5
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { RemainingCredits } from "../remaining_credits";
|
|
8
|
+
import { requestOpenExternalUrl } from "@canva/platform";
|
|
9
|
+
|
|
10
|
+
function renderInTestProvider(node: React.ReactNode): RenderResult {
|
|
11
|
+
return render(
|
|
12
|
+
// In a test environment, you should wrap your apps in `TestAppI18nProvider` and `TestAppUiProvider`, rather than `AppI18nProvider` and `AppUiProvider`
|
|
13
|
+
<TestAppI18nProvider>
|
|
14
|
+
<TestAppUiProvider>{node}</TestAppUiProvider>,
|
|
15
|
+
</TestAppI18nProvider>,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// This test demonstrates how to test code that uses functions from the Canva Apps SDK
|
|
20
|
+
// For more information on testing with the Canva Apps SDK, see https://www.canva.dev/docs/apps/testing/
|
|
21
|
+
describe("Remaining Credit Tests", () => {
|
|
22
|
+
const mockRequestOpenExternalUrl = jest.mocked(requestOpenExternalUrl);
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.resetAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should call requestOpenExternalUrl when the link is clicked", () => {
|
|
29
|
+
// assert that the mock is in the expected clean state
|
|
30
|
+
expect(mockRequestOpenExternalUrl).not.toHaveBeenCalled();
|
|
31
|
+
|
|
32
|
+
const result = renderInTestProvider(<RemainingCredits />);
|
|
33
|
+
|
|
34
|
+
// get a reference to the link to purchase more credits
|
|
35
|
+
const purchaseMoreLink = result.getByRole("button");
|
|
36
|
+
|
|
37
|
+
// programmatically simulate clicking the button
|
|
38
|
+
fireEvent.click(purchaseMoreLink);
|
|
39
|
+
|
|
40
|
+
// we expect that requestOpenExternalUrl has been called
|
|
41
|
+
expect(mockRequestOpenExternalUrl).toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Outlet } from "react-router-dom";
|
|
2
|
+
import { Rows } from "@canva/app-ui-kit";
|
|
3
|
+
import { Footer } from "./components";
|
|
4
|
+
import * as styles from "styles/components.css";
|
|
5
|
+
|
|
6
|
+
export const Home = () => (
|
|
7
|
+
<div className={styles.scrollContainer}>
|
|
8
|
+
<Rows spacing="3u">
|
|
9
|
+
<Outlet />
|
|
10
|
+
<Footer />
|
|
11
|
+
</Rows>
|
|
12
|
+
</div>
|
|
13
|
+
);
|