From 0ef5d7559f1243199e9cedb60ed3502f77e5b048 Mon Sep 17 00:00:00 2001 From: Timothy Leung Date: Mon, 9 Dec 2024 18:03:54 +0000 Subject: [PATCH] Support site snapshot upload --- .changeset/brave-toes-work.md | 5 ++ .../commands/site/deploy/SiteDeployArgs.ts | 2 + .../cli/src/commands/site/deploy/index.ts | 27 +++++++ .../site/deploy/siteDeployCommand.mts | 73 ++++++++++++++++--- .../net/third-party-applications/index.mts | 1 + .../uploadSnapshotVersion.mts | 49 +++++++++++++ 6 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 .changeset/brave-toes-work.md create mode 100644 packages/cli/src/net/third-party-applications/uploadSnapshotVersion.mts diff --git a/.changeset/brave-toes-work.md b/.changeset/brave-toes-work.md new file mode 100644 index 000000000..04c66f574 --- /dev/null +++ b/.changeset/brave-toes-work.md @@ -0,0 +1,5 @@ +--- +"@osdk/cli": patch +--- + +Support site snapshot upload diff --git a/packages/cli/src/commands/site/deploy/SiteDeployArgs.ts b/packages/cli/src/commands/site/deploy/SiteDeployArgs.ts index dff9027f5..404b247e8 100644 --- a/packages/cli/src/commands/site/deploy/SiteDeployArgs.ts +++ b/packages/cli/src/commands/site/deploy/SiteDeployArgs.ts @@ -23,4 +23,6 @@ export interface SiteDeployArgs extends CommonSiteArgs { uploadOnly: boolean; autoVersion?: AutoVersionConfigType; gitTagPrefix?: string; + snapshot: boolean; + snapshotId?: string; } diff --git a/packages/cli/src/commands/site/deploy/index.ts b/packages/cli/src/commands/site/deploy/index.ts index eebe54064..e9e3d6634 100644 --- a/packages/cli/src/commands/site/deploy/index.ts +++ b/packages/cli/src/commands/site/deploy/index.ts @@ -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"], @@ -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. @@ -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( diff --git a/packages/cli/src/commands/site/deploy/siteDeployCommand.mts b/packages/cli/src/commands/site/deploy/siteDeployCommand.mts index 4ee5eb5d3..56dfa898f 100644 --- a/packages/cli/src/commands/site/deploy/siteDeployCommand.mts +++ b/packages/cli/src/commands/site/deploy/siteDeployCommand.mts @@ -24,17 +24,17 @@ 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 + extends Omit { selectedVersion: string | AutoVersionConfig; - directory: string; - uploadOnly: boolean; } export default async function siteDeployCommand( @@ -43,6 +43,8 @@ export default async function siteDeployCommand( application, foundryUrl, uploadOnly, + snapshot, + snapshotId, directory, token, tokenFile, @@ -75,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, // 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( @@ -107,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:", @@ -117,6 +126,46 @@ export default async function siteDeployCommand( } } +async function uploadSnapshot( + clientCtx: InternalClientContext, + application: ThirdPartyAppRid, + siteVersion: string, + snapshotId: string, + archive: archiver.Archiver, +): Promise { + consola.start("Uploading snapshot site files"); + await Promise.all([ + thirdPartyApplications.uploadSnapshotVersion( + clientCtx, + application, + siteVersion, + snapshotId, + Readable.toWeb(archive) as ReadableStream, // 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 { + consola.start("Uploading site files"); + await Promise.all([ + thirdPartyApplications.uploadVersion( + clientCtx, + application, + siteVersion, + Readable.toWeb(archive) as ReadableStream, // 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) => { diff --git a/packages/cli/src/net/third-party-applications/index.mts b/packages/cli/src/net/third-party-applications/index.mts index 4833f7218..3bf89acb6 100644 --- a/packages/cli/src/net/third-party-applications/index.mts +++ b/packages/cli/src/net/third-party-applications/index.mts @@ -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"; diff --git a/packages/cli/src/net/third-party-applications/uploadSnapshotVersion.mts b/packages/cli/src/net/third-party-applications/uploadSnapshotVersion.mts new file mode 100644 index 000000000..b3111af7b --- /dev/null +++ b/packages/cli/src/net/third-party-applications/uploadSnapshotVersion.mts @@ -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 { + 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(); +}