@canva/cli 0.0.1-beta.17 → 0.0.1-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +293 -293
- package/lib/cjs/index.cjs +54 -54
- package/lib/esm/index.mjs +54 -54
- package/package.json +1 -1
- package/templates/base/package.json +3 -2
- package/templates/base/scripts/start/app_runner.ts +9 -4
- 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/common/conf/eslint_general.mjs +6 -0
- package/templates/dam/package.json +4 -2
- package/templates/dam/scripts/start/app_runner.ts +9 -4
- 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/gen_ai/package.json +4 -2
- package/templates/gen_ai/scripts/start/app_runner.ts +9 -4
- 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/hello_world/package.json +4 -2
- package/templates/hello_world/scripts/start/app_runner.ts +9 -4
- 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/package.json
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
7
|
"author": "Canva Pty Ltd.",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@canva/app-ui-kit": "^4.
|
|
9
|
+
"@canva/app-ui-kit": "^4.8.0",
|
|
10
10
|
"@canva/asset": "^2.1.0",
|
|
11
|
-
"@canva/design": "^2.
|
|
11
|
+
"@canva/design": "^2.4.0",
|
|
12
12
|
"@canva/error": "^2.1.0",
|
|
13
13
|
"@canva/platform": "^2.1.0",
|
|
14
14
|
"@canva/user": "^2.1.0",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"prompts": "2.4.2",
|
|
57
57
|
"style-loader": "4.0.0",
|
|
58
58
|
"terser-webpack-plugin": "5.3.11",
|
|
59
|
+
"tree-kill": "1.2.2",
|
|
59
60
|
"ts-jest": "29.2.5",
|
|
60
61
|
"ts-loader": "9.5.2",
|
|
61
62
|
"ts-node": "10.9.2",
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -58,7 +58,7 @@ export class AppRunner {
|
|
|
58
58
|
|
|
59
59
|
await this.maybeRunBackendServer(ctx, table, cert, server);
|
|
60
60
|
|
|
61
|
-
await this.generateAndOpenPreviewUrl(table);
|
|
61
|
+
await this.generateAndOpenPreviewUrl(ctx.openPreview, table);
|
|
62
62
|
|
|
63
63
|
console.log(table.toString(), "\n");
|
|
64
64
|
|
|
@@ -173,7 +173,10 @@ export class AppRunner {
|
|
|
173
173
|
/**
|
|
174
174
|
* Calls the Canva CLI to generate a preview URL for the app
|
|
175
175
|
*/
|
|
176
|
-
private readonly generateAndOpenPreviewUrl = async (
|
|
176
|
+
private readonly generateAndOpenPreviewUrl = async (
|
|
177
|
+
openPreview: boolean,
|
|
178
|
+
table: Table.Table,
|
|
179
|
+
) => {
|
|
177
180
|
const previewCellHeader = { content: "Preview your app in Canva" };
|
|
178
181
|
|
|
179
182
|
const generatePreviewResult = await generatePreviewUrl();
|
|
@@ -191,6 +194,8 @@ export class AppRunner {
|
|
|
191
194
|
{ content: "Preview URL", href: generatePreviewResult.data },
|
|
192
195
|
]);
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
if (openPreview) {
|
|
198
|
+
open(generatePreviewResult.data);
|
|
199
|
+
}
|
|
195
200
|
};
|
|
196
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",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import type { ChildProcess } from "child_process";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import * as treeKill from "tree-kill";
|
|
6
|
+
|
|
7
|
+
describe("start script", () => {
|
|
8
|
+
let serverProcess: ChildProcess;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
12
|
+
treeKill(serverProcess.pid);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should execute 'npm run start' and start a dev server", async () => {
|
|
17
|
+
const testServerPort = 8089;
|
|
18
|
+
serverProcess = await spawn(
|
|
19
|
+
`npm run start -- -p ${testServerPort} --no-preview`,
|
|
20
|
+
{
|
|
21
|
+
shell: true,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!serverProcess.pid) {
|
|
26
|
+
throw new Error("Unable to start server");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// wait for the server to start and collect output until it reports the running URL
|
|
30
|
+
let output = "";
|
|
31
|
+
await new Promise<void>((resolve, reject) => {
|
|
32
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
33
|
+
output += data.toString();
|
|
34
|
+
if (output.includes("Development URL")) {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
40
|
+
console.error("Server process error: ", data.toString());
|
|
41
|
+
reject();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// timeout and fail test if the server hasn't correctly started in 10 seconds
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
47
|
+
treeKill(serverProcess.pid);
|
|
48
|
+
}
|
|
49
|
+
reject(new Error("Test timed out"));
|
|
50
|
+
}, 10000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// check that the server is running and accessible
|
|
54
|
+
const expectedServerURL = `http://localhost:${testServerPort}`;
|
|
55
|
+
expect(output).toContain(expectedServerURL);
|
|
56
|
+
expect(serverProcess.exitCode).toBeNull();
|
|
57
|
+
|
|
58
|
+
// clean up
|
|
59
|
+
treeKill(serverProcess.pid);
|
|
60
|
+
}, 15000); // 15 seconds timeout to allow for the server to start
|
|
61
|
+
});
|
|
@@ -180,6 +180,12 @@ export default [
|
|
|
180
180
|
message:
|
|
181
181
|
"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/",
|
|
182
182
|
},
|
|
183
|
+
{
|
|
184
|
+
selector:
|
|
185
|
+
"MemberExpression[object.name='localStorage'], MemberExpression[object.name='sessionStorage']",
|
|
186
|
+
message:
|
|
187
|
+
"Using `localStorage` or `sessionStorage` may not be appropriate for certain types of data, such as sensitive information. If your use case is safe and does not involve storing sensitive data (e.g., access tokens or keys), you can ignore this warning with an eslint-disable comment. Otherwise, seek secure storage alternatives. For more details, refer to the Canva Security Guidelines (https://www.canva.dev/docs/apps/security-guidelines/#store-secrets-securely).",
|
|
188
|
+
},
|
|
183
189
|
],
|
|
184
190
|
"no-restricted-imports": [
|
|
185
191
|
"warn",
|
|
@@ -12,6 +12,7 @@
|
|
|
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
17
|
"test:watch": "jest --watchAll",
|
|
17
18
|
"postinstall": "ts-node ./scripts/copy_env.ts"
|
|
@@ -19,9 +20,9 @@
|
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@canva/app-components": "^1.1.0",
|
|
21
22
|
"@canva/app-i18n-kit": "^1.0.2",
|
|
22
|
-
"@canva/app-ui-kit": "^4.
|
|
23
|
+
"@canva/app-ui-kit": "^4.8.0",
|
|
23
24
|
"@canva/asset": "^2.1.0",
|
|
24
|
-
"@canva/design": "^2.
|
|
25
|
+
"@canva/design": "^2.4.0",
|
|
25
26
|
"@canva/error": "^2.1.0",
|
|
26
27
|
"@canva/platform": "^2.1.0",
|
|
27
28
|
"@canva/user": "^2.1.0",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"react-refresh": "0.16.0",
|
|
89
90
|
"style-loader": "4.0.0",
|
|
90
91
|
"terser-webpack-plugin": "5.3.11",
|
|
92
|
+
"tree-kill": "1.2.2",
|
|
91
93
|
"ts-jest": "29.2.5",
|
|
92
94
|
"ts-loader": "9.5.2",
|
|
93
95
|
"ts-node": "10.9.2",
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -58,7 +58,7 @@ export class AppRunner {
|
|
|
58
58
|
|
|
59
59
|
await this.maybeRunBackendServer(ctx, table, cert, server);
|
|
60
60
|
|
|
61
|
-
await this.generateAndOpenPreviewUrl(table);
|
|
61
|
+
await this.generateAndOpenPreviewUrl(ctx.openPreview, table);
|
|
62
62
|
|
|
63
63
|
console.log(table.toString(), "\n");
|
|
64
64
|
|
|
@@ -173,7 +173,10 @@ export class AppRunner {
|
|
|
173
173
|
/**
|
|
174
174
|
* Calls the Canva CLI to generate a preview URL for the app
|
|
175
175
|
*/
|
|
176
|
-
private readonly generateAndOpenPreviewUrl = async (
|
|
176
|
+
private readonly generateAndOpenPreviewUrl = async (
|
|
177
|
+
openPreview: boolean,
|
|
178
|
+
table: Table.Table,
|
|
179
|
+
) => {
|
|
177
180
|
const previewCellHeader = { content: "Preview your app in Canva" };
|
|
178
181
|
|
|
179
182
|
const generatePreviewResult = await generatePreviewUrl();
|
|
@@ -191,6 +194,8 @@ export class AppRunner {
|
|
|
191
194
|
{ content: "Preview URL", href: generatePreviewResult.data },
|
|
192
195
|
]);
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
if (openPreview) {
|
|
198
|
+
open(generatePreviewResult.data);
|
|
199
|
+
}
|
|
195
200
|
};
|
|
196
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",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import type { ChildProcess } from "child_process";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import * as treeKill from "tree-kill";
|
|
6
|
+
|
|
7
|
+
describe("start script", () => {
|
|
8
|
+
let serverProcess: ChildProcess;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
12
|
+
treeKill(serverProcess.pid);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should execute 'npm run start' and start a dev server", async () => {
|
|
17
|
+
const testServerPort = 8089;
|
|
18
|
+
serverProcess = await spawn(
|
|
19
|
+
`npm run start -- -p ${testServerPort} --no-preview`,
|
|
20
|
+
{
|
|
21
|
+
shell: true,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!serverProcess.pid) {
|
|
26
|
+
throw new Error("Unable to start server");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// wait for the server to start and collect output until it reports the running URL
|
|
30
|
+
let output = "";
|
|
31
|
+
await new Promise<void>((resolve, reject) => {
|
|
32
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
33
|
+
output += data.toString();
|
|
34
|
+
if (output.includes("Development URL")) {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
40
|
+
console.error("Server process error: ", data.toString());
|
|
41
|
+
reject();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// timeout and fail test if the server hasn't correctly started in 10 seconds
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
47
|
+
treeKill(serverProcess.pid);
|
|
48
|
+
}
|
|
49
|
+
reject(new Error("Test timed out"));
|
|
50
|
+
}, 10000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// check that the server is running and accessible
|
|
54
|
+
const expectedServerURL = `http://localhost:${testServerPort}`;
|
|
55
|
+
expect(output).toContain(expectedServerURL);
|
|
56
|
+
expect(serverProcess.exitCode).toBeNull();
|
|
57
|
+
|
|
58
|
+
// clean up
|
|
59
|
+
treeKill(serverProcess.pid);
|
|
60
|
+
}, 15000); // 15 seconds timeout to allow for the server to start
|
|
61
|
+
});
|
|
@@ -12,15 +12,16 @@
|
|
|
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
17
|
"test:watch": "jest --watchAll",
|
|
17
18
|
"postinstall": "ts-node ./scripts/copy_env.ts"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@canva/app-i18n-kit": "^1.0.2",
|
|
21
|
-
"@canva/app-ui-kit": "^4.
|
|
22
|
+
"@canva/app-ui-kit": "^4.8.0",
|
|
22
23
|
"@canva/asset": "^2.1.0",
|
|
23
|
-
"@canva/design": "^2.
|
|
24
|
+
"@canva/design": "^2.4.0",
|
|
24
25
|
"@canva/error": "^2.1.0",
|
|
25
26
|
"@canva/platform": "^2.1.0",
|
|
26
27
|
"@canva/user": "^2.1.0",
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
"react-refresh": "0.16.0",
|
|
93
94
|
"style-loader": "4.0.0",
|
|
94
95
|
"terser-webpack-plugin": "5.3.11",
|
|
96
|
+
"tree-kill": "1.2.2",
|
|
95
97
|
"ts-jest": "29.2.5",
|
|
96
98
|
"ts-loader": "9.5.2",
|
|
97
99
|
"ts-node": "10.9.2",
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -58,7 +58,7 @@ export class AppRunner {
|
|
|
58
58
|
|
|
59
59
|
await this.maybeRunBackendServer(ctx, table, cert, server);
|
|
60
60
|
|
|
61
|
-
await this.generateAndOpenPreviewUrl(table);
|
|
61
|
+
await this.generateAndOpenPreviewUrl(ctx.openPreview, table);
|
|
62
62
|
|
|
63
63
|
console.log(table.toString(), "\n");
|
|
64
64
|
|
|
@@ -173,7 +173,10 @@ export class AppRunner {
|
|
|
173
173
|
/**
|
|
174
174
|
* Calls the Canva CLI to generate a preview URL for the app
|
|
175
175
|
*/
|
|
176
|
-
private readonly generateAndOpenPreviewUrl = async (
|
|
176
|
+
private readonly generateAndOpenPreviewUrl = async (
|
|
177
|
+
openPreview: boolean,
|
|
178
|
+
table: Table.Table,
|
|
179
|
+
) => {
|
|
177
180
|
const previewCellHeader = { content: "Preview your app in Canva" };
|
|
178
181
|
|
|
179
182
|
const generatePreviewResult = await generatePreviewUrl();
|
|
@@ -191,6 +194,8 @@ export class AppRunner {
|
|
|
191
194
|
{ content: "Preview URL", href: generatePreviewResult.data },
|
|
192
195
|
]);
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
if (openPreview) {
|
|
198
|
+
open(generatePreviewResult.data);
|
|
199
|
+
}
|
|
195
200
|
};
|
|
196
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",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import type { ChildProcess } from "child_process";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import * as treeKill from "tree-kill";
|
|
6
|
+
|
|
7
|
+
describe("start script", () => {
|
|
8
|
+
let serverProcess: ChildProcess;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
12
|
+
treeKill(serverProcess.pid);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should execute 'npm run start' and start a dev server", async () => {
|
|
17
|
+
const testServerPort = 8089;
|
|
18
|
+
serverProcess = await spawn(
|
|
19
|
+
`npm run start -- -p ${testServerPort} --no-preview`,
|
|
20
|
+
{
|
|
21
|
+
shell: true,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!serverProcess.pid) {
|
|
26
|
+
throw new Error("Unable to start server");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// wait for the server to start and collect output until it reports the running URL
|
|
30
|
+
let output = "";
|
|
31
|
+
await new Promise<void>((resolve, reject) => {
|
|
32
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
33
|
+
output += data.toString();
|
|
34
|
+
if (output.includes("Development URL")) {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
40
|
+
console.error("Server process error: ", data.toString());
|
|
41
|
+
reject();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// timeout and fail test if the server hasn't correctly started in 10 seconds
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
47
|
+
treeKill(serverProcess.pid);
|
|
48
|
+
}
|
|
49
|
+
reject(new Error("Test timed out"));
|
|
50
|
+
}, 10000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// check that the server is running and accessible
|
|
54
|
+
const expectedServerURL = `http://localhost:${testServerPort}`;
|
|
55
|
+
expect(output).toContain(expectedServerURL);
|
|
56
|
+
expect(serverProcess.exitCode).toBeNull();
|
|
57
|
+
|
|
58
|
+
// clean up
|
|
59
|
+
treeKill(serverProcess.pid);
|
|
60
|
+
}, 15000); // 15 seconds timeout to allow for the server to start
|
|
61
|
+
});
|
|
@@ -12,6 +12,7 @@
|
|
|
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
17
|
"test:watch": "jest --watchAll",
|
|
17
18
|
"test:update": "npm run test -- -u",
|
|
@@ -19,9 +20,9 @@
|
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@canva/app-i18n-kit": "^1.0.2",
|
|
22
|
-
"@canva/app-ui-kit": "^4.
|
|
23
|
+
"@canva/app-ui-kit": "^4.8.0",
|
|
23
24
|
"@canva/asset": "^2.1.0",
|
|
24
|
-
"@canva/design": "^2.
|
|
25
|
+
"@canva/design": "^2.4.0",
|
|
25
26
|
"@canva/error": "^2.1.0",
|
|
26
27
|
"@canva/platform": "^2.1.0",
|
|
27
28
|
"@canva/user": "^2.1.0",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"react-refresh": "0.16.0",
|
|
82
83
|
"style-loader": "4.0.0",
|
|
83
84
|
"terser-webpack-plugin": "5.3.11",
|
|
85
|
+
"tree-kill": "1.2.2",
|
|
84
86
|
"ts-jest": "29.2.5",
|
|
85
87
|
"ts-loader": "9.5.2",
|
|
86
88
|
"ts-node": "10.9.2",
|