Skip to content

Commit

Permalink
Cherry pick #1024, #1049 to 2.0.x (#1053)
Browse files Browse the repository at this point in the history
  • Loading branch information
tzyl authored Dec 11, 2024
1 parent c0b8206 commit a429201
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-toes-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@osdk/cli": patch
---

Support site snapshot upload
5 changes: 5 additions & 0 deletions .changeset/modern-spiders-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@osdk/cli": patch
---

Site version and file limit custom error message and tips
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"consola": "^3.2.3",
"find-up": "^7.0.0",
"open": "^10.1.0",
"pretty-bytes": "^6.1.1",
"semver": "^7.6.3",
"tslib": "^2.6.3",
"yargs": "^17.7.2"
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/site/deploy/SiteDeployArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ export interface SiteDeployArgs extends CommonSiteArgs {
uploadOnly: boolean;
autoVersion?: AutoVersionConfigType;
gitTagPrefix?: string;
snapshot: boolean;
snapshotId?: string;
}
27 changes: 27 additions & 0 deletions packages/cli/src/commands/site/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ const command: CommandModule<
? { default: gitTagPrefix }
: {},
},
snapshot: {
type: "boolean",
description:
"Upload a snapshot version only with automatic retention",
default: false,
},
snapshotId: {
type: "string",
description:
"Optional id to associate with snapshot version as an alias",
},
})
.group(
["directory", "version", "uploadOnly"],
Expand All @@ -87,6 +98,10 @@ const command: CommandModule<
["autoVersion", "gitTagPrefix"],
"Auto Version Options",
)
.group(
["snapshot", "snapshotId"],
"Snapshot Options",
)
.check((args) => {
// This is required because we can't use demandOption with conflicts. conflicts protects us against the case where both are provided.
// So this case is for when nothing is provided.
Expand Down Expand Up @@ -120,6 +135,18 @@ const command: CommandModule<
);
}

if (args.uploadOnly && args.snapshot) {
throw new YargsCheckError(
`--uploadOnly and --snapshot cannot be enabled together`,
);
}

if (args.snapshotId != null && !args.snapshot) {
throw new YargsCheckError(
"--snapshotId is only supported when --snapshot is enabled",
);
}

