@nodetool-ai/deploy 0.7.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-client.d.ts +58 -0
- package/dist/admin-client.d.ts.map +1 -0
- package/dist/admin-client.js +275 -0
- package/dist/admin-client.js.map +1 -0
- package/dist/admin-operations.d.ts +197 -0
- package/dist/admin-operations.d.ts.map +1 -0
- package/dist/admin-operations.js +433 -0
- package/dist/admin-operations.js.map +1 -0
- package/dist/admin-routes.d.ts +306 -0
- package/dist/admin-routes.d.ts.map +1 -0
- package/dist/admin-routes.js +383 -0
- package/dist/admin-routes.js.map +1 -0
- package/dist/api-user-manager.d.ts +72 -0
- package/dist/api-user-manager.d.ts.map +1 -0
- package/dist/api-user-manager.js +89 -0
- package/dist/api-user-manager.js.map +1 -0
- package/dist/auth.d.ts +67 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +142 -0
- package/dist/auth.js.map +1 -0
- package/dist/collection-routes.d.ts +37 -0
- package/dist/collection-routes.d.ts.map +1 -0
- package/dist/collection-routes.js +43 -0
- package/dist/collection-routes.js.map +1 -0
- package/dist/compose.d.ts +52 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +153 -0
- package/dist/compose.js.map +1 -0
- package/dist/configure.d.ts +87 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +104 -0
- package/dist/configure.js.map +1 -0
- package/dist/deploy-to-gcp.d.ts +67 -0
- package/dist/deploy-to-gcp.d.ts.map +1 -0
- package/dist/deploy-to-gcp.js +268 -0
- package/dist/deploy-to-gcp.js.map +1 -0
- package/dist/deploy-to-runpod.d.ts +53 -0
- package/dist/deploy-to-runpod.d.ts.map +1 -0
- package/dist/deploy-to-runpod.js +207 -0
- package/dist/deploy-to-runpod.js.map +1 -0
- package/dist/deploy.d.ts +54 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +82 -0
- package/dist/deploy.js.map +1 -0
- package/dist/deployment-config.d.ts +1073 -0
- package/dist/deployment-config.d.ts.map +1 -0
- package/dist/deployment-config.js +536 -0
- package/dist/deployment-config.js.map +1 -0
- package/dist/docker-run.d.ts +85 -0
- package/dist/docker-run.d.ts.map +1 -0
- package/dist/docker-run.js +202 -0
- package/dist/docker-run.js.map +1 -0
- package/dist/docker.d.ts +98 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +396 -0
- package/dist/docker.js.map +1 -0
- package/dist/fly.d.ts +40 -0
- package/dist/fly.d.ts.map +1 -0
- package/dist/fly.js +291 -0
- package/dist/fly.js.map +1 -0
- package/dist/gcp.d.ts +59 -0
- package/dist/gcp.d.ts.map +1 -0
- package/dist/gcp.js +339 -0
- package/dist/gcp.js.map +1 -0
- package/dist/google-cloud-run-api.d.ts +134 -0
- package/dist/google-cloud-run-api.d.ts.map +1 -0
- package/dist/google-cloud-run-api.js +532 -0
- package/dist/google-cloud-run-api.js.map +1 -0
- package/dist/huggingface.d.ts +69 -0
- package/dist/huggingface.d.ts.map +1 -0
- package/dist/huggingface.js +346 -0
- package/dist/huggingface.js.map +1 -0
- package/dist/image-builder.d.ts +35 -0
- package/dist/image-builder.d.ts.map +1 -0
- package/dist/image-builder.js +157 -0
- package/dist/image-builder.js.map +1 -0
- package/dist/image-spec.d.ts +41 -0
- package/dist/image-spec.d.ts.map +1 -0
- package/dist/image-spec.js +49 -0
- package/dist/image-spec.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +121 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +225 -0
- package/dist/manager.js.map +1 -0
- package/dist/progress.d.ts +91 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +262 -0
- package/dist/progress.js.map +1 -0
- package/dist/railway.d.ts +65 -0
- package/dist/railway.d.ts.map +1 -0
- package/dist/railway.js +284 -0
- package/dist/railway.js.map +1 -0
- package/dist/remote-users.d.ts +74 -0
- package/dist/remote-users.d.ts.map +1 -0
- package/dist/remote-users.js +172 -0
- package/dist/remote-users.js.map +1 -0
- package/dist/runpod-api.d.ts +206 -0
- package/dist/runpod-api.d.ts.map +1 -0
- package/dist/runpod-api.js +703 -0
- package/dist/runpod-api.js.map +1 -0
- package/dist/runpod.d.ts +82 -0
- package/dist/runpod.d.ts.map +1 -0
- package/dist/runpod.js +188 -0
- package/dist/runpod.js.map +1 -0
- package/dist/self-hosted.d.ts +157 -0
- package/dist/self-hosted.d.ts.map +1 -0
- package/dist/self-hosted.js +799 -0
- package/dist/self-hosted.js.map +1 -0
- package/dist/ssh.d.ts +166 -0
- package/dist/ssh.d.ts.map +1 -0
- package/dist/ssh.js +430 -0
- package/dist/ssh.js.map +1 -0
- package/dist/state.d.ts +83 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +368 -0
- package/dist/state.js.map +1 -0
- package/dist/storage-routes.d.ts +55 -0
- package/dist/storage-routes.d.ts.map +1 -0
- package/dist/storage-routes.js +160 -0
- package/dist/storage-routes.js.map +1 -0
- package/dist/sync.d.ts +38 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +130 -0
- package/dist/sync.js.map +1 -0
- package/dist/workflow-syncer.d.ts +51 -0
- package/dist/workflow-syncer.d.ts.map +1 -0
- package/dist/workflow-syncer.js +192 -0
- package/dist/workflow-syncer.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HuggingFace Spaces deployment implementation for NodeTool.
|
|
3
|
+
*
|
|
4
|
+
* Handles deployment to HuggingFace Spaces (Docker type), including:
|
|
5
|
+
* - Dockerfile generation from image spec
|
|
6
|
+
* - HF Space repository creation and management
|
|
7
|
+
* - Git-based deployment (push Dockerfile to HF repo, Spaces builds it)
|
|
8
|
+
* - Space metadata via README.md frontmatter
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
import * as fs from "node:fs/promises";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import { loadImageSpec } from "./image-spec.js";
|
|
16
|
+
import { generateDockerfile } from "./image-builder.js";
|
|
17
|
+
import { StateManager } from "./state.js";
|
|
18
|
+
import { DeploymentStatus } from "./deployment-config.js";
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Run the `huggingface-cli` command and return stdout.
|
|
25
|
+
* Throws a descriptive error when the CLI is not installed.
|
|
26
|
+
*/
|
|
27
|
+
async function hfCli(args, opts) {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await execFileAsync("huggingface-cli", args, {
|
|
30
|
+
timeout: opts?.timeout ?? 60_000,
|
|
31
|
+
cwd: opts?.cwd
|
|
32
|
+
});
|
|
33
|
+
return stdout;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37
|
+
if (msg.includes("ENOENT")) {
|
|
38
|
+
throw new Error("huggingface-cli not found. Install it with: pip install huggingface_hub[cli]\n" +
|
|
39
|
+
"See https://huggingface.co/docs/huggingface_hub/guides/cli for details.");
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`huggingface-cli failed: ${msg}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run a git command and return stdout.
|
|
46
|
+
*/
|
|
47
|
+
async function git(args, opts) {
|
|
48
|
+
try {
|
|
49
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
50
|
+
timeout: opts?.timeout ?? 60_000,
|
|
51
|
+
cwd: opts?.cwd
|
|
52
|
+
});
|
|
53
|
+
return stdout;
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
57
|
+
throw new Error(`git command failed: ${msg}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate the README.md content with HuggingFace Spaces YAML frontmatter.
|
|
62
|
+
*/
|
|
63
|
+
function generateSpaceReadme(deployment) {
|
|
64
|
+
const frontmatter = [
|
|
65
|
+
"---",
|
|
66
|
+
"title: NodeTool",
|
|
67
|
+
'emoji: "\\U0001F527"',
|
|
68
|
+
"colorFrom: blue",
|
|
69
|
+
"colorTo: purple",
|
|
70
|
+
"sdk: docker",
|
|
71
|
+
"app_port: 7777"
|
|
72
|
+
];
|
|
73
|
+
if (deployment.hardware) {
|
|
74
|
+
frontmatter.push(`hardware: ${deployment.hardware}`);
|
|
75
|
+
}
|
|
76
|
+
frontmatter.push("pinned: false");
|
|
77
|
+
frontmatter.push("---");
|
|
78
|
+
frontmatter.push("");
|
|
79
|
+
return frontmatter.join("\n");
|
|
80
|
+
}
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// HuggingFaceDeployer class
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
/**
|
|
85
|
+
* Handles deployment to HuggingFace Spaces.
|
|
86
|
+
*
|
|
87
|
+
* HF Spaces with Docker type build from a Dockerfile pushed to a git repo.
|
|
88
|
+
* This deployer generates the Dockerfile from the image spec, pushes it to
|
|
89
|
+
* the HF Space repository, and lets HuggingFace handle the build and deploy.
|
|
90
|
+
*/
|
|
91
|
+
export class HuggingFaceDeployer {
|
|
92
|
+
deploymentName;
|
|
93
|
+
deployment;
|
|
94
|
+
stateManager;
|
|
95
|
+
constructor(deploymentName, deployment, stateManager) {
|
|
96
|
+
this.deploymentName = deploymentName;
|
|
97
|
+
this.deployment = deployment;
|
|
98
|
+
this.stateManager = stateManager ?? new StateManager();
|
|
99
|
+
}
|
|
100
|
+
// ---------- Public API ----------
|
|
101
|
+
/**
|
|
102
|
+
* Generate a deployment plan showing what changes will be made.
|
|
103
|
+
*/
|
|
104
|
+
async plan() {
|
|
105
|
+
const planResult = {
|
|
106
|
+
deployment_name: this.deploymentName,
|
|
107
|
+
type: "huggingface",
|
|
108
|
+
repo: this.deployment.repo,
|
|
109
|
+
changes: [],
|
|
110
|
+
will_create: [],
|
|
111
|
+
will_update: [],
|
|
112
|
+
will_destroy: []
|
|
113
|
+
};
|
|
114
|
+
const changes = planResult["changes"];
|
|
115
|
+
const willCreate = planResult["will_create"];
|
|
116
|
+
const currentState = await this.stateManager.readState(this.deploymentName);
|
|
117
|
+
if (!currentState || !currentState["last_deployed"]) {
|
|
118
|
+
changes.push("Initial deployment - will create HF Space repository");
|
|
119
|
+
willCreate.push(`HuggingFace Space: ${this.deployment.repo}`);
|
|
120
|
+
willCreate.push("Dockerfile (generated from image spec)");
|
|
121
|
+
willCreate.push("README.md (Space metadata)");
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
changes.push("Will update Dockerfile and push to HF Space");
|
|
125
|
+
planResult["will_update"].push(`HuggingFace Space: ${this.deployment.repo}`);
|
|
126
|
+
}
|
|
127
|
+
if (this.deployment.hardware) {
|
|
128
|
+
changes.push(`Hardware: ${this.deployment.hardware}`);
|
|
129
|
+
}
|
|
130
|
+
if (this.deployment.environment) {
|
|
131
|
+
const envKeys = Object.keys(this.deployment.environment);
|
|
132
|
+
if (envKeys.length > 0) {
|
|
133
|
+
changes.push(`Environment variables: ${envKeys.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return planResult;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Apply the deployment to HuggingFace Spaces.
|
|
140
|
+
*
|
|
141
|
+
* Steps:
|
|
142
|
+
* 1. Load image spec and generate Dockerfile (not built locally)
|
|
143
|
+
* 2. Create or clone the HF Space repository
|
|
144
|
+
* 3. Write Dockerfile and README.md with Space metadata
|
|
145
|
+
* 4. Git commit and push to HuggingFace
|
|
146
|
+
* 5. HF Spaces auto-builds and deploys from the Dockerfile
|
|
147
|
+
* 6. Update deployment state
|
|
148
|
+
*
|
|
149
|
+
* @param opts.dryRun If true, only show what would be done without executing.
|
|
150
|
+
*/
|
|
151
|
+
async apply(opts) {
|
|
152
|
+
if (!/^[\w-]+\/[\w.-]+$/.test(this.deployment.repo)) {
|
|
153
|
+
throw new Error(`Invalid HuggingFace repo: "${this.deployment.repo}". Expected "owner/name"`);
|
|
154
|
+
}
|
|
155
|
+
if (opts?.dryRun) {
|
|
156
|
+
return (await this.plan());
|
|
157
|
+
}
|
|
158
|
+
const results = {
|
|
159
|
+
deployment_name: this.deploymentName,
|
|
160
|
+
status: "success",
|
|
161
|
+
steps: [],
|
|
162
|
+
errors: []
|
|
163
|
+
};
|
|
164
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nodetool-hf-"));
|
|
165
|
+
try {
|
|
166
|
+
await this.stateManager.updateDeploymentStatus(this.deploymentName, DeploymentStatus.DEPLOYING);
|
|
167
|
+
// 1. Generate Dockerfile from image spec
|
|
168
|
+
results.steps.push("Loading image spec...");
|
|
169
|
+
const spec = await loadImageSpec(this.deployment.image);
|
|
170
|
+
results.steps.push("Generating Dockerfile...");
|
|
171
|
+
const dockerfileContent = generateDockerfile(spec);
|
|
172
|
+
// 2. Create or clone the HF repo
|
|
173
|
+
results.steps.push(`Setting up HuggingFace Space: ${this.deployment.repo}...`);
|
|
174
|
+
const token = process.env.HF_TOKEN || process.env.HUGGING_FACE_HUB_TOKEN;
|
|
175
|
+
const authArgs = token
|
|
176
|
+
? ["-c", `http.extraHeader=Authorization: Bearer ${token}`]
|
|
177
|
+
: [];
|
|
178
|
+
await this.ensureRepo(tmpDir, results, authArgs);
|
|
179
|
+
const repoDir = path.join(tmpDir, "repo");
|
|
180
|
+
// 3. Write Dockerfile
|
|
181
|
+
results.steps.push("Writing Dockerfile...");
|
|
182
|
+
await fs.writeFile(path.join(repoDir, "Dockerfile"), dockerfileContent, "utf-8");
|
|
183
|
+
// 4. Write README.md with Space metadata
|
|
184
|
+
results.steps.push("Writing Space metadata (README.md)...");
|
|
185
|
+
const readme = generateSpaceReadme(this.deployment);
|
|
186
|
+
await fs.writeFile(path.join(repoDir, "README.md"), readme, "utf-8");
|
|
187
|
+
// 5. Git commit and push
|
|
188
|
+
results.steps.push("Committing and pushing to HuggingFace...");
|
|
189
|
+
await git(["config", "user.email", "deploy@nodetool.ai"], {
|
|
190
|
+
cwd: repoDir
|
|
191
|
+
});
|
|
192
|
+
await git(["config", "user.name", "NodeTool Deploy"], { cwd: repoDir });
|
|
193
|
+
await git(["add", "-A"], { cwd: repoDir });
|
|
194
|
+
// Check if there are changes to commit
|
|
195
|
+
try {
|
|
196
|
+
await git(["diff", "--cached", "--quiet"], { cwd: repoDir });
|
|
197
|
+
results.steps.push("No changes detected, skipping commit");
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// diff --quiet exits non-zero when there are changes
|
|
201
|
+
await git(["commit", "-m", "Deploy NodeTool via nodetool deploy"], {
|
|
202
|
+
cwd: repoDir
|
|
203
|
+
});
|
|
204
|
+
await git([...authArgs, "push", "origin", "main"], {
|
|
205
|
+
cwd: repoDir,
|
|
206
|
+
timeout: 120_000
|
|
207
|
+
});
|
|
208
|
+
results.steps.push("Pushed to HuggingFace Space");
|
|
209
|
+
}
|
|
210
|
+
results.steps.push("HuggingFace Spaces will now build and deploy the Dockerfile");
|
|
211
|
+
// 6. Update state
|
|
212
|
+
await this.stateManager.writeState(this.deploymentName, {
|
|
213
|
+
status: DeploymentStatus.SERVING,
|
|
214
|
+
repo: this.deployment.repo,
|
|
215
|
+
space_url: `https://huggingface.co/spaces/${this.deployment.repo}`
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
results.status = "error";
|
|
220
|
+
results.errors.push(e instanceof Error ? e.message : String(e));
|
|
221
|
+
await this.stateManager.updateDeploymentStatus(this.deploymentName, DeploymentStatus.ERROR);
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
// Clean up temp directory
|
|
226
|
+
try {
|
|
227
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// ignore cleanup errors
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Destroy the HuggingFace Space.
|
|
237
|
+
*/
|
|
238
|
+
async destroy() {
|
|
239
|
+
const results = {
|
|
240
|
+
deployment_name: this.deploymentName,
|
|
241
|
+
status: "success",
|
|
242
|
+
steps: [],
|
|
243
|
+
errors: []
|
|
244
|
+
};
|
|
245
|
+
try {
|
|
246
|
+
results.steps.push(`Deleting HuggingFace Space: ${this.deployment.repo}...`);
|
|
247
|
+
await hfCli([
|
|
248
|
+
"repo",
|
|
249
|
+
"delete",
|
|
250
|
+
this.deployment.repo,
|
|
251
|
+
"--type",
|
|
252
|
+
"space",
|
|
253
|
+
"--yes"
|
|
254
|
+
]);
|
|
255
|
+
results.steps.push("Space deleted successfully");
|
|
256
|
+
await this.stateManager.updateDeploymentStatus(this.deploymentName, DeploymentStatus.DESTROYED);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
results.status = "error";
|
|
260
|
+
results.errors.push(e instanceof Error ? e.message : String(e));
|
|
261
|
+
await this.stateManager.updateDeploymentStatus(this.deploymentName, DeploymentStatus.ERROR);
|
|
262
|
+
throw e;
|
|
263
|
+
}
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get current deployment status.
|
|
268
|
+
*/
|
|
269
|
+
async status() {
|
|
270
|
+
const statusInfo = {
|
|
271
|
+
deployment_name: this.deploymentName,
|
|
272
|
+
type: "huggingface",
|
|
273
|
+
repo: this.deployment.repo,
|
|
274
|
+
space_url: `https://huggingface.co/spaces/${this.deployment.repo}`
|
|
275
|
+
};
|
|
276
|
+
// Get state from state manager
|
|
277
|
+
const state = await this.stateManager.readState(this.deploymentName);
|
|
278
|
+
if (state) {
|
|
279
|
+
statusInfo["status"] = state["status"] ?? "unknown";
|
|
280
|
+
statusInfo["last_deployed"] = state["last_deployed"];
|
|
281
|
+
}
|
|
282
|
+
// Try to get live status from HF
|
|
283
|
+
try {
|
|
284
|
+
const output = await hfCli([
|
|
285
|
+
"repo",
|
|
286
|
+
"info",
|
|
287
|
+
this.deployment.repo,
|
|
288
|
+
"--type",
|
|
289
|
+
"space"
|
|
290
|
+
]);
|
|
291
|
+
statusInfo["live_status"] = output.trim();
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
statusInfo["live_status_error"] =
|
|
295
|
+
e instanceof Error ? e.message : String(e);
|
|
296
|
+
}
|
|
297
|
+
return statusInfo;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get logs from the HuggingFace Space.
|
|
301
|
+
*
|
|
302
|
+
* HuggingFace Spaces does not expose logs via the CLI. Logs are available
|
|
303
|
+
* through the web UI at the Space URL.
|
|
304
|
+
*/
|
|
305
|
+
async logs(_opts) {
|
|
306
|
+
const spaceUrl = `https://huggingface.co/spaces/${this.deployment.repo}`;
|
|
307
|
+
return (`HuggingFace Spaces logs are not available via the CLI.\n` +
|
|
308
|
+
`View logs in the web UI: ${spaceUrl}\n` +
|
|
309
|
+
`Navigate to the "Logs" tab in your Space settings.`);
|
|
310
|
+
}
|
|
311
|
+
// ---------- Private helpers ----------
|
|
312
|
+
/**
|
|
313
|
+
* Ensure the HF Space repository exists and is cloned to the target directory.
|
|
314
|
+
* Creates the repo if it does not exist, then clones it.
|
|
315
|
+
*/
|
|
316
|
+
async ensureRepo(targetDir, results, authArgs = []) {
|
|
317
|
+
// Try to create the repo (may already exist)
|
|
318
|
+
try {
|
|
319
|
+
await hfCli([
|
|
320
|
+
"repo",
|
|
321
|
+
"create",
|
|
322
|
+
this.deployment.repo,
|
|
323
|
+
"--type",
|
|
324
|
+
"space",
|
|
325
|
+
"--space_sdk",
|
|
326
|
+
"docker",
|
|
327
|
+
"--yes"
|
|
328
|
+
]);
|
|
329
|
+
results.steps.push(`Created HuggingFace Space: ${this.deployment.repo}`);
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
333
|
+
// Ignore "already exists" errors
|
|
334
|
+
if (!msg.includes("already exists") && !msg.includes("409")) {
|
|
335
|
+
throw e;
|
|
336
|
+
}
|
|
337
|
+
results.steps.push(`HuggingFace Space already exists: ${this.deployment.repo}`);
|
|
338
|
+
}
|
|
339
|
+
// Clone into a subdirectory to avoid TOCTOU race on the temp dir
|
|
340
|
+
const repoDir = path.join(targetDir, "repo");
|
|
341
|
+
const repoUrl = `https://huggingface.co/spaces/${this.deployment.repo}`;
|
|
342
|
+
await git([...authArgs, "clone", repoUrl, repoDir], { timeout: 120_000 });
|
|
343
|
+
results.steps.push(`Cloned Space repository: ${this.deployment.repo}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=huggingface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"huggingface.js","sourceRoot":"","sources":["../src/huggingface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker image builder for NodeTool.
|
|
3
|
+
*
|
|
4
|
+
* Generates Dockerfiles from an ImageBuildSpec and runs `docker build`.
|
|
5
|
+
* Layer ordering is designed for optimal Docker cache utilization:
|
|
6
|
+
* base → apt → python install → pip → copy server → copy web UI → entrypoint.
|
|
7
|
+
*/
|
|
8
|
+
import type { ImageBuildSpec } from "./image-spec.js";
|
|
9
|
+
/**
|
|
10
|
+
* Generate a complete Dockerfile from an image build spec.
|
|
11
|
+
*
|
|
12
|
+
* The generated Dockerfile follows a layered approach for cache efficiency:
|
|
13
|
+
* 1. Base image (node:20-slim or nvidia/cuda if CUDA is set)
|
|
14
|
+
* 2. System packages via apt-get (if any)
|
|
15
|
+
* 3. Python installation (if python section present)
|
|
16
|
+
* 4. pip packages (if any)
|
|
17
|
+
* 5. Copy Node.js server bundle
|
|
18
|
+
* 6. Copy web UI build (if mode is fullstack)
|
|
19
|
+
* 7. Entrypoint
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateDockerfile(spec: ImageBuildSpec): string;
|
|
22
|
+
/**
|
|
23
|
+
* Build a Docker image from an image build spec.
|
|
24
|
+
*
|
|
25
|
+
* Generates a Dockerfile in the context directory, runs `docker build`,
|
|
26
|
+
* and cleans up the generated Dockerfile afterwards.
|
|
27
|
+
*
|
|
28
|
+
* @param spec - The image build specification.
|
|
29
|
+
* @param tag - Docker image tag (e.g. "nodetool:latest").
|
|
30
|
+
* @param opts.contextDir - Build context directory. Defaults to current directory.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildImage(spec: ImageBuildSpec, tag: string, opts?: {
|
|
33
|
+
contextDir?: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
//# sourceMappingURL=image-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-builder.d.ts","sourceRoot":"","sources":["../src/image-builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQtD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAyH/D;AAMD;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAmBf"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker image builder for NodeTool.
|
|
3
|
+
*
|
|
4
|
+
* Generates Dockerfiles from an ImageBuildSpec and runs `docker build`.
|
|
5
|
+
* Layer ordering is designed for optimal Docker cache utilization:
|
|
6
|
+
* base → apt → python install → pip → copy server → copy web UI → entrypoint.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile as execFileCb } from "child_process";
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
import * as fs from "fs/promises";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
const execFile = promisify(execFileCb);
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Dockerfile Generation
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Generate a complete Dockerfile from an image build spec.
|
|
18
|
+
*
|
|
19
|
+
* The generated Dockerfile follows a layered approach for cache efficiency:
|
|
20
|
+
* 1. Base image (node:20-slim or nvidia/cuda if CUDA is set)
|
|
21
|
+
* 2. System packages via apt-get (if any)
|
|
22
|
+
* 3. Python installation (if python section present)
|
|
23
|
+
* 4. pip packages (if any)
|
|
24
|
+
* 5. Copy Node.js server bundle
|
|
25
|
+
* 6. Copy web UI build (if mode is fullstack)
|
|
26
|
+
* 7. Entrypoint
|
|
27
|
+
*/
|
|
28
|
+
export function generateDockerfile(spec) {
|
|
29
|
+
// --- Validate package names against shell injection ---
|
|
30
|
+
const SAFE_APT_PKG = /^[a-z0-9.+-]+$/;
|
|
31
|
+
const SAFE_PIP_PKG = /^[a-zA-Z0-9._[\]~=<>!,+-]+$/;
|
|
32
|
+
for (const pkg of spec.apt_packages) {
|
|
33
|
+
if (!SAFE_APT_PKG.test(pkg))
|
|
34
|
+
throw new Error(`Invalid apt package name: ${pkg}`);
|
|
35
|
+
}
|
|
36
|
+
if (spec.python) {
|
|
37
|
+
for (const pkg of spec.python.packages) {
|
|
38
|
+
if (!SAFE_PIP_PKG.test(pkg))
|
|
39
|
+
throw new Error(`Invalid pip package name: ${pkg}`);
|
|
40
|
+
}
|
|
41
|
+
if (!/^https?:\/\//.test(spec.python.index_url)) {
|
|
42
|
+
throw new Error(`Invalid index URL (must start with http:// or https://): ${spec.python.index_url}`);
|
|
43
|
+
}
|
|
44
|
+
for (const url of spec.python.extra_index_urls) {
|
|
45
|
+
if (!/^https?:\/\//.test(url)) {
|
|
46
|
+
throw new Error(`Invalid extra index URL (must start with http:// or https://): ${url}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const lines = [];
|
|
51
|
+
// --- Base image ---
|
|
52
|
+
if (spec.cuda) {
|
|
53
|
+
// CUDA tags require 3-part versions (e.g. 12.1.0, not 12.1)
|
|
54
|
+
const cudaVersion = spec.cuda.split(".").length === 2 ? `${spec.cuda}.0` : spec.cuda;
|
|
55
|
+
lines.push(`FROM nvidia/cuda:${cudaVersion}-runtime-ubuntu22.04`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
lines.push("FROM node:20-slim");
|
|
59
|
+
}
|
|
60
|
+
lines.push("");
|
|
61
|
+
// --- Add deadsnakes PPA for non-default Python versions ---
|
|
62
|
+
if (spec.python) {
|
|
63
|
+
lines.push("RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common \\");
|
|
64
|
+
lines.push(" && add-apt-repository ppa:deadsnakes/ppa \\");
|
|
65
|
+
lines.push(" && apt-get update && rm -rf /var/lib/apt/lists/*");
|
|
66
|
+
lines.push("");
|
|
67
|
+
}
|
|
68
|
+
// --- Collect apt packages ---
|
|
69
|
+
const aptPackages = [...spec.apt_packages];
|
|
70
|
+
if (spec.python) {
|
|
71
|
+
aptPackages.push(`python${spec.python.version}`, `python${spec.python.version}-venv`);
|
|
72
|
+
}
|
|
73
|
+
if (spec.cuda) {
|
|
74
|
+
// Need curl for nodesource setup
|
|
75
|
+
if (!aptPackages.includes("curl")) {
|
|
76
|
+
aptPackages.push("curl");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// --- apt-get install ---
|
|
80
|
+
if (aptPackages.length > 0) {
|
|
81
|
+
lines.push("RUN apt-get update && apt-get install -y --no-install-recommends \\");
|
|
82
|
+
lines.push(` ${aptPackages.join(" ")} \\`);
|
|
83
|
+
lines.push(" && rm -rf /var/lib/apt/lists/*");
|
|
84
|
+
lines.push("");
|
|
85
|
+
}
|
|
86
|
+
// --- Install Node.js on CUDA base (which lacks Node) ---
|
|
87
|
+
if (spec.cuda) {
|
|
88
|
+
lines.push("# Install Node.js");
|
|
89
|
+
lines.push("RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \\");
|
|
90
|
+
lines.push(" && apt-get install -y --no-install-recommends nodejs \\");
|
|
91
|
+
lines.push(" && rm -rf /var/lib/apt/lists/*");
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
94
|
+
// --- Create Python venv (ensures pip targets the correct interpreter) ---
|
|
95
|
+
if (spec.python) {
|
|
96
|
+
lines.push(`RUN python${spec.python.version} -m venv /opt/venv`);
|
|
97
|
+
lines.push('ENV PATH="/opt/venv/bin:$PATH"');
|
|
98
|
+
lines.push("");
|
|
99
|
+
}
|
|
100
|
+
// --- pip install ---
|
|
101
|
+
if (spec.python && spec.python.packages.length > 0) {
|
|
102
|
+
const pipArgs = ["pip install --no-cache-dir"];
|
|
103
|
+
pipArgs.push(`--index-url "${spec.python.index_url}"`);
|
|
104
|
+
for (const url of spec.python.extra_index_urls) {
|
|
105
|
+
pipArgs.push(`--extra-index-url "${url}"`);
|
|
106
|
+
}
|
|
107
|
+
pipArgs.push(spec.python.packages.join(" "));
|
|
108
|
+
lines.push(`RUN ${pipArgs.join(" \\\n ")}`);
|
|
109
|
+
lines.push("");
|
|
110
|
+
}
|
|
111
|
+
// --- Copy server bundle ---
|
|
112
|
+
lines.push("WORKDIR /app");
|
|
113
|
+
lines.push("COPY server/ .");
|
|
114
|
+
// --- Copy web UI (fullstack only) ---
|
|
115
|
+
if (spec.mode === "fullstack") {
|
|
116
|
+
lines.push("COPY web/build/ ./public/");
|
|
117
|
+
}
|
|
118
|
+
// --- Expose and entrypoint ---
|
|
119
|
+
lines.push("EXPOSE 7777");
|
|
120
|
+
lines.push('CMD ["node", "server.js"]');
|
|
121
|
+
lines.push("");
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Image Building
|
|
126
|
+
// ============================================================================
|
|
127
|
+
/**
|
|
128
|
+
* Build a Docker image from an image build spec.
|
|
129
|
+
*
|
|
130
|
+
* Generates a Dockerfile in the context directory, runs `docker build`,
|
|
131
|
+
* and cleans up the generated Dockerfile afterwards.
|
|
132
|
+
*
|
|
133
|
+
* @param spec - The image build specification.
|
|
134
|
+
* @param tag - Docker image tag (e.g. "nodetool:latest").
|
|
135
|
+
* @param opts.contextDir - Build context directory. Defaults to current directory.
|
|
136
|
+
*/
|
|
137
|
+
export async function buildImage(spec, tag, opts) {
|
|
138
|
+
const contextDir = opts?.contextDir ?? process.cwd();
|
|
139
|
+
const dockerfile = generateDockerfile(spec);
|
|
140
|
+
const dockerfilePath = path.join(contextDir, "Dockerfile.nodetool");
|
|
141
|
+
try {
|
|
142
|
+
await fs.writeFile(dockerfilePath, dockerfile, "utf-8");
|
|
143
|
+
await execFile("docker", ["build", "-f", dockerfilePath, "-t", tag, "."], {
|
|
144
|
+
cwd: contextDir,
|
|
145
|
+
maxBuffer: 50 * 1024 * 1024 // 50 MB
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
try {
|
|
150
|
+
await fs.unlink(dockerfilePath);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// ignore cleanup errors
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=image-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-builder.js","sourceRoot":"","sources":["../src/image-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image build specification for NodeTool Docker images.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a Zod schema and loader for `nodetool-image.yaml` files
|
|
5
|
+
* that declaratively describe what goes into a Docker image.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
declare const PythonConfigSchema: z.ZodObject<{
|
|
9
|
+
version: z.ZodString;
|
|
10
|
+
packages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
11
|
+
index_url: z.ZodDefault<z.ZodString>;
|
|
12
|
+
extra_index_urls: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const ImageBuildSpecSchema: z.ZodObject<{
|
|
15
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
16
|
+
fullstack: "fullstack";
|
|
17
|
+
backend: "backend";
|
|
18
|
+
}>>;
|
|
19
|
+
apt_packages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
20
|
+
python: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
version: z.ZodString;
|
|
22
|
+
packages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
23
|
+
index_url: z.ZodDefault<z.ZodString>;
|
|
24
|
+
extra_index_urls: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
26
|
+
cuda: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type ImageBuildSpec = z.infer<typeof ImageBuildSpecSchema>;
|
|
29
|
+
export type PythonConfig = z.infer<typeof PythonConfigSchema>;
|
|
30
|
+
/** Parse and validate raw data into an ImageBuildSpec. */
|
|
31
|
+
export declare function parseImageSpec(data: unknown): ImageBuildSpec;
|
|
32
|
+
/**
|
|
33
|
+
* Load an image build spec from a YAML file.
|
|
34
|
+
*
|
|
35
|
+
* Reads the file at `specPath`, parses it as YAML, and validates it against
|
|
36
|
+
* the ImageBuildSpec schema. An empty or null YAML document produces a valid
|
|
37
|
+
* spec with all defaults applied.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadImageSpec(specPath: string): Promise<ImageBuildSpec>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=image-spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-spec.d.ts","sourceRoot":"","sources":["../src/image-spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,QAAA,MAAM,kBAAkB;;;;;iBAOtB,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAQ/B,CAAC;AAMH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAM9D,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAE5D;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAI7E"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image build specification for NodeTool Docker images.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a Zod schema and loader for `nodetool-image.yaml` files
|
|
5
|
+
* that declaratively describe what goes into a Docker image.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import * as fs from "fs/promises";
|
|
9
|
+
import * as yaml from "js-yaml";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Schemas
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const PythonConfigSchema = z.object({
|
|
14
|
+
version: z
|
|
15
|
+
.string()
|
|
16
|
+
.regex(/^\d+\.\d+$/, "Python version must be 'X.Y' format (e.g. '3.11')"),
|
|
17
|
+
packages: z.array(z.string()).default([]),
|
|
18
|
+
index_url: z.string().default("https://pypi.org/simple"),
|
|
19
|
+
extra_index_urls: z.array(z.string()).default([])
|
|
20
|
+
});
|
|
21
|
+
export const ImageBuildSpecSchema = z.object({
|
|
22
|
+
mode: z.enum(["fullstack", "backend"]).default("fullstack"),
|
|
23
|
+
apt_packages: z.array(z.string()).default([]),
|
|
24
|
+
python: PythonConfigSchema.optional(),
|
|
25
|
+
cuda: z
|
|
26
|
+
.string()
|
|
27
|
+
.regex(/^\d+\.\d+(\.\d+)?$/, "CUDA version must be 'X.Y' or 'X.Y.Z' format")
|
|
28
|
+
.optional()
|
|
29
|
+
});
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Parsing and Loading
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/** Parse and validate raw data into an ImageBuildSpec. */
|
|
34
|
+
export function parseImageSpec(data) {
|
|
35
|
+
return ImageBuildSpecSchema.parse(data);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load an image build spec from a YAML file.
|
|
39
|
+
*
|
|
40
|
+
* Reads the file at `specPath`, parses it as YAML, and validates it against
|
|
41
|
+
* the ImageBuildSpec schema. An empty or null YAML document produces a valid
|
|
42
|
+
* spec with all defaults applied.
|
|
43
|
+
*/
|
|
44
|
+
export async function loadImageSpec(specPath) {
|
|
45
|
+
const raw = await fs.readFile(specPath, "utf-8");
|
|
46
|
+
const data = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
47
|
+
return parseImageSpec(data ?? {});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=image-spec.js.map
|