@pathbit/agentdevkit 0.0.7 → 0.0.8
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/.pbai4devs/scripts/lib/gitignore.mjs +84 -15
- package/.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs +41 -20
- package/.pbai4devs/scripts/preparar-ferramentas/tests/TEST.mjs +81 -0
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +120 -20
- package/.pbai4devs/scripts/sincronizar-agentes/tests/HUMAN_OWNERSHIP.mjs +145 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/LOCAL_IGNORE.mjs +145 -1
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +2 -1
- package/CHANGELOG.md +9 -0
- package/README.en.md +2 -2
- package/README.es.md +2 -2
- package/README.md +2 -2
- package/docs/guides/cli.md +9 -0
- package/docs/guides/installation.md +8 -2
- package/docs/guides/tools-and-testing.md +2 -0
- package/docs/maintainers/packages.md +1 -1
- package/docs/product/roadmap.md +2 -0
- package/docs/specs/README.md +1 -0
- package/docs/specs/v0.0.8/README.md +10 -0
- package/docs/specs/v0.0.8/SPEC.md +62 -0
- package/package.json +1 -1
- package/src/cli.mjs +10 -4
- package/src/install.mjs +61 -22
package/src/install.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, writeFileSync, readdirSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, rmdirSync } from 'node:fs';
|
|
2
|
+
import { chmodSync, existsSync, lstatSync, readFileSync, writeFileSync, readdirSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, rmdirSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { synchronize, isReference } from '../.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs';
|
|
@@ -77,8 +77,24 @@ function walk(root, path = '', filter = () => true) {
|
|
|
77
77
|
return files;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function write(root, path, content) {
|
|
81
|
-
const target = safePath(root, path); mkdirSync(dirname(target), { recursive: true });
|
|
80
|
+
function write(root, path, content, mode) {
|
|
81
|
+
const target = safePath(root, path); mkdirSync(dirname(target), { recursive: true });
|
|
82
|
+
writeFileSync(target, content, mode === undefined ? undefined : { mode });
|
|
83
|
+
if (mode !== undefined) chmodSync(target, mode);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function atomicWrite(root, path, content, mode, onPublished) {
|
|
87
|
+
const file = safePath(root, path);
|
|
88
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
89
|
+
const temporary = `${file}.agentdevkit-${randomUUID()}`;
|
|
90
|
+
try {
|
|
91
|
+
writeFileSync(temporary, content, { flag: 'wx', ...(mode === undefined ? {} : { mode }) });
|
|
92
|
+
if (mode !== undefined) chmodSync(temporary, mode);
|
|
93
|
+
const writtenMode = stat(temporary).mode & 0o777;
|
|
94
|
+
renameSync(temporary, file);
|
|
95
|
+
onPublished?.(writtenMode);
|
|
96
|
+
return writtenMode;
|
|
97
|
+
} finally { rmSync(temporary, { force: true }); }
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
function read(root, path) {
|
|
@@ -294,42 +310,63 @@ export function applyPlan(plan) {
|
|
|
294
310
|
safePath(target, backup);
|
|
295
311
|
for (const change of plan.changes) safePath(target, change.path);
|
|
296
312
|
const before = new Map(plan.changes.map(change => [change.path, read(target, change.path)]));
|
|
313
|
+
const beforeModes = new Map(plan.changes.map(change => {
|
|
314
|
+
const info = stat(safePath(target, change.path));
|
|
315
|
+
return [change.path, info ? info.mode & 0o777 : undefined];
|
|
316
|
+
}));
|
|
297
317
|
for (const change of plan.changes) if ((before.get(change.path) ? hash(before.get(change.path)) : null) !== change.beforeHash) fail('E_CHANGED', `File changed after planning: ${change.path}`);
|
|
298
|
-
for (const [path, content] of before) if (content) write(target, `${backup}/${path}`, content);
|
|
299
|
-
const applied = [];
|
|
318
|
+
for (const [path, content] of before) if (content) write(target, `${backup}/${path}`, content, beforeModes.get(path));
|
|
319
|
+
const applied = [], removedDirectories = [];
|
|
300
320
|
let untracked = false;
|
|
301
321
|
try {
|
|
302
322
|
if (plan.untrack?.paths.length) { applyUntrack(plan.untrack); untracked = true; }
|
|
303
323
|
for (const change of plan.changes) {
|
|
304
324
|
const file = safePath(target, change.path);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
applied.push(change.path);
|
|
325
|
+
const content = change.content;
|
|
326
|
+
const signature = { path: change.path, hash: content === null ? null : hash(content) };
|
|
327
|
+
if (content === null) { rmSync(file, { force: true }); applied.push(signature); }
|
|
328
|
+
else atomicWrite(target, change.path, content, beforeModes.get(change.path), mode => {
|
|
329
|
+
signature.mode = mode; applied.push(signature);
|
|
330
|
+
});
|
|
313
331
|
}
|
|
314
|
-
pruneRemovedDirectories(plan);
|
|
332
|
+
pruneRemovedDirectories(plan, directory => removedDirectories.push(directory));
|
|
315
333
|
const changesFallback = plan.changes.some(change => change.path === '.gitignore');
|
|
316
334
|
// Metadata is committed last; its writer rolls back partial writes itself.
|
|
317
335
|
const ignored = applyLocalIgnore(changesFallback ? planLocalIgnore(target, { outputs: plan.ownedOutputs }) : plan.localIgnore);
|
|
318
336
|
return { changed: Boolean(plan.changes.length || ignored.changed || untracked), backup: [...before.values()].some(Boolean) ? backup : null };
|
|
319
337
|
} catch (error) {
|
|
320
|
-
const rollbackErrors = [];
|
|
321
|
-
for (const
|
|
338
|
+
const rollbackErrors = [], preserved = [];
|
|
339
|
+
for (const removed of [...removedDirectories].sort((a, b) => a.path.length - b.path.length)) {
|
|
340
|
+
let directory, current;
|
|
341
|
+
try { directory = safePath(target, removed.path); current = stat(directory); }
|
|
342
|
+
catch { preserved.push(removed.path); continue; }
|
|
343
|
+
if (current) continue;
|
|
344
|
+
try { mkdirSync(directory, { mode: removed.mode }); chmodSync(directory, removed.mode); }
|
|
345
|
+
catch (failure) { rollbackErrors.push(failure); }
|
|
346
|
+
}
|
|
347
|
+
for (const appliedFile of applied.reverse()) {
|
|
348
|
+
const { path } = appliedFile;
|
|
349
|
+
let unchanged = false;
|
|
350
|
+
try {
|
|
351
|
+
const file = safePath(target, path), info = stat(file);
|
|
352
|
+
unchanged = appliedFile.hash === null ? !info : Boolean(info?.isFile()
|
|
353
|
+
&& (info.mode & 0o777) === appliedFile.mode && hash(readFileSync(file)) === appliedFile.hash);
|
|
354
|
+
} catch { /* Não restaurar quando não é possível comprovar a própria gravação. */ }
|
|
355
|
+
if (!unchanged) { preserved.push(path); continue; }
|
|
322
356
|
const content = before.get(path);
|
|
323
|
-
try { if (content)
|
|
357
|
+
try { if (content) atomicWrite(target, path, content, beforeModes.get(path)); else rmSync(safePath(target, path), { force: true }); }
|
|
324
358
|
catch (failure) { rollbackErrors.push(failure); }
|
|
325
359
|
}
|
|
360
|
+
if (preserved.length) {
|
|
361
|
+
try { error.rollbackPreservedPaths = preserved; } catch { /* Preserve a exceção original. */ }
|
|
362
|
+
}
|
|
326
363
|
if (untracked) try { restoreUntrack(plan.untrack); } catch (failure) { rollbackErrors.push(failure); }
|
|
327
364
|
if (rollbackErrors.length) throw Object.assign(new AggregateError([error, ...rollbackErrors], `${error.message}; rollback incompleto: ${rollbackErrors.map(failure => failure.message).join('; ')}`, { cause: error }), { code: error.code || 'E_OPERATION' });
|
|
328
365
|
throw error;
|
|
329
366
|
}
|
|
330
367
|
}
|
|
331
368
|
|
|
332
|
-
function pruneRemovedDirectories(plan) {
|
|
369
|
+
function pruneRemovedDirectories(plan, onRemoved) {
|
|
333
370
|
const { target } = plan;
|
|
334
371
|
const legacy = '.pbai4devs/adapters/README.md';
|
|
335
372
|
const removed = plan.changes.filter(change => change.action === 'remove' && (allowedManaged(change.path) || change.path === legacy));
|
|
@@ -342,8 +379,9 @@ function pruneRemovedDirectories(plan) {
|
|
|
342
379
|
const boundary = parent.startsWith('.pbai4devs/') ? '.pbai4devs' : adapterRoots.find(root => parent.startsWith(`${root}/`));
|
|
343
380
|
while (boundary && parent.startsWith(`${boundary}/`)) {
|
|
344
381
|
const directory = safePath(target, parent);
|
|
345
|
-
|
|
346
|
-
|
|
382
|
+
const info = stat(directory);
|
|
383
|
+
if (!info?.isDirectory() || readdirSync(directory).length) break;
|
|
384
|
+
try { rmdirSync(directory); onRemoved?.({ path: parent, mode: info.mode & 0o777 }); }
|
|
347
385
|
catch (error) { if (['ENOENT', 'ENOTEMPTY', 'EEXIST'].includes(error.code)) break; throw error; }
|
|
348
386
|
parent = portable(dirname(parent));
|
|
349
387
|
}
|
|
@@ -351,8 +389,9 @@ function pruneRemovedDirectories(plan) {
|
|
|
351
389
|
for (const root of plan.retiredRoots || []) {
|
|
352
390
|
if (!adapterRoots.includes(root)) continue;
|
|
353
391
|
const directory = safePath(target, root);
|
|
354
|
-
|
|
355
|
-
|
|
392
|
+
const info = stat(directory);
|
|
393
|
+
if (!info?.isDirectory()) continue;
|
|
394
|
+
try { if (readdirSync(directory).length === 0) { rmdirSync(directory); onRemoved?.({ path: root, mode: info.mode & 0o777 }); } }
|
|
356
395
|
catch (error) { if (!['ENOENT', 'ENOTEMPTY', 'EEXIST'].includes(error.code)) throw error; }
|
|
357
396
|
}
|
|
358
397
|
}
|