Path: blob/main/src/publish/huggingface/huggingface.ts
6460 views
/*1* huggingface.ts2*3* Copyright (C) 2020-2024 Posit Software, PBC4*/56import { info } from "../../deno_ral/log.ts";7import { dirname, join } from "../../deno_ral/path.ts";8import * as colors from "fmt/colors";9import { ProjectContext } from "../../project/types.ts";10import {11AccountToken,12PublishFiles,13PublishProvider,14} from "../provider-types.ts";15import { PublishOptions, PublishRecord } from "../types.ts";16import { RenderFlags } from "../../command/render/types.ts";17import { gitCmds, gitVersion } from "../../core/git.ts";18import {19anonymousAccount,20gitHubContextForPublish,21verifyContext,22} from "../common/git.ts";23import { throwUnableToPublish } from "../common/errors.ts";24import { Input } from "cliffy/prompt/input.ts";25import { assert } from "testing/asserts";26import { Secret } from "cliffy/prompt/secret.ts";2728export const kHuggingFace = "huggingface";29const kHuggingFaceDescription = "Hugging Face Spaces";3031export const huggingfaceProvider: PublishProvider = {32name: kHuggingFace,33description: kHuggingFaceDescription,34requiresServer: false,35listOriginOnly: false,36accountTokens: () => Promise.resolve([anonymousAccount()]),37authorizeToken,38removeToken: () => {},39publishRecord,40resolveTarget: (41_account: AccountToken,42target: PublishRecord,43): Promise<PublishRecord | undefined> => Promise.resolve(target),44publish,45isUnauthorized: () => false,46isNotFound: () => false,47resolveProjectPath: (path: string) => join(path, "src"),48};4950async function authorizeToken(options: PublishOptions) {51const ghContext = await gitHubContextForPublish(options.input);52const provider = "Hugging Face Spaces";53verifyContext(ghContext, provider);5455if (56!ghContext.originUrl!.match(/^https:\/\/(.*:.*@)?huggingface.co\/spaces\//)57) {58throwUnableToPublish(59"the git repository does not appear to have a Hugging Face Space origin",60provider,61);62}6364// good to go!65return Promise.resolve(anonymousAccount());66}6768async function publishRecord(69input: string | ProjectContext,70): Promise<PublishRecord | undefined> {71const ghContext = await gitHubContextForPublish(input);72if (ghContext.ghPagesRemote) {73return {74id: kHuggingFace,75url: ghContext.siteUrl || ghContext.originUrl,76};77}78}7980async function publish(81_account: AccountToken,82_type: "document" | "site",83input: string,84_title: string,85_slug: string,86_render: (flags?: RenderFlags) => Promise<PublishFiles>,87options: PublishOptions,88_target?: PublishRecord,89): Promise<[PublishRecord | undefined, URL | undefined]> {90// convert input to dir if necessary91input = Deno.statSync(input).isDirectory ? input : dirname(input);9293// check if git version is new enough94const version = await gitVersion();9596// git 2.17.0 appears to be the first to support git-worktree add --track97// https://github.com/git/git/blob/master/Documentation/RelNotes/2.17.0.txt#L36898if (version.compare("2.17.0") < 0) {99throw new Error(100"git version 2.17.0 or higher is required to publish to GitHub Pages",101);102}103104// get context105const ghContext = await gitHubContextForPublish(options.input);106verifyContext(ghContext, "Hugging Face Spaces");107108if (109!ghContext.originUrl!.match(/^https:\/\/.*:.*@huggingface.co\/spaces\//)110) {111const previousRemotePath = ghContext.originUrl!.match(112/.*huggingface.co(\/spaces\/.*)/,113);114assert(previousRemotePath);115info(colors.yellow([116"The current git repository needs to be reconfigured to allow `quarto publish`",117"to publish to Hugging Face Spaces. Please enter your username and authentication token.",118"Refer to https://huggingface.co/blog/password-git-deprecation#switching-to-personal-access-token",119"for more information on how to obtain a personal access token.",120].join("\n")));121const username = await Input.prompt({122indent: "",123message: "Hugging Face username",124});125const token = await Secret.prompt({126indent: "",127message: "Hugging Face authentication token:",128hint: "Create a token at https://huggingface.co/settings/tokens",129});130await gitCmds(input, [131[132"remote",133"set-url",134"origin",135`https://${username}:${token}@huggingface.co${previousRemotePath![1]}`,136],137]);138}139140// sync from remote and push to main141await gitCmds(input, [142["stash"],143["fetch", "origin", "main"],144]);145try {146await gitCmds(input, [147["merge", "origin/main"],148]);149} catch (_e) {150info(colors.yellow([151"Could not merge origin/main. This is likely because of git conflicts.",152"Please resolve those manually and run `quarto publish` again.",153].join("\n")));154return Promise.resolve([155undefined,156undefined,157]);158}159try {160await gitCmds(input, [161["stash", "pop"],162]);163} catch (_e) {164info(colors.yellow([165"Could not pop git stash.",166"This is likely because there are no changes to push to the repository.",167].join("\n")));168return Promise.resolve([169undefined,170undefined,171]);172}173await gitCmds(input, [174["add", "-Af", "."],175["commit", "--allow-empty", "-m", "commit from `quarto publish`"],176["push", "origin", "main"],177]);178179// warn users about latency between push and remote publish180info(colors.yellow(181"NOTE: Hugging Face Space sites build the content remotely and use caching.\n" +182"You will need to wait a moment for Hugging Face to rebuild your site, and\n" +183"then click the refresh button within your web browser to see changes after deployment.\n\n" +184"Specifically, you need to:\n" +185"- wait for your space's status to go from 'Building' to 'Running'\n" +186" (this is visible in the status bar above the space)\n" +187"- force-reload the web page by holding Shift and hitting the reload button in your browser.\n",188));189await new Promise((resolve) => setTimeout(resolve, 3000));190191return Promise.resolve([192undefined,193new URL(ghContext.originUrl!),194]);195}196197198