@canva/cli 1.8.0 → 1.10.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/cli.js +266 -366
  3. package/lib/cjs/index.cjs +6 -6
  4. package/lib/esm/index.mjs +6 -6
  5. package/package.json +1 -1
  6. package/templates/base/backend/database/database.ts +2 -2
  7. package/templates/base/backend/routers/auth.ts +5 -5
  8. package/templates/base/backend/routers/oauth.ts +4 -4
  9. package/templates/base/package.json +1 -2
  10. package/templates/base/scripts/copy_env.ts +2 -2
  11. package/templates/base/scripts/ssl/ssl.ts +3 -3
  12. package/templates/base/scripts/start/app_runner.ts +29 -7
  13. package/templates/base/scripts/start/context.ts +2 -2
  14. package/templates/base/scripts/start/start.ts +1 -1
  15. package/templates/base/scripts/start.tests.ts +1 -1
  16. package/templates/base/tsconfig.json +7 -4
  17. package/templates/base/utils/backend/base_backend/create.ts +7 -7
  18. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +3 -5
  19. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  20. package/templates/base/utils/use_add_element.ts +10 -0
  21. package/templates/base/webpack.config.ts +4 -4
  22. package/templates/common/utils/backend/base_backend/create.ts +7 -7
  23. package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  24. package/templates/common/utils/table_wrapper.ts +80 -37
  25. package/templates/common/utils/use_add_element.ts +10 -0
  26. package/templates/common/utils/use_overlay_hook.ts +6 -4
  27. package/templates/dam/backend/routers/dam.ts +37 -19
  28. package/templates/dam/backend/server.ts +2 -2
  29. package/templates/dam/package.json +1 -2
  30. package/templates/dam/scripts/copy_env.ts +2 -2
  31. package/templates/dam/scripts/ssl/ssl.ts +3 -3
  32. package/templates/dam/scripts/start/app_runner.ts +29 -7
  33. package/templates/dam/scripts/start/context.ts +2 -2
  34. package/templates/dam/scripts/start/start.ts +1 -1
  35. package/templates/dam/tsconfig.json +7 -4
  36. package/templates/dam/utils/backend/base_backend/create.ts +7 -7
  37. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  38. package/templates/dam/webpack.config.ts +4 -4
  39. package/templates/data_connector/package.json +2 -2
  40. package/templates/data_connector/scripts/copy_env.ts +2 -2
  41. package/templates/data_connector/scripts/ssl/ssl.ts +3 -3
  42. package/templates/data_connector/scripts/start/app_runner.ts +29 -7
  43. package/templates/data_connector/scripts/start/context.ts +2 -2
  44. package/templates/data_connector/scripts/start/start.ts +1 -1
  45. package/templates/data_connector/src/utils/data_table.ts +2 -1
  46. package/templates/data_connector/src/utils/tests/data_table.test.ts +2 -2
  47. package/templates/data_connector/tsconfig.json +7 -4
  48. package/templates/data_connector/webpack.config.ts +4 -4
  49. package/templates/gen_ai/backend/database/database.ts +2 -2
  50. package/templates/gen_ai/backend/routers/image.ts +5 -7
  51. package/templates/gen_ai/backend/server.ts +2 -2
  52. package/templates/gen_ai/package.json +2 -2
  53. package/templates/gen_ai/scripts/copy_env.ts +2 -2
  54. package/templates/gen_ai/scripts/ssl/ssl.ts +3 -3
  55. package/templates/gen_ai/scripts/start/app_runner.ts +29 -7
  56. package/templates/gen_ai/scripts/start/context.ts +2 -2
  57. package/templates/gen_ai/scripts/start/start.ts +1 -1
  58. package/templates/gen_ai/src/components/image_grid.tsx +6 -2
  59. package/templates/gen_ai/src/components/prompt_input.tsx +4 -1
  60. package/templates/gen_ai/tsconfig.json +7 -4
  61. package/templates/gen_ai/utils/backend/base_backend/create.ts +7 -7
  62. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +3 -5
  63. package/templates/gen_ai/webpack.config.ts +4 -4
  64. package/templates/hello_world/package.json +2 -2
  65. package/templates/hello_world/scripts/copy_env.ts +2 -2
  66. package/templates/hello_world/scripts/ssl/ssl.ts +3 -3
  67. package/templates/hello_world/scripts/start/app_runner.ts +29 -7
  68. package/templates/hello_world/scripts/start/context.ts +2 -2
  69. package/templates/hello_world/scripts/start/start.ts +1 -1
  70. package/templates/hello_world/src/tests/app.tests.tsx +1 -1
  71. package/templates/hello_world/tsconfig.json +7 -4
  72. package/templates/hello_world/utils/use_add_element.ts +10 -0
  73. package/templates/hello_world/webpack.config.ts +4 -4
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-console */
3
- import * as fs from "fs";
4
- import * as path from "path";
3
+ import fs from "fs";
4
+ import path from "path";
5
5
 
