Skip to content

Commit

Permalink
[2/] Improvement: Introduce new command structure (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
zeyadkhaled authored Feb 7, 2024
1 parent 9f3876a commit 16efd1e
Show file tree
Hide file tree
Showing 26 changed files with 554 additions and 167 deletions.
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import auth from "./commands/auth/index.js";
import site from "./commands/site/index.js";
import typescript from "./commands/typescript/index.js";
import { ExitProcessError } from "./ExitProcessError.js";
import { logConfigFileMiddleware } from "./yargs/logConfigFileMiddleware.js";
import { logVersionMiddleware } from "./yargs/logVersionMiddleware.js";

export async function cli(args: string[] = process.argv) {
Expand All @@ -39,6 +40,7 @@ export async function cli(args: string[] = process.argv) {
)
.demandCommand()
.middleware(logVersionMiddleware, true)
.middleware(logConfigFileMiddleware)
.strict()
.command({
command: "unstable",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/site/CommonSiteArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type { CliCommonArgs } from "../../CliCommonArgs.js";
import type { ThirdPartyAppRid } from "../../net/ThirdPartyAppRid.js";

export interface CommonSiteArgs extends CliCommonArgs {
appRid: ThirdPartyAppRid;
baseUrl: string;
application: ThirdPartyAppRid;
foundryUrl: string;
token?: string;
tokenFile?: string;
}
7 changes: 5 additions & 2 deletions packages/cli/src/commands/site/deploy/SiteDeployArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import type { CommonSiteArgs } from "../CommonSiteArgs.js";

export interface SiteDeployArgs extends CommonSiteArgs {
siteVersion?: string;
clearVersion?: boolean;
version?: string;
directory: string;
uploadOnly: boolean;
autoVersion?: string;
gitTagPrefix?: string;
}
122 changes: 95 additions & 27 deletions packages/cli/src/commands/site/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,120 @@
* limitations under the License.
*/

import { consola } from "consola";
import type { CommandModule } from "yargs";
import type { LoadedFoundryConfig, SiteConfig } from "../../../util/config.js";
import configLoader from "../../../util/configLoader.js";
import type { CommonSiteArgs } from "../CommonSiteArgs.js";
import type { SiteDeployArgs } from "./SiteDeployArgs.js";

export const command: CommandModule<
const command: CommandModule<
CommonSiteArgs,
SiteDeployArgs
> = {
command: "deploy",
describe: "Deploy an uploaded version",
builder: (argv) => {
builder: async (argv) => {
const config: LoadedFoundryConfig | undefined = await configLoader();
const siteConfig: SiteConfig | undefined = config?.foundryConfig.site;
const directory = siteConfig?.directory;
const autoVersion = siteConfig?.autoVersion;
const gitTagPrefix = autoVersion?.tagPrefix;

return argv
.options({
"siteVersion": {
directory: {
type: "string",
conflicts: "clearVersion",
// group: "Deploy Version",
// implies: { "clearVersion": "false" },
description: "Directory to deploy",
...directory
? { default: directory }
: { demandOption: true },
},
undeploy: {
alias: "clearVersion",
description: "Causes the site to no longer be deployed",
uploadOnly: {
type: "boolean",
conflicts: "siteVersion",
// group: "Deploy Version",
// implies: { "siteVersion": "" },
description: "Upload the directory but do not deploy it",
default: false,
},
version: {
type: "string",
description: "Version to deploy",
...autoVersion == null
? { conflicts: "autoVersion" }
: {},
},
autoVersion: {
type: "string",
description:
"Enables autoversioning. Can be set to 'git-describe' to use git describe to determine the version.",
...(autoVersion != null)
? { default: autoVersion.type }
: { conflicts: "version" },
},
gitTagPrefix: {
type: "string",
description:
"Prefix to match git tags against when --autoVersion=git-describe is used. If not provided, a default prefix 'v' is used.",
...gitTagPrefix
? { default: gitTagPrefix }
: {},
},
}).group(
["siteVersion", "clearVersion"],
"Version To Deploy (requires one of)",
);
["autoVersion", "gitTagPrefix"],
"Autoversion Arguments",
)
.group(
["version", "directory", "uploadOnly"],
"Common Arguments",
)
.check((argv) => {
// 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.
if (
autoVersion == null && argv.autoVersion == null
&& argv.version == null
) {
throw new Error(
"One of --version or --autoVersion must be specified",
);
}

if (
(autoVersion?.type !== "git-describe"
|| argv.autoVersion !== "git-describe")
&& argv.gitTagPrefix != null
) {
throw new Error(
`--gitTagPrefix is only supported when --autoVersion=git-describe`,
);
}

if (autoVersion != null && argv.autoVersion !== autoVersion.type) {
consola.debug(
`Overriding "autoVersion" from config file with ${argv.autoVersion}`,
);
if (argv.autoVersion !== "git-describe") {
throw new Error(
`Only 'git-describe' is supported for autoVersion`,
);
}
}

if (directory != null && argv.directory !== directory) {
consola.debug(
`Overriding "directory" from config file with ${argv.directory}`,
);
}

// .check((args) => {
// if (
// (args.siteVersion && args.clearVersion)
// || (!args.siteVersion && args.clearVersion == undefined)
// ) {
// // consola.error("Only one of --siteVersion or --clearVersion may be provided");
// throw new Error(
// "Only one of --siteVersion or --clearVersion may be provided",
// );
// }
// });
if (gitTagPrefix != null && argv.gitTagPrefix !== gitTagPrefix) {
consola.debug(
`Overriding "gitTagPrefix" from config file with ${argv.gitTagPrefix}`,
);
}
return true;
});
},
handler: async (args) => {
const command = await import("./handleSiteDeploy.mjs");
const command = await import("./siteDeployCommand.mjs");
await command.default(args);
},
};
Expand Down
94 changes: 94 additions & 0 deletions packages/cli/src/commands/site/deploy/siteDeployCommand.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2023 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 { consola } from "consola";

import {
artifacts,
ArtifactsSitesAdminV2Service,
createConjureContext,
thirdPartyApplicationService,
} from "#net";
import archiver from "archiver";
import * as fs from "node:fs";
import { Readable } from "node:stream";
import { ExitProcessError } from "../../../ExitProcessError.js";
import { autoVersion as findAutoVersion } from "../../../util/autoVersion.js";
import type { SiteDeployArgs } from "./SiteDeployArgs.js";

export default async function siteDeployCommand(
{
version,
application,
foundryUrl,
autoVersion,
gitTagPrefix,
uploadOnly,
directory,
}: SiteDeployArgs,
) {
if (!version && !autoVersion) {
throw new ExitProcessError(
2,
"Either version or autoVersion must be specified",
);
}

const siteVersion = !version ? await findAutoVersion(gitTagPrefix) : version;
if (autoVersion) {
consola.info(
`Auto version inferred next version to be: ${siteVersion}`,
);
}

const stat = await fs.promises.stat(directory);
if (!stat.isDirectory()) {
consola.error("Specified path is not a directory");
throw new ExitProcessError(2);
}

consola.start("Zippping site files");

const archive = archiver("zip").directory(directory, false);

await Promise.all([
artifacts.SiteAssetArtifactsService.uploadZippedSiteAsset(
foundryUrl,
application,
siteVersion,
Readable.toWeb(archive) as ReadableStream<any>, // This cast is because the dom fetch doesnt align type wise with streams
),
archive.finalize(),
]);

consola.success("Upload complete");

if (!uploadOnly) {
const repositoryRid = await thirdPartyApplicationService
.fetchWebsiteRepositoryRid(foundryUrl, application);

const ctx = createConjureContext(foundryUrl, "/artifacts/api");
await ArtifactsSitesAdminV2Service.updateDeployedVersion(
ctx,
repositoryRid,
{ siteVersion: { version: siteVersion } },
);

consola.success(`Deployed ${siteVersion} successfully`);
} else {
consola.debug("Upload only mode enabled, skipping deployment");
}
}
80 changes: 61 additions & 19 deletions packages/cli/src/commands/site/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,82 @@
* limitations under the License.
*/

import type * as yargs from "yargs";
import { consola } from "consola";
import type { CommandModule } from "yargs";
import type { CliCommonArgs } from "../../CliCommonArgs.js";
import { ExitProcessError } from "../../ExitProcessError.js";
import type { ThirdPartyAppRid } from "../../net/ThirdPartyAppRid.js";
import type { LoadedFoundryConfig } from "../../util/config.js";
import configLoader from "../../util/configLoader.js";
import type { CommonSiteArgs } from "./CommonSiteArgs.js";
import siteDelete from "./delete/index.js";
import siteDeploy from "./deploy/index.js";
import upload from "./upload/index.js";
import versions from "./versions/index.js";
import deploy from "./deploy/index.js";
import version from "./version/index.js";

const site: yargs.CommandModule<CliCommonArgs, CommonSiteArgs> = {
const command: CommandModule<CliCommonArgs, CommonSiteArgs> = {
command: "site",
describe: "Manage your site",
builder: (argv) => {
builder: async (argv) => {
const config: LoadedFoundryConfig | undefined = await configLoader();
const application = config?.foundryConfig.site.application;
const foundryUrl = config?.foundryConfig.foundryUrl;
return argv
.options({
appRid: {
application: {
type: "string",
demandOption: true,
coerce: (a) => a as ThirdPartyAppRid,
...application
? { default: application }
: { demandOption: true },
description: "Application RID",
},
baseUrl: {
foundryUrl: {
type: "string",
demandOption: true,
...foundryUrl
? { default: foundryUrl }
: { demandOption: true },
description:
"Foundry Stack URL with Protocol (e.g. https://example.palantirfoundry.com)",
},
token: {
type: "string",
conflicts: "tokenFile",
description: "Foundry API Token",
},
tokenFile: {
type: "string",
conflicts: "token",
description: "Path to a file containing your Foundry API Token",
},
})
.group(["appRid", "baseUrl"], "Common Arguments")
.command(versions)
.command(upload)
.command(siteDelete)
.command(siteDeploy)
.group(
["application", "foundryUrl", "token", "tokenFile"],
"Common Arguments",
)
.command(version)
.command(deploy)
.check((argv) => {
if (application != null && argv.application !== application) {
consola.debug(
`Overriding "application" from config file with ${argv.application}`,
);
}

if (foundryUrl != null && argv.foundryUrl !== foundryUrl) {
consola.debug(
`Overriding "foundryUrl" from config file with ${argv.foundryUrl}`,
);
}

if (!argv.foundryUrl.startsWith("https://")) {
throw new ExitProcessError(1, "foundryUrl must start with https://");
}

argv.foundryUrl = argv.foundryUrl.replace(/\/$/, "");
return true;
})
.demandCommand();
},
handler: async (args) => {
},
handler: async (args) => {},
};

export default site;
export default command;
Loading

0 comments on commit 16efd1e

Please sign in to comment.