Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/publish hugging face #9064

Merged
merged 4 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions src/command/publish/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export const publishCommand =
"Publish document (prompt for provider)",
"quarto publish document.qmd",
)
.example(
"Publish project to Hugging Face Spaces",
"quarto publish huggingface",
)
.example(
"Publish project to Netlify",
"quarto publish netlify",
Expand Down Expand Up @@ -163,7 +167,7 @@ async function publishAction(
await initYamlIntelligence();

// coalesce options
const publishOptions = await createPublishOptions(options, path);
const publishOptions = await createPublishOptions(options, provider, path);

// helper to publish (w/ account confirmation)
const doPublish = async (
Expand Down Expand Up @@ -302,6 +306,7 @@ async function publish(

async function createPublishOptions(
options: PublishCommandOptions,
provider?: PublishProvider,
path?: string,
): Promise<PublishOptions> {
const nbContext = notebookContext();
Expand All @@ -315,27 +320,30 @@ async function createPublishOptions(
// determine publish input
let input: ProjectContext | string | undefined;

if (provider && provider.resolveProjectPath) {
const resolvedPath = provider.resolveProjectPath(path);
try {
if (Deno.statSync(resolvedPath).isDirectory) {
path = resolvedPath;
}
} catch (_e) {
// ignore
}
}

// check for directory (either website or single-file project)
const project = (await projectContext(path, nbContext)) ||
singleFileProjectContext(path, nbContext);
if (Deno.statSync(path).isDirectory) {
if (project) {
if (projectIsWebsite(project)) {
input = project;
} else if (
projectIsManuscript(project) && project.files.input.length > 0
) {
input = project;
} else if (project.files.input.length === 1) {
input = project.files.input[0];
}
if (projectIsWebsite(project)) {
input = project;
} else if (
projectIsManuscript(project) && project.files.input.length > 0
) {
input = project;
} else if (project.files.input.length === 1) {
input = project.files.input[0];
} else {
const inputFiles = await projectInputFiles(project);
if (inputFiles.files.length === 1) {
input = inputFiles.files[0];
}
}
if (!input) {
throw new Error(
`The specified path (${path}) is not a website, manuscript or book project so cannot be published.`,
);
Expand Down
36 changes: 36 additions & 0 deletions src/core/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,42 @@

import { which } from "./path.ts";
import { execProcess } from "./process.ts";
import SemVer from "semver/mod.ts";

export async function gitCmds(dir: string, cmds: Array<string[]>) {
for (const cmd of cmds) {
if (
!(await execProcess({
cmd: ["git", ...cmd],
cwd: dir,
})).success
) {
throw new Error();
}
}
}

export async function gitVersion(): Promise<SemVer> {
const result = await execProcess(
{
cmd: ["git", "--version"],
stdout: "piped",
},
);
if (!result.success) {
throw new Error(
"Unable to determine git version. Please check that git is installed and available on your PATH.",
);
}
const match = result.stdout?.match(/git version (\d+\.\d+\.\d+)/);
if (match) {
return new SemVer(match[1]);
} else {
throw new Error(
`Unable to determine git version from string ${result.stdout}`,
);
}
}

export async function lsFiles(
cwd?: string,
Expand Down
11 changes: 11 additions & 0 deletions src/publish/common/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* errors.ts
*
* Copyright (C) 2020-2024 Posit Software, PBC
*/

export const throwUnableToPublish = (reason: string, provider: string) => {
throw new Error(
`Unable to publish to ${provider} (${reason})`,
);
};
66 changes: 66 additions & 0 deletions src/publish/common/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* git.ts
*
* Copyright (C) 2020-2024 Posit Software, PBC
*/

import { websiteBaseurl } from "../../project/types/website/website-config.ts";
import { gitHubContext } from "../../core/github.ts";
import { ProjectContext } from "../../project/types.ts";
import { dirname } from "../../deno_ral/path.ts";
import { AccountToken, AccountTokenType } from "../provider-types.ts";
import { PublishOptions } from "../types.ts";
import { GitHubContext } from "../../core/github-types.ts";
import { throwUnableToPublish } from "./errors.ts";

export async function gitHubContextForPublish(input: string | ProjectContext) {
// Create the base context
const dir = typeof input === "string" ? dirname(input) : input.dir;
const context = await gitHubContext(dir);

// always prefer configured website URL
if (typeof input !== "string") {
const configSiteUrl = websiteBaseurl(input?.config);
if (configSiteUrl) {
context.siteUrl = configSiteUrl;
}
}
return context;
}

export function anonymousAccount(): AccountToken {
return {
type: AccountTokenType.Anonymous,
name: "anonymous",
server: null,
token: "anonymous",
};
}

export function verifyContext(
ghContext: GitHubContext,
provider: string,
) {
if (!ghContext.git) {
throwUnableToPublish(
"git does not appear to be installed on this system",
provider,
);
}

// validate we are in a git repo
if (!ghContext.repo) {
throwUnableToPublish(
"the target directory is not a git repository",
provider,
);
}

// validate that we have an origin
if (!ghContext.originUrl) {
throwUnableToPublish(
"the git repository does not have a remote origin",
provider,
);
}
}
91 changes: 7 additions & 84 deletions src/publish/gh-pages/gh-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { execProcess } from "../../core/process.ts";
import { ProjectContext } from "../../project/types.ts";
import {
AccountToken,
AccountTokenType,
PublishFiles,
PublishProvider,
} from "../provider-types.ts";
Expand All @@ -27,10 +26,13 @@ import { sleep } from "../../core/wait.ts";
import { joinUrl } from "../../core/url.ts";
import { completeMessage, withSpinner } from "../../core/console.ts";
import { renderForPublish } from "../common/publish.ts";
import { websiteBaseurl } from "../../project/types/website/website-config.ts";
import { RenderFlags } from "../../command/render/types.ts";
import SemVer from "semver/mod.ts";
import { gitHubContext } from "../../core/github.ts";
import { gitCmds, gitVersion } from "../../core/git.ts";
import {
anonymousAccount,
gitHubContextForPublish,
verifyContext,
} from "../common/git.ts";

export const kGhpages = "gh-pages";
const kGhpagesDescription = "GitHub Pages";
Expand All @@ -50,35 +52,13 @@ export const ghpagesProvider: PublishProvider = {
isNotFound,
};

function anonymousAccount(): AccountToken {
return {
type: AccountTokenType.Anonymous,
name: "anonymous",
server: null,
token: "anonymous",
};
}

function accountTokens() {
return Promise.resolve([anonymousAccount()]);
}

async function authorizeToken(options: PublishOptions) {
const ghContext = await gitHubContextForPublish(options.input);

if (!ghContext.git) {
throwUnableToPublish("git does not appear to be installed on this system");
}

// validate we are in a git repo
if (!ghContext.repo) {
throwUnableToPublish("the target directory is not a git repository");
}

// validate that we have an origin
if (!ghContext.originUrl) {
throwUnableToPublish("the git repository does not have a remote origin");
}
verifyContext(ghContext, "GitHub Pages");

// good to go!
return Promise.resolve(anonymousAccount());
Expand Down Expand Up @@ -106,28 +86,6 @@ function resolveTarget(
return Promise.resolve(target);
}

async function gitVersion(): Promise<SemVer> {
const result = await execProcess(
{
cmd: ["git", "--version"],
stdout: "piped",
},
);
if (!result.success) {
throw new Error(
"Unable to determine git version. Please check that git is installed and available on your PATH.",
);
}
const match = result.stdout?.match(/git version (\d+\.\d+\.\d+)/);
if (match) {
return new SemVer(match[1]);
} else {
throw new Error(
`Unable to determine git version from string ${result.stdout}`,
);
}
}

async function publish(
_account: AccountToken,
type: "document" | "site",
Expand Down Expand Up @@ -405,38 +363,3 @@ async function gitCreateGhPages(dir: string) {
["push", "origin", `HEAD:gh-pages`],
]);
}

async function gitCmds(dir: string, cmds: Array<string[]>) {
for (const cmd of cmds) {
if (
!(await execProcess({
cmd: ["git", ...cmd],
cwd: dir,
})).success
) {
throw new Error();
}
}
}

// validate we have git
const throwUnableToPublish = (reason: string) => {
throw new Error(
`Unable to publish to GitHub Pages (${reason})`,
);
};

async function gitHubContextForPublish(input: string | ProjectContext) {
// Create the base context
const dir = typeof input === "string" ? dirname(input) : input.dir;
const context = await gitHubContext(dir);

// always prefer configured website URL
if (typeof input !== "string") {
const configSiteUrl = websiteBaseurl(input?.config);
if (configSiteUrl) {
context.siteUrl = configSiteUrl;
}
}
return context;
}
Loading
Loading