6
6
  const envPath = path.resolve(__dirname, "..", ".env");
7
7
  const templatePath = path.resolve(__dirname, "..", ".env.template");
@@ -1,7 +1,7 @@
1
- import * as crypto from "crypto";
2
- import * as fs from "fs/promises";
1
+ import crypto from "crypto";
2
+ import fs from "fs/promises";
3
3
  import { pki } from "node-forge";
4
- import * as path from "path";
4
+ import path from "path";
5
5
 
6
6
  const SSL_CERT_DIR = path.resolve(process.cwd(), "..", "..", ".ssl");
7
7
  const CERT_FILE = path.resolve(SSL_CERT_DIR, "certificate.pem");
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-console */
2
2
  import { generatePreviewUrl } from "@canva/cli";
3
- import * as ngrok from "@ngrok/ngrok";
4
- import * as chalk from "chalk";
5
- import * as Table from "cli-table3";
6
- import * as nodemon from "nodemon";
3
+ import ngrok from "@ngrok/ngrok";
4
+ import chalk from "chalk";
5
+ import Table from "cli-table3";
6
+ import nodemon from "nodemon";
7
7
  import open from "open";
8
- import * as webpack from "webpack";
9
- import * as WebpackDevServer from "webpack-dev-server";
8
+ import os from "os";
9
+ import webpack from "webpack";
10
+ import WebpackDevServer from "webpack-dev-server";
10
11
  import { buildConfig } from "../../webpack.config";
11
12
  import type { Certificate } from "../ssl/ssl";
12
13
  import { createOrRetrieveCertificate } from "../ssl/ssl";
@@ -18,6 +19,22 @@ export const errorChalk = chalk.bgRed.bold;
18
19
  export const highlightChalk = chalk.greenBright.bold;
19
20
  export const linkChalk = chalk.cyan;
20
21
 