return true;
}).middleware((args) =>
logDeployCommandConfigFileOverride(
Expand Down
80 changes: 66 additions & 14 deletions packages/cli/src/commands/site/deploy/siteDeployCommand.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ import { colorize } from "consola/utils";
import * as fs from "node:fs";
import path from "node:path";
import { Readable } from "node:stream";
import prettyBytes from "pretty-bytes";
import type { InternalClientContext } from "../../../net/internalClientContext.mjs";
import type { ThirdPartyAppRid } from "../../../net/ThirdPartyAppRid.js";
import { autoVersion as findAutoVersion } from "../../../util/autoVersion.js";
import type { AutoVersionConfig } from "../../../util/config.js";
import { loadToken } from "../../../util/token.js";
import type { SiteDeployArgs } from "./SiteDeployArgs.js";

interface SiteDeployInternalArgs
extends Omit<SiteDeployArgs, "version" | "autoVersion">
extends Omit<SiteDeployArgs, "version" | "autoVersion" | "gitTagPrefix">
{
selectedVersion: string | AutoVersionConfig;
directory: string;
uploadOnly: boolean;
}

export default async function siteDeployCommand(
Expand All @@ -42,6 +43,8 @@ export default async function siteDeployCommand(
application,
foundryUrl,
uploadOnly,
snapshot,
snapshotId,
directory,
token,
tokenFile,
Expand Down Expand Up @@ -74,17 +77,24 @@ export default async function siteDeployCommand(
const archive = archiver("zip").directory(directory, false);
logArchiveStats(archive);

consola.start("Uploading site files");
await Promise.all([
thirdPartyApplications.uploadVersion(
if (snapshot) {
await uploadSnapshot(
clientCtx,
application,
siteVersion,
Readable.toWeb(archive) as ReadableStream<any>, // This cast is because the dom fetch doesn't align type wise with streams
),
archive.finalize(),
]);
consola.success("Upload complete");
snapshotId ?? "",
archive,
);
consola.info("Snapshot mode enabled, skipping deployment");
return;
}

await upload(
clientCtx,
application,
siteVersion,
archive,
);

if (!uploadOnly) {
const website = await thirdPartyApplications.deployWebsite(
Expand All @@ -106,7 +116,7 @@ export default async function siteDeployCommand(
application,
);
const domain = website?.subdomains[0];
consola.debug("Upload only mode enabled, skipping deployment");
consola.info("Upload only mode enabled, skipping deployment");
if (domain != null) {
logSiteLink(
"Preview link:",
Expand All @@ -116,6 +126,46 @@ export default async function siteDeployCommand(
}
}

async function uploadSnapshot(
clientCtx: InternalClientContext,
application: ThirdPartyAppRid,
siteVersion: string,
snapshotId: string,
archive: archiver.Archiver,
): Promise<void> {
consola.start("Uploading snapshot site files");
await Promise.all([
thirdPartyApplications.uploadSnapshotVersion(
clientCtx,
application,
siteVersion,
snapshotId,
Readable.toWeb(archive) as ReadableStream<any>, // This cast is because the dom fetch doesn't align type wise with streams
),
archive.finalize(),
]);
consola.success("Snapshot upload complete");
}

async function upload(
clientCtx: InternalClientContext,
application: ThirdPartyAppRid,
siteVersion: string,
archive: archiver.Archiver,
): Promise<void> {
consola.start("Uploading site files");
await Promise.all([
thirdPartyApplications.uploadVersion(
clientCtx,
application,
siteVersion,
Readable.toWeb(archive) as ReadableStream<any>, // This cast is because the dom fetch doesn't align type wise with streams
),
archive.finalize(),
]);
consola.success("Upload complete");
}

function logArchiveStats(archive: archiver.Archiver): void {
let archiveStats = { fileCount: 0, bytes: 0 };
archive.on("progress", (progress) => {
Expand All @@ -125,8 +175,10 @@ function logArchiveStats(archive: archiver.Archiver): void {
};
});
archive.on("finish", () => {
consola.debug(
`Zipped ${archiveStats.fileCount} files and ${archiveStats.bytes} bytes`,
consola.info(
`Zipped ${
prettyBytes(archiveStats.bytes, { binary: true })
} total over ${archiveStats.fileCount} files`,
);
});
}
Expand Down
34 changes: 34 additions & 0 deletions packages/cli/src/net/createFetch.mts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
PalantirApiError,
} from "@osdk/shared.net";
import { consola } from "consola";
import prettyBytes from "pretty-bytes";
import { USER_AGENT } from "./UserAgent.js";

export function createFetch(
Expand Down Expand Up @@ -76,6 +77,39 @@ function handleFetchError(e: unknown): Promise<Response> {
message = "The site version already exists";
} else if (e.errorName === "VersionNotFound") {
message = "The site version could not be found";
} else if (e.errorName === "VersionLimitExceeded") {
const { versionLimit } = e.parameters ?? {};
const versionLimitPart = versionLimit != null
? ` (Limit: ${versionLimit} versions)`
: "";
message = `The site contains too many versions${versionLimitPart}`;
tip =
"Run the `site version delete` command to delete an old version and try again";
} else if (e.errorName === "FileCountLimitExceeded") {
const { fileCountLimit } = e.parameters ?? {};
const fileCountLimitPart = fileCountLimit != null
? ` (Limit: ${fileCountLimit} files)`
: "";
message = `The .zip file contains too many files${fileCountLimitPart}`;
tip =
"Reduce the number of files in the production build to below the limit";
} else if (e.errorName === "FileSizeLimitExceeded") {
const { currentFilePath, currentFileSizeBytes, fileSizeBytesLimit } =
e.parameters ?? {};
const currentFilePathPart = currentFilePath != null
? ` "${currentFilePath}"`
: "";
const currentFileSizePart = currentFileSizeBytes != null
? ` (${prettyBytes(parseInt(currentFileSizeBytes), { binary: true })})`
: "";
const fileSizeLimitPart = fileSizeBytesLimit != null
? ` (Limit: ${
prettyBytes(parseInt(fileSizeBytesLimit), { binary: true })
})`
: "";
message =
`The .zip file contains a file${currentFilePathPart}${currentFileSizePart} that is too large${fileSizeLimitPart}`;
tip = "Ensure all files in the production build are below the size limit";
} else {
const { errorCode, errorName, errorInstanceId, parameters } = e;
// Include extra info about the original API error in CLI error messages
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/net/third-party-applications/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { getWebsite } from "./getWebsite.mjs";
export { listVersions } from "./listVersions.mjs";
export type { ListVersionsResponse } from "./ListVersionsResponse.mjs";
export { undeployWebsite } from "./undeployWebsite.mjs";
export { uploadSnapshotVersion } from "./uploadSnapshotVersion.mjs";
export { uploadVersion } from "./uploadVersion.mjs";
export type { Version } from "./Version.mjs";
export type { Website } from "./Website.mjs";
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createFetch } from "../createFetch.mjs";
import type { InternalClientContext } from "../internalClientContext.mjs";
import type { ThirdPartyAppRid } from "../ThirdPartyAppRid.js";
import type { Version } from "./Version.mjs";

export async function uploadSnapshotVersion(
ctx: InternalClientContext,
thirdPartyAppRid: ThirdPartyAppRid,
version: string,
snapshotId: string,
zipFile: ReadableStream | Blob | BufferSource,
): Promise<Version> {
const fetch = createFetch(ctx.tokenProvider);
const url =
`${ctx.foundryUrl}/api/v2/thirdPartyApplications/${thirdPartyAppRid}/website/versions/uploadSnapshot?version=${version}&preview=true${
snapshotId !== ""
? `&snapshotIdentifier=${snapshotId}`
: ""
}`;

const result = await fetch(
url,
{
method: "POST",
body: zipFile,
headers: {
"Content-Type": "application/octet-stream",
},
duplex: "half", // Node hates me
} satisfies RequestInit & { duplex: "half" } as any,
);
return result.json();
}
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a429201

Please sign in to comment.