@canva/cli 1.9.0 → 1.11.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/CHANGELOG.md +23 -0
- package/cli.js +472 -463
- package/lib/cjs/index.cjs +2 -2
- package/lib/esm/index.mjs +2 -2
- package/lib/index.d.ts +2 -0
- package/package.json +1 -1
- package/templates/base/package.json +3 -3
- package/templates/base/styles/components.css +18 -0
- package/templates/base/tsconfig.json +5 -3
- package/templates/base/utils/backend/base_backend/create.ts +3 -3
- package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
- package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/base/webpack.config.ts +1 -1
- package/templates/common/.env.template +1 -1
- package/templates/common/utils/backend/base_backend/create.ts +3 -3
- package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/dam/backend/routers/dam.ts +35 -17
- package/templates/dam/canva-app.json +5 -0
- package/templates/dam/package.json +4 -3
- package/templates/dam/src/index.tsx +3 -21
- package/templates/dam/src/intents/design_editor/index.tsx +25 -0
- package/templates/dam/tsconfig.json +5 -3
- package/templates/dam/utils/backend/base_backend/create.ts +3 -3
- package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/dam/webpack.config.ts +1 -1
- package/templates/data_connector/package.json +4 -3
- package/templates/data_connector/src/api/data_sources/designs.tsx +1 -1
- package/templates/data_connector/src/api/data_sources/templates.tsx +1 -1
- package/templates/data_connector/src/components/header.tsx +1 -1
- package/templates/data_connector/src/index.tsx +2 -66
- package/templates/data_connector/src/{app.tsx → intents/data_connector/app.tsx} +3 -3
- package/templates/data_connector/src/{entrypoint.tsx → intents/data_connector/entrypoint.tsx} +5 -5
- package/templates/data_connector/src/{home.tsx → intents/data_connector/home.tsx} +1 -1
- package/templates/data_connector/src/intents/data_connector/index.tsx +56 -0
- package/templates/data_connector/src/pages/error.tsx +1 -1
- package/templates/data_connector/src/pages/login.tsx +1 -1
- package/templates/data_connector/src/routes/protected_route.tsx +1 -1
- package/templates/data_connector/src/routes/routes.tsx +3 -3
- package/templates/data_connector/src/utils/data_table.ts +2 -1
- package/templates/data_connector/src/utils/tests/data_table.test.ts +2 -2
- package/templates/data_connector/styles/components.css +18 -0
- package/templates/data_connector/tsconfig.json +5 -3
- package/templates/data_connector/webpack.config.ts +1 -1
- package/templates/gen_ai/backend/routers/image.ts +4 -6
- package/templates/gen_ai/canva-app.json +5 -0
- package/templates/gen_ai/package.json +5 -3
- package/templates/gen_ai/src/components/footer.tsx +1 -1
- package/templates/gen_ai/src/components/image_grid.tsx +6 -2
- package/templates/gen_ai/src/components/loading_results.tsx +1 -1
- package/templates/gen_ai/src/components/prompt_input.tsx +5 -2
- package/templates/gen_ai/src/index.tsx +3 -14
- package/templates/gen_ai/src/{app.tsx → intents/design_editor/app.tsx} +3 -3
- package/templates/gen_ai/src/{home.tsx → intents/design_editor/home.tsx} +1 -1
- package/templates/gen_ai/src/intents/design_editor/index.tsx +17 -0
- package/templates/gen_ai/src/pages/error.tsx +1 -1
- package/templates/gen_ai/src/routes/routes.tsx +2 -2
- package/templates/gen_ai/styles/components.css +18 -0
- package/templates/gen_ai/tsconfig.json +5 -3
- package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
- package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
- package/templates/gen_ai/webpack.config.ts +1 -1
- package/templates/hello_world/canva-app.json +5 -0
- package/templates/hello_world/package.json +5 -3
- package/templates/hello_world/src/index.tsx +3 -21
- package/templates/hello_world/src/{app.tsx → intents/design_editor/app.tsx} +26 -3
- package/templates/hello_world/src/intents/design_editor/index.tsx +25 -0
- package/templates/hello_world/src/{tests → intents/design_editor/tests}/app.tests.tsx +20 -14
- package/templates/hello_world/styles/components.css +18 -0
- package/templates/hello_world/tsconfig.json +5 -3
- package/templates/hello_world/webpack.config.ts +1 -1
- package/templates/optional/AGENTS.md +80 -2
- package/templates/optional/CLAUDE.md +80 -2
- package/templates/base/utils/use_add_element.ts +0 -48
- package/templates/base/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/table_wrapper.ts +0 -477
- package/templates/common/utils/use_add_element.ts +0 -48
- package/templates/common/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/use_overlay_hook.ts +0 -74
- package/templates/common/utils/use_selection_hook.ts +0 -37
- package/templates/hello_world/utils/use_add_element.ts +0 -48
- package/templates/hello_world/utils/use_feature_support.ts +0 -28
- /package/templates/dam/src/{adapter.ts → intents/design_editor/adapter.ts} +0 -0
- /package/templates/dam/src/{app.tsx → intents/design_editor/app.tsx} +0 -0
- /package/templates/dam/src/{config.ts → intents/design_editor/config.ts} +0 -0
- /package/templates/dam/src/{index.css → intents/design_editor/index.css} +0 -0
- /package/templates/data_connector/src/{paths.ts → routes/paths.ts} +0 -0
- /package/templates/gen_ai/src/{paths.ts → routes/paths.ts} +0 -0
- /package/templates/hello_world/src/{tests → intents/design_editor/tests}/__snapshots__/app.tests.tsx.snap +0 -0
|
@@ -196,7 +196,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
196
196
|
return {};
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
const { port, enableHmr, appOrigin,
|
|
199
|
+
const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
|
|
200
200
|
options;
|
|
201
201
|
const host = "localhost";
|
|
202
202
|
|
|
@@ -29,8 +29,9 @@ The app, once submitted to Canva, will be rendered in a sandboxed iframe within
|
|
|
29
29
|
## Architecture
|
|
30
30
|
|
|
31
31
|
- **Main App**:
|
|
32
|
-
- `src/index.tsx` - Main application
|
|
33
|
-
- `src/
|
|
32
|
+
- `src/index.tsx` - Main application React application entry point.
|
|
33
|
+
- `src/intents/[intent]/index.tsx` - Every intent that the app implements should be contained in the intents path.
|
|
34
|
+
- `src/intents/[intent]/app.tsx` - Main application component for each intent, which should be split into smaller components as needed, following React best practices.
|
|
34
35
|
- `src/styles/component.css` - Main application styles.
|
|
35
36
|
- `utils/` - Utility functions and helpers, which can be used across the app.
|
|
36
37
|
- `scripts/` - Scripts for building, and running the app, should not contain any business logic.
|
|
@@ -74,3 +75,80 @@ The app, once submitted to Canva, will be rendered in a sandboxed iframe within
|
|
|
74
75
|
- **Required**: AI agents should verify and encourage the user to enable Canva Dev MCP server.
|
|
75
76
|
- **Documentation**: <https://www.canva.dev/docs/apps/mcp-server.md>
|
|
76
77
|
- **If not setup**: Read the docs and prompt user to configure the MCP server for enhanced Canva APIs assistance.
|
|
78
|
+
|
|
79
|
+
## Intent-based architecture
|
|
80
|
+
|
|
81
|
+
All apps should follow the intent-based architecture pattern. Each root index file should call the prepare function for each enabled intent. Each intent index file should implement the full intent contract.
|
|
82
|
+
|
|
83
|
+
### File structure
|
|
84
|
+
|
|
85
|
+
Organize code with a dedicated folder for each intent under `src/intents/`:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── intents/
|
|
90
|
+
│ ├── design_editor/
|
|
91
|
+
│ │ ├── index.tsx
|
|
92
|
+
│ │ └── editor_app.tsx
|
|
93
|
+
│ └── content_publisher/
|
|
94
|
+
│ ├── index.tsx
|
|
95
|
+
│ ├── preview_ui.tsx
|
|
96
|
+
│ └── setting_ui.tsx
|
|
97
|
+
└── index.tsx
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Root index file
|
|
101
|
+
|
|
102
|
+
The root `src/index.tsx` should be minimal, containing only:
|
|
103
|
+
|
|
104
|
+
- Importing each prepare function
|
|
105
|
+
- Importing each intent
|
|
106
|
+
- The prepare call for each intent implementation.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { prepareContentPublisher } from "@canva/intents/content";
|
|
110
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
111
|
+
|
|
112
|
+
import contentPublisher from "./intents/content_publisher";
|
|
113
|
+
import designEditor from "./intents/design_editor";
|
|
114
|
+
|
|
115
|
+
prepareContentPublisher(contentPublisher);
|
|
116
|
+
prepareDesignEditor(designEditor);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Intent index files
|
|
120
|
+
|
|
121
|
+
Each intent's `index.tsx` file should:
|
|
122
|
+
|
|
123
|
+
- Import dependencies, including the app ui kit css.
|
|
124
|
+
- Implement the full contract for the intent and use this as the default export from the file.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// src/intents/design_editor/index.tsx
|
|
128
|
+
import "@canva/app-ui-kit/styles.css";
|
|
129
|
+
import type { DesignEditorIntent } from "@canva/intents/design";
|
|
130
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
131
|
+
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
132
|
+
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
133
|
+
import { createRoot } from "react-dom/client";
|
|
134
|
+
import { App } from "./app";
|
|
135
|
+
|
|
136
|
+
async function render() {
|
|
137
|
+
const root = createRoot(document.getElementById("root") as Element);
|
|
138
|
+
|
|
139
|
+
root.render(
|
|
140
|
+
<AppI18nProvider>
|
|
141
|
+
<AppUiProvider>
|
|
142
|
+
<App />
|
|
143
|
+
</AppUiProvider>
|
|
144
|
+
</AppI18nProvider>,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const designEditor: DesignEditorIntent = { render };
|
|
149
|
+
export default designEditor;
|
|
150
|
+
|
|
151
|
+
if (module.hot) {
|
|
152
|
+
module.hot.accept("./app", render);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
@@ -29,8 +29,9 @@ The app, once submitted to Canva, will be rendered in a sandboxed iframe within
|
|
|
29
29
|
## Architecture
|
|
30
30
|
|
|
31
31
|
- **Main App**:
|
|
32
|
-
- `src/index.tsx` - Main application
|
|
33
|
-
- `src/
|
|
32
|
+
- `src/index.tsx` - Main application React application entry point.
|
|
33
|
+
- `src/intents/[intent]/index.tsx` - Every intent that the app implements should be contained in the intents path.
|
|
34
|
+
- `src/intents/[intent]/app.tsx` - Main application component for each intent, which should be split into smaller components as needed, following React best practices.
|
|
34
35
|
- `src/styles/component.css` - Main application styles.
|
|
35
36
|
- `utils/` - Utility functions and helpers, which can be used across the app.
|
|
36
37
|
- `scripts/` - Scripts for building, and running the app, should not contain any business logic.
|
|
@@ -74,3 +75,80 @@ The app, once submitted to Canva, will be rendered in a sandboxed iframe within
|
|
|
74
75
|
- **Required**: AI agents should verify and encourage the user to enable Canva Dev MCP server.
|
|
75
76
|
- **Documentation**: <https://www.canva.dev/docs/apps/mcp-server.md>
|
|
76
77
|
- **If not setup**: Read the docs and prompt user to configure the MCP server for enhanced Canva APIs assistance.
|
|
78
|
+
|
|
79
|
+
## Intent-based architecture
|
|
80
|
+
|
|
81
|
+
All apps should follow the intent-based architecture pattern. Each root index file should call the prepare function for each enabled intent. Each intent index file should implement the full intent contract.
|
|
82
|
+
|
|
83
|
+
### File structure
|
|
84
|
+
|
|
85
|
+
Organize code with a dedicated folder for each intent under `src/intents/`:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── intents/
|
|
90
|
+
│ ├── design_editor/
|
|
91
|
+
│ │ ├── index.tsx
|
|
92
|
+
│ │ └── editor_app.tsx
|
|
93
|
+
│ └── content_publisher/
|
|
94
|
+
│ ├── index.tsx
|
|
95
|
+
│ ├── preview_ui.tsx
|
|
96
|
+
│ └── setting_ui.tsx
|
|
97
|
+
└── index.tsx
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Root index file
|
|
101
|
+
|
|
102
|
+
The root `src/index.tsx` should be minimal, containing only:
|
|
103
|
+
|
|
104
|
+
- Importing each prepare function
|
|
105
|
+
- Importing each intent
|
|
106
|
+
- The prepare call for each intent implementation.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { prepareContentPublisher } from "@canva/intents/content";
|
|
110
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
111
|
+
|
|
112
|
+
import contentPublisher from "./intents/content_publisher";
|
|
113
|
+
import designEditor from "./intents/design_editor";
|
|
114
|
+
|
|
115
|
+
prepareContentPublisher(contentPublisher);
|
|
116
|
+
prepareDesignEditor(designEditor);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Intent index files
|
|
120
|
+
|
|
121
|
+
Each intent's `index.tsx` file should:
|
|
122
|
+
|
|
123
|
+
- Import dependencies, including the app ui kit css.
|
|
124
|
+
- Implement the full contract for the intent and use this as the default export from the file.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// src/intents/design_editor/index.tsx
|
|
128
|
+
import "@canva/app-ui-kit/styles.css";
|
|
129
|
+
import type { DesignEditorIntent } from "@canva/intents/design";
|
|
130
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
131
|
+
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
132
|
+
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
133
|
+
import { createRoot } from "react-dom/client";
|
|
134
|
+
import { App } from "./app";
|
|
135
|
+
|
|
136
|
+
async function render() {
|
|
137
|
+
const root = createRoot(document.getElementById("root") as Element);
|
|
138
|
+
|
|
139
|
+
root.render(
|
|
140
|
+
<AppI18nProvider>
|
|
141
|
+
<AppUiProvider>
|
|
142
|
+
<App />
|
|
143
|
+
</AppUiProvider>
|
|
144
|
+
</AppI18nProvider>,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const designEditor: DesignEditorIntent = { render };
|
|
149
|
+
export default designEditor;
|
|
150
|
+
|
|
151
|
+
if (module.hot) {
|
|
152
|
+
module.hot.accept("./app", render);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EmbedElement,
|
|
3
|
-
ImageElement,
|
|
4
|
-
RichtextElement,
|
|
5
|
-
TableElement,
|
|
6
|
-
TextElement,
|
|
7
|
-
VideoElement,
|
|
8
|
-
} from "@canva/design";
|
|
9
|
-
import { addElementAtCursor, addElementAtPoint } from "@canva/design";
|
|
10
|
-
import { features } from "@canva/platform";
|
|
11
|
-
import { useEffect, useState } from "react";
|
|
12
|
-
import { useFeatureSupport } from "./use_feature_support";
|
|
13
|
-
|
|
14
|
-
type AddElementParams =
|
|
15
|
-
| ImageElement
|
|
16
|
-
| VideoElement
|
|
17
|
-
| EmbedElement
|
|
18
|
-
| TextElement
|
|
19
|
-
| RichtextElement
|
|
20
|
-
| TableElement;
|
|
21
|
-
|
|
22
|
-
export const useAddElement = () => {
|
|
23
|
-
const isSupported = useFeatureSupport();
|
|
24
|
-
|
|
25
|
-
// Store a wrapped addElement function that checks feature support
|
|
26
|
-
const [addElement, setAddElement] = useState(() => {
|
|
27
|
-
return (element: AddElementParams) => {
|
|
28
|
-
if (features.isSupported(addElementAtPoint)) {
|
|
29
|
-
return addElementAtPoint(element);
|
|
30
|
-
} else if (features.isSupported(addElementAtCursor)) {
|
|
31
|
-
return addElementAtCursor(element);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const addElement = (element: AddElementParams) => {
|
|
38
|
-
if (isSupported(addElementAtPoint)) {
|
|
39
|
-
return addElementAtPoint(element);
|
|
40
|
-
} else if (isSupported(addElementAtCursor)) {
|
|
41
|
-
return addElementAtCursor(element);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
setAddElement(() => addElement);
|
|
45
|
-
}, [isSupported]);
|
|
46
|
-
|
|
47
|
-
return addElement;
|
|
48
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { features } from "@canva/platform";
|
|
2
|
-
import type { Feature } from "@canva/platform";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* This hook allows re-rendering of a React component whenever
|
|
7
|
-
* the state of feature support changes in Canva.
|
|
8
|
-
*
|
|
9
|
-
* @returns isSupported - callback to inspect a Canva SDK method.
|
|
10
|
-
**/
|
|
11
|
-
export function useFeatureSupport() {
|
|
12
|
-
// Store a wrapped function that checks feature support
|
|
13
|
-
const [isSupported, setIsSupported] = useState(() => {
|
|
14
|
-
return (...args: Feature[]) => features.isSupported(...args);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
// create new function ref when feature support changes to trigger
|
|
19
|
-
// re-render
|
|
20
|
-
return features.registerOnSupportChange(() => {
|
|
21
|
-
setIsSupported(() => {
|
|
22
|
-
return (...args: Feature[]) => features.isSupported(...args);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
return isSupported;
|
|
28
|
-
}
|