22
+ /**
23
+ * Returns the appropriate modifier key text based on the user's operating system.
24
+ * @returns "cmd" for macOS, "ctrl" for Windows and Linux
25
+ */
26
+ export function getModifierKey(): string {
27
+ const platform = os.platform();
28
+ switch (platform) {
29
+ case "darwin": // macOS
30
+ return "cmd";
31
+ case "win32": // Windows
32
+ return "ctrl";
33
+ default: // Linux and others
34
+ return "ctrl";
35
+ }
36
+ }
37
+
21
38
  export class AppRunner {
22
39
  async run(ctx: Context) {
23
40
  console.log(
@@ -189,9 +206,14 @@ export class AppRunner {
189
206
  return;
190
207
  }
191
208
 
209
+ const modifierKey = getModifierKey();
210
+
192
211
  table.push([
193
212
  previewCellHeader,
194
- { content: "Preview URL", href: generatePreviewResult.data },
213
+ {
214
+ content: `Preview URL (${modifierKey} + click)`,
215
+ href: generatePreviewResult.data,
216
+ },
195
217
  ]);
196
218
 
197
219
  if (openPreview) {
@@ -1,5 +1,5 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import fs from "fs";
2
+ import path from "path";
3
3
 
4
4
  interface CliArgs {
5
5
  example?: string;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import * as yargs from "yargs";
2
+ import yargs from "yargs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
  import { AppRunner } from "./app_runner";
5
5
  import { Context } from "./context";
@@ -18,7 +18,8 @@
18
18
  "experimentalDecorators": true,
19
19
  "importHelpers": true,
20
20
  "noImplicitOverride": true,
21
- "moduleResolution": "node",
21
+ "moduleResolution": "bundler",
22
+ "esModuleInterop": true,
22
23
  "rootDir": ".",
23
24
  "outDir": "dist",
24
25
  "strict": true,
@@ -27,7 +28,10 @@
27
28
  "sourceMap": true,
28
29
  "inlineSources": true,
29
30
  "module": "ESNext",
30
- "noImplicitAny": false,
31
+ "noImplicitAny": true,
32
+ "noImplicitReturns": true,
33
+ "noFallthroughCasesInSwitch": true,
34
+ "noUncheckedIndexedAccess": true,
31
35
  "removeComments": true,
32
36
  "preserveConstEnums": true,
33
37
  "allowSyntheticDefaultImports": true,
@@ -43,8 +47,7 @@
43
47
  "./utils/**/*",
44
48
  "./scripts/**/*",
45
49
  "./declarations/declarations.d.ts",
46
- "./styles/**/*",
47
- "./node_modules/@types/**/*"
50
+ "./styles/**/*"
48
51
  ],
49
52
  "ts-node": {
50
53
  "compilerOptions": {
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable no-console */
2
2
  import debug from "debug";
3
- import * as express from "express";
3
+ import express from "express";
4
4
  import type { NextFunction, Request, Response } from "express";
5
- import * as fs from "fs";
6
- import * as http from "http";
7
- import * as https from "https";
5
+ import fs from "fs";
6
+ import http from "http";
7
+ import https from "https";
8
8
 
9
9
  const serverDebug = debug("server");
10
10
 
@@ -41,12 +41,12 @@ export function createBaseServer(router: express.Router): BaseServer {
41
41
  app.disable("x-powered-by");
42
42
 
43
43
  // Health check endpoint
44
- app.get("/healthz", (req, res: Response) => {
44
+ app.get("/healthz", (req, res) => {
45
45
  res.sendStatus(200);
46
46
  });
47
47
 
48
48
  // logging middleware
49
- app.use((req: Request, res: Response, next: NextFunction) => {
49
+ app.use((req, _res, next) => {
50
50
  serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
51
51
  next();
52
52
  });
@@ -62,7 +62,7 @@ export function createBaseServer(router: express.Router): BaseServer {
62
62
  });
63
63
 
64
64
  // default error handler
65
- app.use((err, req, res, next) => {
65
+ app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
66
66
  console.error(err.stack);
67
67
  res.status(500).send({
68
68
  error: "something went wrong",
@@ -1,10 +1,7 @@
1
1
  /* eslint-disable no-console */
2
- import * as chalk from "chalk";
3
- import * as debug from "debug";
2
+ import debug from "debug";
4
3
  import type { NextFunction, Request, Response } from "express";
5
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
- import Express from "express-serve-static-core";
7
- import * as jwt from "jsonwebtoken";
4
+ import jwt from "jsonwebtoken";
8
5
  import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
9
6
 
10
7
  /**
@@ -116,7 +113,7 @@ export function createJwtMiddleware(
116
113
  userId: payload.userId,
117
114
  };
118
115
 
119
- next();
116
+ return next();
120
117
  } catch (e) {
121
118
  if (e instanceof JWTAuthorizationError) {
122
119
  return sendUnauthorizedResponse(res, e.message);
@@ -125,9 +122,7 @@ export function createJwtMiddleware(
125
122
  if (e instanceof SigningKeyNotFoundError) {
126
123
  return sendUnauthorizedResponse(
127
124
  res,
128
- `Public key not found. ${chalk.bgRedBright(
129
- "Ensure you have the correct App_ID set",
130
- )}.`,
125
+ `Public key not found. Ensure you have the correct App_ID set`,
131
126
  );
132
127
  }
133
128
 
@@ -139,7 +134,7 @@ export function createJwtMiddleware(
139
134
  return sendUnauthorizedResponse(res, "Token expired");
140
135
  }
141
136
 
142
- next(e);
137
+ return next(e);
143
138
  }
144
139
  };
145
140
  }
@@ -1,9 +1,9 @@
1
1
  import type { Configuration } from "webpack";
2
2
  import { DefinePlugin, optimize } from "webpack";
3
- import * as path from "path";
4
- import * as TerserPlugin from "terser-webpack-plugin";
3
+ import path from "path";
4
+ import TerserPlugin from "terser-webpack-plugin";
5
5
  import { transform } from "@formatjs/ts-transformer";
6
- import * as chalk from "chalk";
6
+ import chalk from "chalk";
7
7
  import { config } from "dotenv";
8
8
  import { Configuration as DevServerConfiguration } from "webpack-dev-server";
9
9
 
@@ -196,7 +196,7 @@ function buildDevConfig(options?: DevConfig): {
196
196
  return {};
197
197
  }
198
198
 
199
- const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
199
+ const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
200
200
  options;
201
201
  const host = "localhost";
202
202
 
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@canva/app-i18n-kit": "^1.1.1",
23
- "@canva/app-ui-kit": "^5.2.0",
23
+ "@canva/app-ui-kit": "^5.2.1",
24
24
  "@canva/asset": "^2.2.1",
25
25
  "@canva/design": "^2.7.3",
26
26
  "@canva/error": "^2.1.0",
@@ -42,8 +42,8 @@
42
42
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
43
43
  "@svgr/webpack": "8.1.0",
44
44
  "@testing-library/react": "16.3.0",
45
+ "@types/debug": "4.1.12",
45
46
  "@types/express": "4.17.21",
46
- "@types/express-serve-static-core": "5.0.7",
47
47
  "@types/jest": "29.5.14",
48
48
  "@types/jsonwebtoken": "9.0.10",
49
49
  "@types/node": "20.19.2",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-console */
3
- import * as fs from "fs";
4
- import * as path from "path";
3
+ import fs from "fs";
4
+ import path from "path";
5
5
 
6
6
  const envPath = path.resolve(__dirname, "..", ".env");
7
7
  const templatePath = path.resolve(__dirname, "..", ".env.template");
@@ -1,7 +1,7 @@
1
- import * as crypto from "crypto";
2
- import * as fs from "fs/promises";
1
+ import crypto from "crypto";
2
+ import fs from "fs/promises";
3
3
  import { pki } from "node-forge";
4
- import * as path from "path";
4
+ import path from "path";
5
5
 
6
6
  const SSL_CERT_DIR = path.resolve(process.cwd(), "..", "..", ".ssl");
7
7
  const CERT_FILE = path.resolve(SSL_CERT_DIR, "certificate.pem");
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-console */
2
2
  import { generatePreviewUrl } from "@canva/cli";
3
- import * as ngrok from "@ngrok/ngrok";
4
- import * as chalk from "chalk";
5
- import * as Table from "cli-table3";
6
- import * as nodemon from "nodemon";
3
+ import ngrok from "@ngrok/ngrok";
4
+ import chalk from "chalk";
5
+ import Table from "cli-table3";
6
+ import nodemon from "nodemon";
7
7
  import open from "open";
8
- import * as webpack from "webpack";
9
- import * as WebpackDevServer from "webpack-dev-server";
8
+ import os from "os";
9
+ import webpack from "webpack";
10
+ import WebpackDevServer from "webpack-dev-server";
10
11
  import { buildConfig } from "../../webpack.config";
11
12
  import type { Certificate } from "../ssl/ssl";
12
13
  import { createOrRetrieveCertificate } from "../ssl/ssl";
@@ -18,6 +19,22 @@ export const errorChalk = chalk.bgRed.bold;
18
19
  export const highlightChalk = chalk.greenBright.bold;
19
20
  export const linkChalk = chalk.cyan;
20
21
 
22
+ /**
23
+ * Returns the appropriate modifier key text based on the user's operating system.
24
+ * @returns "cmd" for macOS, "ctrl" for Windows and Linux
25
+ */
26
+ export function getModifierKey(): string {
27
+ const platform = os.platform();
28
+ switch (platform) {
29
+ case "darwin": // macOS
30
+ return "cmd";
31
+ case "win32": // Windows
32
+ return "ctrl";
33
+ default: // Linux and others
34
+ return "ctrl";
35
+ }
36
+ }
37
+
21
38
  export class AppRunner {
22
39
  async run(ctx: Context) {
23
40
  console.log(
@@ -189,9 +206,14 @@ export class AppRunner {
189
206
  return;
190
207
  }
191
208
 
209
+ const modifierKey = getModifierKey();
210
+
192
211
  table.push([
193
212
  previewCellHeader,
194
- { content: "Preview URL", href: generatePreviewResult.data },
213
+ {
214
+ content: `Preview URL (${modifierKey} + click)`,
215
+ href: generatePreviewResult.data,
216
+ },
195
217
  ]);
196
218
 
197
219
  if (openPreview) {
@@ -1,5 +1,5 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import fs from "fs";
2
+ import path from "path";
3
3
 
4
4
  interface CliArgs {
5
5
  example?: string;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import * as yargs from "yargs";
2
+ import yargs from "yargs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
  import { AppRunner } from "./app_runner";
5
5
  import { Context } from "./context";
@@ -12,7 +12,8 @@ import type { APIResponseItem } from "src/api";
12
12
  export interface DataTableColumn<T extends APIResponseItem> {
13
13
  label: string;
14
14
  getValue: keyof T | ((result: T) => boolean | string | number | Date);
15
- toCell: (value) => DataTableCell;
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ toCell: (value: any) => DataTableCell;
16
17
  }
17
18
 
18
19
  export function toDataTable<T extends APIResponseItem>(
@@ -92,8 +92,8 @@ describe("data table utils", () => {
92
92
  const result = toDataTable(testItems, columns, 10);
93
93
 
94
94
  // Check the custom column values
95
- expect(result.rows[0].cells[5].value).toBe("Test Item 1 (42)");
96
- expect(result.rows[1].cells[5].value).toBe("Test Item 2 (0)");
95
+ expect(result.rows[0]?.cells[5]?.value).toBe("Test Item 1 (42)");
96
+ expect(result.rows[1]?.cells[5]?.value).toBe("Test Item 2 (0)");
97
97
  });
98
98
 
99
99
  test("cell formatter functions create correct cell structures", () => {
@@ -18,7 +18,8 @@
18
18
  "experimentalDecorators": true,
19
19
  "importHelpers": true,
20
20
  "noImplicitOverride": true,
21
- "moduleResolution": "node",
21
+ "moduleResolution": "bundler",
22
+ "esModuleInterop": true,
22
23
  "rootDir": ".",
23
24
  "outDir": "dist",
24
25
  "strict": true,
@@ -27,7 +28,10 @@
27
28
  "sourceMap": true,
28
29
  "inlineSources": true,
29
30
  "module": "ESNext",
30
- "noImplicitAny": false,
31
+ "noImplicitAny": true,
32
+ "noImplicitReturns": true,
33
+ "noFallthroughCasesInSwitch": true,
34
+ "noUncheckedIndexedAccess": true,
31
35
  "removeComments": true,
32
36
  "preserveConstEnums": true,
33
37
  "allowSyntheticDefaultImports": true,
@@ -43,8 +47,7 @@
43
47
  "./utils/**/*",
44
48
  "./scripts/**/*",
45
49
  "./declarations/declarations.d.ts",
46
- "./styles/**/*",
47
- "./node_modules/@types/**/*"
50
+ "./styles/**/*"
48
51
  ],
49
52
  "ts-node": {
50
53
  "compilerOptions": {
@@ -1,9 +1,9 @@
1
1
  import type { Configuration } from "webpack";
2
2
  import { DefinePlugin, optimize } from "webpack";
3
- import * as path from "path";
4
- import * as TerserPlugin from "terser-webpack-plugin";
3
+ import path from "path";
4
+ import TerserPlugin from "terser-webpack-plugin";
5
5
  import { transform } from "@formatjs/ts-transformer";
6
- import * as chalk from "chalk";
6
+ import chalk from "chalk";
7
7
  import { config } from "dotenv";
8
8
  import { Configuration as DevServerConfiguration } from "webpack-dev-server";
9
9
 
@@ -196,7 +196,7 @@ function buildDevConfig(options?: DevConfig): {
196
196
  return {};
197
197
  }
198
198
 
199
- const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
199
+ const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
200
200
  options;
201
201
  const host = "localhost";
202
202
 
@@ -1,5 +1,5 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
3
 
4
4
  /**
5
5
  * This file creates a "database" out of a JSON file. It's only for
@@ -8,7 +8,7 @@
8
8
  * Use this code as a learning resource, but do not deploy it in a real backend without significant modifications
9
9
  * to ensure reliability, security, and scalability.
10
10
  */
11
- import * as express from "express";
11
+ import express from "express";
12
12
 
13
13
  interface ImageResponse {
14
14
  fullsize: { width: number; height: number; url: string };
@@ -151,7 +151,7 @@ export const createImageRouter = () => {
151
151
  // Add the job to the jobQueue along with the timeoutId
152
152
  jobQueue.push({ jobId, prompt, timeoutId });
153
153
 
154
- res.status(200).send({
154
+ return res.status(200).send({
155
155
  jobId,
156
156
  });
157
157
  });
@@ -208,15 +208,13 @@ export const createImageRouter = () => {
208
208
  }
209
209
 
210
210
  const index = jobQueue.findIndex((job) => job.jobId === jobId);
211
- if (index !== -1) {
211
+ const job = jobQueue[index];
212
+ if (index !== -1 && job) {
212
213
  cancelledJobs.push({ jobId });
213
214
  // If the job is found, remove it from the jobQueue
214
- const { timeoutId } = jobQueue[index];
215
215
  jobQueue.splice(index, 1);
216
216
  // Also clear the timeout associated with this job if it exists
217
- if (timeoutId) {
218
- clearTimeout(timeoutId);
219
- }
217
+ clearTimeout(job.timeoutId);
220
218
  return res.status(200).send("Job successfully cancelled.");
221
219
  }
222
220
 
@@ -1,5 +1,5 @@
1
- import * as cors from "cors";
2
- import * as express from "express";
1
+ import cors from "cors";
2
+ import express from "express";
3
3
  import { createBaseServer } from "../utils/backend/base_backend/create";
4
4
  import { createImageRouter } from "./routers/image";
5
5
 
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@canva/app-i18n-kit": "^1.1.1",
22
- "@canva/app-ui-kit": "^5.2.0",
22
+ "@canva/app-ui-kit": "^5.2.1",
23
23
  "@canva/asset": "^2.2.1",
24
24
  "@canva/design": "^2.7.3",
25
25
  "@canva/error": "^2.1.0",
@@ -44,9 +44,9 @@
44
44
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
45
45
  "@svgr/webpack": "8.1.0",
46
46
  "@testing-library/react": "16.3.0",
47
+ "@types/cors": "2.8.19",
47
48
  "@types/debug": "4.1.12",
48
49
  "@types/express": "4.17.21",
49
- "@types/express-serve-static-core": "5.0.7",
50
50
  "@types/jest": "29.5.14",
51
51
  "@types/jsonwebtoken": "9.0.10",
52
52
  "@types/node": "20.19.2",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-console */
3
- import * as fs from "fs";
4
- import * as path from "path";
3
+ import fs from "fs";
4
+ import path from "path";
5
5
 
6
6
  const envPath = path.resolve(__dirname, "..", ".env");
7
7
  const templatePath = path.resolve(__dirname, "..", ".env.template");
@@ -1,7 +1,7 @@
1
- import * as crypto from "crypto";
2
- import * as fs from "fs/promises";
1
+ import crypto from "crypto";
2
+ import fs from "fs/promises";
3
3
  import { pki } from "node-forge";
4
- import * as path from "path";
4
+ import path from "path";
5
5
 
6
6
  const SSL_CERT_DIR = path.resolve(process.cwd(), "..", "..", ".ssl");
7
7
  const CERT_FILE = path.resolve(SSL_CERT_DIR, "certificate.pem");
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-console */
2
2
  import { generatePreviewUrl } from "@canva/cli";
3
- import * as ngrok from "@ngrok/ngrok";
4
- import * as chalk from "chalk";
5
- import * as Table from "cli-table3";
6
- import * as nodemon from "nodemon";
3
+ import ngrok from "@ngrok/ngrok";
4
+ import chalk from "chalk";
5
+ import Table from "cli-table3";
6
+ import nodemon from "nodemon";
7
7
  import open from "open";
8
- import * as webpack from "webpack";
9
- import * as WebpackDevServer from "webpack-dev-server";
8
+ import os from "os";
9
+ import webpack from "webpack";
10
+ import WebpackDevServer from "webpack-dev-server";
10
11
  import { buildConfig } from "../../webpack.config";
11
12
  import type { Certificate } from "../ssl/ssl";
12
13
  import { createOrRetrieveCertificate } from "../ssl/ssl";
@@ -18,6 +19,22 @@ export const errorChalk = chalk.bgRed.bold;
18
19
  export const highlightChalk = chalk.greenBright.bold;
19
20
  export const linkChalk = chalk.cyan;
20
21
 
22
+ /**
23
+ * Returns the appropriate modifier key text based on the user's operating system.
24
+ * @returns "cmd" for macOS, "ctrl" for Windows and Linux
25
+ */
26
+ export function getModifierKey(): string {
27
+ const platform = os.platform();
28
+ switch (platform) {
29
+ case "darwin": // macOS
30
+ return "cmd";
31
+ case "win32": // Windows
32
+ return "ctrl";
33
+ default: // Linux and others
34
+ return "ctrl";
35
+ }
36
+ }
37
+
21
38
  export class AppRunner {
22
39
  async run(ctx: Context) {
23
40
  console.log(
@@ -189,9 +206,14 @@ export class AppRunner {
189
206
  return;
190
207
  }
191
208
 
209
+ const modifierKey = getModifierKey();
210
+
192
211
  table.push([
193
212
  previewCellHeader,
194
- { content: "Preview URL", href: generatePreviewResult.data },
213
+ {
214
+ content: `Preview URL (${modifierKey} + click)`,
215
+ href: generatePreviewResult.data,
216
+ },
195
217
  ]);
196
218
 
197
219
  if (openPreview) {
@@ -1,5 +1,5 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import fs from "fs";
2
+ import path from "path";
3
3
 
4
4
  interface CliArgs {
5
5
  example?: string;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import * as yargs from "yargs";
2
+ import yargs from "yargs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
  import { AppRunner } from "./app_runner";
5
5
  import { Context } from "./context";
@@ -34,7 +34,9 @@ export const ImageGrid = () => {
34
34
  ) => {
35
35
  const parentNode = event.currentTarget.parentElement;
36
36
  try {
37
- parentNode?.classList.add(styles.hidden);
37
+ if (styles.hidden) {
38
+ parentNode?.classList.add(styles.hidden);
39
+ }
38
40
 
39
41
  await ui.startDragToPoint(event, {
40
42
  type: "image",
@@ -50,7 +52,9 @@ export const ImageGrid = () => {
50
52
  },
51
53
  });
52
54
  } finally {
53
- parentNode?.classList.remove(styles.hidden);
55
+ if (styles.hidden) {
56
+ parentNode?.classList.remove(styles.hidden);
57
+ }
54
58
  }
55
59
  };
56
60
 
@@ -59,8 +59,11 @@ const generateExamplePrompt = (currentPrompt: string): string => {
59
59
  const MAX_ATTEMPTS = 3;
60
60
 
61
61
  while (currentPrompt === newPrompt && attempts < MAX_ATTEMPTS) {
62
- newPrompt =
62
+ const randomPrompt =
63
63
  examplePrompts[Math.floor(Math.random() * examplePrompts.length)];
64
+ if (randomPrompt) {
65
+ newPrompt = randomPrompt;
66
+ }
64
67
  attempts++;
65
68
  }
66
69