Skip to content
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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,16 @@ jobs:
npm run coverage --workspace explorer
npm run dry --workspace explorer
npm run mutate --workspace explorer
- run: npm run build --workspace explorer && npm run build --workspace space
- name: Check setup
run: |
npm run format --workspace setup
npm run lint --workspace setup
npm run typecheck --workspace setup
npm run test --workspace setup
npm run coverage --workspace setup
npm run dry --workspace setup
npm run mutate --workspace setup
- run: npm run build --workspace explorer && npm run build --workspace space && npm run build --workspace setup
- name: Slophammer
uses: dutifuldev/slophammer@main
with:
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ COPY package.json package-lock.json ./
COPY shared/package.json shared/
COPY space/package.json space/
COPY explorer/package.json explorer/
COPY setup/package.json setup/
RUN npm ci
COPY tsconfig.base.json ./
COPY shared/ shared/
Expand All @@ -20,6 +21,7 @@ COPY package.json package-lock.json ./
COPY shared/package.json shared/
COPY space/package.json space/
COPY explorer/package.json explorer/
COPY setup/package.json setup/
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/shared/dist shared/dist
COPY --from=build /app/space/dist space/dist
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,23 @@ design and delivery plan.

## Set up a pool (once, by the pool owner)

Requires a personal `hf auth login` with write access to the target namespace:
Requires Node.js 22+, npm, the Hugging Face CLI, and a personal `hf auth login`
with write access to the target namespace:

```sh
scripts/deploy-space.sh <namespace> # creates the dataset repo + Docker Space, sets secrets
npm ci
npm run setup
```

The script prints the two remaining manual steps: storing a fine-grained
`HF_TOKEN` Space secret (write access to the one dataset repo) and optionally
seeding an existing xtap-store archive:
The setup flow creates or updates the private dataset repo and public Docker
Space, configures the Space variables and generated secrets, verifies the
dataset-only `HF_TOKEN`, and can seed an existing xtap-store archive.

The lower-level scripts are still available when you want to do those steps
manually:

```sh
scripts/deploy-space.sh <namespace>
scripts/seed-dataset.sh <namespace>/xtap-pool-data <hf-username> ~/xtap-store/data/tweets
```

Expand Down
4 changes: 2 additions & 2 deletions docs/implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ buffering; ephemeral disk must never hold unpersisted data). Commits use
`@huggingface/hub` `commit()` with retry; concurrent ingests serialized
through a single writer queue.

Config via Space secrets/variables: `HF_TOKEN` (fine-grained, write on the
one dataset repo), `POOL_SIGNING_SECRET`, `SESSION_SECRET`, `ALLOWED_USERS`
Config via Space secrets/variables: `HF_TOKEN` (fine-grained, read/write on
the one dataset repo), `POOL_SIGNING_SECRET`, `SESSION_SECRET`, `ALLOWED_USERS`
(comma-separated HF usernames), `DATASET_REPO`.

README metadata: `sdk: docker`, `hf_oauth: true`, scopes `openid profile`.
Expand Down
57 changes: 44 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
"workspaces": [
"shared",
"space",
"explorer"
"explorer",
"setup"
],
"scripts": {
"setup": "npm run build --workspace setup && npm run start --workspace setup",
"format": "prettier --check .",
"format:write": "prettier --write .",
"lint": "eslint .",
"typecheck": "tsc -p tsconfig.json --noEmit && tsc --build shared space explorer",
"typecheck": "tsc -p tsconfig.json --noEmit && tsc --build shared space explorer setup",
"test": "vitest run",
"test:extension": "node --test extension/tests/*.test.mjs",
"coverage": "vitest run --coverage",
"dry": "slophammer-ts dry .",
"mutate": "stryker run",
"build": "npm run build --workspace explorer && npm run build --workspace space",
"build": "npm run build --workspace explorer && npm run build --workspace space && npm run build --workspace setup",
"check": "npm run format && npm run lint && npm run typecheck && npm test && npm run test:extension && npm run test:extension:native && npm run coverage && npm run dry",
"test:extension:native": "uvx pytest extension/tests -q"
},
Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy-space.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ if "SECRETS_INITIALIZED" not in variables:
api.add_space_variable(space, "SECRETS_INITIALIZED", "1")
print("Set DATASET_REPO, ALLOWED_USERS, POOL_SIGNING_SECRET, SESSION_SECRET.")
print("Remaining manual steps:")
print(f" 1. Create a fine-grained token with write access to {dataset} only,")
print(f" 1. Create a fine-grained token with read/write access to {dataset} only,")
print(f" then: python3 -c \"from huggingface_hub import HfApi; HfApi().add_space_secret('{space}', 'HF_TOKEN', '<token>')\"")
print(f" 2. Optionally seed history: scripts/seed-dataset.sh {dataset} <hf-username> ~/xtap-store/data/tweets")
PY
Expand Down
5 changes: 5 additions & 0 deletions setup/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage/
dist/
.stryker-tmp/
reports/
*.tsbuildinfo
25 changes: 25 additions & 0 deletions setup/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@xtap-pool/setup",
"version": "0.1.0",
"private": true,
"type": "module",
"bin": {
"xtap-pool-setup": "dist/src/main.js"
},
"scripts": {
"build": "tsc --build",
"dev": "npm run build && npm run start",
"start": "node dist/src/main.js",
"format": "prettier --check .",
"lint": "eslint .",
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
"test": "vitest run",
"coverage": "vitest run --coverage",
"dry": "cd .. && slophammer-ts dry .",
"mutate": "stryker run stryker.conf.json"
},
"dependencies": {
"@clack/prompts": "^0.11.0",
"@huggingface/hub": "^2.13.2"
}
}
62 changes: 62 additions & 0 deletions setup/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export type SetupConfig = {
namespace: string;
spaceRepo: string;
datasetRepo: string;
allowedUsers: readonly string[];
};

const REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
const USERNAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;

export function defaultSetupConfig(username: string): SetupConfig {
return {
namespace: username,
spaceRepo: `${username}/xtap-pool`,
datasetRepo: `${username}/xtap-pool-data`,
allowedUsers: [username],
};
}

export function normalizeUsers(input: string): readonly string[] {
return [
...new Set(
input
.split(",")
.map((user) => user.trim())
.filter((user) => user.length > 0),
),
];
}

export function usersValue(users: readonly string[]): string {
return users.join(",");
}

export function repoInNamespace(namespace: string, repoName: string): string {
return `${namespace}/${repoName}`;
}

export function validateRepoId(value: string): string | undefined {
return REPO_ID.test(value) ? undefined : "Use owner/name, for example osolmaz/xtap-pool.";
}

export function validateNamespace(value: string): string | undefined {
return USERNAME.test(value) ? undefined : "Use a Hugging Face username or organization name.";
}

export function validateUserList(value: string): string | undefined {
const users = normalizeUsers(value);
if (users.length === 0) return "Enter at least one Hugging Face username.";
return users.every((user) => USERNAME.test(user))
? undefined
: "Use comma-separated Hugging Face usernames.";
}

export function spacePublicUrl(spaceRepo: string): string {
const [namespace = "", name = ""] = spaceRepo.split("/");
return `https://${namespace}-${name}.hf.space`;
}

export function tokenSettingsUrl(): string {
return "https://huggingface.co/settings/tokens/new?tokenType=fineGrained";
}
Loading
Loading