@floomhq/floom 1.0.40 → 1.0.41

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.
@@ -1,10 +1,9 @@
1
1
  import { constants } from "node:fs";
2
2
  import { lstat, mkdir, open, rename, rm, stat } from "node:fs/promises";
3
- import { join, relative, resolve, sep } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
5
  import { CONFIG_DIR } from "./config.js";
5
6
  const MANIFEST_VERSION = 1;
6
- const MANIFEST_PATH = join(CONFIG_DIR, "sync-manifest.json");
7
- const LOCK_PATH = join(CONFIG_DIR, "sync.lock");
8
7
  const LOCK_TIMEOUT_MS = 60_000;
9
8
  const LOCK_STALE_MS = 5 * 60_000;
10
9
  const SLUG_RE = /^[A-Za-z0-9_-]{1,128}$/;
@@ -55,6 +54,24 @@ const ROOT_SUPPORT_FILES = new Set([
55
54
  function emptyManifest() {
56
55
  return { version: MANIFEST_VERSION, files: {} };
57
56
  }
57
+ function expandHome(path) {
58
+ if (path === "~")
59
+ return homedir();
60
+ if (path.startsWith("~/"))
61
+ return join(homedir(), path.slice(2));
62
+ return path;
63
+ }
64
+ export function syncManifestPath() {
65
+ if (process.env.FLOOM_SYNC_MANIFEST_PATH)
66
+ return expandHome(process.env.FLOOM_SYNC_MANIFEST_PATH);
67
+ return join(CONFIG_DIR, "sync-manifest.json");
68
+ }
69
+ function syncManifestDir() {
70
+ return dirname(syncManifestPath());
71
+ }
72
+ function syncLockPath() {
73
+ return join(syncManifestDir(), "sync.lock");
74
+ }
58
75
  function isEntryForKey(key, value) {
59
76
  if (!value || typeof value !== "object")
60
77
  return false;
@@ -88,7 +105,7 @@ function isPackageFilePath(packagePath) {
88
105
  export async function readSyncManifest() {
89
106
  try {
90
107
  await ensureSyncManifestDir();
91
- const handle = await open(MANIFEST_PATH, constants.O_RDONLY | constants.O_NOFOLLOW);
108
+ const handle = await open(syncManifestPath(), constants.O_RDONLY | constants.O_NOFOLLOW);
92
109
  let body;
93
110
  try {
94
111
  body = await handle.readFile("utf8");
@@ -117,20 +134,23 @@ export async function readSyncManifest() {
117
134
  }
118
135
  export async function writeSyncManifest(manifest) {
119
136
  await ensureSyncManifestDir();
120
- const dir = await open(CONFIG_DIR, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
121
- const tmpBase = `sync-manifest.json.${process.pid}.${Date.now()}`;
137
+ const path = syncManifestPath();
138
+ const dirPath = dirname(path);
139
+ const targetName = basename(path);
140
+ const dir = await open(dirPath, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
141
+ const tmpBase = `${targetName}.${process.pid}.${Date.now()}`;
122
142
  const body = JSON.stringify(manifest, null, 2);
123
143
  try {
124
144
  for (let attempt = 0; attempt < 10; attempt += 1) {
125
145
  const tmpName = attempt === 0 ? `${tmpBase}.tmp` : `${tmpBase}.${attempt}.tmp`;
126
- const tmpPath = childPath(dir, CONFIG_DIR, tmpName);
146
+ const tmpPath = childPath(dir, dirPath, tmpName);
127
147
  let handle = null;
128
148
  try {
129
149
  handle = await open(tmpPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
130
150
  await handle.writeFile(body, "utf8");
131
151
  await handle.close();
132
152
  handle = null;
133
- await rename(tmpPath, childPath(dir, CONFIG_DIR, "sync-manifest.json"));
153
+ await rename(tmpPath, childPath(dir, dirPath, targetName));
134
154
  return;
135
155
  }
136
156
  catch (err) {
@@ -154,8 +174,9 @@ function childPath(parent, fallbackParent, name) {
154
174
  return join(resolve(fallbackParent), name);
155
175
  }
156
176
  export async function ensureSyncManifestDir() {
157
- await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
158
- const stat = await lstat(CONFIG_DIR);
177
+ const dir = syncManifestDir();
178
+ await mkdir(dir, { recursive: true, mode: 0o700 });
179
+ const stat = await lstat(dir);
159
180
  if (stat.isSymbolicLink()) {
160
181
  const err = new Error("sync manifest directory is a symbolic link");
161
182
  err.code = "ELOOP";
@@ -169,19 +190,20 @@ export async function ensureSyncManifestDir() {
169
190
  }
170
191
  export async function withSyncLock(fn) {
171
192
  await ensureSyncManifestDir();
193
+ const lockPath = syncLockPath();
172
194
  const startedAt = Date.now();
173
195
  for (;;) {
174
196
  try {
175
- await mkdir(LOCK_PATH, { mode: 0o700 });
197
+ await mkdir(lockPath, { mode: 0o700 });
176
198
  break;
177
199
  }
178
200
  catch (err) {
179
201
  if (err.code !== "EEXIST")
180
202
  throw err;
181
203
  try {
182
- const lockStat = await stat(LOCK_PATH);
204
+ const lockStat = await stat(lockPath);
183
205
  if (Date.now() - lockStat.mtimeMs > LOCK_STALE_MS) {
184
- await rm(LOCK_PATH, { recursive: true, force: true });
206
+ await rm(lockPath, { recursive: true, force: true });
185
207
  continue;
186
208
  }
187
209
  }
@@ -200,7 +222,7 @@ export async function withSyncLock(fn) {
200
222
  return await fn();
201
223
  }
202
224
  finally {
203
- await rm(LOCK_PATH, { recursive: true, force: true }).catch(() => { });
225
+ await rm(lockPath, { recursive: true, force: true }).catch(() => { });
204
226
  }
205
227
  }
206
228
  export function manifestKey(root, target) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "1.0.40",
3
+ "version": "1.0.41",
4
4
  "description": "Sync AI skills across agents and machines.",
5
5
  "license": "MIT",
6
6
  "type": "module",