@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
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
import { createRoot } from "react-dom/client";
|
|
2
|
-
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
3
|
-
import { ErrorBoundary } from "react-error-boundary";
|
|
4
|
-
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
5
|
-
import { routes } from "./routes";
|
|
6
|
-
import { ErrorPage } from "./pages";
|
|
7
|
-
import { ContextProvider } from "./context";
|
|
8
2
|
import "@canva/app-ui-kit/styles.css";
|
|
9
|
-
import {
|
|
3
|
+
import { App } from "./app";
|
|
10
4
|
|
|
11
5
|
const root = createRoot(document.getElementById("root") as Element);
|
|
12
6
|
|
|
13
7
|
function render() {
|
|
14
|
-
root.render(
|
|
15
|
-
<AppI18nProvider>
|
|
16
|
-
<AppUiProvider>
|
|
17
|
-
<ErrorBoundary fallback={<ErrorPage />}>
|
|
18
|
-
<ContextProvider>
|
|
19
|
-
<RouterProvider router={createHashRouter(routes)} />
|
|
20
|
-
</ContextProvider>
|
|
21
|
-
</ErrorBoundary>
|
|
22
|
-
</AppUiProvider>
|
|
23
|
-
</AppI18nProvider>,
|
|
24
|
-
);
|
|
8
|
+
root.render(<App />);
|
|
25
9
|
}
|
|
26
10
|
|
|
27
11
|
render();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Home } from "src/home";
|
|
2
2
|
import { ErrorPage, GeneratePage, ResultsPage } from "src/pages";
|
|
3
3
|
|
|
4
4
|
export enum Paths {
|
|
@@ -9,7 +9,7 @@ export enum Paths {
|
|
|
9
9
|
export const routes = [
|
|
10
10
|
{
|
|
11
11
|
path: Paths.HOME,
|
|
12
|
-
element: <
|
|
12
|
+
element: <Home />,
|
|
13
13
|
errorElement: <ErrorPage />,
|
|
14
14
|
children: [
|
|
15
15
|
{
|
|
@@ -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
|
}
|
|
@@ -12,54 +12,61 @@
|
|
|
12
12
|
"lint:fix": "eslint . --fix",
|
|
13
13
|
"lint:types": "tsc",
|
|
14
14
|
"start": "ts-node ./scripts/start/start.ts",
|
|
15
|
-
"test": "jest --no-cache
|
|
15
|
+
"test": "jest --no-cache",
|
|
16
16
|
"test:watch": "jest --watchAll",
|
|
17
|
+
"test:update": "npm run test -- -u",
|
|
17
18
|
"postinstall": "ts-node ./scripts/copy-env.ts"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@canva/app-i18n-kit": "^1.0.
|
|
21
|
-
"@canva/app-ui-kit": "^4.
|
|
22
|
-
"@canva/
|
|
23
|
-
"@canva/
|
|
24
|
-
"@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",
|
|
25
28
|
"react": "18.3.1",
|
|
26
29
|
"react-dom": "18.3.1",
|
|
27
30
|
"react-intl": "6.8.7"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"@eslint/eslintrc": "3.
|
|
31
|
-
"@eslint/js": "9.
|
|
32
|
-
"@formatjs/cli": "6.3.
|
|
33
|
-
"@formatjs/ts-transformer": "3.13.
|
|
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",
|
|
34
37
|
"@ngrok/ngrok": "1.4.1",
|
|
38
|
+
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
|
35
39
|
"@svgr/webpack": "8.1.0",
|
|
40
|
+
"@testing-library/react": "16.1.0",
|
|
36
41
|
"@types/express": "4.17.21",
|
|
37
42
|
"@types/express-serve-static-core": "4.19.6",
|
|
38
43
|
"@types/jest": "29.5.14",
|
|
39
44
|
"@types/jsonwebtoken": "9.0.7",
|
|
40
45
|
"@types/node": "20.10.0",
|
|
41
|
-
"@types/node-fetch": "2.6.
|
|
46
|
+
"@types/node-fetch": "2.6.12",
|
|
42
47
|
"@types/node-forge": "1.3.11",
|
|
43
48
|
"@types/nodemon": "1.19.6",
|
|
44
49
|
"@types/react": "18.3.12",
|
|
45
50
|
"@types/react-dom": "18.3.1",
|
|
46
51
|
"@types/webpack-env": "1.18.5",
|
|
47
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
48
|
-
"@typescript-eslint/parser": "8.
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "8.18.0",
|
|
53
|
+
"@typescript-eslint/parser": "8.18.0",
|
|
49
54
|
"chalk": "4.1.2",
|
|
50
55
|
"cli-table3": "0.6.5",
|
|
51
56
|
"css-loader": "7.1.2",
|
|
52
57
|
"css-modules-typescript-loader": "4.0.1",
|
|
53
58
|
"cssnano": "7.0.6",
|
|
54
|
-
"debug": "4.
|
|
55
|
-
"dotenv": "16.4.
|
|
56
|
-
"eslint": "9.
|
|
57
|
-
"eslint-plugin-formatjs": "5.2.
|
|
59
|
+
"debug": "4.4.0",
|
|
60
|
+
"dotenv": "16.4.7",
|
|
61
|
+
"eslint": "9.16.0",
|
|
62
|
+
"eslint-plugin-formatjs": "5.2.8",
|
|
58
63
|
"eslint-plugin-jest": "28.9.0",
|
|
59
64
|
"eslint-plugin-react": "7.37.2",
|
|
60
|
-
"express": "4.21.
|
|
65
|
+
"express": "4.21.2",
|
|
61
66
|
"express-basic-auth": "1.2.1",
|
|
62
67
|
"jest": "29.7.0",
|
|
68
|
+
"jest-css-modules-transform": "4.4.2",
|
|
69
|
+
"jest-environment-jsdom": "29.7.0",
|
|
63
70
|
"jsonwebtoken": "9.0.2",
|
|
64
71
|
"jwks-rsa": "3.1.0",
|
|
65
72
|
"mini-css-extract-plugin": "2.9.2",
|
|
@@ -67,7 +74,8 @@
|
|
|
67
74
|
"node-forge": "1.3.1",
|
|
68
75
|
"nodemon": "3.0.1",
|
|
69
76
|
"postcss-loader": "8.1.1",
|
|
70
|
-
"prettier": "3.
|
|
77
|
+
"prettier": "3.4.2",
|
|
78
|
+
"react-refresh": "0.16.0",
|
|
71
79
|
"style-loader": "4.0.0",
|
|
72
80
|
"terser-webpack-plugin": "5.3.10",
|
|
73
81
|
"ts-jest": "29.2.5",
|
|
@@ -75,7 +83,7 @@
|
|
|
75
83
|
"ts-node": "10.9.2",
|
|
76
84
|
"typescript": "5.5.4",
|
|
77
85
|
"url-loader": "4.1.1",
|
|
78
|
-
"webpack": "5.
|
|
86
|
+
"webpack": "5.97.1",
|
|
79
87
|
"webpack-cli": "5.1.4",
|
|
80
88
|
"webpack-dev-server": "5.1.0",
|
|
81
89
|
"yargs": "17.7.2"
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Button, Rows, Text } from "@canva/app-ui-kit";
|
|
2
|
+
import { requestOpenExternalUrl } from "@canva/platform";
|
|
2
3
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
3
4
|
import * as styles from "styles/components.css";
|
|
4
5
|
import { useAddElement } from "utils/use_add_element";
|
|
5
6
|
|
|
7
|
+
export const DOCS_URL = "https://www.canva.dev/docs/apps/";
|
|
8
|
+
|
|
6
9
|
export const App = () => {
|
|
7
10
|
const addElement = useAddElement();
|
|
8
11
|
|
|
@@ -13,6 +16,16 @@ export const App = () => {
|
|
|
13
16
|
});
|
|
14
17
|
};
|
|
15
18
|
|
|
19
|
+
const openExternalUrl = async (url: string) => {
|
|
20
|
+
const response = await requestOpenExternalUrl({
|
|
21
|
+
url,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (response.status === "aborted") {
|
|
25
|
+
// user decided not to navigate to the link
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
16
29
|
const intl = useIntl();
|
|
17
30
|
|
|
18
31
|
return (
|
|
@@ -37,6 +50,13 @@ export const App = () => {
|
|
|
37
50
|
"Button text to do something cool. Creates a new text element when pressed.",
|
|
38
51
|
})}
|
|
39
52
|
</Button>
|
|
53
|
+
<Button variant="secondary" onClick={() => openExternalUrl(DOCS_URL)}>
|
|
54
|
+
{intl.formatMessage({
|
|
55
|
+
defaultMessage: "Open Canva Apps SDK docs",
|
|
56
|
+
description:
|
|
57
|
+
"Button text to open Canva Apps SDK docs. Opens an external URL when pressed.",
|
|
58
|
+
})}
|
|
59
|
+
</Button>
|
|
40
60
|
</Rows>
|
|
41
61
|
</div>
|
|
42
62
|
);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`Hello World Tests should have a consistent snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="scrollContainer"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="jv_R6g"
|
|
10
|
+
style="--NZu1Zw: 16px;"
|
|
11
|
+
>
|
|
12
|
+
<p
|
|
13
|
+
class="fFOiLQ _5Ob_nQ fM_HdA"
|
|
14
|
+
>
|
|
15
|
+
To make changes to this app, edit the
|
|
16
|
+
<code>
|
|
17
|
+
src/app.tsx
|
|
18
|
+
</code>
|
|
19
|
+
file, then close and reopen the app in the editor to preview the changes.
|
|
20
|
+
</p>
|
|
21
|
+
<button
|
|
22
|
+
class="_1QoxDw Qkd66A tYI0Vw o4TrkA zKTE_w Qkd66A tYI0Vw lsXp_w ubW6qw cwOZMg zQlusQ uRvRjQ"
|
|
23
|
+
type="button"
|
|
24
|
+
>
|
|
25
|
+
<span
|
|
26
|
+
class="_38oWvQ"
|
|
27
|
+
>
|
|
28
|
+
Do something cool
|
|
29
|
+
</span>
|
|
30
|
+
</button>
|
|
31
|
+
<button
|
|
32
|
+
class="_1QoxDw Qkd66A tYI0Vw o4TrkA NT2yCg Qkd66A tYI0Vw lsXp_w cwOZMg zQlusQ uRvRjQ"
|
|
33
|
+
type="button"
|
|
34
|
+
>
|
|
35
|
+
<span
|
|
36
|
+
class="_38oWvQ"
|
|
37
|
+
>
|
|
38
|
+
Open Canva Apps SDK docs
|
|
39
|
+
</span>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
,
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { RenderResult } from "@testing-library/react";
|
|
4
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
5
|
+
import { TestAppI18nProvider } from "@canva/app-i18n-kit";
|
|
6
|
+
import { TestAppUiProvider } from "@canva/app-ui-kit";
|
|
7
|
+
import { requestOpenExternalUrl } from "@canva/platform";
|
|
8
|
+
import { useAddElement } from "utils/use_add_element";
|
|
9
|
+
import { App, DOCS_URL } from "../app";
|
|
10
|
+
|
|
11
|
+
function renderInTestProvider(node: React.ReactNode): RenderResult {
|
|
12
|
+
return render(
|
|
13
|
+
// In a test environment, you should wrap your apps in `TestAppI18nProvider` and `TestAppUiProvider`, rather than `AppI18nProvider` and `AppUiProvider`
|
|
14
|
+
<TestAppI18nProvider>
|
|
15
|
+
<TestAppUiProvider>{node}</TestAppUiProvider>,
|
|
16
|
+
</TestAppI18nProvider>,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
jest.mock("utils/use_add_element");
|
|
21
|
+
|
|
22
|
+
// This test demonstrates how to test code that uses functions from the Canva Apps SDK
|
|
23
|
+
// For more information on testing with the Canva Apps SDK, see https://www.canva.dev/docs/apps/testing/
|
|
24
|
+
describe("Hello World Tests", () => {
|
|
25
|
+
// Mocking the useAddElement hook
|
|
26
|
+
const mockAddElement = jest.fn();
|
|
27
|
+
const mockAddUseElement = jest.mocked(useAddElement);
|
|
28
|
+
const mockRequestOpenExternalUrl = jest.mocked(requestOpenExternalUrl);
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.resetAllMocks();
|
|
32
|
+
mockAddUseElement.mockReturnValue(mockAddElement);
|
|
33
|
+
mockRequestOpenExternalUrl.mockResolvedValue({ status: "completed" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// this test uses a mock in place of the useAddElement hook
|
|
37
|
+
it("should add a text element when the button is clicked", () => {
|
|
38
|
+
// assert that the mocks are in the expected clean state
|
|
39
|
+
expect(mockAddUseElement).not.toHaveBeenCalled();
|
|
40
|
+
expect(mockAddElement).not.toHaveBeenCalled();
|
|
41
|
+
|
|
42
|
+
const result = renderInTestProvider(<App />);
|
|
43
|
+
|
|
44
|
+
// the hook should have been called in the render process but not the callback
|
|
45
|
+
expect(mockAddUseElement).toHaveBeenCalled();
|
|
46
|
+
expect(mockAddElement).not.toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
// get a reference to the do something cool button element
|
|
49
|
+
const doSomethingCoolBtn = result.getByRole("button", {
|
|
50
|
+
name: "Do something cool",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// programmatically simulate clicking the button
|
|
54
|
+
fireEvent.click(doSomethingCoolBtn);
|
|
55
|
+
|
|
56
|
+
// we expect that addElement has been called by the button's click handler
|
|
57
|
+
expect(mockAddElement).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// this test uses a mock in place of the @canva/platform requestOpenExternalUrl function
|
|
61
|
+
it("should call `requestOpenExternalUrl` when the button is clicked", () => {
|
|
62
|
+
expect(mockRequestOpenExternalUrl).not.toHaveBeenCalled();
|
|
63
|
+
|
|
64
|
+
const result = renderInTestProvider(<App />);
|
|
65
|
+
|
|
66
|
+
// get a reference to the Apps SDK button by name
|
|
67
|
+
const sdkButton = result.getByRole("button", {
|
|
68
|
+
name: "Open Canva Apps SDK docs",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(mockRequestOpenExternalUrl).not.toHaveBeenCalled();
|
|
72
|
+
fireEvent.click(sdkButton);
|
|
73
|
+
expect(mockRequestOpenExternalUrl).toHaveBeenCalled();
|
|
74
|
+
|
|
75
|
+
// assert that the requestOpenExternalUrl function was called with the expected arguments
|
|
76
|
+
expect(mockRequestOpenExternalUrl.mock.calls[0][0]).toEqual({
|
|
77
|
+
url: DOCS_URL,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// this test demonstrates the use of a snapshot test
|
|
82
|
+
it("should have a consistent snapshot", () => {
|
|
83
|
+
const result = renderInTestProvider(<App />);
|
|
84
|
+
expect(result.container).toMatchSnapshot();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -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
|
}
|