@zuplo/cli 1.18.0 → 1.20.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/README.md +2 -0
- package/dist/cli.js +4 -0
- package/dist/cmds/delete.js +29 -0
- package/dist/cmds/list.js +20 -0
- package/dist/delete/handler.js +73 -0
- package/dist/delete/poll-deployment.js +20 -0
- package/dist/deploy/handler.js +4 -4
- package/dist/deploy/poll-deployment.js +8 -6
- package/dist/list/handler.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,9 @@ dotenv.config();
|
|
|
3
3
|
import { gte } from "semver";
|
|
4
4
|
import { hideBin } from "yargs/helpers";
|
|
5
5
|
import yargs from "yargs/yargs";
|
|
6
|
+
import deleteZup from "./cmds/delete.js";
|
|
6
7
|
import deploy from "./cmds/deploy.js";
|
|
8
|
+
import list from "./cmds/list.js";
|
|
7
9
|
import test from "./cmds/test.js";
|
|
8
10
|
import { printCriticalFailureToConsoleAndExit } from "./common/output.js";
|
|
9
11
|
const MIN_NODE_VERSION = "18.0.0";
|
|
@@ -11,7 +13,9 @@ if (gte(process.versions.node, MIN_NODE_VERSION)) {
|
|
|
11
13
|
void yargs(hideBin(process.argv))
|
|
12
14
|
.env("ZUPLO")
|
|
13
15
|
.version(false)
|
|
16
|
+
.command(deleteZup)
|
|
14
17
|
.command(deploy)
|
|
18
|
+
.command(list)
|
|
15
19
|
.command(test)
|
|
16
20
|
.demandCommand()
|
|
17
21
|
.strictCommands()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import setBlocking from "../common/output.js";
|
|
2
|
+
import { deleteZup } from "../delete/handler.js";
|
|
3
|
+
export default {
|
|
4
|
+
desc: "Deletes the zup at the URL",
|
|
5
|
+
command: "delete",
|
|
6
|
+
builder: (yargs) => {
|
|
7
|
+
return yargs
|
|
8
|
+
.option("api-key", {
|
|
9
|
+
type: "string",
|
|
10
|
+
describe: "The API Key from Zuplo",
|
|
11
|
+
envVar: "API_KEY",
|
|
12
|
+
})
|
|
13
|
+
.option("url", {
|
|
14
|
+
type: "string",
|
|
15
|
+
describe: "The URL of the zup to delete",
|
|
16
|
+
})
|
|
17
|
+
.option("wait", {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
describe: "Should the CLI wait until the zup is deleted",
|
|
20
|
+
})
|
|
21
|
+
.boolean("wait")
|
|
22
|
+
.demandOption(["api-key", "url"])
|
|
23
|
+
.middleware([setBlocking]);
|
|
24
|
+
},
|
|
25
|
+
handler: async (argv) => {
|
|
26
|
+
await deleteZup(argv);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import setBlocking from "../common/output.js";
|
|
2
|
+
import { list } from "../list/handler.js";
|
|
3
|
+
export default {
|
|
4
|
+
desc: "Lists all deployed zups",
|
|
5
|
+
command: "list",
|
|
6
|
+
builder: (yargs) => {
|
|
7
|
+
return yargs
|
|
8
|
+
.option("api-key", {
|
|
9
|
+
type: "string",
|
|
10
|
+
describe: "The API Key from Zuplo",
|
|
11
|
+
envVar: "API_KEY",
|
|
12
|
+
})
|
|
13
|
+
.demandOption(["api-key"])
|
|
14
|
+
.middleware([setBlocking]);
|
|
15
|
+
},
|
|
16
|
+
handler: async (argv) => {
|
|
17
|
+
await list(argv);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { logger } from "../common/logger.js";
|
|
2
|
+
import { printCriticalFailureToConsoleAndExit, printResultToConsoleAndExitGracefully, } from "../common/output.js";
|
|
3
|
+
import settings from "../common/settings.js";
|
|
4
|
+
import { pingDeployment } from "./poll-deployment.js";
|
|
5
|
+
export async function deleteZup(argv) {
|
|
6
|
+
let deploymentName;
|
|
7
|
+
try {
|
|
8
|
+
let url = argv.url;
|
|
9
|
+
if (!/^(http:\/\/|https:\/\/)/i.test(argv.url)) {
|
|
10
|
+
url = "https://" + url;
|
|
11
|
+
}
|
|
12
|
+
const parsedUrl = new URL(argv.url);
|
|
13
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
14
|
+
logger.error(`Extra username and/or password was included in the URL.`);
|
|
15
|
+
printCriticalFailureToConsoleAndExit(`Error: Extraneous username and/or password in the URL. Only include the hostname.`);
|
|
16
|
+
}
|
|
17
|
+
if (parsedUrl.pathname !== "/") {
|
|
18
|
+
logger.error(`Extra pathname was included in the URL: ${parsedUrl.pathname}`);
|
|
19
|
+
printCriticalFailureToConsoleAndExit(`Error: Extraneous pathname in URL: ${parsedUrl.pathname}. Only include the hostname.`);
|
|
20
|
+
}
|
|
21
|
+
if (parsedUrl.search !== "") {
|
|
22
|
+
logger.error(`Extra search params were included in the URL: ${parsedUrl.search}`);
|
|
23
|
+
printCriticalFailureToConsoleAndExit(`Error: Extraneous search params in URL: ${parsedUrl.search}. Only include the hostname.`);
|
|
24
|
+
}
|
|
25
|
+
deploymentName = parsedUrl.hostname.split(".")[0];
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
logger.error(err, "Failed to parse the URL");
|
|
29
|
+
printCriticalFailureToConsoleAndExit(`Error: Failed to parse the URL: ${argv.url}. Ensure you have entered a valid URL.`);
|
|
30
|
+
}
|
|
31
|
+
const whoAmIResponse = await fetch(`${settings.ZUPLO_DEVELOPER_API_ENDPOINT}/v1/who-am-i`, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${argv["api-key"]}`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (whoAmIResponse.ok) {
|
|
38
|
+
const { account, project } = await whoAmIResponse.json();
|
|
39
|
+
const deleteResponse = await fetch(`${settings.ZUPLO_DEVELOPER_API_ENDPOINT}/v1/accounts/${account}/projects/${project}/deployments/${deploymentName}`, {
|
|
40
|
+
method: "DELETE",
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${argv["api-key"]}`,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
if (deleteResponse.ok) {
|
|
46
|
+
if (argv.wait) {
|
|
47
|
+
const deleted = await pingDeployment(argv);
|
|
48
|
+
if (!deleted) {
|
|
49
|
+
logger.error(`Failed to confirm deletion of zup within alloted time frame.`);
|
|
50
|
+
printCriticalFailureToConsoleAndExit(`Error: Failed to confirm deletion of zup within alloted time frame of ${settings.MAX_POLL_RETRIES * settings.POLL_INTERVAL} ms. Your zup is still be available at ${argv.url}.`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
printResultToConsoleAndExitGracefully(`Deleted ${argv.url}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
printResultToConsoleAndExitGracefully(`Enqueued deletion of ${argv.url}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
logger.error({
|
|
62
|
+
status: deleteResponse.status,
|
|
63
|
+
statusText: deleteResponse.statusText,
|
|
64
|
+
}, "Failed to enqueue the deletion request");
|
|
65
|
+
printCriticalFailureToConsoleAndExit("Error: Failed to enqueue the deletion request. Try again later.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
logger.error({ status: whoAmIResponse.status, statusText: whoAmIResponse.statusText }, "Failed to determine who-am-i");
|
|
70
|
+
printCriticalFailureToConsoleAndExit("Error: Failed to validate the API key. Check your API key.");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { printDiagnosticsToConsole } from "../common/output.js";
|
|
2
|
+
import settings from "../common/settings.js";
|
|
3
|
+
function wait(duration = settings.POLL_INTERVAL) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
5
|
+
}
|
|
6
|
+
export async function pingDeployment(argv) {
|
|
7
|
+
for (let pollRetry = 0; pollRetry < settings.MAX_POLL_RETRIES; pollRetry++) {
|
|
8
|
+
printDiagnosticsToConsole(`Waiting for zup to be deleted... (${pollRetry}/${settings.MAX_POLL_RETRIES})`);
|
|
9
|
+
const response = await fetch(`${argv.url}/__zuplo/build`);
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
await wait();
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=poll-deployment.js.map
|
package/dist/deploy/handler.js
CHANGED
|
@@ -28,12 +28,12 @@ export async function deploy(argv) {
|
|
|
28
28
|
if (uploadResponse.ok) {
|
|
29
29
|
printDiagnosticsToConsole(`Deploying the current branch ${archiveMetadata.metadata.branch} to project ${project} on account ${account}...`);
|
|
30
30
|
const fileId = parse(new URL(uploadUrl).pathname).base.replace(ARCHIVE_EXTENSION, "");
|
|
31
|
-
const { url } = await pollDeployment(argv, fileId, account, project);
|
|
31
|
+
const { url, buildResult } = await pollDeployment(argv, fileId, account, project);
|
|
32
32
|
if (url) {
|
|
33
33
|
printResultToConsoleAndExitGracefully(`Deployed to ${url}`);
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
printCriticalFailureToConsoleAndExit(`Failed to deploy the current branch ${archiveMetadata.metadata.branch} to project ${project} on account ${account}
|
|
36
|
+
printCriticalFailureToConsoleAndExit(`Failed to deploy the current branch ${archiveMetadata.metadata.branch} to project ${project} on account ${account}. Here's the build result: ${JSON.stringify(buildResult, null, 2)}`);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
@@ -49,12 +49,12 @@ export async function deploy(argv) {
|
|
|
49
49
|
status: uploadUrlResponse.status,
|
|
50
50
|
statusText: uploadUrlResponse.statusText,
|
|
51
51
|
}, "Failed to retrieve uploadUrl");
|
|
52
|
-
|
|
52
|
+
printCriticalFailureToConsoleAndExit("Error: Failed to determine where to upload your files. Try again later.");
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
56
|
logger.error({ status: whoAmIResponse.status, statusText: whoAmIResponse.statusText }, "Failed to determine who-am-i");
|
|
57
|
-
|
|
57
|
+
printCriticalFailureToConsoleAndExit("Error: Failed to validate the API key. Check your API key.");
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
//# sourceMappingURL=handler.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logger } from "../common/logger.js";
|
|
1
2
|
import { printDiagnosticsToConsole } from "../common/output.js";
|
|
2
3
|
import settings from "../common/settings.js";
|
|
3
4
|
function wait(duration = settings.POLL_INTERVAL) {
|
|
@@ -13,7 +14,8 @@ export async function pollDeployment(argv, fileId, account, project) {
|
|
|
13
14
|
},
|
|
14
15
|
});
|
|
15
16
|
if (response.ok) {
|
|
16
|
-
const
|
|
17
|
+
const rawJSON = await response.text();
|
|
18
|
+
const { status, url, buildResult } = JSON.parse(rawJSON);
|
|
17
19
|
switch (status) {
|
|
18
20
|
case "IN_PROGRESS":
|
|
19
21
|
await wait();
|
|
@@ -23,6 +25,7 @@ export async function pollDeployment(argv, fileId, account, project) {
|
|
|
23
25
|
if (zupResponse.ok) {
|
|
24
26
|
return {
|
|
25
27
|
url,
|
|
28
|
+
buildResult,
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
else {
|
|
@@ -30,7 +33,7 @@ export async function pollDeployment(argv, fileId, account, project) {
|
|
|
30
33
|
}
|
|
31
34
|
case "ERROR":
|
|
32
35
|
return {
|
|
33
|
-
|
|
36
|
+
buildResult,
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -39,11 +42,10 @@ export async function pollDeployment(argv, fileId, account, project) {
|
|
|
39
42
|
continue;
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
42
|
-
|
|
45
|
+
logger.error(`Unexpected error from server while polling for deployment: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
46
|
+
throw new Error(`Unexpected response from server while polling for deployment: ${response.status} ${response.statusText}`);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
return {
|
|
46
|
-
url: undefined,
|
|
47
|
-
};
|
|
49
|
+
return {};
|
|
48
50
|
}
|
|
49
51
|
//# sourceMappingURL=poll-deployment.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { logger } from "../common/logger.js";
|
|
2
|
+
import { printCriticalFailureToConsoleAndExit, printDiagnosticsToConsole, printResultToConsoleAndExitGracefully, } from "../common/output.js";
|
|
3
|
+
import settings from "../common/settings.js";
|
|
4
|
+
export async function list(argv) {
|
|
5
|
+
const whoAmIResponse = await fetch(`${settings.ZUPLO_DEVELOPER_API_ENDPOINT}/v1/who-am-i`, {
|
|
6
|
+
method: "GET",
|
|
7
|
+
headers: {
|
|
8
|
+
Authorization: `Bearer ${argv["api-key"]}`,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
if (whoAmIResponse.ok) {
|
|
12
|
+
const { account, project } = await whoAmIResponse.json();
|
|
13
|
+
const listResponse = await fetch(`${settings.ZUPLO_DEVELOPER_API_ENDPOINT}/v1/accounts/${account}/projects/${project}/deployments`, {
|
|
14
|
+
method: "GET",
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${argv["api-key"]}`,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (listResponse.ok) {
|
|
20
|
+
const { data: deployments } = await listResponse.json();
|
|
21
|
+
const output = deployments.map((deployment) => deployment.url).join("\n");
|
|
22
|
+
printResultToConsoleAndExitGracefully(output);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
logger.error({
|
|
26
|
+
status: listResponse.status,
|
|
27
|
+
statusText: listResponse.statusText,
|
|
28
|
+
}, "Failed to list deployed zups");
|
|
29
|
+
printDiagnosticsToConsole("Error: Failed to list deployed zups. Try again later.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
logger.error({ status: whoAmIResponse.status, statusText: whoAmIResponse.statusText }, "Failed to determine who-am-i");
|
|
34
|
+
printCriticalFailureToConsoleAndExit("Error: Failed to validate the API key. Check your API key.");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=handler.js.map
|