@almadar/workspace 0.9.0 → 0.10.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.
- package/dist/__tests__/git-snapshots.test.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +132 -7
- 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 +12 -1
- package/dist/types.d.ts +34 -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,
|
|
@@ -2097,6 +2168,55 @@ var WorkspaceServiceImpl = class {
|
|
|
2097
2168
|
const linked = await this.git.hasRemote("origin");
|
|
2098
2169
|
return { ...s, linked };
|
|
2099
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
|
+
}
|
|
2100
2220
|
// === Observation ===
|
|
2101
2221
|
subscribe(observer) {
|
|
2102
2222
|
return this.sinks.subscribe(observer);
|
|
@@ -2252,8 +2372,12 @@ async function openWorkspaceInternal(opts) {
|
|
|
2252
2372
|
}
|
|
2253
2373
|
}
|
|
2254
2374
|
let git;
|
|
2255
|
-
if (!bare && opts.
|
|
2256
|
-
|
|
2375
|
+
if (!bare && opts.backend !== "memory") {
|
|
2376
|
+
try {
|
|
2377
|
+
git = await ensureGitInit(backend, resolved.workDir);
|
|
2378
|
+
} catch {
|
|
2379
|
+
git = void 0;
|
|
2380
|
+
}
|
|
2257
2381
|
}
|
|
2258
2382
|
const service = new WorkspaceServiceImpl({
|
|
2259
2383
|
workDir: resolved.workDir,
|
|
@@ -2262,7 +2386,8 @@ async function openWorkspaceInternal(opts) {
|
|
|
2262
2386
|
embedder: opts.embedder ?? createDefaultEmbedder(),
|
|
2263
2387
|
appId: resolved.appId,
|
|
2264
2388
|
git,
|
|
2265
|
-
github: opts.github
|
|
2389
|
+
github: opts.github,
|
|
2390
|
+
archiveBackend: opts.archiveBackend
|
|
2266
2391
|
});
|
|
2267
2392
|
if (!bare) {
|
|
2268
2393
|
await service.index.warm();
|