tensorlake 0.5.17 → 0.5.19

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/index.cjs CHANGED
@@ -35,7 +35,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
35
35
  var init_defaults = __esm({
36
36
  "src/defaults.ts"() {
37
37
  "use strict";
38
- SDK_VERSION = "0.5.17";
38
+ SDK_VERSION = "0.5.19";
39
39
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
40
40
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
41
41
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -4503,11 +4503,6 @@ var init_image = __esm({
4503
4503
  * and registers the snapshot as a named sandbox template.
4504
4504
  */
4505
4505
  async build(options = {}) {
4506
- if ((options.registeredName ?? this._name) === DEFAULT_IMAGE_NAME) {
4507
- console.warn(
4508
- `Building sandbox image with the default name "${DEFAULT_IMAGE_NAME}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
4509
- );
4510
- }
4511
4506
  const { createSandboxImage: createSandboxImage2 } = await Promise.resolve().then(() => (init_sandbox_image(), sandbox_image_exports));
4512
4507
  return createSandboxImage2(this, options);
4513
4508
  }
@@ -4522,263 +4517,61 @@ var init_image = __esm({
4522
4517
  // src/sandbox-image.ts
4523
4518
  var sandbox_image_exports = {};
4524
4519
  __export(sandbox_image_exports, {
4520
+ __setNativeBindingForTest: () => __setNativeBindingForTest,
4525
4521
  createSandboxImage: () => createSandboxImage,
4526
- defaultRegisteredName: () => defaultRegisteredName,
4527
- loadDockerfilePlan: () => loadDockerfilePlan,
4528
- loadImagePlan: () => loadImagePlan,
4529
- logicalDockerfileLines: () => logicalDockerfileLines,
4530
- registerImage: () => registerImage,
4531
4522
  runCreateSandboxImageCli: () => runCreateSandboxImageCli
4532
4523
  });
4533
- function defaultRegisteredName(dockerfilePath) {
4534
- const parsed = import_node_path.default.parse(dockerfilePath);
4535
- if (parsed.name.toLowerCase() === "dockerfile") {
4536
- const parentName = import_node_path.default.basename(import_node_path.default.dirname(dockerfilePath)).trim();
4537
- return parentName || "sandbox-image";
4524
+ function resolveRequire() {
4525
+ if (typeof module !== "undefined" && module.exports != null && typeof require !== "undefined") {
4526
+ return require;
4538
4527
  }
4539
- return parsed.name || "sandbox-image";
4528
+ return (0, import_node_module.createRequire)(import_meta.url);
4540
4529
  }
4541
- function logicalDockerfileLines(dockerfileText) {
4542
- const logicalLines = [];
4543
- let parts = [];
4544
- let startLine = null;
4545
- for (const [index, rawLine] of dockerfileText.split(/\r?\n/).entries()) {
4546
- const lineNumber = index + 1;
4547
- const stripped = rawLine.trim();
4548
- if (parts.length === 0 && (!stripped || stripped.startsWith("#"))) {
4549
- continue;
4550
- }
4551
- if (startLine == null) {
4552
- startLine = lineNumber;
4553
- }
4554
- let line = rawLine.replace(/\s+$/, "");
4555
- const continued = line.endsWith("\\");
4556
- if (continued) {
4557
- line = line.slice(0, -1);
4558
- }
4559
- const normalized = line.trim();
4560
- if (normalized && !normalized.startsWith("#")) {
4561
- parts.push(normalized);
4562
- }
4563
- if (continued) {
4564
- continue;
4565
- }
4566
- if (parts.length > 0) {
4567
- logicalLines.push({
4568
- lineNumber: startLine ?? lineNumber,
4569
- line: parts.join(" ")
4570
- });
4571
- }
4572
- parts = [];
4573
- startLine = null;
4574
- }
4575
- if (parts.length > 0) {
4576
- logicalLines.push({
4577
- lineNumber: startLine ?? 1,
4578
- line: parts.join(" ")
4579
- });
4580
- }
4581
- return logicalLines;
4582
- }
4583
- function splitInstruction(line, lineNumber) {
4584
- const trimmed = line.trim();
4585
- if (!trimmed) {
4586
- throw new Error(`line ${lineNumber}: empty Dockerfile instruction`);
4587
- }
4588
- const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/);
4589
- if (!match) {
4590
- throw new Error(`line ${lineNumber}: invalid Dockerfile instruction`);
4591
- }
4592
- return {
4593
- keyword: match[1].toUpperCase(),
4594
- value: (match[2] ?? "").trim()
4595
- };
4596
- }
4597
- function shellSplit(input) {
4598
- const tokens = [];
4599
- let current = "";
4600
- let quote = null;
4601
- let escape = false;
4602
- for (let i = 0; i < input.length; i++) {
4603
- const char = input[i];
4604
- if (escape) {
4605
- current += char;
4606
- escape = false;
4607
- continue;
4608
- }
4609
- if (quote == null) {
4610
- if (/\s/.test(char)) {
4611
- if (current) {
4612
- tokens.push(current);
4613
- current = "";
4614
- }
4615
- continue;
4616
- }
4617
- if (char === "'" || char === '"') {
4618
- quote = char;
4619
- continue;
4620
- }
4621
- if (char === "\\") {
4622
- escape = true;
4623
- continue;
4624
- }
4625
- current += char;
4626
- continue;
4627
- }
4628
- if (quote === "'") {
4629
- if (char === "'") {
4630
- quote = null;
4631
- } else {
4632
- current += char;
4633
- }
4634
- continue;
4635
- }
4636
- if (char === '"') {
4637
- quote = null;
4638
- continue;
4639
- }
4640
- if (char === "\\") {
4641
- const next = input[++i];
4642
- if (next == null) {
4643
- throw new Error(`unterminated escape sequence in '${input}'`);
4644
- }
4645
- current += next;
4646
- continue;
4647
- }
4648
- current += char;
4649
- }
4650
- if (escape) {
4651
- throw new Error(`unterminated escape sequence in '${input}'`);
4652
- }
4653
- if (quote != null) {
4654
- throw new Error(`unterminated quoted string in '${input}'`);
4655
- }
4656
- if (current) {
4657
- tokens.push(current);
4530
+ function loadNativeBinding() {
4531
+ if (cachedNativeBinding) return cachedNativeBinding;
4532
+ if (cachedNativeBindingError) throw cachedNativeBindingError;
4533
+ try {
4534
+ const { loadNative } = resolveRequire()("../lib/runtime.cjs");
4535
+ cachedNativeBinding = loadNative();
4536
+ return cachedNativeBinding;
4537
+ } catch (error) {
4538
+ cachedNativeBindingError = error instanceof Error ? error : new Error(String(error));
4539
+ throw cachedNativeBindingError;
4658
4540
  }
4659
- return tokens;
4660
4541
  }
4661
- function stripLeadingFlags(value) {
4662
- const flags = {};
4663
- let remaining = value.trimStart();
4664
- while (remaining.startsWith("--")) {
4665
- const firstSpace = remaining.indexOf(" ");
4666
- if (firstSpace === -1) {
4667
- throw new Error(`invalid Dockerfile flag syntax: ${value}`);
4668
- }
4669
- const token = remaining.slice(0, firstSpace);
4670
- const rest = remaining.slice(firstSpace + 1).trimStart();
4671
- const flagBody = token.slice(2);
4672
- if (flagBody.includes("=")) {
4673
- const [key, flagValue2] = flagBody.split(/=(.*)/s, 2);
4674
- flags[key] = flagValue2;
4675
- remaining = rest;
4676
- continue;
4677
- }
4678
- const [flagValue, ...restTokens] = shellSplit(rest);
4679
- if (flagValue == null) {
4680
- throw new Error(`missing value for Dockerfile flag '${token}'`);
4681
- }
4682
- flags[flagBody] = flagValue;
4683
- remaining = restTokens.join(" ");
4684
- }
4685
- return { flags, remaining };
4542
+ function __setNativeBindingForTest(binding) {
4543
+ cachedNativeBinding = binding;
4544
+ cachedNativeBindingError = void 0;
4686
4545
  }
4687
- function parseFromValue(value, lineNumber) {
4688
- const { remaining } = stripLeadingFlags(value);
4689
- const tokens = shellSplit(remaining);
4690
- if (tokens.length === 0) {
4691
- throw new Error(`line ${lineNumber}: FROM must include a base image`);
4692
- }
4693
- if (tokens.length > 1 && tokens[1].toLowerCase() !== "as") {
4694
- throw new Error(`line ${lineNumber}: unsupported FROM syntax '${value}'`);
4695
- }
4696
- return tokens[0];
4546
+ function nonEmpty(value) {
4547
+ return value && value.length > 0 ? value : void 0;
4697
4548
  }
4698
- function buildPlanFromDockerfileText(dockerfileText, dockerfilePath, contextDir, registeredName) {
4699
- let baseImage;
4700
- const instructions = [];
4701
- for (const logicalLine of logicalDockerfileLines(dockerfileText)) {
4702
- const { keyword, value } = splitInstruction(
4703
- logicalLine.line,
4704
- logicalLine.lineNumber
4705
- );
4706
- if (keyword === "FROM") {
4707
- if (baseImage != null) {
4708
- throw new Error(
4709
- `line ${logicalLine.lineNumber}: multi-stage Dockerfiles are not supported for sandbox image creation`
4710
- );
4711
- }
4712
- baseImage = parseFromValue(value, logicalLine.lineNumber);
4713
- continue;
4714
- }
4715
- if (UNSUPPORTED_DOCKERFILE_INSTRUCTIONS.has(keyword)) {
4716
- throw new Error(
4717
- `line ${logicalLine.lineNumber}: Dockerfile instruction '${keyword}' is not supported for sandbox image creation`
4718
- );
4719
- }
4720
- instructions.push({
4721
- keyword,
4722
- value,
4723
- lineNumber: logicalLine.lineNumber
4724
- });
4725
- }
4726
- if (!baseImage) {
4727
- throw new Error("Dockerfile must contain a FROM instruction");
4728
- }
4729
- return {
4730
- dockerfilePath,
4731
- contextDir,
4732
- registeredName: registeredName ?? defaultRegisteredName(dockerfilePath),
4733
- dockerfileText,
4734
- baseImage,
4735
- instructions
4736
- };
4737
- }
4738
- async function loadDockerfilePlan(dockerfilePath, registeredName) {
4739
- const resolvedPath = import_node_path.default.resolve(dockerfilePath);
4740
- const fileStats = await (0, import_promises.stat)(resolvedPath).catch(() => null);
4741
- if (!fileStats?.isFile()) {
4742
- throw new Error(`Dockerfile not found: ${dockerfilePath}`);
4743
- }
4744
- const dockerfileText = await (0, import_promises.readFile)(resolvedPath, "utf8");
4745
- return buildPlanFromDockerfileText(
4746
- dockerfileText,
4747
- resolvedPath,
4748
- import_node_path.default.dirname(resolvedPath),
4749
- registeredName
4750
- );
4751
- }
4752
- function loadImagePlan(image, options = {}) {
4753
- const contextDir = import_node_path.default.resolve(options.contextDir ?? process.cwd());
4754
- const dockerfileText = dockerfileContent(image);
4755
- const logicalLines = logicalDockerfileLines(dockerfileText);
4756
- const instructions = image.baseImage == null ? logicalLines : logicalLines.slice(1);
4549
+ function buildContextFromEnv() {
4550
+ const debugRaw = (process.env.TENSORLAKE_DEBUG ?? "").toLowerCase();
4551
+ const debug = ["1", "true", "yes", "on"].includes(debugRaw);
4757
4552
  return {
4758
- dockerfilePath: import_node_path.default.join(contextDir, "Dockerfile"),
4759
- contextDir,
4760
- registeredName: options.registeredName ?? image.name,
4761
- dockerfileText,
4762
- baseImage: image.baseImage ?? void 0,
4763
- instructions: instructions.map(({ line, lineNumber }) => {
4764
- const parsed = splitInstruction(line, lineNumber);
4765
- return {
4766
- keyword: parsed.keyword,
4767
- value: parsed.value,
4768
- lineNumber
4769
- };
4770
- })
4553
+ apiUrl: nonEmpty(process.env.TENSORLAKE_API_URL) ?? "https://api.tensorlake.ai",
4554
+ apiKey: nonEmpty(process.env.TENSORLAKE_API_KEY),
4555
+ personalAccessToken: nonEmpty(process.env.TENSORLAKE_PAT),
4556
+ namespace: nonEmpty(process.env.INDEXIFY_NAMESPACE) ?? "default",
4557
+ organizationId: nonEmpty(process.env.TENSORLAKE_ORGANIZATION_ID),
4558
+ projectId: nonEmpty(process.env.TENSORLAKE_PROJECT_ID),
4559
+ debug
4771
4560
  };
4772
4561
  }
4773
- function ndjsonStdoutEmit(event) {
4774
- process.stdout.write(`${JSON.stringify(event)}
4775
- `);
4562
+ function defaultRegisteredName(dockerfilePath) {
4563
+ const parsed = import_node_path.default.parse(dockerfilePath);
4564
+ if (parsed.name.toLowerCase() === "dockerfile") {
4565
+ const parentName = import_node_path.default.basename(import_node_path.default.dirname(dockerfilePath)).trim();
4566
+ return parentName || "sandbox-image";
4567
+ }
4568
+ return parsed.name || "sandbox-image";
4776
4569
  }
4777
4570
  function noopEmit(_event) {
4778
4571
  }
4779
4572
  function stderrEmit(event) {
4780
- const type = typeof event.type === "string" ? event.type : "";
4781
4573
  const message = typeof event.message === "string" ? event.message : "";
4574
+ const type = typeof event.type === "string" ? event.type : "";
4782
4575
  if (type === "build_log") {
4783
4576
  const stream = typeof event.stream === "string" ? event.stream : "stdout";
4784
4577
  process.stderr.write(`[${stream}] ${message}
@@ -4788,557 +4581,103 @@ function stderrEmit(event) {
4788
4581
  `);
4789
4582
  }
4790
4583
  }
4791
- function debugEnabled() {
4792
- return ["1", "true", "yes", "on"].includes(
4793
- (process.env.TENSORLAKE_DEBUG ?? "").toLowerCase()
4794
- );
4584
+ function ndjsonStdoutEmit(event) {
4585
+ process.stdout.write(`${JSON.stringify(event)}
4586
+ `);
4795
4587
  }
4796
- function buildContextFromEnv() {
4797
- return {
4798
- apiUrl: process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai",
4799
- apiKey: process.env.TENSORLAKE_API_KEY,
4800
- personalAccessToken: process.env.TENSORLAKE_PAT,
4801
- namespace: process.env.INDEXIFY_NAMESPACE ?? "default",
4802
- organizationId: process.env.TENSORLAKE_ORGANIZATION_ID,
4803
- projectId: process.env.TENSORLAKE_PROJECT_ID,
4804
- debug: debugEnabled()
4588
+ function eventToEmitDict(event) {
4589
+ const out = {
4590
+ type: event.eventType,
4591
+ message: event.message
4805
4592
  };
4593
+ if (event.stream != null) out.stream = event.stream;
4594
+ return out;
4806
4595
  }
4807
- function createDefaultClient(context) {
4808
- const useScopeHeaders = context.personalAccessToken != null && context.apiKey == null;
4809
- return new SandboxClient({
4810
- apiUrl: context.apiUrl,
4811
- apiKey: context.apiKey ?? context.personalAccessToken,
4812
- organizationId: useScopeHeaders ? context.organizationId : void 0,
4813
- projectId: useScopeHeaders ? context.projectId : void 0,
4814
- namespace: context.namespace
4815
- });
4816
- }
4817
- function baseApiUrl(context) {
4818
- return context.apiUrl.replace(/\/+$/, "");
4819
- }
4820
- function scopedBuildsPath(context) {
4821
- return `/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-template-builds`;
4822
- }
4823
- function platformHeaders(context) {
4824
- const headers = {
4825
- Authorization: `Bearer ${context.bearerToken}`,
4826
- "Content-Type": "application/json"
4827
- };
4828
- if (context.useScopeHeaders) {
4829
- headers["X-Forwarded-Organization-Id"] = context.organizationId;
4830
- headers["X-Forwarded-Project-Id"] = context.projectId;
4596
+ async function createSandboxImage(source, options = {}, deps = {}) {
4597
+ const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
4598
+ const context = buildContextFromEnv();
4599
+ let dockerfilePath;
4600
+ let dockerfileText;
4601
+ let nativeContextDir;
4602
+ let effectiveName;
4603
+ if (source instanceof Image) {
4604
+ if (!source.baseImage) {
4605
+ throw new Error("Image must have a baseImage to build");
4606
+ }
4607
+ const resolvedContext = import_node_path.default.resolve(options.contextDir ?? process.cwd());
4608
+ dockerfilePath = import_node_path.default.join(resolvedContext, "Dockerfile");
4609
+ let text = dockerfileContent(source);
4610
+ if (!text.endsWith("\n")) text += "\n";
4611
+ dockerfileText = text;
4612
+ nativeContextDir = resolvedContext;
4613
+ effectiveName = options.registeredName ?? source.name;
4614
+ } else if (typeof source === "string") {
4615
+ const resolvedPath = import_node_path.default.resolve(source);
4616
+ if (!(0, import_node_fs.existsSync)(resolvedPath)) {
4617
+ throw new Error(`Dockerfile not found: ${source}`);
4618
+ }
4619
+ dockerfilePath = resolvedPath;
4620
+ dockerfileText = void 0;
4621
+ nativeContextDir = void 0;
4622
+ effectiveName = options.registeredName ?? defaultRegisteredName(resolvedPath);
4623
+ } else {
4624
+ throw new TypeError(
4625
+ `source must be an Image or a Dockerfile path, got ${typeof source}`
4626
+ );
4831
4627
  }
4832
- return headers;
4833
- }
4834
- async function requestJson(url, init, errorPrefix) {
4835
- const response = await fetch(url, init);
4836
- if (!response.ok) {
4837
- throw new Error(
4838
- `${errorPrefix} (HTTP ${response.status}): ${await response.text()}`
4628
+ if (effectiveName === DEFAULT_IMAGE_NAME2) {
4629
+ console.warn(
4630
+ `Building sandbox image with the default name "${DEFAULT_IMAGE_NAME2}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
4839
4631
  );
4840
4632
  }
4841
- const text = await response.text();
4842
- return text ? JSON.parse(text) : {};
4843
- }
4844
- async function resolveBuildContext(context) {
4845
4633
  const bearerToken = context.apiKey ?? context.personalAccessToken;
4846
4634
  if (!bearerToken) {
4847
- throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
4848
- }
4849
- if (context.apiKey) {
4850
- const scope = await requestJson(
4851
- `${baseApiUrl(context)}/platform/v1/keys/introspect`,
4852
- {
4853
- method: "POST",
4854
- headers: {
4855
- Authorization: `Bearer ${bearerToken}`,
4856
- "Content-Type": "application/json"
4857
- }
4858
- },
4859
- "API key introspection failed"
4860
- );
4861
- if (!scope.organizationId || !scope.projectId) {
4862
- throw new Error("API key introspection response is missing organizationId or projectId");
4863
- }
4864
- return {
4865
- ...context,
4866
- bearerToken,
4867
- organizationId: scope.organizationId,
4868
- projectId: scope.projectId,
4869
- useScopeHeaders: false
4870
- };
4871
- }
4872
- if (!context.organizationId || !context.projectId) {
4873
- throw new Error(
4874
- "Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
4875
- );
4876
- }
4877
- return {
4878
- ...context,
4879
- bearerToken,
4880
- organizationId: context.organizationId,
4881
- projectId: context.projectId,
4882
- useScopeHeaders: true
4883
- };
4884
- }
4885
- async function prepareRootfsBuild(context, plan, isPublic) {
4886
- if (!plan.baseImage) {
4887
- throw new Error("Sandbox image builds require a Dockerfile FROM image or Image baseImage");
4888
- }
4889
- const spec = await requestJson(
4890
- `${baseApiUrl(context)}${scopedBuildsPath(context)}`,
4891
- {
4892
- method: "POST",
4893
- headers: platformHeaders(context),
4894
- body: JSON.stringify({
4895
- name: plan.registeredName,
4896
- dockerfile: plan.dockerfileText,
4897
- baseImage: plan.baseImage,
4898
- public: isPublic
4899
- })
4900
- },
4901
- "failed to prepare sandbox image build"
4902
- );
4903
- return { prepared: parsePreparedBuild(spec), spec };
4904
- }
4905
- function parsePreparedBuild(raw) {
4906
- const builder = raw.builder;
4907
- if (!builder) {
4908
- throw new Error("platform API response is missing rootfs builder configuration");
4909
- }
4910
- const prepared = {
4911
- ...raw,
4912
- buildId: requiredString(raw, "buildId"),
4913
- snapshotId: requiredString(raw, "snapshotId"),
4914
- snapshotUri: requiredString(raw, "snapshotUri"),
4915
- rootfsNodeKind: requiredString(raw, "rootfsNodeKind"),
4916
- builder: {
4917
- image: requiredString(builder, "image"),
4918
- command: requiredString(builder, "command"),
4919
- cpus: requiredNumber(builder, "cpus"),
4920
- memoryMb: requiredNumber(builder, "memoryMb"),
4921
- diskMb: requiredNumber(builder, "diskMb")
4922
- }
4923
- };
4924
- const parent = raw.parent;
4925
- if (parent != null) {
4926
- prepared.parent = {
4927
- parentManifestUri: requiredString(parent, "parentManifestUri"),
4928
- rootfsDiskBytes: optionalNumber(parent, "rootfsDiskBytes")
4929
- };
4635
+ throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT credentials.");
4930
4636
  }
4931
- return prepared;
4932
- }
4933
- async function completeRootfsBuild(context, buildId, request) {
4934
- return requestJson(
4935
- `${baseApiUrl(context)}${scopedBuildsPath(context)}/${encodeURIComponent(buildId)}/complete`,
4637
+ emit({ type: "status", message: `Building image '${effectiveName}'...` });
4638
+ const binding = loadNativeBinding();
4639
+ let emitError;
4640
+ const resultJson = await binding.buildSandboxImage(
4936
4641
  {
4937
- method: "POST",
4938
- headers: platformHeaders(context),
4939
- body: JSON.stringify(request)
4642
+ apiUrl: context.apiUrl,
4643
+ bearerToken,
4644
+ dockerfilePath,
4645
+ registeredName: effectiveName,
4646
+ diskMb: options.diskMb,
4647
+ builderDiskMb: options.builderDiskMb,
4648
+ cpus: options.cpus,
4649
+ memoryMb: options.memoryMb,
4650
+ isPublic: options.isPublic ?? false,
4651
+ organizationId: context.organizationId,
4652
+ projectId: context.projectId,
4653
+ namespace: context.namespace,
4654
+ useScopeHeaders: context.personalAccessToken != null && context.apiKey == null,
4655
+ userAgent: void 0,
4656
+ dockerfileText,
4657
+ contextDir: nativeContextDir
4940
4658
  },
4941
- "failed to complete sandbox image build"
4942
- );
4943
- }
4944
- async function resolvedDockerConfigJson() {
4945
- const configDir = process.env.DOCKER_CONFIG ?? import_node_path.default.join((0, import_node_os.homedir)(), ".docker");
4946
- const configPath = import_node_path.default.join(configDir, "config.json");
4947
- try {
4948
- const content = await (0, import_promises.readFile)(configPath, "utf8");
4949
- const parsed = JSON.parse(content);
4950
- const auths = parsed.auths;
4951
- if (auths != null && Object.keys(auths).length > 0) {
4952
- return JSON.stringify({ auths });
4953
- }
4954
- } catch (error) {
4955
- if (error.code === "ENOENT") {
4956
- return void 0;
4957
- }
4958
- throw error;
4959
- }
4960
- return void 0;
4961
- }
4962
- function rootfsDiskBytes(diskMb, prepared) {
4963
- if (diskMb != null) {
4964
- return diskMb * 1024 * 1024;
4965
- }
4966
- if (prepared.parent != null) {
4967
- if (prepared.parent.rootfsDiskBytes == null) {
4968
- throw new Error(
4969
- "platform API did not return parent rootfsDiskBytes for diff build; pass diskMb explicitly or update Platform API"
4970
- );
4971
- }
4972
- return prepared.parent.rootfsDiskBytes;
4973
- }
4974
- return DEFAULT_ROOTFS_DISK_MB * 1024 * 1024;
4975
- }
4976
- function rootfsDiskBytesToMb(bytes) {
4977
- return Math.ceil(bytes / (1024 * 1024));
4978
- }
4979
- async function buildRootfsSpec(preparedSpec, prepared, plan, diskMb) {
4980
- const spec = {
4981
- ...preparedSpec,
4982
- dockerfile: plan.dockerfileText,
4983
- contextDir: REMOTE_CONTEXT_DIR,
4984
- baseImage: plan.baseImage,
4985
- rootfsDiskBytes: rootfsDiskBytes(diskMb, prepared)
4986
- };
4987
- const dockerConfigJson = await resolvedDockerConfigJson();
4988
- if (dockerConfigJson != null) {
4989
- spec.dockerConfigJson = dockerConfigJson;
4990
- }
4991
- return spec;
4992
- }
4993
- function rootfsBuilderExecutable(executable) {
4994
- return executable === ROOTFS_BUILDER_COMMAND ? `${ROOTFS_BUILDER_BIN_DIR}/${ROOTFS_BUILDER_COMMAND}` : executable;
4995
- }
4996
- function rootfsBuilderEnv() {
4997
- return { PATH: ROOTFS_BUILDER_PATH };
4998
- }
4999
- async function runRootfsBuilder(sandbox, command, emit, sleep3) {
5000
- const parts = shellSplit(command);
5001
- const [executable, ...commandArgs] = parts;
5002
- if (!executable) {
5003
- throw new Error("empty rootfs builder command returned by platform API");
5004
- }
5005
- await runStreaming(
5006
- sandbox,
5007
- emit,
5008
- sleep3,
5009
- rootfsBuilderExecutable(executable),
5010
- [...commandArgs, "--spec", REMOTE_SPEC_PATH, "--metadata-out", REMOTE_METADATA_PATH],
5011
- rootfsBuilderEnv(),
5012
- REMOTE_BUILD_DIR
5013
- );
5014
- }
5015
- function metadataString(metadata, snakeKey, camelKey) {
5016
- const value = metadata[snakeKey] ?? metadata[camelKey];
5017
- return typeof value === "string" ? value : void 0;
5018
- }
5019
- function metadataNumber(metadata, snakeKey, camelKey) {
5020
- const value = metadata[snakeKey] ?? metadata[camelKey];
5021
- if (typeof value === "number") {
5022
- return value;
5023
- }
5024
- if (typeof value === "string" && value.trim()) {
5025
- const parsed = Number(value);
5026
- return Number.isFinite(parsed) ? parsed : void 0;
5027
- }
5028
- return void 0;
5029
- }
5030
- function completeRequestFromMetadata(prepared, metadata) {
5031
- const rootfsNodeKind = metadataString(metadata, "rootfs_node_kind", "rootfsNodeKind") ?? prepared.rootfsNodeKind;
5032
- const parentManifestUri = metadataString(metadata, "parent_manifest_uri", "parentManifestUri") ?? (rootfsNodeKind === "diff" ? prepared.parent?.parentManifestUri : void 0);
5033
- if (rootfsNodeKind === "diff" && parentManifestUri == null) {
5034
- throw new Error("rootfs diff build completed without parent_manifest_uri");
5035
- }
5036
- const snapshotFormatVersion = metadataString(
5037
- metadata,
5038
- "snapshot_format_version",
5039
- "snapshotFormatVersion"
5040
- );
5041
- const snapshotSizeBytes = metadataNumber(
5042
- metadata,
5043
- "snapshot_size_bytes",
5044
- "snapshotSizeBytes"
5045
- );
5046
- const rootfsDiskBytesValue = metadataNumber(
5047
- metadata,
5048
- "rootfs_disk_bytes",
5049
- "rootfsDiskBytes"
5050
- );
5051
- if (!snapshotFormatVersion) {
5052
- throw new Error("rootfs builder metadata is missing snapshot_format_version");
5053
- }
5054
- if (snapshotSizeBytes == null) {
5055
- throw new Error("rootfs builder metadata is missing numeric snapshot_size_bytes");
5056
- }
5057
- if (rootfsDiskBytesValue == null) {
5058
- throw new Error("rootfs builder metadata is missing numeric rootfs_disk_bytes");
5059
- }
5060
- return {
5061
- snapshotId: metadataString(metadata, "snapshot_id", "snapshotId") ?? prepared.snapshotId,
5062
- snapshotUri: metadataString(metadata, "snapshot_uri", "snapshotUri") ?? prepared.snapshotUri,
5063
- snapshotFormatVersion,
5064
- snapshotSizeBytes,
5065
- rootfsDiskBytes: rootfsDiskBytesValue,
5066
- rootfsNodeKind,
5067
- ...parentManifestUri ? { parentManifestUri } : {}
5068
- };
5069
- }
5070
- function requiredString(object, key) {
5071
- const value = object[key];
5072
- if (typeof value !== "string" || value.length === 0) {
5073
- throw new Error(`expected '${key}' to be a non-empty string`);
5074
- }
5075
- return value;
5076
- }
5077
- function requiredNumber(object, key) {
5078
- const value = object[key];
5079
- if (typeof value !== "number" || !Number.isFinite(value)) {
5080
- throw new Error(`expected '${key}' to be a finite number`);
5081
- }
5082
- return value;
5083
- }
5084
- function optionalNumber(object, key) {
5085
- const value = object[key];
5086
- if (value == null) {
5087
- return void 0;
5088
- }
5089
- if (typeof value !== "number" || !Number.isFinite(value)) {
5090
- throw new Error(`expected '${key}' to be a finite number`);
5091
- }
5092
- return value;
5093
- }
5094
- async function runChecked(sandbox, command, args, env, workingDir, user) {
5095
- const options = {
5096
- args,
5097
- env,
5098
- workingDir
5099
- };
5100
- if (user != null) {
5101
- options.user = user;
5102
- }
5103
- const result = await sandbox.run(command, options);
5104
- if (result.exitCode !== 0) {
5105
- throw new Error(
5106
- `Command '${command} ${args.join(" ")}' failed with exit code ${result.exitCode}`
5107
- );
5108
- }
5109
- return result;
5110
- }
5111
- async function runStreaming(sandbox, emit, sleep3, command, args = [], env, workingDir) {
5112
- const proc = await sandbox.startProcess(command, {
5113
- args,
5114
- env,
5115
- workingDir,
5116
- user: ROOTFS_BUILDER_PROCESS_USER
5117
- });
5118
- let stdoutSeen = 0;
5119
- let stderrSeen = 0;
5120
- let info;
5121
- while (true) {
5122
- const stdoutResp = await sandbox.getStdout(proc.pid);
5123
- emitOutputLines(emit, "stdout", stdoutResp, stdoutSeen);
5124
- stdoutSeen = stdoutResp.lines.length;
5125
- const stderrResp = await sandbox.getStderr(proc.pid);
5126
- emitOutputLines(emit, "stderr", stderrResp, stderrSeen);
5127
- stderrSeen = stderrResp.lines.length;
5128
- info = await sandbox.getProcess(proc.pid);
5129
- if (info.status !== "running" /* RUNNING */) {
5130
- const finalStdout = await sandbox.getStdout(proc.pid);
5131
- emitOutputLines(emit, "stdout", finalStdout, stdoutSeen);
5132
- stdoutSeen = finalStdout.lines.length;
5133
- const finalStderr = await sandbox.getStderr(proc.pid);
5134
- emitOutputLines(emit, "stderr", finalStderr, stderrSeen);
5135
- break;
5136
- }
5137
- await sleep3(300);
5138
- }
5139
- for (let i = 0; i < 10; i++) {
5140
- if (info.exitCode != null || info.signal != null) {
5141
- break;
4659
+ (event) => {
4660
+ try {
4661
+ emit(eventToEmitDict(event));
4662
+ } catch (error) {
4663
+ emitError ??= error;
4664
+ }
5142
4665
  }
5143
- await sleep3(200);
5144
- info = await sandbox.getProcess(proc.pid);
5145
- }
5146
- const exitCode = info.exitCode != null ? info.exitCode : info.signal != null ? -info.signal : 0;
5147
- if (exitCode !== 0) {
5148
- throw new Error(
5149
- `Command '${command} ${args.join(" ")}' failed with exit code ${exitCode}`
5150
- );
5151
- }
5152
- }
5153
- function emitOutputLines(emit, stream, response, seen) {
5154
- for (const line of response.lines.slice(seen)) {
5155
- emit({ type: "build_log", stream, message: line });
5156
- }
5157
- }
5158
- async function ensureRemoteBuildRoot(sandbox) {
5159
- await runChecked(
5160
- sandbox,
5161
- "mkdir",
5162
- ["-p", REMOTE_BUILD_DIR],
5163
- void 0,
5164
- void 0,
5165
- ROOTFS_BUILDER_PROCESS_USER
5166
4666
  );
5167
- await runChecked(
5168
- sandbox,
5169
- "chmod",
5170
- ["0777", REMOTE_BUILD_DIR],
5171
- void 0,
5172
- void 0,
5173
- ROOTFS_BUILDER_PROCESS_USER
5174
- );
5175
- }
5176
- async function ensureRemoteDirectory(sandbox, remotePath) {
5177
- await runChecked(sandbox, "mkdir", ["-p", remotePath]);
5178
- }
5179
- async function ensureRemoteParentDir(sandbox, remotePath) {
5180
- await ensureRemoteDirectory(sandbox, import_node_path.default.posix.dirname(remotePath));
5181
- }
5182
- async function copyLocalPathToSandbox(sandbox, localPath, remotePath) {
5183
- const fileStats = await (0, import_promises.stat)(localPath).catch(() => null);
5184
- if (!fileStats) {
5185
- throw new Error(`Local path not found: ${localPath}`);
5186
- }
5187
- if (fileStats.isFile()) {
5188
- await ensureRemoteParentDir(sandbox, remotePath);
5189
- await sandbox.writeFile(remotePath, await (0, import_promises.readFile)(localPath));
5190
- return;
5191
- }
5192
- if (!fileStats.isDirectory()) {
5193
- throw new Error(`Local path not found: ${localPath}`);
5194
- }
5195
- const entries = await (0, import_promises.readdir)(localPath, { withFileTypes: true });
5196
- for (const entry of entries) {
5197
- const sourcePath = import_node_path.default.join(localPath, entry.name);
5198
- const destinationPath = import_node_path.default.posix.join(remotePath, entry.name);
5199
- if (entry.isDirectory()) {
5200
- await ensureRemoteDirectory(sandbox, destinationPath);
5201
- await copyLocalPathToSandbox(sandbox, sourcePath, destinationPath);
5202
- } else if (entry.isFile()) {
5203
- await ensureRemoteParentDir(sandbox, destinationPath);
5204
- await sandbox.writeFile(destinationPath, await (0, import_promises.readFile)(sourcePath));
5205
- }
5206
- }
5207
- }
5208
- async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes2, isPublic, snapshotFormatVersion) {
5209
- const bearerToken = context.apiKey ?? context.personalAccessToken;
5210
- if (!bearerToken) {
5211
- throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
4667
+ if (emitError != null) {
4668
+ throw emitError;
5212
4669
  }
5213
- const baseUrl = context.apiUrl.replace(/\/+$/, "");
5214
- const headers = {
5215
- Authorization: `Bearer ${bearerToken}`,
5216
- "Content-Type": "application/json"
5217
- };
5218
- let url;
5219
- if (context.apiKey) {
5220
- url = `${baseUrl}/platform/v1/sandbox-templates`;
5221
- } else {
5222
- if (!context.organizationId || !context.projectId) {
5223
- throw new Error(
5224
- "Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
5225
- );
5226
- }
5227
- url = `${baseUrl}/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-templates`;
5228
- headers["X-Forwarded-Organization-Id"] = context.organizationId;
5229
- headers["X-Forwarded-Project-Id"] = context.projectId;
5230
- }
5231
- const response = await fetch(url, {
5232
- method: "POST",
5233
- headers,
5234
- body: JSON.stringify({
5235
- name,
5236
- dockerfile,
5237
- snapshotId,
5238
- snapshotSandboxId,
5239
- snapshotUri,
5240
- ...snapshotFormatVersion ? { snapshotFormatVersion } : {},
5241
- snapshotSizeBytes,
5242
- rootfsDiskBytes: rootfsDiskBytes2,
5243
- public: isPublic
5244
- })
5245
- });
5246
- if (!response.ok) {
5247
- throw new Error(
5248
- `${response.status} ${response.statusText}: ${await response.text()}`
5249
- );
4670
+ let result = {};
4671
+ if (resultJson.trim().length > 0) {
4672
+ result = JSON.parse(resultJson);
5250
4673
  }
5251
- const text = await response.text();
5252
- return text ? JSON.parse(text) : {};
5253
- }
5254
- async function createSandboxImage(source, options = {}, deps = {}) {
5255
- const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
5256
- const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5257
- const context = buildContextFromEnv();
5258
- const clientFactory = deps.createClient ?? createDefaultClient;
5259
- const sourceLabel = typeof source === "string" ? source : `Image(${source.name})`;
5260
- emit({ type: "status", message: `Loading ${sourceLabel}...` });
5261
- const plan = typeof source === "string" ? await loadDockerfilePlan(source, options.registeredName) : loadImagePlan(source, options);
5262
4674
  emit({
5263
- type: "status",
5264
- message: `Selected image name: ${plan.registeredName}`
4675
+ type: "image_registered",
4676
+ name: effectiveName,
4677
+ image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
5265
4678
  });
5266
- emit({ type: "status", message: "Preparing rootfs build..." });
5267
- const resolvedContext = await resolveBuildContext(context);
5268
- const { prepared, spec: preparedSpec } = await prepareRootfsBuild(
5269
- resolvedContext,
5270
- plan,
5271
- options.isPublic ?? false
5272
- );
5273
- emit({
5274
- type: "status",
5275
- message: prepared.rootfsNodeKind === "diff" ? "Build mode: RootfsDiff" : "Build mode: RootfsBase"
5276
- });
5277
- const client = clientFactory(context);
5278
- let sandbox;
5279
- try {
5280
- const outputRootfsDiskBytes = rootfsDiskBytes(options.diskMb, prepared);
5281
- const builderDiskMb = Math.max(
5282
- rootfsDiskBytesToMb(outputRootfsDiskBytes),
5283
- options.builderDiskMb ?? prepared.builder.diskMb
5284
- );
5285
- emit({
5286
- type: "status",
5287
- message: `Creating rootfs builder sandbox from ${prepared.builder.image}...`
5288
- });
5289
- sandbox = await client.createAndConnect({
5290
- image: prepared.builder.image,
5291
- cpus: options.cpus ?? prepared.builder.cpus,
5292
- memoryMb: options.memoryMb ?? prepared.builder.memoryMb,
5293
- diskMb: builderDiskMb
5294
- });
5295
- emit({
5296
- type: "status",
5297
- message: `Rootfs builder sandbox ${sandbox.sandboxId} is running`
5298
- });
5299
- emit({ type: "status", message: "Uploading build context..." });
5300
- await ensureRemoteBuildRoot(sandbox);
5301
- await copyLocalPathToSandbox(sandbox, plan.contextDir, REMOTE_CONTEXT_DIR);
5302
- const spec = await buildRootfsSpec(
5303
- preparedSpec,
5304
- prepared,
5305
- plan,
5306
- options.diskMb
5307
- );
5308
- await ensureRemoteParentDir(sandbox, REMOTE_SPEC_PATH);
5309
- await sandbox.writeFile(
5310
- REMOTE_SPEC_PATH,
5311
- new TextEncoder().encode(JSON.stringify(spec, null, 2))
5312
- );
5313
- emit({ type: "status", message: "Running offline rootfs builder..." });
5314
- await runRootfsBuilder(sandbox, prepared.builder.command, emit, sleep3);
5315
- const metadataBytes = await sandbox.readFile(REMOTE_METADATA_PATH);
5316
- const metadata = JSON.parse(
5317
- new TextDecoder().decode(metadataBytes)
5318
- );
5319
- const completeRequest = completeRequestFromMetadata(prepared, metadata);
5320
- emit({ type: "status", message: "Completing image registration..." });
5321
- const result = await completeRootfsBuild(
5322
- resolvedContext,
5323
- prepared.buildId,
5324
- completeRequest
5325
- );
5326
- emit({
5327
- type: "image_registered",
5328
- name: plan.registeredName,
5329
- image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
5330
- });
5331
- emit({ type: "done" });
5332
- return result;
5333
- } finally {
5334
- if (sandbox) {
5335
- try {
5336
- await sandbox.terminate();
5337
- } catch {
5338
- }
5339
- }
5340
- client.close();
5341
- }
4679
+ emit({ type: "done" });
4680
+ return result;
5342
4681
  }
5343
4682
  async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5344
4683
  const parsed = (0, import_node_util.parseArgs)({
@@ -5355,7 +4694,9 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5355
4694
  });
5356
4695
  const dockerfilePath = parsed.positionals[0];
5357
4696
  if (!dockerfilePath) {
5358
- throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]");
4697
+ throw new Error(
4698
+ "Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]"
4699
+ );
5359
4700
  }
5360
4701
  const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
5361
4702
  const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
@@ -5388,32 +4729,17 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5388
4729
  { emit: ndjsonStdoutEmit }
5389
4730
  );
5390
4731
  }
5391
- var import_promises, import_node_os, import_node_path, import_node_util, DEFAULT_ROOTFS_DISK_MB, REMOTE_BUILD_DIR, REMOTE_CONTEXT_DIR, REMOTE_SPEC_PATH, REMOTE_METADATA_PATH, ROOTFS_BUILDER_BIN_DIR, ROOTFS_BUILDER_PATH, ROOTFS_BUILDER_COMMAND, ROOTFS_BUILDER_PROCESS_USER, UNSUPPORTED_DOCKERFILE_INSTRUCTIONS;
4732
+ var import_node_module, import_node_path, import_node_fs, import_node_util, import_meta, cachedNativeBinding, cachedNativeBindingError, DEFAULT_IMAGE_NAME2;
5392
4733
  var init_sandbox_image = __esm({
5393
4734
  "src/sandbox-image.ts"() {
5394
4735
  "use strict";
5395
- import_promises = require("fs/promises");
5396
- import_node_os = require("os");
4736
+ import_node_module = require("module");
5397
4737
  import_node_path = __toESM(require("path"), 1);
4738
+ import_node_fs = require("fs");
5398
4739
  import_node_util = require("util");
5399
- init_models();
5400
- init_client();
5401
4740
  init_image();
5402
- DEFAULT_ROOTFS_DISK_MB = 10 * 1024;
5403
- REMOTE_BUILD_DIR = "/var/lib/tensorlake/rootfs-builder/build";
5404
- REMOTE_CONTEXT_DIR = "/var/lib/tensorlake/rootfs-builder/build/context";
5405
- REMOTE_SPEC_PATH = "/var/lib/tensorlake/rootfs-builder/build/spec.json";
5406
- REMOTE_METADATA_PATH = "/var/lib/tensorlake/rootfs-builder/build/metadata.json";
5407
- ROOTFS_BUILDER_BIN_DIR = "/usr/local/bin";
5408
- ROOTFS_BUILDER_PATH = "/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
5409
- ROOTFS_BUILDER_COMMAND = "tl-rootfs-build";
5410
- ROOTFS_BUILDER_PROCESS_USER = "root";
5411
- UNSUPPORTED_DOCKERFILE_INSTRUCTIONS = /* @__PURE__ */ new Set([
5412
- "ARG",
5413
- "ONBUILD",
5414
- "SHELL",
5415
- "USER"
5416
- ]);
4741
+ import_meta = {};
4742
+ DEFAULT_IMAGE_NAME2 = "default";
5417
4743
  }
5418
4744
  });
5419
4745