@canva/cli 0.0.1-beta.3 → 0.0.1-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -104
- package/cli.js +661 -394
- package/lib/cjs/index.cjs +5 -0
- package/lib/esm/index.mjs +5 -0
- package/package.json +19 -4
- package/templates/base/eslint.config.mjs +2 -27
- package/templates/base/package.json +31 -25
- package/templates/base/scripts/copy_env.ts +10 -0
- package/templates/base/scripts/start/app_runner.ts +39 -2
- package/templates/base/scripts/start/context.ts +12 -6
- package/templates/base/scripts/start/start.ts +11 -0
- package/templates/base/scripts/start/tests/start.tests.ts +61 -0
- package/templates/base/{webpack.config.cjs → webpack.config.ts} +46 -46
- package/templates/common/.cursor/mcp.json +8 -0
- package/templates/common/.gitignore.template +5 -6
- package/templates/common/.nvmrc +1 -0
- package/templates/common/.prettierrc +21 -0
- package/templates/common/.vscode/extensions.json +6 -0
- package/templates/common/.vscode/mcp.json +9 -0
- package/templates/common/README.md +4 -7
- package/templates/common/jest.config.mjs +29 -2
- package/templates/common/jest.setup.ts +20 -0
- package/templates/common/utils/backend/base_backend/create.ts +104 -0
- package/templates/common/utils/backend/jwt_middleware/index.ts +1 -0
- package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
- package/templates/common/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
- package/templates/common/utils/table_wrapper.ts +477 -0
- package/templates/common/utils/tests/table_wrapper.tests.ts +252 -0
- package/templates/common/utils/use_add_element.ts +48 -0
- package/templates/common/utils/use_feature_support.ts +28 -0
- package/templates/common/utils/use_overlay_hook.ts +74 -0
- package/templates/common/utils/use_selection_hook.ts +37 -0
- package/templates/dam/backend/routers/dam.ts +23 -19
- package/templates/dam/eslint.config.mjs +2 -28
- package/templates/dam/package.json +43 -39
- package/templates/dam/scripts/copy_env.ts +10 -0
- package/templates/dam/scripts/start/app_runner.ts +39 -2
- package/templates/dam/scripts/start/context.ts +12 -6
- package/templates/dam/scripts/start/start.ts +11 -0
- 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/{hello_world/webpack.config.cjs → dam/webpack.config.ts} +46 -46
- package/templates/data_connector/README.md +84 -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/copy_env.ts +10 -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 +69 -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/{gen_ai/webpack.config.cjs → data_connector/webpack.config.ts} +46 -46
- package/templates/gen_ai/README.md +1 -40
- package/templates/gen_ai/backend/routers/image.ts +11 -11
- package/templates/gen_ai/backend/server.ts +0 -7
- package/templates/gen_ai/eslint.config.mjs +2 -27
- package/templates/gen_ai/package.json +48 -42
- package/templates/gen_ai/scripts/copy_env.ts +10 -0
- package/templates/gen_ai/scripts/start/app_runner.ts +39 -2
- package/templates/gen_ai/scripts/start/context.ts +12 -6
- package/templates/gen_ai/scripts/start/start.ts +11 -0
- package/templates/gen_ai/scripts/start/tests/start.tests.ts +61 -0
- package/templates/gen_ai/src/api/api.ts +24 -79
- package/templates/gen_ai/src/app.tsx +16 -10
- package/templates/gen_ai/src/components/footer.messages.ts +0 -5
- package/templates/gen_ai/src/components/footer.tsx +7 -25
- package/templates/gen_ai/src/components/index.ts +0 -1
- package/templates/gen_ai/src/components/loading_results.tsx +4 -8
- package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +43 -0
- package/templates/gen_ai/src/context/app_context.tsx +5 -33
- package/templates/gen_ai/src/context/context.messages.ts +1 -12
- 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/{dam/webpack.config.cjs → gen_ai/webpack.config.ts} +46 -46
- package/templates/hello_world/eslint.config.mjs +2 -27
- package/templates/hello_world/package.json +47 -34
- package/templates/hello_world/scripts/copy_env.ts +10 -0
- package/templates/hello_world/scripts/start/app_runner.ts +39 -2
- package/templates/hello_world/scripts/start/context.ts +12 -6
- package/templates/hello_world/scripts/start/start.ts +11 -0
- package/templates/hello_world/scripts/start/tests/start.tests.ts +61 -0
- 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.ts +270 -0
- package/templates/common/conf/eslint-general.mjs +0 -277
- package/templates/common/conf/eslint-i18n.mjs +0 -23
- package/templates/gen_ai/backend/routers/oauth.ts +0 -393
- package/templates/gen_ai/src/components/logged_in_status.tsx +0 -44
- package/templates/gen_ai/src/services/auth.tsx +0 -26
- package/templates/gen_ai/src/services/index.ts +0 -1
|
@@ -1,30 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type { Configuration } from "webpack";
|
|
2
|
+
import { DefinePlugin, optimize } from "webpack";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as TerserPlugin from "terser-webpack-plugin";
|
|
5
|
+
import { transform } from "@formatjs/ts-transformer";
|
|
6
|
+
import * as chalk from "chalk";
|
|
7
|
+
import { config } from "dotenv";
|
|
8
|
+
import { Configuration as DevServerConfiguration } from "webpack-dev-server";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @returns {Object}
|
|
22
|
-
*/
|
|
23
|
-
function buildConfig({
|
|
10
|
+
config();
|
|
11
|
+
|
|
12
|
+
type DevConfig = {
|
|
13
|
+
port: number;
|
|
14
|
+
enableHmr: boolean;
|
|
15
|
+
enableHttps: boolean;
|
|
16
|
+
appOrigin?: string;
|
|
17
|
+
appId?: string; // Deprecated in favour of appOrigin
|
|
18
|
+
certFile?: string;
|
|
19
|
+
keyFile?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function buildConfig({
|
|
24
23
|
devConfig,
|
|
25
24
|
appEntry = path.join(process.cwd(), "src", "index.tsx"),
|
|
26
25
|
backendHost = process.env.CANVA_BACKEND_HOST,
|
|
27
|
-
|
|
26
|
+
// For IN_HARNESS, refer to the following docs for more information: https://www.canva.dev/docs/apps/test-harness/
|
|
27
|
+
inHarness = process.env.IN_HARNESS === "true",
|
|
28
|
+
}: {
|
|
29
|
+
devConfig?: DevConfig;
|
|
30
|
+
appEntry?: string;
|
|
31
|
+
backendHost?: string;
|
|
32
|
+
inHarness?: boolean;
|
|
33
|
+
} = {}): Configuration & DevServerConfiguration {
|
|
28
34
|
const mode = devConfig ? "development" : "production";
|
|
29
35
|
|
|
30
36
|
if (!backendHost) {
|
|
@@ -45,9 +51,14 @@ function buildConfig({
|
|
|
45
51
|
return {
|
|
46
52
|
mode,
|
|
47
53
|
context: path.resolve(process.cwd(), "./"),
|
|
48
|
-
entry:
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
entry: inHarness
|
|
55
|
+
? {
|
|
56
|
+
harness: path.join(process.cwd(), "harness", "harness.tsx"),
|
|
57
|
+
init: path.join(process.cwd(), "harness", "init.ts"),
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
app: appEntry,
|
|
61
|
+
},
|
|
51
62
|
target: "web",
|
|
52
63
|
resolve: {
|
|
53
64
|
alias: {
|
|
@@ -59,7 +70,7 @@ function buildConfig({
|
|
|
59
70
|
extensions: [".ts", ".tsx", ".js", ".css", ".svg", ".woff", ".woff2"],
|
|
60
71
|
},
|
|
61
72
|
infrastructureLogging: {
|
|
62
|
-
level: "none",
|
|
73
|
+
level: inHarness ? "info" : "none",
|
|
63
74
|
},
|
|
64
75
|
module: {
|
|
65
76
|
rules: [
|
|
@@ -173,32 +184,23 @@ function buildConfig({
|
|
|
173
184
|
}),
|
|
174
185
|
// Apps can only submit a single JS file via the developer portal
|
|
175
186
|
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
176
|
-
],
|
|
187
|
+
].filter(Boolean),
|
|
177
188
|
...buildDevConfig(devConfig),
|
|
178
189
|
};
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
* @param {boolean} [options.enableHmr]
|
|
186
|
-
* @param {boolean} [options.enableHttps]
|
|
187
|
-
* @param {string} [options.appOrigin]
|
|
188
|
-
* @param {string} [options.appId] - Deprecated in favour of appOrigin
|
|
189
|
-
* @param {string} [options.certFile]
|
|
190
|
-
* @param {string} [options.keyFile]
|
|
191
|
-
* @returns {Object|null}
|
|
192
|
-
*/
|
|
193
|
-
function buildDevConfig(options) {
|
|
192
|
+
function buildDevConfig(options?: DevConfig): {
|
|
193
|
+
devtool?: string;
|
|
194
|
+
devServer?: DevServerConfiguration;
|
|
195
|
+
} {
|
|
194
196
|
if (!options) {
|
|
195
|
-
return
|
|
197
|
+
return {};
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
|
|
199
201
|
options;
|
|
200
202
|
|
|
201
|
-
let devServer = {
|
|
203
|
+
let devServer: DevServerConfiguration = {
|
|
202
204
|
server: enableHttps
|
|
203
205
|
? {
|
|
204
206
|
type: "https",
|
|
@@ -265,6 +267,4 @@ function buildDevConfig(options) {
|
|
|
265
267
|
};
|
|
266
268
|
}
|
|
267
269
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
module.exports.buildConfig = buildConfig;
|
|
270
|
+
export default buildConfig;
|
|
@@ -1,42 +1,3 @@
|
|
|
1
|
-
## Run the development server with ngrok to use authentication with the app
|
|
2
|
-
|
|
3
|
-
These steps demonstrate how to start the local development server with ngrok and https.
|
|
4
|
-
|
|
5
|
-
From your app's root directory
|
|
6
|
-
|
|
7
|
-
1. Stop any running scripts, and run the following command to launch the backend and frontend development servers. The `--ngrok` parameter exposes the backend server via a publicly accessible URL.
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm start --ngrok
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
2. After ngrok is running, copy your ngrok url
|
|
14
|
-
(e.g. `https://0000-0000.ngrok-free.app`) to the clipboard.
|
|
15
|
-
|
|
16
|
-
1. Go to your app in the [Developer Portal](https://www.canva.com/developers/apps).
|
|
17
|
-
2. Navigate to the "Authentication" section of your app.
|
|
18
|
-
3. Click the "Add provider" button under the "OAuth 2.0" tab
|
|
19
|
-
4. Configure the provider as follows:
|
|
20
|
-
- Provider: local (or name of your choice)
|
|
21
|
-
- Client ID: `client_id` (exactly this string, in a real app you'd have some opaque identifier)
|
|
22
|
-
- Client Secret: `client_secret` (same here)
|
|
23
|
-
- Authorization server URL: Your ngrok url followed by `/auth/authorize` e.g. `https://0000-0000.ngrok-free.app/auth/authorize`
|
|
24
|
-
- Token exchange URL: Your ngrok url followed by `/auth/token` e.g. `https://0000-0000.ngrok-free.app/auth/token`
|
|
25
|
-
- Revocation exchange URL: (leave blank)
|
|
26
|
-
- Proof of Key for Code Exchange (PKCE): off
|
|
27
|
-
Note: Your ngrok URL changes each time you restart ngrok. Keep these fields up to
|
|
28
|
-
date to ensure your example authentication step will run.
|
|
29
|
-
|
|
30
|
-
3. Navigate to your app at `https://www.canva.com/developers/apps` and make sure that under Configuration, App source is pointing to your localhost url.
|
|
31
|
-
|
|
32
|
-
4. Navigate to your app at `https://www.canva.com/developers/apps`, and click **Preview** to preview the app.
|
|
33
|
-
1. A new screen will appear asking if you want to authenticate.
|
|
34
|
-
Press **Connect** to start the authentication flow.
|
|
35
|
-
2. A ngrok screen may appear. If it does, select **Visit Site**
|
|
36
|
-
3. An authentication popup will appear. For the username, enter `username`, and
|
|
37
|
-
for the password enter `password`.
|
|
38
|
-
4. If successful, you will be redirected back to your app.
|
|
39
|
-
|
|
40
1
|
## Generative AI Template
|
|
41
2
|
|
|
42
3
|
This template captures best practices for improving user experience in your application.
|
|
@@ -59,7 +20,7 @@ In this template, we've included a basic obscenity filter to stop users from cre
|
|
|
59
20
|
|
|
60
21
|
### Backend
|
|
61
22
|
|
|
62
|
-
This template includes a simple Express server as a sample backend. Please note that this server is not production-ready, and we advise using it solely for instructional purposes to demonstrate API calls.
|
|
23
|
+
This template includes a simple Express server as a sample backend. Please note that this server is not production-ready, and we advise using it solely for instructional purposes to demonstrate API calls. If you require authentication for your app, we recommend looking at the authentication example provided in the [starter kit](https://github.com/canva-sdks/canva-apps-sdk-starter-kit).
|
|
63
24
|
|
|
64
25
|
### Thumbnails
|
|
65
26
|
|
|
@@ -22,48 +22,48 @@ const imageUrls: ImageResponse[] = [
|
|
|
22
22
|
fullsize: {
|
|
23
23
|
width: 1280,
|
|
24
24
|
height: 853,
|
|
25
|
-
url: "https://
|
|
25
|
+
url: "https://images.pexels.com/photos/1145720/pexels-photo-1145720.jpeg?auto=compress&cs=tinysrgb&w=1280&h=853&dpr=2",
|
|
26
26
|
},
|
|
27
27
|
thumbnail: {
|
|
28
28
|
width: 640,
|
|
29
29
|
height: 427,
|
|
30
|
-
url: "https://
|
|
30
|
+
url: "https://images.pexels.com/photos/1145720/pexels-photo-1145720.jpeg?auto=compress&cs=tinysrgb&w=640&h=427&dpr=2",
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
fullsize: {
|
|
35
35
|
width: 1280,
|
|
36
36
|
height: 853,
|
|
37
|
-
url: "https://
|
|
37
|
+
url: "https://images.pexels.com/photos/4010108/pexels-photo-4010108.jpeg?auto=compress&cs=tinysrgb&w=1280&h=863&dpr=2",
|
|
38
38
|
},
|
|
39
39
|
thumbnail: {
|
|
40
40
|
width: 640,
|
|
41
41
|
height: 427,
|
|
42
|
-
url: "https://
|
|
42
|
+
url: "https://images.pexels.com/photos/4010108/pexels-photo-4010108.jpeg?auto=compress&cs=tinysrgb&w=640&h=427&dpr=2",
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
fullsize: {
|
|
47
47
|
width: 1280,
|
|
48
48
|
height: 853,
|
|
49
|
-
url: "https://
|
|
49
|
+
url: "https://images.pexels.com/photos/1327496/pexels-photo-1327496.jpeg?auto=compress&cs=tinysrgb&w=1280&h=853&dpr=2",
|
|
50
50
|
},
|
|
51
51
|
thumbnail: {
|
|
52
52
|
width: 640,
|
|
53
53
|
height: 427,
|
|
54
|
-
url: "https://
|
|
54
|
+
url: "https://images.pexels.com/photos/1327496/pexels-photo-1327496.jpeg?auto=compress&cs=tinysrgb&w=640&h=427&dpr=2",
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
fullsize: {
|
|
59
59
|
width: 1280,
|
|
60
60
|
height: 853,
|
|
61
|
-
url: "https://
|
|
61
|
+
url: "https://images.pexels.com/photos/4693135/pexels-photo-4693135.jpeg?auto=compress&cs=tinysrgb&w=1280&h=853&dpr=2",
|
|
62
62
|
},
|
|
63
63
|
thumbnail: {
|
|
64
64
|
width: 640,
|
|
65
65
|
height: 427,
|
|
66
|
-
url: "https://
|
|
66
|
+
url: "https://images.pexels.com/photos/4693135/pexels-photo-4693135.jpeg?auto=compress&cs=tinysrgb&w=640&h=427&dpr=2",
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
69
|
];
|
|
@@ -93,7 +93,7 @@ export const createImageRouter = () => {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* GET endpoint to retrieve user credits.
|
|
96
|
-
*
|
|
96
|
+
* Returns the current number of credits available to the user.
|
|
97
97
|
*/
|
|
98
98
|
router.get(Routes.CREDITS, async (req, res) => {
|
|
99
99
|
res.status(200).send({
|
|
@@ -103,7 +103,7 @@ export const createImageRouter = () => {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* POST endpoint to purchase credits.
|
|
106
|
-
*
|
|
106
|
+
* Increments the user's credits by the number of credits in a bundle.
|
|
107
107
|
* This endpoint should be backed by proper input validation to prevent misuse.
|
|
108
108
|
*/
|
|
109
109
|
router.post(Routes.PURCHASE_CREDITS, async (req, res) => {
|
|
@@ -115,7 +115,7 @@ export const createImageRouter = () => {
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* GET endpoint to generate images based on a prompt.
|
|
118
|
-
*
|
|
118
|
+
* Generates images based on the provided prompt and adds a job to the processing queue.
|
|
119
119
|
* If there are not enough credits, it returns a 403 error.
|
|
120
120
|
* If the prompt parameter is missing, it returns a 400 error.
|
|
121
121
|
* Once the job is added to the queue, it returns a jobId that can be used to check the job status.
|
|
@@ -2,7 +2,6 @@ import * as express from "express";
|
|
|
2
2
|
import * as cors from "cors";
|
|
3
3
|
import { createBaseServer } from "../utils/backend/base_backend/create";
|
|
4
4
|
import { createImageRouter } from "./routers/image";
|
|
5
|
-
import { createAuthRouter } from "./routers/oauth";
|
|
6
5
|
|
|
7
6
|
async function main() {
|
|
8
7
|
const router = express.Router();
|
|
@@ -37,12 +36,6 @@ async function main() {
|
|
|
37
36
|
*/
|
|
38
37
|
router.use(cors());
|
|
39
38
|
|
|
40
|
-
/**
|
|
41
|
-
* Add routes for authorisation.
|
|
42
|
-
*/
|
|
43
|
-
const authRouter = createAuthRouter();
|
|
44
|
-
router.use(authRouter);
|
|
45
|
-
|
|
46
39
|
/**
|
|
47
40
|
* Add routes for image generation.
|
|
48
41
|
*/
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
{
|
|
@@ -20,20 +7,8 @@ export default [
|
|
|
20
7
|
"**/dist",
|
|
21
8
|
"**/*.d.ts",
|
|
22
9
|
"**/*.d.tsx",
|
|
23
|
-
"**/sdk",
|
|
24
|
-
"**/internal",
|
|
25
10
|
"**/*.config.*",
|
|
26
11
|
],
|
|
27
12
|
},
|
|
28
|
-
...
|
|
29
|
-
"eslint:recommended",
|
|
30
|
-
"plugin:@typescript-eslint/recommended",
|
|
31
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
32
|
-
"plugin:@typescript-eslint/strict",
|
|
33
|
-
"plugin:@typescript-eslint/stylistic",
|
|
34
|
-
"plugin:react/recommended",
|
|
35
|
-
"plugin:jest/recommended",
|
|
36
|
-
),
|
|
37
|
-
...general,
|
|
38
|
-
...i18n,
|
|
13
|
+
...canvaPlugin.configs.apps,
|
|
39
14
|
];
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"name": "gen_ai",
|
|
4
4
|
"description": "An example app demonstrating common patterns that you might use in a generative AI app.",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"extract": "formatjs extract
|
|
7
|
-
"build": "webpack --config webpack.config.
|
|
6
|
+
"extract": "formatjs extract \"src/**/*.{ts,tsx}\" --out-file dist/messages_en.json",
|
|
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,82 +12,88 @@
|
|
|
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
|
-
"test:watch": "jest --watchAll"
|
|
17
|
+
"test:watch": "jest --watchAll",
|
|
18
|
+
"postinstall": "ts-node ./scripts/copy_env.ts"
|
|
17
19
|
},
|
|
18
20
|
"dependencies": {
|
|
19
|
-
"@canva/app-i18n-kit": "^
|
|
20
|
-
"@canva/app-ui-kit": "^4.
|
|
21
|
-
"@canva/asset": "^2.
|
|
22
|
-
"@canva/design": "^2.1
|
|
23
|
-
"@canva/
|
|
24
|
-
"@canva/
|
|
25
|
-
"
|
|
21
|
+
"@canva/app-i18n-kit": "^1.0.2",
|
|
22
|
+
"@canva/app-ui-kit": "^4.9.0",
|
|
23
|
+
"@canva/asset": "^2.2.0",
|
|
24
|
+
"@canva/design": "^2.4.1",
|
|
25
|
+
"@canva/error": "^2.1.0",
|
|
26
|
+
"@canva/platform": "^2.1.0",
|
|
27
|
+
"@canva/user": "^2.1.0",
|
|
28
|
+
"cookie-parser": "1.4.7",
|
|
26
29
|
"cors": "2.8.5",
|
|
27
|
-
"html-react-parser": "5.
|
|
28
|
-
"obscenity": "0.4.
|
|
30
|
+
"html-react-parser": "5.2.2",
|
|
31
|
+
"obscenity": "0.4.1",
|
|
29
32
|
"react": "18.3.1",
|
|
30
33
|
"react-dom": "18.3.1",
|
|
31
|
-
"react-error-boundary": "4.
|
|
32
|
-
"react-intl": "6.
|
|
33
|
-
"react-router-dom": "6.
|
|
34
|
+
"react-error-boundary": "4.1.2",
|
|
35
|
+
"react-intl": "6.8.7",
|
|
36
|
+
"react-router-dom": "6.28.0"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@eslint
|
|
37
|
-
"@
|
|
38
|
-
"@formatjs/cli": "6.
|
|
39
|
-
"@formatjs/ts-transformer": "3.13.
|
|
39
|
+
"@canva/app-eslint-plugin": "^1.0.0-beta.3",
|
|
40
|
+
"@canva/cli": ">= 0.0.1-beta.13 < 0.0.2",
|
|
41
|
+
"@formatjs/cli": "6.3.15",
|
|
42
|
+
"@formatjs/ts-transformer": "3.13.27",
|
|
40
43
|
"@ngrok/ngrok": "1.4.1",
|
|
44
|
+
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
|
41
45
|
"@svgr/webpack": "8.1.0",
|
|
46
|
+
"@testing-library/react": "16.1.0",
|
|
42
47
|
"@types/debug": "4.1.12",
|
|
43
48
|
"@types/express": "4.17.21",
|
|
44
|
-
"@types/express-serve-static-core": "4.19.
|
|
45
|
-
"@types/jest": "29.5.
|
|
46
|
-
"@types/jsonwebtoken": "9.0.
|
|
49
|
+
"@types/express-serve-static-core": "4.19.6",
|
|
50
|
+
"@types/jest": "29.5.14",
|
|
51
|
+
"@types/jsonwebtoken": "9.0.7",
|
|
47
52
|
"@types/node": "20.10.0",
|
|
48
|
-
"@types/node-fetch": "2.6.
|
|
53
|
+
"@types/node-fetch": "2.6.12",
|
|
49
54
|
"@types/node-forge": "1.3.11",
|
|
50
55
|
"@types/nodemon": "1.19.6",
|
|
51
56
|
"@types/prompts": "2.4.9",
|
|
52
|
-
"@types/react": "18.3.
|
|
53
|
-
"@types/react-dom": "18.3.
|
|
57
|
+
"@types/react": "18.3.12",
|
|
58
|
+
"@types/react-dom": "18.3.1",
|
|
59
|
+
"@types/webpack": "5.28.5",
|
|
60
|
+
"@types/webpack-dev-server": "4.7.1",
|
|
54
61
|
"@types/webpack-env": "1.18.5",
|
|
55
|
-
"@typescript-eslint/eslint-plugin": "8.2.0",
|
|
56
|
-
"@typescript-eslint/parser": "8.2.0",
|
|
57
62
|
"chalk": "4.1.2",
|
|
58
63
|
"cli-table3": "0.6.5",
|
|
59
64
|
"css-loader": "7.1.2",
|
|
60
65
|
"css-modules-typescript-loader": "4.0.1",
|
|
61
|
-
"cssnano": "7.0.
|
|
62
|
-
"debug": "4.
|
|
63
|
-
"dotenv": "16.4.
|
|
64
|
-
"eslint": "8.57.1",
|
|
65
|
-
"eslint-plugin-formatjs": "4.13.3",
|
|
66
|
-
"eslint-plugin-jest": "28.8.0",
|
|
67
|
-
"eslint-plugin-react": "7.35.0",
|
|
66
|
+
"cssnano": "7.0.6",
|
|
67
|
+
"debug": "4.4.0",
|
|
68
|
+
"dotenv": "16.4.7",
|
|
68
69
|
"exponential-backoff": "3.1.1",
|
|
69
|
-
"express": "4.
|
|
70
|
+
"express": "4.21.2",
|
|
70
71
|
"express-basic-auth": "1.2.1",
|
|
71
72
|
"jest": "29.7.0",
|
|
73
|
+
"jest-css-modules-transform": "4.4.2",
|
|
74
|
+
"jest-environment-jsdom": "29.7.0",
|
|
72
75
|
"jsonwebtoken": "9.0.2",
|
|
73
76
|
"jwks-rsa": "3.1.0",
|
|
74
|
-
"mini-css-extract-plugin": "2.9.
|
|
77
|
+
"mini-css-extract-plugin": "2.9.2",
|
|
75
78
|
"node-fetch": "3.3.2",
|
|
76
79
|
"node-forge": "1.3.1",
|
|
77
80
|
"nodemon": "3.0.1",
|
|
81
|
+
"open": "8.4.2",
|
|
78
82
|
"postcss-loader": "8.1.1",
|
|
79
|
-
"prettier": "3.
|
|
83
|
+
"prettier": "3.4.2",
|
|
80
84
|
"prompts": "2.4.2",
|
|
85
|
+
"react-refresh": "0.16.0",
|
|
81
86
|
"style-loader": "4.0.0",
|
|
82
|
-
"terser-webpack-plugin": "5.3.
|
|
83
|
-
"
|
|
84
|
-
"ts-
|
|
87
|
+
"terser-webpack-plugin": "5.3.11",
|
|
88
|
+
"tree-kill": "1.2.2",
|
|
89
|
+
"ts-jest": "29.2.5",
|
|
90
|
+
"ts-loader": "9.5.2",
|
|
85
91
|
"ts-node": "10.9.2",
|
|
86
92
|
"typescript": "5.5.4",
|
|
87
93
|
"url-loader": "4.1.1",
|
|
88
|
-
"webpack": "5.
|
|
94
|
+
"webpack": "5.97.1",
|
|
89
95
|
"webpack-cli": "5.1.4",
|
|
90
|
-
"webpack-dev-server": "5.0
|
|
96
|
+
"webpack-dev-server": "5.2.0",
|
|
91
97
|
"yargs": "17.7.2"
|
|
92
98
|
}
|
|
93
99
|
}
|
|
@@ -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,12 +1,14 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import type { Context } from "./context";
|
|
3
3
|
import * as chalk from "chalk";
|
|
4
|
-
import { buildConfig } from "../../webpack.config
|
|
4
|
+
import { buildConfig } from "../../webpack.config";
|
|
5
5
|
import * as ngrok from "@ngrok/ngrok";
|
|
6
6
|
import * as nodemon from "nodemon";
|
|
7
7
|
import * as Table from "cli-table3";
|
|
8
8
|
import * as webpack from "webpack";
|
|
9
9
|
import * as WebpackDevServer from "webpack-dev-server";
|
|
10
|
+
import * as open from "open";
|
|
11
|
+
import { generatePreviewUrl } from "@canva/cli";
|
|
10
12
|
import type { Certificate } from "../ssl/ssl";
|
|
11
13
|
import { createOrRetrieveCertificate } from "../ssl/ssl";
|
|
12
14
|
|
|
@@ -46,12 +48,18 @@ export class AppRunner {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
const table = new Table(
|
|
51
|
+
const table = new Table({
|
|
52
|
+
colWidths: [30, 80],
|
|
53
|
+
wordWrap: true,
|
|
54
|
+
wrapOnWordBoundary: true,
|
|
55
|
+
});
|
|
50
56
|
|
|
51
57
|
const server = await this.runWebpackDevServer(ctx, table, cert);
|
|
52
58
|
|
|
53
59
|
await this.maybeRunBackendServer(ctx, table, cert, server);
|
|
54
60
|
|
|
61
|
+
await this.generateAndOpenPreviewUrl(ctx.openPreview, table);
|
|
62
|
+
|
|
55
63
|
console.log(table.toString(), "\n");
|
|
56
64
|
|
|
57
65
|
console.log(
|
|
@@ -161,4 +169,33 @@ export class AppRunner {
|
|
|
161
169
|
|
|
162
170
|
return server;
|
|
163
171
|
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Calls the Canva CLI to generate a preview URL for the app
|
|
175
|
+
*/
|
|
176
|
+
private readonly generateAndOpenPreviewUrl = async (
|
|
177
|
+
openPreview: boolean,
|
|
178
|
+
table: Table.Table,
|
|
179
|
+
) => {
|
|
180
|
+
const previewCellHeader = { content: "Preview your app in Canva" };
|
|
181
|
+
|
|
182
|
+
const generatePreviewResult = await generatePreviewUrl();
|
|
183
|
+
|
|
184
|
+
if (!generatePreviewResult.success) {
|
|
185
|
+
table.push([
|
|
186
|
+
previewCellHeader,
|
|
187
|
+
{ content: warnChalk(generatePreviewResult.message) },
|
|
188
|
+
]);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
table.push([
|
|
193
|
+
previewCellHeader,
|
|
194
|
+
{ content: "Preview URL", href: generatePreviewResult.data },
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if (openPreview) {
|
|
198
|
+
open(generatePreviewResult.data);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
164
201
|
}
|
|
@@ -3,8 +3,10 @@ import * as path from "path";
|
|
|
3
3
|
|
|
4
4
|
interface CliArgs {
|
|
5
5
|
example?: string;
|
|
6
|
-
useHttps
|
|
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
|
}
|
|
@@ -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",
|