@sealant/sdk 0.11.0 → 0.12.1

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.
@@ -14,6 +14,7 @@ import { randomUUID } from "node:crypto";
14
14
  import { SealantError } from "../errors.js";
15
15
  import { mapWorkspaceCredentials } from "./credentials.js";
16
16
  import { parseTtlSeconds } from "./duration.js";
17
+ import { discoverLinkedWorktreeMetadataMount } from "./linked-worktree.js";
17
18
  const sanitizeRepoSlug = (value) => {
18
19
  const slug = value
19
20
  .toLowerCase()
@@ -43,6 +44,23 @@ export const buildCreateWorkspaceRequest = (options, config) => {
43
44
  .filter((s) => s.length > 0)
44
45
  .pop() ?? sourceName;
45
46
  const credentials = mapWorkspaceCredentials(options.credentials);
47
+ const linkedWorktreeMount = options.source === undefined ? null : discoverLinkedWorktreeMetadataMount(options.source.path);
48
+ const explicitMounts = options.mounts ?? [];
49
+ const existingMetadataMount = linkedWorktreeMount === null
50
+ ? undefined
51
+ : explicitMounts.find((mount) => mount.mountPath === linkedWorktreeMount.mountPath);
52
+ if (linkedWorktreeMount !== null &&
53
+ existingMetadataMount !== undefined &&
54
+ (existingMetadataMount.hostPath !== linkedWorktreeMount.hostPath ||
55
+ existingMetadataMount.readOnly !== false)) {
56
+ throw new SealantError(`Mount path ${linkedWorktreeMount.mountPath} is required for writable linked-worktree Git metadata and conflicts with an explicit mount.`, { code: "linked_worktree_mount_conflict" });
57
+ }
58
+ const mounts = [
59
+ ...explicitMounts,
60
+ ...(linkedWorktreeMount === null || existingMetadataMount !== undefined
61
+ ? []
62
+ : [linkedWorktreeMount]),
63
+ ];
46
64
  const spec = {
47
65
  version: "1",
48
66
  sources: {
@@ -55,10 +73,10 @@ export const buildCreateWorkspaceRequest = (options, config) => {
55
73
  ...(options.ref === undefined ? {} : { ref: options.ref }),
56
74
  }
57
75
  : { kind: "mount", hostPath: options.source?.path },
58
- ...(options.mounts === undefined || options.mounts.length === 0
76
+ ...(mounts.length === 0
59
77
  ? {}
60
78
  : {
61
- mounts: options.mounts.map((mount) => ({
79
+ mounts: mounts.map((mount) => ({
62
80
  hostPath: mount.hostPath,
63
81
  mountPath: mount.mountPath,
64
82
  // Omitted = the blueprint's default (read-only). Only an explicit choice is sent.
@@ -0,0 +1,12 @@
1
+ export interface LinkedWorktreeMetadataMount {
2
+ readonly hostPath: string;
3
+ readonly mountPath: string;
4
+ readonly readOnly: false;
5
+ }
6
+ /**
7
+ * A linked Git worktree carries a `.git` POINTER FILE rather than its repository metadata. Docker
8
+ * mounting only the worktree preserves that file but not the absolute host path it names. Discover
9
+ * the shared Git directory so the workspace creator can bind it at the same absolute path inside
10
+ * the container; the pointer then works unchanged and no repository data is copied.
11
+ */
12
+ export declare const discoverLinkedWorktreeMetadataMount: (sourcePath: string) => LinkedWorktreeMetadataMount | null;
@@ -0,0 +1,74 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { SealantError } from "../errors.js";
4
+ const isMissingPath = (cause) => typeof cause === "object" &&
5
+ cause !== null &&
6
+ "code" in cause &&
7
+ (cause.code === "ENOENT" || cause.code === "ENOTDIR");
8
+ const readIfPresent = (file) => {
9
+ try {
10
+ return readFileSync(file, "utf8");
11
+ }
12
+ catch (cause) {
13
+ if (isMissingPath(cause))
14
+ return null;
15
+ throw new SealantError(`Could not inspect mounted workspace Git metadata at ${file}.`, {
16
+ code: "mount_source_git_inspection_failed",
17
+ cause,
18
+ });
19
+ }
20
+ };
21
+ const requireDirectory = (directory) => {
22
+ try {
23
+ if (statSync(directory).isDirectory())
24
+ return;
25
+ }
26
+ catch (cause) {
27
+ throw new SealantError(`Mounted workspace Git metadata points to an unreadable directory: ${directory}.`, { code: "mount_source_git_metadata_invalid", cause });
28
+ }
29
+ throw new SealantError(`Mounted workspace Git metadata points to a non-directory path: ${directory}.`, { code: "mount_source_git_metadata_invalid" });
30
+ };
31
+ const isWithin = (child, parent) => {
32
+ const fromParent = relative(parent, child);
33
+ return fromParent === "" || (!fromParent.startsWith("..") && !isAbsolute(fromParent));
34
+ };
35
+ /**
36
+ * A linked Git worktree carries a `.git` POINTER FILE rather than its repository metadata. Docker
37
+ * mounting only the worktree preserves that file but not the absolute host path it names. Discover
38
+ * the shared Git directory so the workspace creator can bind it at the same absolute path inside
39
+ * the container; the pointer then works unchanged and no repository data is copied.
40
+ */
41
+ export const discoverLinkedWorktreeMetadataMount = (sourcePath) => {
42
+ const dotGit = join(sourcePath, ".git");
43
+ let dotGitStat;
44
+ try {
45
+ dotGitStat = statSync(dotGit);
46
+ }
47
+ catch (cause) {
48
+ if (isMissingPath(cause))
49
+ return null;
50
+ throw new SealantError(`Could not inspect mounted workspace Git entry at ${dotGit}.`, {
51
+ code: "mount_source_git_inspection_failed",
52
+ cause,
53
+ });
54
+ }
55
+ // A normal repository already carries its complete `.git` directory inside the primary mount.
56
+ if (!dotGitStat.isFile())
57
+ return null;
58
+ const pointer = readIfPresent(dotGit);
59
+ const gitDirValue = pointer?.match(/^gitdir:\s*(.+)\s*$/m)?.[1]?.trim();
60
+ // A non-Git folder is still a valid mount source. Only Git's documented pointer shape opts in.
61
+ if (gitDirValue === undefined || gitDirValue === "")
62
+ return null;
63
+ const gitDir = resolve(dirname(dotGit), gitDirValue);
64
+ requireDirectory(gitDir);
65
+ const commonDirValue = readIfPresent(join(gitDir, "commondir"))?.trim();
66
+ const metadataRoot = commonDirValue === undefined || commonDirValue === ""
67
+ ? gitDir
68
+ : resolve(gitDir, commonDirValue);
69
+ requireDirectory(metadataRoot);
70
+ // Unusual but already functional: the pointer resolves within the primary mount itself.
71
+ if (isWithin(metadataRoot, sourcePath))
72
+ return null;
73
+ return { hostPath: metadataRoot, mountPath: metadataRoot, readOnly: false };
74
+ };
package/dist/types.d.ts CHANGED
@@ -94,6 +94,9 @@ export interface WorkspaceCredentialsOptions {
94
94
  * A workspace sourced from a CALLER-OWNED host directory instead of a fresh clone. The platform
95
95
  * bind-mounts `path` as the workspace working directory and treats it as caller-owned: writes
96
96
  * persist across workspace stop/restart/expiry, and the path is never reprovisioned or deleted.
97
+ * When `path` is a linked Git worktree, the SDK also binds its shared Git metadata at the absolute
98
+ * path named by the worktree's `.git` pointer. The metadata remains caller-owned and host-backed;
99
+ * no repository data is copied into container-owned storage.
97
100
  * The install must allowlist the path's root (`SEALANT_MOUNT_ALLOWED_STORE_ROOTS`); paths
98
101
  * outside the allowlist are rejected at create. Credentials and dotfiles options compose
99
102
  * unchanged. Clone-based workspaces remain the right shape for independent verification.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "The fluent public SDK for Sealant — create a workspace, run a harness, replay the record.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "effect": "^4.0.0-beta.85",
30
- "@sealant/api-contracts": "^0.11.0"
30
+ "@sealant/api-contracts": "^0.12.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@effect/vitest": "^4.0.0-beta.85",