@almadar/workspace 0.8.0 → 0.10.0
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/__tests__/git-snapshots.test.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +135 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/backends/local.d.ts +2 -0
- package/dist/internal/backends/memory.d.ts +3 -0
- package/dist/internal/git-client.d.ts +17 -0
- package/dist/internal/types.d.ts +3 -0
- package/dist/internal/workspace-manager.d.ts +1 -1
- package/dist/service.d.ts +19 -2
- package/dist/types.d.ts +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,6 @@ export { openWorkspace } from './open-workspace.js';
|
|
|
10
10
|
export { listWorkspaces } from './list-workspaces.js';
|
|
11
11
|
export { deleteWorkspace } from './delete-workspace.js';
|
|
12
12
|
export { openAccount } from './account.js';
|
|
13
|
-
export type { WorkspaceService, WorkspaceObserver, WorkspaceWriteEvent, WorkspaceWatchEvent, OpenWorkspaceOptions, ListWorkspacesOptions, WorkspaceSummary, RestoreBackend, GitHubConfig, GitStatusInfo, FileTreeNode, AccountService, AccountConfig, ProviderCredential, AuthToken, AccountIdentity, OpenAccountOptions, } from './types.js';
|
|
13
|
+
export type { WorkspaceService, WorkspaceObserver, WorkspaceWriteEvent, WorkspaceWatchEvent, OpenWorkspaceOptions, ListWorkspacesOptions, WorkspaceSummary, RestoreBackend, GitHubConfig, GitStatusInfo, SnapshotMeta, WorkspaceArchiveBackend, FileTreeNode, AccountService, AccountConfig, ProviderCredential, AuthToken, AccountIdentity, OpenAccountOptions, } from './types.js';
|
|
14
14
|
export type { WorkspaceIndex, EmbedderPort, ResolveResult, ResolveOptions, TraitRefEmit, WorkspaceIndexStats, OrbitalIndexEntry, ExtraTraitIdentity, RetrievalResult, RetrievalOptions, RecentlyEditedOptions, EventEdge, EntityBinding, RuleBinding, RecencyEntry, IntentMaps, ComposedMaps, BM25Document, BM25Table, BM25Options, WorkspaceIndexManifest, } from './workspace-index/types.js';
|
|
15
15
|
export { DEFAULT_COERCION_THRESHOLD, DEFAULT_RETRIEVAL_TOP_K, RRF_K, WORKSPACE_INDEX_SCHEMA_VERSION, } from './workspace-index/types.js';
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,13 @@ var LocalBackend = class {
|
|
|
21
21
|
fs.mkdirSync(path2.dirname(absPath), { recursive: true });
|
|
22
22
|
fs.writeFileSync(absPath, content, "utf-8");
|
|
23
23
|
}
|
|
24
|
+
async readFileBytes(absPath) {
|
|
25
|
+
return fs.promises.readFile(absPath);
|
|
26
|
+
}
|
|
27
|
+
async writeFileBytes(absPath, bytes) {
|
|
28
|
+
await fs.promises.mkdir(path2.dirname(absPath), { recursive: true });
|
|
29
|
+
await fs.promises.writeFile(absPath, bytes);
|
|
30
|
+
}
|
|
24
31
|
exists(absPath) {
|
|
25
32
|
return fs.existsSync(absPath);
|
|
26
33
|
}
|
|
@@ -77,6 +84,7 @@ var LocalBackend = class {
|
|
|
77
84
|
var MemoryBackend = class {
|
|
78
85
|
constructor() {
|
|
79
86
|
this.files = /* @__PURE__ */ new Map();
|
|
87
|
+
this.bytes = /* @__PURE__ */ new Map();
|
|
80
88
|
this.dirs = /* @__PURE__ */ new Set();
|
|
81
89
|
}
|
|
82
90
|
async readFile(absPath) {
|
|
@@ -93,6 +101,14 @@ var MemoryBackend = class {
|
|
|
93
101
|
writeFileSync(absPath, content) {
|
|
94
102
|
this.files.set(absPath, content);
|
|
95
103
|
}
|
|
104
|
+
async readFileBytes(absPath) {
|
|
105
|
+
const b = this.bytes.get(absPath);
|
|
106
|
+
if (b === void 0) throw new Error(`ENOENT: ${absPath}`);
|
|
107
|
+
return b;
|
|
108
|
+
}
|
|
109
|
+
async writeFileBytes(absPath, bytes) {
|
|
110
|
+
this.bytes.set(absPath, bytes);
|
|
111
|
+
}
|
|
96
112
|
exists(absPath) {
|
|
97
113
|
if (this.files.has(absPath) || this.dirs.has(absPath)) return true;
|
|
98
114
|
const prefix = absPath.endsWith("/") ? absPath : absPath + "/";
|
|
@@ -364,6 +380,7 @@ function createProjectOrbTemplate(projectName, appId) {
|
|
|
364
380
|
function serializeJson(value) {
|
|
365
381
|
return JSON.stringify(value, null, 2);
|
|
366
382
|
}
|
|
383
|
+
var GIT_BINARY = process.env.ALMADAR_GIT_BINARY || "git";
|
|
367
384
|
var GitClient = class {
|
|
368
385
|
constructor(cwd, backend) {
|
|
369
386
|
this.cwd = cwd;
|
|
@@ -384,13 +401,12 @@ var GitClient = class {
|
|
|
384
401
|
}
|
|
385
402
|
async commit(message) {
|
|
386
403
|
try {
|
|
387
|
-
|
|
388
|
-
const match = out.match(/\[[\w/.-]+ ([a-f0-9]+)\]/);
|
|
389
|
-
return match ? match[1] : null;
|
|
404
|
+
await this.exec(["commit", "-m", message, "--allow-empty-message"]);
|
|
390
405
|
} catch (err) {
|
|
391
406
|
if (err instanceof Error && err.message.includes("nothing to commit")) return null;
|
|
392
407
|
throw err;
|
|
393
408
|
}
|
|
409
|
+
return this.headSha();
|
|
394
410
|
}
|
|
395
411
|
async tag(name, message) {
|
|
396
412
|
const args = ["tag"];
|
|
@@ -446,13 +462,51 @@ var GitClient = class {
|
|
|
446
462
|
return null;
|
|
447
463
|
}
|
|
448
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Commit history of `filepath` (newest first) — the snapshot list. NUL-delimited
|
|
467
|
+
* fields so commit subjects with tabs/spaces parse cleanly.
|
|
468
|
+
*/
|
|
469
|
+
async log(filepath, limit) {
|
|
470
|
+
const args = ["log", "--format=%H%x00%at%x00%s"];
|
|
471
|
+
if (limit !== void 0) args.push("-n", String(limit));
|
|
472
|
+
if (filepath) args.push("--", filepath);
|
|
473
|
+
let out;
|
|
474
|
+
try {
|
|
475
|
+
out = await this.exec(args);
|
|
476
|
+
} catch {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
const entries = [];
|
|
480
|
+
for (const line of out.split("\n")) {
|
|
481
|
+
if (!line) continue;
|
|
482
|
+
const [oid, ts, subject] = line.split("\0");
|
|
483
|
+
if (oid && ts) entries.push({ oid, timestamp: Number(ts) * 1e3, subject: subject ?? "" });
|
|
484
|
+
}
|
|
485
|
+
return entries;
|
|
486
|
+
}
|
|
487
|
+
/** Content of `filepath` at commit `oid` — the restore read. Null if absent. */
|
|
488
|
+
async show(oid, filepath) {
|
|
489
|
+
try {
|
|
490
|
+
return await this.exec(["show", `${oid}:${filepath}`]);
|
|
491
|
+
} catch {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/** Bundle the whole repo into a single delta-compressed file (the durable archive). */
|
|
496
|
+
async bundleAll(outAbsPath) {
|
|
497
|
+
await this.exec(["bundle", "create", outAbsPath, "--all"]);
|
|
498
|
+
}
|
|
499
|
+
/** Clone a bundle file into `targetDir` (hydrate a fresh workspace from the archive). */
|
|
500
|
+
async cloneBundle(bundleAbsPath, targetDir) {
|
|
501
|
+
await execGit(["clone", bundleAbsPath, targetDir], path2.dirname(bundleAbsPath));
|
|
502
|
+
}
|
|
449
503
|
exec(args) {
|
|
450
504
|
return execGit(args, this.cwd);
|
|
451
505
|
}
|
|
452
506
|
};
|
|
453
507
|
function execGit(args, cwd) {
|
|
454
508
|
return new Promise((resolve, reject) => {
|
|
455
|
-
execFile(
|
|
509
|
+
execFile(GIT_BINARY, args, { cwd, maxBuffer: 64 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
456
510
|
if (err) {
|
|
457
511
|
const message = stderr?.trim() || stdout?.trim() || err.message;
|
|
458
512
|
reject(new Error(`git ${args[0]}: ${message}`));
|
|
@@ -491,9 +545,25 @@ async function writeMintTemplatesIfMissing(backend, workDir, userId, projectName
|
|
|
491
545
|
);
|
|
492
546
|
}
|
|
493
547
|
}
|
|
548
|
+
var GITIGNORE = [
|
|
549
|
+
"# Almadar \u2014 version only the app; ignore generated/churny state",
|
|
550
|
+
"/.almadar/sessions/",
|
|
551
|
+
"/.almadar/trace.jsonl",
|
|
552
|
+
"/.almadar/index.json",
|
|
553
|
+
"/.almadar/snapshots/",
|
|
554
|
+
"/.almadar/changesets/",
|
|
555
|
+
"/.almadar/history-meta.json",
|
|
556
|
+
"/apps/",
|
|
557
|
+
"/.almadar-archive.bundle",
|
|
558
|
+
""
|
|
559
|
+
].join("\n");
|
|
494
560
|
async function ensureGitInit(backend, workDir) {
|
|
495
561
|
const git = new GitClient(workDir, backend);
|
|
496
562
|
await git.init();
|
|
563
|
+
const gitignorePath = path2.join(workDir, ".gitignore");
|
|
564
|
+
if (!backend.exists(gitignorePath)) {
|
|
565
|
+
await backend.writeFile(gitignorePath, GITIGNORE);
|
|
566
|
+
}
|
|
497
567
|
return git;
|
|
498
568
|
}
|
|
499
569
|
function readAppMarker(backend, workDir) {
|
|
@@ -1700,6 +1770,7 @@ var WorkspaceServiceImpl = class {
|
|
|
1700
1770
|
this._appId = args.appId;
|
|
1701
1771
|
this.git = args.git;
|
|
1702
1772
|
this.github = args.github;
|
|
1773
|
+
this.archiveBackend = args.archiveBackend;
|
|
1703
1774
|
this.index = new WorkspaceIndexImpl({
|
|
1704
1775
|
workDir: this.workDir,
|
|
1705
1776
|
backend: this.backend,
|
|
@@ -1718,6 +1789,14 @@ var WorkspaceServiceImpl = class {
|
|
|
1718
1789
|
setAppId(id) {
|
|
1719
1790
|
this._appId = id;
|
|
1720
1791
|
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Set (or clear) the GitHub config after open. Lets a consumer resolve the
|
|
1794
|
+
* link/token lazily — e.g. from a workspace file read post-open — and wire
|
|
1795
|
+
* `commitAndPush`/`pullIfLinked` without knowing it at construction time.
|
|
1796
|
+
*/
|
|
1797
|
+
setGitHub(config) {
|
|
1798
|
+
this.github = config;
|
|
1799
|
+
}
|
|
1721
1800
|
// === Helpers ===
|
|
1722
1801
|
/** Run `op` under a per-path serial lock. */
|
|
1723
1802
|
withLock(absPath, op) {
|
|
@@ -2089,6 +2168,55 @@ var WorkspaceServiceImpl = class {
|
|
|
2089
2168
|
const linked = await this.git.hasRemote("origin");
|
|
2090
2169
|
return { ...s, linked };
|
|
2091
2170
|
}
|
|
2171
|
+
// === Snapshots (git history of schema.orb) ===
|
|
2172
|
+
async snapshot(reason) {
|
|
2173
|
+
if (!this.git) return null;
|
|
2174
|
+
await this.git.addAll();
|
|
2175
|
+
const oid = await this.git.commit(`snapshot: ${reason}`);
|
|
2176
|
+
return oid ? { oid } : null;
|
|
2177
|
+
}
|
|
2178
|
+
async listSnapshots(opts) {
|
|
2179
|
+
if (!this.git) return [];
|
|
2180
|
+
const entries = await this.git.log(WORKSPACE_LAYOUT.SCHEMA_FILE, opts?.limit);
|
|
2181
|
+
return entries.map((e) => ({
|
|
2182
|
+
id: e.oid,
|
|
2183
|
+
timestamp: e.timestamp,
|
|
2184
|
+
reason: e.subject.startsWith("snapshot: ") ? e.subject.slice("snapshot: ".length) : e.subject
|
|
2185
|
+
}));
|
|
2186
|
+
}
|
|
2187
|
+
async restore(oid) {
|
|
2188
|
+
if (!this.git) return null;
|
|
2189
|
+
return this.git.show(oid, WORKSPACE_LAYOUT.SCHEMA_FILE);
|
|
2190
|
+
}
|
|
2191
|
+
async archive() {
|
|
2192
|
+
if (!this.git || !this.archiveBackend) return;
|
|
2193
|
+
const bundlePath = path2.join(this.workDir, ".almadar-archive.bundle");
|
|
2194
|
+
await this.git.bundleAll(bundlePath);
|
|
2195
|
+
const bytes = await this.backend.readFileBytes(bundlePath);
|
|
2196
|
+
await this.archiveBackend.saveBundle(bytes);
|
|
2197
|
+
await this.backend.rm(bundlePath).catch(() => {
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
async hydrateFromArchive() {
|
|
2201
|
+
if (!this.git || !this.archiveBackend) return false;
|
|
2202
|
+
const bytes = await this.archiveBackend.loadBundle();
|
|
2203
|
+
if (!bytes) return false;
|
|
2204
|
+
const base = path2.basename(this.workDir);
|
|
2205
|
+
const bundlePath = path2.join(path2.dirname(this.workDir), `${base}.bundle`);
|
|
2206
|
+
const cloneDir = path2.join(path2.dirname(this.workDir), `${base}.hydrate`);
|
|
2207
|
+
await this.backend.writeFileBytes(bundlePath, bytes);
|
|
2208
|
+
try {
|
|
2209
|
+
await this.backend.rm(cloneDir, { recursive: true }).catch(() => {
|
|
2210
|
+
});
|
|
2211
|
+
await this.git.cloneBundle(bundlePath, cloneDir);
|
|
2212
|
+
await this.backend.rm(this.workDir, { recursive: true });
|
|
2213
|
+
await this.backend.rename(cloneDir, this.workDir);
|
|
2214
|
+
return true;
|
|
2215
|
+
} finally {
|
|
2216
|
+
await this.backend.rm(bundlePath).catch(() => {
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2092
2220
|
// === Observation ===
|
|
2093
2221
|
subscribe(observer) {
|
|
2094
2222
|
return this.sinks.subscribe(observer);
|
|
@@ -2244,7 +2372,7 @@ async function openWorkspaceInternal(opts) {
|
|
|
2244
2372
|
}
|
|
2245
2373
|
}
|
|
2246
2374
|
let git;
|
|
2247
|
-
if (!bare && opts.
|
|
2375
|
+
if (!bare && opts.backend !== "memory") {
|
|
2248
2376
|
git = await ensureGitInit(backend, resolved.workDir);
|
|
2249
2377
|
}
|
|
2250
2378
|
const service = new WorkspaceServiceImpl({
|
|
@@ -2254,7 +2382,8 @@ async function openWorkspaceInternal(opts) {
|
|
|
2254
2382
|
embedder: opts.embedder ?? createDefaultEmbedder(),
|
|
2255
2383
|
appId: resolved.appId,
|
|
2256
2384
|
git,
|
|
2257
|
-
github: opts.github
|
|
2385
|
+
github: opts.github,
|
|
2386
|
+
archiveBackend: opts.archiveBackend
|
|
2258
2387
|
});
|
|
2259
2388
|
if (!bare) {
|
|
2260
2389
|
await service.index.warm();
|