@huggingface/hub
Version:
Utilities to interact with the Hugging Face hub
682 lines (672 loc) • 23.9 kB
JavaScript
import {
HUB_URL,
createBranch,
createRepo,
deleteBranch,
deleteRepo,
repoExists,
typedEntries,
uploadFilesWithProgress
} from "./chunk-T7NFKRXN.mjs";
import "./chunk-GAR7ORU2.mjs";
// cli.ts
import { parseArgs } from "util";
import { pathToFileURL } from "url";
import { stat } from "fs/promises";
import { basename, join } from "path";
// package.json
var version = "2.6.4";
// cli.ts
var UploadProgressManager = class {
multibar = null;
fileBars = /* @__PURE__ */ new Map();
isQuiet;
cliProgressAvailable = false;
constructor(isQuiet = false) {
this.isQuiet = isQuiet;
}
async initialize() {
if (this.isQuiet)
return;
try {
const cliProgress = await import("./cli-progress-A335VRBC.mjs");
this.cliProgressAvailable = true;
this.multibar = new cliProgress.MultiBar(
{
clearOnComplete: false,
hideCursor: true,
format: " {bar} | {filename} | {percentage}% | {state}",
barCompleteChar: "\u2588",
barIncompleteChar: "\u2591"
},
cliProgress.Presets.shades_grey
);
} catch (error) {
this.cliProgressAvailable = false;
}
}
handleEvent(event) {
if (this.isQuiet)
return;
if (event.event === "phase") {
this.logPhase(event.phase);
} else if (event.event === "fileProgress") {
this.updateFileProgress(event.path, event.progress, event.state);
}
}
logPhase(phase) {
if (this.isQuiet)
return;
const phaseMessages = {
preuploading: "\u{1F4CB} Preparing files for upload...",
uploadingLargeFiles: "\u2B06\uFE0F Uploading files...",
committing: "\u2728 Finalizing commit..."
};
console.log(`
${phaseMessages[phase] || phase}`);
}
updateFileProgress(path, progress, state) {
if (this.isQuiet)
return;
if (this.cliProgressAvailable && this.multibar) {
let bar = this.fileBars.get(path);
if (!bar) {
bar = this.multibar.create(100, 0, {
filename: this.truncateFilename(path, 100),
state
});
this.fileBars.set(path, bar);
}
if (progress >= 1) {
bar.update(100, { state: state === "hashing" ? "\u2713 hashed" : "\u2713 uploaded" });
} else {
const percentage = Math.round(progress * 100);
bar.update(percentage, { state });
}
} else {
const percentage = Math.round(progress * 100);
const truncatedPath = this.truncateFilename(path, 100);
if (progress >= 1) {
const statusIcon = state === "hashing" ? "\u2713 hashed" : "\u2713 uploaded";
console.log(`${statusIcon}: ${truncatedPath}`);
} else if (percentage % 25 === 0) {
console.log(`${state}: ${truncatedPath} (${percentage}%)`);
}
}
}
truncateFilename(filename, maxLength) {
if (filename.length <= maxLength)
return filename;
return "..." + filename.slice(-(maxLength - 3));
}
stop() {
if (!this.isQuiet && this.cliProgressAvailable && this.multibar) {
this.multibar.stop();
}
}
};
var commands = {
upload: {
description: "Upload a folder to a repo on the Hub",
args: [
{
name: "repo-name",
description: "The name of the repo to upload to",
positional: true,
required: true
},
{
name: "local-folder",
description: "The local folder to upload. Defaults to the current working directory",
positional: true,
default: () => process.cwd()
},
{
name: "path-in-repo",
description: "The path in the repo to upload the folder to. Defaults to the root of the repo",
positional: true,
default: "."
},
{
name: "quiet",
short: "q",
description: "Suppress all output",
boolean: true
},
{
name: "repo-type",
enum: ["dataset", "model", "space"],
description: "The type of repo to upload to. Defaults to model. You can also prefix the repo name with the type, e.g. datasets/username/repo-name"
},
{
name: "revision",
description: "The revision to upload to. Defaults to the main branch",
default: "main"
},
{
name: "commit-message",
description: "The commit message to use. Defaults to 'Upload files using @huggingface/hub'",
default: "Upload files using @huggingface/hub"
},
{
name: "private",
description: "If creating a new repo, make it private",
boolean: true
},
{
name: "token",
description: "The access token to use for authentication. If not provided, the HF_TOKEN environment variable will be used.",
default: process.env.HF_TOKEN
}
]
},
branch: {
description: "Manage repository branches",
subcommands: {
create: {
description: "Create a new branch in a repo, or update an existing one",
args: [
{
name: "repo-name",
description: "The name of the repo to create the branch in",
positional: true,
required: true
},
{
name: "branch",
description: "The name of the branch to create",
positional: true,
required: true
},
{
name: "repo-type",
enum: ["dataset", "model", "space"],
description: "The type of the repo to create the branch into. Defaults to model. You can also prefix the repo name with the type, e.g. datasets/username/repo-name"
},
{
name: "revision",
description: "The revision to create the branch from. Defaults to the main branch, or existing branch if it exists."
},
{
name: "empty",
boolean: true,
description: "Create an empty branch. This will erase all previous commits on the branch if it exists."
},
{
name: "force",
short: "f",
boolean: true,
description: "Overwrite the branch if it already exists. Otherwise, throws an error if the branch already exists. No-ops if no revision is provided and the branch exists."
},
{
name: "token",
description: "The access token to use for authentication. If not provided, the HF_TOKEN environment variable will be used.",
default: process.env.HF_TOKEN
}
]
},
delete: {
description: "Delete a branch in a repo",
args: [
{
name: "repo-name",
description: "The name of the repo to delete the branch from",
positional: true,
required: true
},
{
name: "branch",
description: "The name of the branch to delete",
positional: true,
required: true
},
{
name: "repo-type",
enum: ["dataset", "model", "space"],
description: "The type of repo to delete the branch from. Defaults to model. You can also prefix the repo name with the type, e.g. datasets/username/repo-name"
},
{
name: "token",
description: "The access token to use for authentication. If not provided, the HF_TOKEN environment variable will be used.",
default: process.env.HF_TOKEN
}
]
}
}
},
repo: {
description: "Manage repositories on the Hub",
subcommands: {
delete: {
description: "Delete a repository from the Hub",
args: [
{
name: "repo-name",
description: "The name of the repo to delete. You can also prefix the repo name with the type, e.g. datasets/username/repo-name",
positional: true,
required: true
},
{
name: "repo-type",
enum: ["dataset", "model", "space"],
description: "The type of the repo to delete. Defaults to model. You can also prefix the repo name with the type, e.g. datasets/username/repo-name"
},
{
name: "token",
description: "The access token to use for authentication. If not provided, the HF_TOKEN environment variable will be used.",
default: process.env.HF_TOKEN
}
]
}
}
},
version: {
description: "Print the version of the CLI",
args: []
}
};
var mainCommandName = process.argv[2];
var subCommandName;
var cliArgs;
if (mainCommandName && mainCommandName in commands && commands[mainCommandName] && "subcommands" in commands[mainCommandName]) {
subCommandName = process.argv[3];
cliArgs = process.argv.slice(4);
} else {
cliArgs = process.argv.slice(3);
}
async function run() {
switch (mainCommandName) {
case void 0:
case "--help":
case "help": {
const helpArgs = mainCommandName === "help" ? process.argv.slice(3) : [];
if (helpArgs.length > 0) {
const cmdName = helpArgs[0];
if (cmdName && commands[cmdName]) {
const cmdDef = commands[cmdName];
if ("subcommands" in cmdDef) {
if (helpArgs.length > 1) {
const subCmdName = helpArgs[1];
if (subCmdName in cmdDef.subcommands && cmdDef.subcommands[subCmdName]) {
console.log(detailedUsageForSubcommand(cmdName, subCmdName));
break;
} else {
console.error(`Error: Unknown subcommand '${subCmdName}' for command '${cmdName}'.`);
console.log(listSubcommands(cmdName, cmdDef));
process.exitCode = 1;
break;
}
} else {
console.log(listSubcommands(cmdName, cmdDef));
break;
}
} else {
console.log(detailedUsageForCommand(cmdName));
break;
}
} else {
console.error(`Error: Unknown command '${cmdName}' for help.`);
process.exitCode = 1;
}
} else {
console.log(
`Hugging Face CLI Tools (hfjs)
Available commands:
` + typedEntries(commands).map(([name, def]) => ` ${usage(name)}: ${def.description}`).join("\n")
);
console.log("\nTo get help on a specific command, run `hfjs help <command>` or `hfjs <command> --help`");
console.log(
"For commands with subcommands (like 'branch'), run `hfjs help <command> <subcommand>` or `hfjs <command> <subcommand> --help`"
);
if (mainCommandName === void 0) {
process.exitCode = 1;
}
}
break;
}
case "upload": {
const cmdDef = commands.upload;
if (cliArgs[0] === "--help" || cliArgs[0] === "-h") {
console.log(detailedUsageForCommand("upload"));
break;
}
const parsedArgs = advParseArgs(cliArgs, cmdDef.args, "upload");
const {
repoName,
localFolder,
repoType,
revision,
token,
quiet,
commitMessage,
pathInRepo,
private: isPrivate
} = parsedArgs;
const repoId = repoType ? { type: repoType, name: repoName } : repoName;
if (!await repoExists({ repo: repoId, revision, accessToken: token, hubUrl: process.env.HF_ENDPOINT ?? HUB_URL })) {
if (!quiet) {
console.log(`Repo ${repoName} does not exist. Creating it...`);
}
await createRepo({
repo: repoId,
accessToken: token,
private: !!isPrivate,
hubUrl: process.env.HF_ENDPOINT ?? HUB_URL
});
}
const isFile = (await stat(localFolder)).isFile();
const files = isFile ? [
{
content: pathToFileURL(localFolder),
path: join(pathInRepo, `${basename(localFolder)}`).replace(/^[.]?\//, "")
}
] : [{ content: pathToFileURL(localFolder), path: pathInRepo.replace(/^[.]?\//, "") }];
const progressManager = new UploadProgressManager(!!quiet);
await progressManager.initialize();
try {
for await (const event of uploadFilesWithProgress({
repo: repoId,
files,
branch: revision,
accessToken: token,
commitTitle: commitMessage?.trim().split("\n")[0],
commitDescription: commitMessage?.trim().split("\n").slice(1).join("\n").trim(),
hubUrl: process.env.HF_ENDPOINT ?? HUB_URL,
useXet: true
})) {
progressManager.handleEvent(event);
}
if (!quiet) {
console.log("\n\u2705 Upload completed successfully!");
}
} catch (error) {
progressManager.stop();
throw error;
} finally {
progressManager.stop();
}
break;
}
case "branch": {
const branchCommandGroup = commands.branch;
const currentSubCommandName = subCommandName;
if (cliArgs[0] === "--help" || cliArgs[0] === "-h") {
if (currentSubCommandName && branchCommandGroup.subcommands[currentSubCommandName]) {
console.log(detailedUsageForSubcommand("branch", currentSubCommandName));
} else {
console.log(listSubcommands("branch", branchCommandGroup));
}
break;
}
if (!currentSubCommandName || !branchCommandGroup.subcommands[currentSubCommandName]) {
console.error(`Error: Missing or invalid subcommand for 'branch'.`);
console.log(listSubcommands("branch", branchCommandGroup));
process.exitCode = 1;
break;
}
const subCmdDef = branchCommandGroup.subcommands[currentSubCommandName];
switch (currentSubCommandName) {
case "create": {
const parsedArgs = advParseArgs(cliArgs, subCmdDef.args, "branch create");
const { repoName, branch, revision, empty, repoType, token, force } = parsedArgs;
await createBranch({
repo: repoType ? { type: repoType, name: repoName } : repoName,
branch,
accessToken: token,
revision,
empty: empty ?? void 0,
overwrite: force ?? void 0,
hubUrl: process.env.HF_ENDPOINT ?? HUB_URL
});
console.log(`Branch '${branch}' created successfully in repo '${repoName}'.`);
break;
}
case "delete": {
const parsedArgs = advParseArgs(cliArgs, subCmdDef.args, "branch delete");
const { repoName, branch, repoType, token } = parsedArgs;
await deleteBranch({
repo: repoType ? { type: repoType, name: repoName } : repoName,
branch,
accessToken: token,
hubUrl: process.env.HF_ENDPOINT ?? HUB_URL
});
console.log(`Branch '${branch}' deleted successfully from repo '${repoName}'.`);
break;
}
default:
console.error(`Error: Unknown subcommand '${currentSubCommandName}' for 'branch'.`);
console.log(listSubcommands("branch", branchCommandGroup));
process.exitCode = 1;
break;
}
break;
}
case "repo": {
const repoCommandGroup = commands.repo;
const currentSubCommandName = subCommandName;
if (cliArgs[0] === "--help" || cliArgs[0] === "-h") {
if (currentSubCommandName && repoCommandGroup.subcommands[currentSubCommandName]) {
console.log(detailedUsageForSubcommand("repo", currentSubCommandName));
} else {
console.log(listSubcommands("repo", repoCommandGroup));
}
break;
}
if (!currentSubCommandName || !repoCommandGroup.subcommands[currentSubCommandName]) {
console.error(`Error: Missing or invalid subcommand for 'repo'.`);
console.log(listSubcommands("repo", repoCommandGroup));
process.exitCode = 1;
break;
}
const subCmdDef = repoCommandGroup.subcommands[currentSubCommandName];
switch (currentSubCommandName) {
case "delete": {
const parsedArgs = advParseArgs(cliArgs, subCmdDef.args, `repo ${currentSubCommandName}`);
const { repoName, repoType, token } = parsedArgs;
const repoDesignation = repoType ? { type: repoType, name: repoName } : repoName;
await deleteRepo({
repo: repoDesignation,
accessToken: token,
hubUrl: process.env.HF_ENDPOINT ?? HUB_URL
});
console.log(`Repository '${repoName}' deleted successfully.`);
break;
}
default:
console.error(`Error: Unknown subcommand '${currentSubCommandName}' for 'repo'.`);
console.log(listSubcommands("repo", repoCommandGroup));
process.exitCode = 1;
break;
}
break;
}
case "version": {
if (cliArgs[0] === "--help" || cliArgs[0] === "-h") {
console.log(detailedUsageForCommand("version"));
break;
}
console.log(`hfjs version: ${version}`);
break;
}
default:
console.error("Command not found: " + mainCommandName);
console.log(
`
Available commands:
` + typedEntries(commands).map(([name, def]) => ` ${usage(name)}: ${def.description}`).join("\n")
);
console.log("\nTo get help on a specific command, run `hfjs help <command>` or `hfjs <command> --help`");
process.exitCode = 1;
break;
}
}
run().catch((err) => {
console.error("\x1B[31mError:\x1B[0m", err.message);
console.error(err);
process.exitCode = 1;
});
function usage(commandName, subCommandName2) {
const commandEntry = commands[commandName];
let cmdArgs;
let fullCommandName = commandName;
if ("subcommands" in commandEntry) {
if (subCommandName2 && subCommandName2 in commandEntry.subcommands) {
cmdArgs = commandEntry.subcommands[subCommandName2].args;
fullCommandName = `${commandName} ${subCommandName2}`;
} else {
return `${commandName} <subcommand>`;
}
} else {
cmdArgs = commandEntry.args;
}
return `${fullCommandName} ${(cmdArgs || []).map((arg) => {
if (arg.positional) {
return arg.required ? `<${arg.name}>` : `[${arg.name}]`;
}
return `[--${arg.name}${arg.short ? `|-${arg.short}` : ""}${arg.enum ? ` {${arg.enum.join("|")}}` : arg.boolean ? "" : ` <${arg.name.toUpperCase().replace(/-/g, "_")}>`}]`;
}).join(" ")}`.trim();
}
function _detailedUsage(args, usageLine, commandDescription) {
let ret = `usage: hfjs ${usageLine}
`;
if (commandDescription) {
ret += `
${commandDescription}
`;
}
const positionals = args.filter((p) => p.positional);
const options = args.filter((p) => !p.positional);
if (positionals.length > 0) {
ret += `
Positional arguments:
`;
for (const arg of positionals) {
ret += ` ${arg.name} ${arg.description}${arg.default ? ` (default: ${typeof arg.default === "function" ? arg.default() : arg.default})` : ""}
`;
}
}
if (options.length > 0) {
ret += `
Options:
`;
for (const arg of options) {
const nameAndAlias = `--${arg.name}${arg.short ? `, -${arg.short}` : ""}`;
const valueHint = arg.enum ? `{${arg.enum.join("|")}}` : arg.boolean ? "" : `<${arg.name.toUpperCase().replace(/-/g, "_")}>`;
ret += ` ${nameAndAlias}${valueHint ? " " + valueHint : ""} ${arg.description}${arg.default !== void 0 ? ` (default: ${typeof arg.default === "function" ? arg.default() : arg.default})` : ""}
`;
}
}
ret += `
`;
return ret;
}
function detailedUsageForCommand(commandName) {
const commandDef = commands[commandName];
if ("subcommands" in commandDef) {
return listSubcommands(commandName, commandDef);
}
return _detailedUsage(commandDef.args, usage(commandName), commandDef.description);
}
function detailedUsageForSubcommand(commandName, subCommandName2) {
const commandGroup = commands[commandName];
if (!("subcommands" in commandGroup) || !(subCommandName2 in commandGroup.subcommands)) {
throw new Error(`Subcommand ${subCommandName2} not found for ${commandName}`);
}
const subCommandDef = commandGroup.subcommands[subCommandName2];
return _detailedUsage(subCommandDef.args, usage(commandName, subCommandName2), subCommandDef.description);
}
function listSubcommands(commandName, commandGroup) {
let ret = `usage: hfjs ${commandName} <subcommand> [options]
`;
ret += `${commandGroup.description}
`;
ret += `Available subcommands for '${commandName}':
`;
ret += typedEntries(commandGroup.subcommands).map(([subName, subDef]) => ` ${subName} ${subDef.description}`).join("\n");
ret += `
Run \`hfjs help ${commandName} <subcommand>\` for more information on a specific subcommand.`;
return ret;
}
function advParseArgs(args, argDefs, commandNameForError) {
const { tokens } = parseArgs({
options: Object.fromEntries(
argDefs.filter((arg) => !arg.positional).map((arg) => {
const optionConfig = {
type: arg.boolean ? "boolean" : "string",
...arg.short && { short: arg.short },
...arg.default !== void 0 && {
default: typeof arg.default === "function" ? arg.default() : arg.default
}
};
return [arg.name, optionConfig];
})
),
args,
allowPositionals: true,
strict: false,
// We do custom validation based on tokens and argDefs
tokens: true
});
const expectedPositionals = argDefs.filter((arg) => arg.positional);
const providedPositionalTokens = tokens.filter((token) => token.kind === "positional");
if (providedPositionalTokens.length < expectedPositionals.filter((arg) => arg.required).length) {
throw new Error(
`Command '${commandNameForError}': Missing required positional arguments. Usage: hfjs ${usage(
commandNameForError.split(" ")[0],
commandNameForError.split(" ")[1]
)}`
);
}
if (providedPositionalTokens.length > expectedPositionals.length) {
throw new Error(
`Command '${commandNameForError}': Too many positional arguments. Usage: hfjs ${usage(
commandNameForError.split(" ")[0],
commandNameForError.split(" ")[1]
)}`
);
}
const result = {};
for (const argDef of argDefs) {
if (argDef.default !== void 0) {
result[argDef.name] = typeof argDef.default === "function" ? argDef.default() : argDef.default;
} else if (argDef.boolean) {
result[argDef.name] = false;
}
}
providedPositionalTokens.forEach((token, i) => {
if (expectedPositionals[i]) {
result[expectedPositionals[i].name] = token.value;
}
});
tokens.filter((token) => token.kind === "option").forEach((token) => {
const argDef = argDefs.find((def) => def.name === token.name || def.short === token.name);
if (!argDef) {
throw new Error(`Command '${commandNameForError}': Unknown option: ${token.rawName}`);
}
if (argDef.boolean) {
result[argDef.name] = true;
} else {
if (token.value === void 0) {
throw new Error(`Command '${commandNameForError}': Missing value for option: ${token.rawName}`);
}
if (argDef.enum && !argDef.enum.includes(token.value)) {
throw new Error(
`Command '${commandNameForError}': Invalid value '${token.value}' for option ${token.rawName}. Expected one of: ${argDef.enum.join(", ")}`
);
}
result[argDef.name] = token.value;
}
});
for (const argDef of argDefs) {
if (argDef.required && result[argDef.name] === void 0) {
throw new Error(`Command '${commandNameForError}': Missing required argument: ${argDef.name}`);
}
}
return Object.fromEntries(
Object.entries(result).map(([name, val]) => [kebabToCamelCase(name), val])
);
}
function kebabToCamelCase(str) {
return str.replace(/-./g, (match) => match[1].toUpperCase());
}