@canva/cli 0.0.1-beta.1

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.
Files changed (109) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +206 -0
  3. package/cli.js +566 -0
  4. package/package.json +30 -0
  5. package/templates/base/backend/database/database.ts +42 -0
  6. package/templates/base/backend/routers/auth.ts +285 -0
  7. package/templates/base/declarations/declarations.d.ts +29 -0
  8. package/templates/base/eslint.config.mjs +309 -0
  9. package/templates/base/package.json +83 -0
  10. package/templates/base/scripts/ssl/ssl.ts +131 -0
  11. package/templates/base/scripts/start/app_runner.ts +164 -0
  12. package/templates/base/scripts/start/context.ts +165 -0
  13. package/templates/base/scripts/start/start.ts +35 -0
  14. package/templates/base/styles/components.css +38 -0
  15. package/templates/base/tsconfig.json +54 -0
  16. package/templates/base/utils/backend/base_backend/create.ts +104 -0
  17. package/templates/base/utils/backend/jwt_middleware/index.ts +1 -0
  18. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  19. package/templates/base/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  20. package/templates/base/webpack.config.cjs +270 -0
  21. package/templates/common/.env.template +6 -0
  22. package/templates/common/.gitignore.template +9 -0
  23. package/templates/common/LICENSE.md +48 -0
  24. package/templates/common/README.md +250 -0
  25. package/templates/common/jest.config.mjs +8 -0
  26. package/templates/dam/backend/database/database.ts +42 -0
  27. package/templates/dam/backend/routers/auth.ts +285 -0
  28. package/templates/dam/backend/routers/dam.ts +86 -0
  29. package/templates/dam/backend/server.ts +65 -0
  30. package/templates/dam/declarations/declarations.d.ts +29 -0
  31. package/templates/dam/eslint.config.mjs +309 -0
  32. package/templates/dam/package.json +90 -0
  33. package/templates/dam/scripts/ssl/ssl.ts +131 -0
  34. package/templates/dam/scripts/start/app_runner.ts +164 -0
  35. package/templates/dam/scripts/start/context.ts +165 -0
  36. package/templates/dam/scripts/start/start.ts +35 -0
  37. package/templates/dam/src/adapter.ts +44 -0
  38. package/templates/dam/src/app.tsx +147 -0
  39. package/templates/dam/src/config.ts +95 -0
  40. package/templates/dam/src/index.css +10 -0
  41. package/templates/dam/src/index.tsx +22 -0
  42. package/templates/dam/tsconfig.json +54 -0
  43. package/templates/dam/utils/backend/base_backend/create.ts +104 -0
  44. package/templates/dam/utils/backend/jwt_middleware/index.ts +1 -0
  45. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  46. package/templates/dam/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  47. package/templates/dam/webpack.config.cjs +270 -0
  48. package/templates/gen_ai/README.md +27 -0
  49. package/templates/gen_ai/backend/database/database.ts +42 -0
  50. package/templates/gen_ai/backend/routers/auth.ts +285 -0
  51. package/templates/gen_ai/backend/routers/image.ts +234 -0
  52. package/templates/gen_ai/backend/server.ts +56 -0
  53. package/templates/gen_ai/declarations/declarations.d.ts +29 -0
  54. package/templates/gen_ai/eslint.config.mjs +309 -0
  55. package/templates/gen_ai/package.json +92 -0
  56. package/templates/gen_ai/scripts/ssl/ssl.ts +131 -0
  57. package/templates/gen_ai/scripts/start/app_runner.ts +164 -0
  58. package/templates/gen_ai/scripts/start/context.ts +165 -0
  59. package/templates/gen_ai/scripts/start/start.ts +35 -0
  60. package/templates/gen_ai/src/api/api.ts +228 -0
  61. package/templates/gen_ai/src/api/index.ts +1 -0
  62. package/templates/gen_ai/src/app.tsx +13 -0
  63. package/templates/gen_ai/src/components/app_error.tsx +18 -0
  64. package/templates/gen_ai/src/components/footer.messages.ts +53 -0
  65. package/templates/gen_ai/src/components/footer.tsx +157 -0
  66. package/templates/gen_ai/src/components/image_grid.tsx +96 -0
  67. package/templates/gen_ai/src/components/index.ts +8 -0
  68. package/templates/gen_ai/src/components/loading_results.tsx +169 -0
  69. package/templates/gen_ai/src/components/logged_in_status.tsx +44 -0
  70. package/templates/gen_ai/src/components/prompt_input.messages.ts +14 -0
  71. package/templates/gen_ai/src/components/prompt_input.tsx +149 -0
  72. package/templates/gen_ai/src/components/remaining_credits.tsx +75 -0
  73. package/templates/gen_ai/src/components/report_box.tsx +53 -0
  74. package/templates/gen_ai/src/config.ts +21 -0
  75. package/templates/gen_ai/src/context/app_context.tsx +174 -0
  76. package/templates/gen_ai/src/context/context.messages.ts +41 -0
  77. package/templates/gen_ai/src/context/index.ts +2 -0
  78. package/templates/gen_ai/src/context/use_app_context.ts +17 -0
  79. package/templates/gen_ai/src/index.tsx +31 -0
  80. package/templates/gen_ai/src/pages/error.tsx +41 -0
  81. package/templates/gen_ai/src/pages/generate.tsx +9 -0
  82. package/templates/gen_ai/src/pages/index.ts +3 -0
  83. package/templates/gen_ai/src/pages/results.tsx +31 -0
  84. package/templates/gen_ai/src/routes/index.ts +1 -0
  85. package/templates/gen_ai/src/routes/routes.tsx +26 -0
  86. package/templates/gen_ai/src/services/auth.tsx +31 -0
  87. package/templates/gen_ai/src/services/index.ts +1 -0
  88. package/templates/gen_ai/src/utils/index.ts +1 -0
  89. package/templates/gen_ai/src/utils/obscenity_filter.ts +33 -0
  90. package/templates/gen_ai/styles/components.css +38 -0
  91. package/templates/gen_ai/styles/utils.css +3 -0
  92. package/templates/gen_ai/tsconfig.json +54 -0
  93. package/templates/gen_ai/utils/backend/base_backend/create.ts +104 -0
  94. package/templates/gen_ai/utils/backend/jwt_middleware/index.ts +1 -0
  95. package/templates/gen_ai/utils/backend/jwt_middleware/jwt_middleware.ts +229 -0
  96. package/templates/gen_ai/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +630 -0
  97. package/templates/gen_ai/webpack.config.cjs +270 -0
  98. package/templates/hello_world/declarations/declarations.d.ts +29 -0
  99. package/templates/hello_world/eslint.config.mjs +309 -0
  100. package/templates/hello_world/package.json +73 -0
  101. package/templates/hello_world/scripts/ssl/ssl.ts +131 -0
  102. package/templates/hello_world/scripts/start/app_runner.ts +164 -0
  103. package/templates/hello_world/scripts/start/context.ts +165 -0
  104. package/templates/hello_world/scripts/start/start.ts +35 -0
  105. package/templates/hello_world/src/app.tsx +41 -0
  106. package/templates/hello_world/src/index.tsx +22 -0
  107. package/templates/hello_world/styles/components.css +38 -0
  108. package/templates/hello_world/tsconfig.json +54 -0
  109. package/templates/hello_world/webpack.config.cjs +270 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * DISCLAIMER:
