Skip to content

Commit 751baef

Browse files
authored
Merge pull request #9064 from quarto-dev/feature/publish-hugging-face
Feature/publish hugging face
2 parents b9e30ae + e175d49 commit 751baef

File tree

8 files changed

+344
-110
lines changed

8 files changed

+344
-110
lines changed

src/command/publish/cmd.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ export const publishCommand =
9191
"Publish document (prompt for provider)",
9292
"quarto publish document.qmd",
9393
)
94+
.example(
95+
"Publish project to Hugging Face Spaces",
96+
"quarto publish huggingface",
97+
)
9498
.example(
9599
"Publish project to Netlify",
96100
"quarto publish netlify",
@@ -163,7 +167,7 @@ async function publishAction(
163167
await initYamlIntelligence();
164168

165169
// coalesce options
166-
const publishOptions = await createPublishOptions(options, path);
170+
const publishOptions = await createPublishOptions(options, provider, path);
167171

168172
// helper to publish (w/ account confirmation)
169173
const doPublish = async (
@@ -302,6 +306,7 @@ async function publish(
302306

303307
async function createPublishOptions(
304308
options: PublishCommandOptions,
309+
provider?: PublishProvider,
305310
path?: string,
306311
): Promise<PublishOptions> {
307312
const nbContext = notebookContext();
@@ -315,27 +320,30 @@ async function createPublishOptions(
315320
// determine publish input
316321
let input: ProjectContext | string | undefined;
317322

323+
if (provider && provider.resolveProjectPath) {
324+
const resolvedPath = provider.resolveProjectPath(path);
325+
try {
326+
if (Deno.statSync(resolvedPath).isDirectory) {
327+
path = resolvedPath;
328+
}
329+
} catch (_e) {
330+
// ignore
331+
}
332+
}
333+
318334
// check for directory (either website or single-file project)
319335
const project = (await projectContext(path, nbContext)) ||
320336
singleFileProjectContext(path, nbContext);
321337
if (Deno.statSync(path).isDirectory) {
322-
if (project) {
323-
if (projectIsWebsite(project)) {
324-
input = project;
325-
} else if (
326-
projectIsManuscript(project) && project.files.input.length > 0
327-
) {
328-
input = project;
329-
} else if (project.files.input.length === 1) {
330-
input = project.files.input[0];
331-
}
338+
if (projectIsWebsite(project)) {
339+
input = project;
340+
} else if (
341+
projectIsManuscript(project) && project.files.input.length > 0
342+
) {
343+
input = project;
344+
} else if (project.files.input.length === 1) {
345+
input = project.files.input[0];
332346
} else {
333-
const inputFiles = await projectInputFiles(project);
334-
if (inputFiles.files.length === 1) {
335-
input = inputFiles.files[0];
336-
}
337-
}
338-
if (!input) {
339347
throw new Error(
340348
`The specified path (${path}) is not a website, manuscript or book project so cannot be published.`,
341349
);

src/core/git.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,42 @@
66

77
import { which } from "./path.ts";
88
import { execProcess } from "./process.ts";
9+
import SemVer from "semver/mod.ts";
10+
11+
export async function gitCmds(dir: string, cmds: Array<string[]>) {
12+
for (const cmd of cmds) {
13+
if (
14+
!(await execProcess({
15+
cmd: ["git", ...cmd],
16+
cwd: dir,
17+
})).success
18+
) {
19+
throw new Error();
20+
}
21+
}
22+
}
23+
24+
export async function gitVersion(): Promise<SemVer> {
25+
const result = await execProcess(
26+
{
27+
cmd: ["git", "--version"],
28+
stdout: "piped",
29+
},
30+
);
31+
if (!result.success) {
32+
throw new Error(
33+
"Unable to determine git version. Please check that git is installed and available on your PATH.",
34+
);
35+
}
36+
const match = result.stdout?.match(/git version (\d+\.\d+\.\d+)/);
37+
if (match) {
38+
return new SemVer(match[1]);
39+
} else {
40+
throw new Error(
41+
`Unable to determine git version from string ${result.stdout}`,
42+
);
43+
}
44+
}
945

1046
export async function lsFiles(
1147
cwd?: string,

src/publish/common/errors.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* errors.ts
3+
*
4+
* Copyright (C) 2020-2024 Posit Software, PBC
5+
*/
6+
7+
export const throwUnableToPublish = (reason: string, provider: string) => {
8+
throw new Error(
9+
`Unable to publish to ${provider} (${reason})`,
10+
);
11+
};

src/publish/common/git.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* git.ts
3+
*
4+
* Copyright (C) 2020-2024 Posit Software, PBC
5+
*/
6+
7+
import { websiteBaseurl } from "../../project/types/website/website-config.ts";
8+
import { gitHubContext } from "../../core/github.ts";
9+
import { ProjectContext } from "../../project/types.ts";
10+
import { dirname } from "../../deno_ral/path.ts";
11+
import { AccountToken, AccountTokenType } from "../provider-types.ts";
12+
import { PublishOptions } from "../types.ts";
13+
import { GitHubContext } from "../../core/github-types.ts";
14+
import { throwUnableToPublish } from "./errors.ts";
15+
16+
export async function gitHubContextForPublish(input: string | ProjectContext) {
17+
// Create the base context
18+
const dir = typeof input === "string" ? dirname(input) : input.dir;
19+
const context = await gitHubContext(dir);
20+
21+
// always prefer configured website URL
22+
if (typeof input !== "string") {
23+
const configSiteUrl = websiteBaseurl(input?.config);
24+
if (configSiteUrl) {
25+
context.siteUrl = configSiteUrl;
26+
}
27+
}
28+
return context;
29+
}
30+
31+
export function anonymousAccount(): AccountToken {
32+
return {
33+
type: AccountTokenType.Anonymous,
34+
name: "anonymous",
35+
server: null,
36+
token: "anonymous",
37+
};
38+
}
39+
40+
export function verifyContext(
41+
ghContext: GitHubContext,
42+
provider: string,
43+
) {
44+
if (!ghContext.git) {
45+
throwUnableToPublish(
46+
"git does not appear to be installed on this system",
47+
provider,
48+
);
49+
}
50+
51+
// validate we are in a git repo
52+
if (!ghContext.repo) {
53+
throwUnableToPublish(
54+
"the target directory is not a git repository",
55+
provider,
56+
);
57+
}
58+
59+
// validate that we have an origin
60+
if (!ghContext.originUrl) {
61+
throwUnableToPublish(
62+
"the git repository does not have a remote origin",
63+
provider,
64+
);
65+
}
66+
}

src/publish/gh-pages/gh-pages.ts

Lines changed: 7 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { execProcess } from "../../core/process.ts";
1717
import { ProjectContext } from "../../project/types.ts";
1818
import {
1919
AccountToken,
20-
AccountTokenType,
2120
PublishFiles,
2221
PublishProvider,
2322
} from "../provider-types.ts";
@@ -27,10 +26,13 @@ import { sleep } from "../../core/wait.ts";
2726
import { joinUrl } from "../../core/url.ts";
2827
import { completeMessage, withSpinner } from "../../core/console.ts";
2928
import { renderForPublish } from "../common/publish.ts";
30-
import { websiteBaseurl } from "../../project/types/website/website-config.ts";
3129
import { RenderFlags } from "../../command/render/types.ts";
32-
import SemVer from "semver/mod.ts";
33-
import { gitHubContext } from "../../core/github.ts";
30+
import { gitCmds, gitVersion } from "../../core/git.ts";
31+
import {
32+
anonymousAccount,
33+
gitHubContextForPublish,
34+
verifyContext,
35+
} from "../common/git.ts";
3436

3537
export const kGhpages = "gh-pages";
3638
const kGhpagesDescription = "GitHub Pages";
@@ -50,35 +52,13 @@ export const ghpagesProvider: PublishProvider = {
5052
isNotFound,
5153
};
5254

53-
function anonymousAccount(): AccountToken {
54-
return {
55-
type: AccountTokenType.Anonymous,
56-
name: "anonymous",
57-
server: null,
58-
token: "anonymous",
59-
};
60-
}
61-
6255
function accountTokens() {
6356
return Promise.resolve([anonymousAccount()]);
6457
}
6558

6659
async function authorizeToken(options: PublishOptions) {
6760
const ghContext = await gitHubContextForPublish(options.input);
68-
69-
if (!ghContext.git) {
70-
throwUnableToPublish("git does not appear to be installed on this system");
71-
}
72-
73-
// validate we are in a git repo
74-
if (!ghContext.repo) {
75-
throwUnableToPublish("the target directory is not a git repository");
76-
}
77-
78-
// validate that we have an origin
79-
if (!ghContext.originUrl) {
80-
throwUnableToPublish("the git repository does not have a remote origin");
81-
}
61+
verifyContext(ghContext, "GitHub Pages");
8262

8363
// good to go!
8464
return Promise.resolve(anonymousAccount());
@@ -106,28 +86,6 @@ function resolveTarget(
10686
return Promise.resolve(target);
10787
}
10888

109-
async function gitVersion(): Promise<SemVer> {
110-
const result = await execProcess(
111-
{
112-
cmd: ["git", "--version"],
113-
stdout: "piped",
114-
},
115-
);
116-
if (!result.success) {
117-
throw new Error(
118-
"Unable to determine git version. Please check that git is installed and available on your PATH.",
119-
);
120-
}
121-
const match = result.stdout?.match(/git version (\d+\.\d+\.\d+)/);
122-
if (match) {
123-
return new SemVer(match[1]);
124-
} else {
125-
throw new Error(
126-
`Unable to determine git version from string ${result.stdout}`,
127-
);
128-
}
129-
}
130-
13189
async function publish(
13290
_account: AccountToken,
13391
type: "document" | "site",
@@ -405,38 +363,3 @@ async function gitCreateGhPages(dir: string) {
405363
["push", "origin", `HEAD:gh-pages`],
406364
]);
407365
}
408-
409-
async function gitCmds(dir: string, cmds: Array<string[]>) {
410-
for (const cmd of cmds) {
411-
if (
412-
!(await execProcess({
413-
cmd: ["git", ...cmd],
414-
cwd: dir,
415-
})).success
416-
) {
417-
throw new Error();
418-
}
419-
}
420-
}
421-
422-
// validate we have git
423-
const throwUnableToPublish = (reason: string) => {
424-
throw new Error(
425-
`Unable to publish to GitHub Pages (${reason})`,
426-
);
427-
};
428-
429-
async function gitHubContextForPublish(input: string | ProjectContext) {
430-
// Create the base context
431-
const dir = typeof input === "string" ? dirname(input) : input.dir;
432-
const context = await gitHubContext(dir);
433-
434-
// always prefer configured website URL
435-
if (typeof input !== "string") {
436-
const configSiteUrl = websiteBaseurl(input?.config);
437-
if (configSiteUrl) {
438-
context.siteUrl = configSiteUrl;
439-
}
440-
}
441-
return context;
442-
}

0 commit comments

Comments
 (0)