3
+ * This file contains a demonstration of how to implement a simple API with Express.js for educational purposes only.
4
+ * It is NOT SUITABLE for use in a production environment. It lacks many essential features such as error handling,
5
+ * input validation, authentication, and security measures. Additionally, the in-memory job queue is for illustrative
6
+ * purposes only and is not efficient or scalable. For production use, consider using robust libraries and frameworks,
7
+ * implementing proper error handling, security measures, and using appropriate database and job queue solutions.
8
+ * Use this code as a learning resource, but do not deploy it in a real backend without significant modifications
9
+ * to ensure reliability, security, and scalability.
10
+ */
11
+ import * as express from "express";
12
+
13
+ interface ImageResponse {
14
+ fullsize: { width: number; height: number; url: string };
15
+ thumbnail: { width: number; height: number; url: string };
16
+ }
17
+
18
+ // Array of placeholder image URLs.
19
+ // In a real-world scenario, these URLs would point to dynamically generated images.
20
+ const imageUrls: ImageResponse[] = [
21
+ {
22
+ fullsize: {
23
+ width: 1280,
24
+ height: 853,
25
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/11/youtube-background-7764170_1280.jpg",
26
+ },
27
+ thumbnail: {
28
+ width: 640,
29
+ height: 427,
30
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/11/youtube-background-7764170_640.jpg",
31
+ },
32
+ },
33
+ {
34
+ fullsize: {
35
+ width: 1280,
36
+ height: 853,
37
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/12/youtube-background-7764172_1280.jpg",
38
+ },
39
+ thumbnail: {
40
+ width: 640,
41
+ height: 427,
42
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/12/youtube-background-7764172_640.jpg",
43
+ },
44
+ },
45
+ {
46
+ fullsize: {
47
+ width: 1280,
48
+ height: 853,
49
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/07/colorful-7764162_1280.jpg",
50
+ },
51
+ thumbnail: {
52
+ width: 640,
53
+ height: 427,
54
+ url: "https://cdn.pixabay.com/photo/2023/02/03/05/07/colorful-7764162_640.jpg",
55
+ },
56
+ },
57
+ {
58
+ fullsize: {
59
+ width: 1280,
60
+ height: 853,
61
+ url: "https://cdn.pixabay.com/photo/2023/02/03/04/57/swirls-7764142_1280.jpg",
62
+ },
63
+ thumbnail: {
64
+ width: 640,
65
+ height: 427,
66
+ url: "https://cdn.pixabay.com/photo/2023/02/03/04/57/swirls-7764142_640.jpg",
67
+ },
68
+ },
69
+ ];
70
+
71
+ export const createImageRouter = () => {
72
+ const enum Routes {
73
+ CREDITS = "/api/credits",
74
+ PURCHASE_CREDITS = "/api/purchase-credits",
75
+ QUEUE_IMAGE_GENERATION = "/api/queue-image-generation",
76
+ JOB_STATUS = "/api/job-status",
77
+ CANCEL_JOB = "/api/job-status/cancel",
78
+ }
79
+
80
+ const router = express.Router();
81
+ const jobQueue: {
82
+ jobId: string;
83
+ prompt: string;
84
+ timeoutId: NodeJS.Timeout;
85
+ }[] = [];
86
+ const completedJobs: Record<string, ImageResponse[]> = {};
87
+ const cancelledJobs: { jobId: string }[] = [];
88
+
89
+ // Initial credit allocation for users, which decreases with each use.
90
+ // Users receive 10 credits initially and can purchase additional credits in bundles.
91
+ let credits = 10;
92
+ const CREDITS_IN_BUNDLE = 10;
93
+
94
+ /**
95
+ * GET endpoint to retrieve user credits.
96
+ * Requires authentication. Returns the current number of credits available to the user.
97
+ */
98
+ router.get(Routes.CREDITS, async (req, res) => {
99
+ res.status(200).send({
100
+ credits,
101
+ });
102
+ });
103
+
104
+ /**
105
+ * POST endpoint to purchase credits.
106
+ * Requires authentication. Increments the user's credits by the number of credits in a bundle.
107
+ * This endpoint should be backed by proper input validation to prevent misuse.
108
+ */
109
+ router.post(Routes.PURCHASE_CREDITS, async (req, res) => {
110
+ credits += CREDITS_IN_BUNDLE;
111
+ res.status(200).send({
112
+ credits,
113
+ });
114
+ });
115
+
116
+ /**
117
+ * GET endpoint to generate images based on a prompt.
118
+ * Requires authentication. Generates images based on the provided prompt and adds a job to the processing queue.
119
+ * If there are not enough credits, it returns a 403 error.
120
+ * If the prompt parameter is missing, it returns a 400 error.
121
+ * Once the job is added to the queue, it returns a jobId that can be used to check the job status.
122
+ * Note: The job processing time is simulated to be 5 seconds.
123
+ */
124
+ router.get(Routes.QUEUE_IMAGE_GENERATION, async (req, res) => {
125
+ if (credits <= 0) {
126
+ return res
127
+ .status(403)
128
+ .send("Not enough credits required to generate images.");
129
+ }
130
+
131
+ const prompt = req.query.prompt as string;
132
+ if (!prompt) {
133
+ return res.status(400).send("Missing prompt parameter.");
134
+ }
135
+
136
+ const jobId = generateJobId();
137
+
138
+ const timeoutId = setTimeout(() => {
139
+ const index = jobQueue.findIndex((job) => job.jobId === jobId);
140
+ if (index !== -1) {
141
+ jobQueue.splice(index, 1);
142
+ completedJobs[jobId] = imageUrls.map((image) => {
143
+ return { ...image, label: prompt };
144
+ });
145
+
146
+ // Reduce credits by 1 when images are successfully generated
147
+ credits -= 1;
148
+ }
149
+ }, 5000); // Simulating 5 seconds processing time
150
+
151
+ // Add the job to the jobQueue along with the timeoutId
152
+ jobQueue.push({ jobId, prompt, timeoutId });
153
+
154
+ res.status(200).send({
155
+ jobId,
156
+ });
157
+ });
158
+
159
+ /**
160
+ * GET endpoint to check the status of a job.
161
+ * Retrieves the status of a job identified by its jobId parameter.
162
+ * If the job is completed, it returns the images generated by the job.
163
+ * If the job is still in the processing queue, it returns "processing".
164
+ * If the job has been cancelled, it returns "cancelled".
165
+ * If the job is not found, it returns a 404 error.
166
+ */
167
+ router.get(Routes.JOB_STATUS, async (req, res) => {
168
+ const jobId = req.query.jobId as string;
169
+
170
+ if (!jobId) {
171
+ return res.status(400).send("Missing jobId parameter.");
172
+ }
173
+
174
+ if (completedJobs[jobId]) {
175
+ return res.status(200).send({
176
+ status: "completed",
177
+ images: completedJobs[jobId],
178
+ credits,
179
+ });
180
+ }
181
+
182
+ if (jobQueue.some((job) => job.jobId === jobId)) {
183
+ return res.status(200).send({
184
+ status: "processing",
185
+ });
186
+ }
187
+
188
+ if (cancelledJobs.some((job) => job.jobId === jobId)) {
189
+ return res.status(200).send({
190
+ status: "cancelled",
191
+ });
192
+ }
193
+
194
+ return res.status(404).send("Job not found.");
195
+ });
196
+
197
+ /**
198
+ * POST endpoint to cancel a job.
199
+ * Cancels a job identified by its jobId parameter.
200
+ * If the job is found and successfully cancelled, it removes the job from the processing queue and adds it to the cancelled jobs array.
201
+ * If the job is not found, it returns a 404 error.
202
+ */
203
+ router.post(Routes.CANCEL_JOB, async (req, res) => {
204
+ const jobId = req.query.jobId as string;
205
+
206
+ if (!jobId) {
207
+ return res.status(400).send("Missing jobId parameter.");
208
+ }
209
+
210
+ const index = jobQueue.findIndex((job) => job.jobId === jobId);
211
+ if (index !== -1) {
212
+ cancelledJobs.push({ jobId });
213
+ // If the job is found, remove it from the jobQueue
214
+ const { timeoutId } = jobQueue[index];
215
+ jobQueue.splice(index, 1);
216
+ // Also clear the timeout associated with this job if it exists
217
+ if (timeoutId) {
218
+ clearTimeout(timeoutId);
219
+ }
220
+ return res.status(200).send("Job successfully cancelled.");
221
+ }
222
+
223
+ return res.status(404).send("Job not found.");
224
+ });
225
+
226
+ /**
227
+ * Generates a unique job ID.
228
+ */
229
+ function generateJobId(): string {
230
+ return Math.random().toString(36).substring(2, 15);
231
+ }
232
+
233
+ return router;
234
+ };
@@ -0,0 +1,56 @@
1
+ import * as express from "express";
2
+ import * as cors from "cors";
3
+ import { createBaseServer } from "../utils/backend/base_backend/create";
4
+ import { createImageRouter } from "./routers/image";
5
+ import { createAuthRouter } from "./routers/auth";
6
+
7
+ async function main() {
8
+ const router = express.Router();
9
+
10
+ /**
11
+ * TODO: Configure your CORS Policy
12
+ *
13
+ * Cross-Origin Resource Sharing
14
+ * ([CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS)) is an
15
+ * [HTTP](https://developer.mozilla.org/en-US/docs/Glossary/HTTP)-header based
16
+ * mechanism that allows a server to indicate any
17
+ * [origins](https://developer.mozilla.org/en-US/docs/Glossary/Origin)
18
+ * (domain, scheme, or port) other than its own from which a browser should
19
+ * permit loading resources.
20
+ *
21
+ * A basic CORS configuration would include the origin of your app in the
22
+ * following example:
23
+ * const corsOptions = {
24
+ * origin: 'https://app-abcdefg.canva-apps.com',
25
+ * optionsSuccessStatus: 200
26
+ * }
27
+ *
28
+ * The origin of your app is https://app-${APP_ID}.canva-apps.com, and note
29
+ * that the APP_ID should to be converted to lowercase.
30
+ *
31
+ * https://www.npmjs.com/package/cors#configuring-cors
32
+ *
33
+ * You may need to include multiple permissible origins, or dynamic origins
34
+ * based on the environment in which the server is running. Further
35
+ * information can be found
36
+ * [here](https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin).
37
+ */
38
+ router.use(cors());
39
+
40
+ /**
41
+ * Add routes for authorisation.
42
+ */
43
+ const authRouter = createAuthRouter();
44
+ router.use(authRouter);
45
+
46
+ /**
47
+ * Add routes for image generation.
48
+ */
49
+ const imageRouter = createImageRouter();
50
+ router.use(imageRouter);
51
+
52
+ const server = createBaseServer(router);
53
+ server.start(process.env.CANVA_BACKEND_PORT);
54
+ }
55
+
56
+ main();
@@ -0,0 +1,29 @@
1
+ declare module "*.css" {
2
+ const styles: { [className: string]: string };
3
+ export = styles;
4
+ }
5
+
6
+ declare module "*.jpg" {
7
+ const content: string;
8
+ export default content;
9
+ }
10
+
11
+ declare module "*.jpeg" {
12
+ const content: string;
13
+ export default content;
14
+ }
15
+
16
+ declare module "*.png" {
17
+ const content: string;
18
+ export default content;
19
+ }
20
+
21
+ declare module "*.svg" {
22
+ const content: React.FunctionComponent<{
23
+ size?: "tiny" | "small" | "medium" | "large";
24
+ className?: string;
25
+ }>;
26
+ export default content;
27
+ }
28
+
29
+ declare const BACKEND_HOST: string;
@@ -0,0 +1,309 @@
1
+ import typescriptEslint from "@typescript-eslint/eslint-plugin";
2
+ import jest from "eslint-plugin-jest";
3
+ import react from "eslint-plugin-react";
4
+ import globals from "globals";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import js from "@eslint/js";
8
+ import { FlatCompat } from "@eslint/eslintrc";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const compat = new FlatCompat({
13
+ baseDirectory: __dirname,
14
+ recommendedConfig: js.configs.recommended,
15
+ allConfig: js.configs.all,
16
+ });
17
+
18
+ export default [
19
+ {
20
+ ignores: [
21
+ "**/node_modules/",
22
+ "**/dist",
23
+ "**/*.d.ts",
24
+ "**/*.d.tsx",
25
+ "**/sdk",
26
+ "**/internal",
27
+ "**/*.config.*",
28
+ ],
29
+ },
30
+ ...compat.extends(
31
+ "eslint:recommended",
32
+ "plugin:@typescript-eslint/recommended",
33
+ "plugin:@typescript-eslint/eslint-recommended",
34
+ "plugin:@typescript-eslint/strict",
35
+ "plugin:@typescript-eslint/stylistic",
36
+ "plugin:react/recommended",
37
+ "plugin:jest/recommended"
38
+ ),
39
+ {
40
+ plugins: {
41
+ "@typescript-eslint": typescriptEslint,
42
+ jest,
43
+ react,
44
+ },
45
+ languageOptions: {
46
+ globals: {
47
+ ...globals.serviceworker,
48
+ ...globals.browser,
49
+ },
50
+ },
51
+ settings: {
52
+ react: {
53
+ version: "detect",
54
+ },
55
+ },
56
+ rules: {
57
+ "@typescript-eslint/no-non-null-assertion": "warn",
58
+ "@typescript-eslint/no-empty-function": "off",
59
+ "@typescript-eslint/consistent-type-imports": "error",
60
+ "@typescript-eslint/no-explicit-any": "warn",
61
+ "@typescript-eslint/no-empty-interface": "warn",
62
+ "@typescript-eslint/consistent-type-definitions": "off",
63
+ "@typescript-eslint/explicit-member-accessibility": [
64
+ "error",
65
+ {
66
+ accessibility: "no-public",
67
+ overrides: {
68
+ parameterProperties: "off",
69
+ },
70
+ },
71
+ ],
72
+ "@typescript-eslint/naming-convention": [
73
+ "error",
74
+ {
75
+ selector: "typeLike",
76
+ format: ["PascalCase"],
77
+ leadingUnderscore: "allow",
78
+ },
79
+ ],
80
+ "no-invalid-this": "off",
81
+ "@typescript-eslint/no-invalid-this": "error",
82
+ "@typescript-eslint/no-unused-expressions": [
83
+ "error",
84
+ {
85
+ allowShortCircuit: true,
86
+ allowTernary: true,
87
+ },
88
+ ],
89
+ "no-unused-vars": "off",
90
+ "@typescript-eslint/no-unused-vars": [
91
+ "error",
92
+ {
93
+ vars: "all",
94
+ varsIgnorePattern: "^_",
95
+ args: "none",
96
+ ignoreRestSiblings: true,
97
+ },
98
+ ],
99
+ "@typescript-eslint/no-require-imports": "error",
100
+ "jest/no-restricted-matchers": [
101
+ "error",
102
+ {
103
+ toContainElement:
104
+ "toContainElement is not recommended as it encourages testing the internals of the components",
105
+ toContainHTML:
106
+ "toContainHTML is not recommended as it encourages testing the internals of the components",
107
+ toHaveAttribute:
108
+ "toHaveAttribute is not recommended as it encourages testing the internals of the components",
109
+ toHaveClass:
110
+ "toHaveClass is not recommended as it encourages testing the internals of the components",
111
+ toHaveStyle:
112
+ "toHaveStyle is not recommended as it encourages testing the internals of the components",
113
+ },
114
+ ],
115
+ "react/jsx-curly-brace-presence": [
116
+ "error",
117
+ {
118
+ props: "never",
119
+ children: "never",
120
+ },
121
+ ],
122
+ "react/jsx-tag-spacing": [
123
+ "error",
124
+ {
125
+ closingSlash: "never",
126
+ beforeSelfClosing: "allow",
127
+ afterOpening: "never",
128
+ beforeClosing: "allow",
129
+ },
130
+ ],
131
+ "react/self-closing-comp": "error",
132
+ "react/no-unescaped-entities": "off",
133
+ "react/jsx-uses-react": "off",
134
+ "react/react-in-jsx-scope": "off",
135
+ "default-case": "error",
136
+ eqeqeq: [
137
+ "error",
138
+ "always",
139
+ {
140
+ null: "never",
141
+ },
142
+ ],
143
+ "no-caller": "error",
144
+ "no-console": "error",
145
+ "no-eval": "error",
146
+ "no-inner-declarations": "error",
147
+ "no-new-wrappers": "error",
148
+ "no-restricted-globals": [
149
+ "error",
150
+ {
151
+ name: "fit",
152
+ message: "Don't focus tests",
153
+ },
154
+ {
155
+ name: "fdescribe",
156
+ message: "Don't focus tests",
157
+ },
158
+ {
159
+ name: "length",
160
+ message:
161
+ "Undefined length - Did you mean to use window.length instead?",
162
+ },
163
+ {
164
+ name: "name",
165
+ message: "Undefined name - Did you mean to use window.name instead?",
166
+ },
167
+ {
168
+ name: "status",
169
+ message:
170
+ "Undefined status - Did you mean to use window.status instead?",
171
+ },
172
+ {
173
+ name: "spyOn",
174
+ message: "Don't use spyOn directly, use jest.spyOn",
175
+ },
176
+ ],
177
+ "no-restricted-properties": [
178
+ "error",
179
+ {
180
+ property: "bind",
181
+ message: "Don't o.f.bind(o, ...), use () => o.f(...)",
182
+ },
183
+ {
184
+ object: "ReactDOM",
185
+ property: "findDOMNode",
186
+ message: "Don't use ReactDOM.findDOMNode() as it is deprecated",
187
+ },
188
+ ],
189
+ "no-restricted-syntax": [
190
+ "error",
191
+ {
192
+ selector: "AccessorProperty, TSAbstractAccessorProperty",
193
+ message:
194
+ "Accessor property syntax is not allowed, use getter and setters.",
195
+ },
196
+ {
197
+ selector: "PrivateIdentifier",
198
+ message:
199
+ "Private identifiers are not allowed, use TypeScript private fields.",
200
+ },
201
+ {
202
+ selector:
203
+ "JSXOpeningElement[name.name = /^[A-Z]/] > JSXAttribute[name.name = /-/]",
204
+ message:
205
+ "Passing hyphenated props to custom components is not type-safe. Prefer a camelCased equivalent if available. (See https://github.com/microsoft/TypeScript/issues/55182)",
206
+ },
207
+ {
208
+ selector:
209
+ "CallExpression[callee.object.name='window'][callee.property.name='open']",
210
+ message:
211
+ "Apps are currently not allowed to open popups, or new tabs via browser APIs. Please use `requestOpenExternalUrl` from `@canva/platform` to link to external URLs. To learn more, see https://www.canva.dev/docs/apps/api/platform-request-open-external-url/",
212
+ },
213
+ ],
214
+ "no-return-await": "error",
215
+ "no-throw-literal": "error",
216
+ "no-undef-init": "error",
217
+ "no-var": "error",
218
+ "object-shorthand": "error",
219
+ "prefer-const": [
220
+ "error",
221
+ {
222
+ destructuring: "all",
223
+ },
224
+ ],
225
+ "prefer-object-spread": "error",
226
+ "prefer-rest-params": "error",
227
+ "prefer-spread": "error",
228
+ radix: "error",
229
+ },
230
+ },
231
+ {
232
+ files: ["**/*.tsx"],
233
+ rules: {
234
+ "react/no-deprecated": "error",
235
+ "react/forbid-elements": [
236
+ "error",
237
+ {
238
+ forbid: [
239
+ {
240
+ element: "video",
241
+ message:
242
+ "Don't use HTML video directly. Instead, use the App UI Kit <VideoCard /> as this respects users' auto-playing preferences",
243
+ },
244
+ {
245
+ element: "em",
246
+ message:
247
+ "Don't use <em> to italicize text. Canva's UI fonts don't support italic font style.",
248
+ },
249
+ {
250
+ element: "i",
251
+ message:
252
+ "Don't use <i> to italicize text. Canva's UI fonts don't support italic font style.",
253
+ },
254
+ {
255
+ element: "iframe",
256
+ message:
257
+ "Canva Apps aren't allowed to contain iframes. You should either recreate the UI you want to show in the iframe in the app directly, or link to your page via a `<Link>` tag. For more info see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
258
+ },
259
+ {
260
+ element: "script",
261
+ message:
262
+ "Script tags are not allowed in Canva SDK Apps. You should import JavaScript modules instead. For more info see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
263
+ },
264
+ {
265
+ element: "a",
266
+ message:
267
+ "Don't use <a> tags. Instead, use the <Link> component from the App UI Kit, and remember to open the url via the requestOpenExternalUrl method from @canva/platform.",
268
+ },
269
+ {
270
+ element: "img",
271
+ message:
272
+ "Have you considered using <ImageCard /> from the App UI Kit instead?",
273
+ },
274
+ {
275
+ element: "embed",
276
+ message:
277
+ "Have you considered using <EmbedCard /> from the App UI Kit instead?",
278
+ },
279
+ {
280
+ element: "audio",
281
+ message:
282
+ "Have you considered using <AudioCard /> from the App UI Kit instead?",
283
+ },
284
+ {
285
+ element: "button",
286
+ message:
287
+ "Rather than using the native HTML <button> element, use the <Button> component from the App UI Kit for consistency and accessibility.",
288
+ },
289
+ {
290
+ element: "input",
291
+ message:
292
+ "Wherever possible, prefer using the form inputs from the App UI Kit for consistency and accessibility (TextInput, Checkbox, FileInput, etc).",
293
+ },
294
+ {
295
+ element: "base",
296
+ message:
297
+ "The <base> tag is not supported in Canva Apps. We recommend using hash-based routing. For more on what is and isn't allowed in Canva Apps see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
298
+ },
299
+ {
300
+ element: "link",
301
+ message:
302
+ "If you're trying to include a css stylesheet, we recommend importing css using React, or using embedded stylesheets. For more on what is and isn't allowed in Canva Apps see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
303
+ },
304
+ ],
305
+ },
306
+ ],
307
+ },
308
+ },
309
+ ];