@attentionform/astra 0.4.0 → 0.4.2
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/README.md +1 -1
- package/dist/cli.js +190 -57
- package/dist/packages/cli/src/cli/history-support.d.ts +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Renders one saved-file page to PNG/JPEG via headless LibreOffice (same renderer
|
|
|
110
110
|
Lists recorded sidecar commits newest first; read-only; `--init` (alias `--track`) records the current file as the baseline when no sidecar exists yet.
|
|
111
111
|
|
|
112
112
|
### 13. Revert to Recorded State (`astra revert`)
|
|
113
|
-
Restores the
|
|
113
|
+
Restores the package state `--steps <n>` commits back (every package part, comment metadata and document properties included) and records the restore as a new commit (append-only). Commits recorded before 0.4.2 hold fewer parts, so restoring from one prints a warning; `preview-revert` previews the same change read-only.
|
|
114
114
|
|
|
115
115
|
### 14. Redo Reverted State (`astra redo`)
|
|
116
116
|
Re-applies the state replaced by the most recent revert (one redo per revert head).
|
package/dist/cli.js
CHANGED
|
@@ -6175,6 +6175,9 @@ var RSIDS_EMPTY_RE = /<w:rsids>\s*<\/w:rsids>/g;
|
|
|
6175
6175
|
var RSIDS_SELF_CLOSING_RE = /<w:rsids\s*\/>/g;
|
|
6176
6176
|
var AUTHOR_ATTR_RE = /\s+(w|w15):author="[^"]*"/g;
|
|
6177
6177
|
var INITIALS_ATTR_RE = /\s+w:initials="[^"]*"/g;
|
|
6178
|
+
var PRESENCE_INFO_TAG_RE = /<w15:presenceInfo\b(?:[^>"']|"[^"]*"|'[^']*')*>/g;
|
|
6179
|
+
var PRESENCE_PROVIDER_NONE_RE = /\sw15:providerId=(["'])None\1/;
|
|
6180
|
+
var PRESENCE_USERID_RE = /(\sw15:userId=)(?:"[^"]*"|'[^']*')/;
|
|
6178
6181
|
var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
6179
6182
|
var PHONE_RE = /(\+\d[\d\s\-().]{6,}\d|\(\d{3}\)\s*\d{3}[\s\-.]\d{4}|\b\d{3}[\s\-.]\d{3}[\s\-.]\d{4}\b)/g;
|
|
6180
6183
|
var CLAUSE_ACRONYM_RE = /\b(dfars|far|cfr|nist)\b/gi;
|
|
@@ -6946,6 +6949,15 @@ function sanitizeDocumentXml(xml, opts, partName = "word XML part", hooks) {
|
|
|
6946
6949
|
o.replacementAuthor.split(/[\s._-]+/).filter(Boolean).map((s) => s[0]).join("").slice(0, 4).toUpperCase() || "A"
|
|
6947
6950
|
);
|
|
6948
6951
|
out = out.replace(INITIALS_ATTR_RE, ` w:initials="${initials}"`);
|
|
6952
|
+
if (partName === "word/people.xml") {
|
|
6953
|
+
out = out.replace(PRESENCE_INFO_TAG_RE, (tag) => {
|
|
6954
|
+
if (!PRESENCE_PROVIDER_NONE_RE.test(tag)) return tag;
|
|
6955
|
+
return tag.replace(PRESENCE_USERID_RE, (_m, open) => {
|
|
6956
|
+
purgedAuthors += 1;
|
|
6957
|
+
return `${open}"${replacement === "" ? "Anonymous" : replacement}"`;
|
|
6958
|
+
});
|
|
6959
|
+
});
|
|
6960
|
+
}
|
|
6949
6961
|
}
|
|
6950
6962
|
let acceptedRevisions = 0;
|
|
6951
6963
|
let unresolvedRevisions = [];
|
|
@@ -10579,9 +10591,10 @@ var DocxParser = class _DocxParser {
|
|
|
10579
10591
|
/**
|
|
10580
10592
|
* Restores the package to a previously captured snapshot: every entry is
|
|
10581
10593
|
* restored (entries added after the snapshot are removed), then documentDom
|
|
10582
|
-
* and stylesDom are reloaded and stable IDs re-assigned.
|
|
10594
|
+
* and stylesDom are reloaded and stable IDs re-assigned. The restored package is exactly the snapshot;
|
|
10595
|
+
* only a restore that mixes a pre-0.4.2 commit with live parts passes `registerCoreProperties`.
|
|
10583
10596
|
*/
|
|
10584
|
-
restorePackageSnapshot(snapshot3) {
|
|
10597
|
+
restorePackageSnapshot(snapshot3, options) {
|
|
10585
10598
|
for (const entry of [...this.zip.getEntries()]) {
|
|
10586
10599
|
if (!snapshot3.has(entry.entryName)) {
|
|
10587
10600
|
this.zip.deleteFile(entry);
|
|
@@ -10600,8 +10613,15 @@ var DocxParser = class _DocxParser {
|
|
|
10600
10613
|
continue;
|
|
10601
10614
|
}
|
|
10602
10615
|
const buffer = Buffer.from(data);
|
|
10603
|
-
|
|
10604
|
-
|
|
10616
|
+
const liveEntry = this.zip.getEntry(entryName);
|
|
10617
|
+
if (liveEntry) {
|
|
10618
|
+
let unchanged = false;
|
|
10619
|
+
try {
|
|
10620
|
+
unchanged = !liveEntry.isDirectory && liveEntry.header.size === buffer.length && readPartData(liveEntry, entryName).equals(buffer);
|
|
10621
|
+
} catch {
|
|
10622
|
+
unchanged = false;
|
|
10623
|
+
}
|
|
10624
|
+
if (!unchanged) this.zip.updateFile(entryName, buffer);
|
|
10605
10625
|
} else {
|
|
10606
10626
|
this.zip.addFile(entryName, buffer);
|
|
10607
10627
|
}
|
|
@@ -10626,7 +10646,7 @@ var DocxParser = class _DocxParser {
|
|
|
10626
10646
|
} else {
|
|
10627
10647
|
this.stylesDom = null;
|
|
10628
10648
|
}
|
|
10629
|
-
registerExistingCorePropertiesPart(this.getZipAdapter());
|
|
10649
|
+
if (options?.registerCoreProperties) registerExistingCorePropertiesPart(this.getZipAdapter());
|
|
10630
10650
|
this.invalidateMediaCache();
|
|
10631
10651
|
this.assignStableIds();
|
|
10632
10652
|
this._bumpNodePagesRevision();
|
|
@@ -13886,7 +13906,55 @@ function getRepoDir(docPath) {
|
|
|
13886
13906
|
if (root !== null) return import_path3.default.join(root, historyKey(import_path3.default.dirname(docPath)), import_path3.default.basename(docPath));
|
|
13887
13907
|
return import_path3.default.join(import_path3.default.dirname(docPath), ".astra", import_path3.default.basename(docPath));
|
|
13888
13908
|
}
|
|
13909
|
+
var SNAPSHOT_FORMAT_MARKER = ".astra-snapshot-format";
|
|
13910
|
+
var SNAPSHOT_FORMAT_VERSION = "2";
|
|
13911
|
+
function isCompleteSnapshotMarker(content) {
|
|
13912
|
+
if (content === void 0) return false;
|
|
13913
|
+
return Buffer.from(content).toString("utf8").trim() === SNAPSHOT_FORMAT_VERSION;
|
|
13914
|
+
}
|
|
13915
|
+
var ESCAPED_DIR = "__astra_escaped__";
|
|
13916
|
+
var ESCAPED_NAME_MAX = 255;
|
|
13917
|
+
function needsHistoryEscape(entryName) {
|
|
13918
|
+
const name2 = entryName.replace(/\\/g, "/");
|
|
13919
|
+
if (name2 === SNAPSHOT_FORMAT_MARKER || name2 === "document.xml") return true;
|
|
13920
|
+
if (name2 === ESCAPED_DIR || name2.startsWith(`${ESCAPED_DIR}/`)) return true;
|
|
13921
|
+
return name2.split("/").some((segment) => segment.toLowerCase().startsWith(".git"));
|
|
13922
|
+
}
|
|
13923
|
+
function isUnrecordableName(entryName) {
|
|
13924
|
+
const name2 = entryName.replace(/\\/g, "/");
|
|
13925
|
+
if (name2 === "" || name2.endsWith("/") || name2.startsWith("/") || /^[a-zA-Z]:/.test(name2)) return true;
|
|
13926
|
+
return name2.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
|
|
13927
|
+
}
|
|
13889
13928
|
function isTrackedPackageFile(entryName) {
|
|
13929
|
+
const name2 = entryName.replace(/\\/g, "/");
|
|
13930
|
+
if (name2 === "word/document.xml") return false;
|
|
13931
|
+
return !isUnrecordableName(name2);
|
|
13932
|
+
}
|
|
13933
|
+
function historyPathFor(entryName) {
|
|
13934
|
+
const name2 = entryName.replace(/\\/g, "/");
|
|
13935
|
+
if (!needsHistoryEscape(name2)) return name2;
|
|
13936
|
+
const encoded = Buffer.from(name2, "utf8").toString("base64url");
|
|
13937
|
+
if (encoded.length > ESCAPED_NAME_MAX) {
|
|
13938
|
+
throw Object.assign(
|
|
13939
|
+
new Error(`E_VALIDATION: the package entry name is too long to record in history (${name2.length} characters).`),
|
|
13940
|
+
{
|
|
13941
|
+
code: "E_VALIDATION",
|
|
13942
|
+
remedy: "Rename or remove that package part (for example with sanitize_document), then save again."
|
|
13943
|
+
}
|
|
13944
|
+
);
|
|
13945
|
+
}
|
|
13946
|
+
return `${ESCAPED_DIR}/${encoded}`;
|
|
13947
|
+
}
|
|
13948
|
+
function entryNameFor(repoPath) {
|
|
13949
|
+
const rel = repoPath.replace(/\\/g, "/");
|
|
13950
|
+
if (!rel.startsWith(`${ESCAPED_DIR}/`)) return rel;
|
|
13951
|
+
return Buffer.from(rel.slice(ESCAPED_DIR.length + 1), "base64url").toString("utf8");
|
|
13952
|
+
}
|
|
13953
|
+
function isRepoPartPath(repoPath) {
|
|
13954
|
+
const rel = repoPath.replace(/\\/g, "/");
|
|
13955
|
+
return rel !== "document.xml" && rel !== SNAPSHOT_FORMAT_MARKER && rel !== "" && !rel.startsWith(".git/");
|
|
13956
|
+
}
|
|
13957
|
+
function isLegacyTrackedPackageFile(entryName) {
|
|
13890
13958
|
const name2 = entryName.replace(/\\/g, "/");
|
|
13891
13959
|
if (name2 === "word/document.xml") return false;
|
|
13892
13960
|
if (name2 === "word/styles.xml") return true;
|
|
@@ -13910,7 +13978,7 @@ function toRepoRelative(entryName) {
|
|
|
13910
13978
|
}
|
|
13911
13979
|
async function writeAndAdd(repoDir, relPath, content) {
|
|
13912
13980
|
const safeRel = toRepoRelative(relPath);
|
|
13913
|
-
if (
|
|
13981
|
+
if (isTraversalName(safeRel)) {
|
|
13914
13982
|
throw new Error(`Security Violation: refusing to track package path '${relPath}'`);
|
|
13915
13983
|
}
|
|
13916
13984
|
const fullPath = import_path3.default.join(repoDir, safeRel);
|
|
@@ -13922,6 +13990,59 @@ async function writeAndAdd(repoDir, relPath, content) {
|
|
|
13922
13990
|
await writeFileNoFollow(fullPath, content);
|
|
13923
13991
|
await import_isomorphic_git.default.add({ fs: import_fs4.default, dir: repoDir, filepath: safeRel });
|
|
13924
13992
|
}
|
|
13993
|
+
function isTraversalName(entryName) {
|
|
13994
|
+
const name2 = toRepoRelative(entryName);
|
|
13995
|
+
return name2.split("/").includes("..") || import_path3.default.isAbsolute(name2) || /^[a-zA-Z]:/.test(name2);
|
|
13996
|
+
}
|
|
13997
|
+
async function writeSnapshotParts(repoDir, extraFiles) {
|
|
13998
|
+
const written = /* @__PURE__ */ new Set();
|
|
13999
|
+
if (!extraFiles) return written;
|
|
14000
|
+
for (const [name2, content] of Object.entries(extraFiles)) {
|
|
14001
|
+
if (isTraversalName(name2)) {
|
|
14002
|
+
await writeAndAdd(repoDir, name2, content);
|
|
14003
|
+
continue;
|
|
14004
|
+
}
|
|
14005
|
+
if (!isTrackedPackageFile(name2)) continue;
|
|
14006
|
+
const repoPath = historyPathFor(name2);
|
|
14007
|
+
await writeAndAdd(repoDir, repoPath, content);
|
|
14008
|
+
written.add(repoPath);
|
|
14009
|
+
}
|
|
14010
|
+
return written;
|
|
14011
|
+
}
|
|
14012
|
+
async function indexedPartPaths(repoDir) {
|
|
14013
|
+
const tracked = await import_isomorphic_git.default.listFiles({ fs: import_fs4.default, dir: repoDir });
|
|
14014
|
+
return new Set(tracked.map((t) => toRepoRelative(t)).filter(isRepoPartPath));
|
|
14015
|
+
}
|
|
14016
|
+
async function snapshotIsProvenComplete(repoDir, extraFiles, written) {
|
|
14017
|
+
if (!extraFiles) return false;
|
|
14018
|
+
try {
|
|
14019
|
+
const indexed = await indexedPartPaths(repoDir);
|
|
14020
|
+
if (indexed.size !== written.size) return false;
|
|
14021
|
+
for (const p of written) if (!indexed.has(p)) return false;
|
|
14022
|
+
return true;
|
|
14023
|
+
} catch {
|
|
14024
|
+
return false;
|
|
14025
|
+
}
|
|
14026
|
+
}
|
|
14027
|
+
async function applySnapshotMarker(repoDir, complete) {
|
|
14028
|
+
if (complete) {
|
|
14029
|
+
await writeAndAdd(repoDir, SNAPSHOT_FORMAT_MARKER, `${SNAPSHOT_FORMAT_VERSION}
|
|
14030
|
+
`);
|
|
14031
|
+
return;
|
|
14032
|
+
}
|
|
14033
|
+
try {
|
|
14034
|
+
await import_promises2.default.unlink(import_path3.default.join(repoDir, SNAPSHOT_FORMAT_MARKER));
|
|
14035
|
+
} catch {
|
|
14036
|
+
}
|
|
14037
|
+
try {
|
|
14038
|
+
await import_isomorphic_git.default.remove({ fs: import_fs4.default, dir: repoDir, filepath: SNAPSHOT_FORMAT_MARKER });
|
|
14039
|
+
} catch {
|
|
14040
|
+
}
|
|
14041
|
+
const stillIndexed = (await import_isomorphic_git.default.listFiles({ fs: import_fs4.default, dir: repoDir })).some((t) => toRepoRelative(t) === SNAPSHOT_FORMAT_MARKER);
|
|
14042
|
+
if (stillIndexed) {
|
|
14043
|
+
throw new Error("History commit refused: could not clear the snapshot marker of an earlier commit.");
|
|
14044
|
+
}
|
|
14045
|
+
}
|
|
13925
14046
|
async function chmod0700BestEffort(dir) {
|
|
13926
14047
|
if (process.platform === "win32") return;
|
|
13927
14048
|
try {
|
|
@@ -14297,11 +14418,8 @@ async function initAstraRepo(docPath, initialXml, extraFiles) {
|
|
|
14297
14418
|
const repoDir = await ensureAstraGitRepo(docPath);
|
|
14298
14419
|
await import_isomorphic_git.default.init({ fs: import_fs4.default, dir: repoDir });
|
|
14299
14420
|
await writeAndAdd(repoDir, "document.xml", initialXml);
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
await writeAndAdd(repoDir, name2, content);
|
|
14303
|
-
}
|
|
14304
|
-
}
|
|
14421
|
+
const writtenParts = await writeSnapshotParts(repoDir, extraFiles);
|
|
14422
|
+
await applySnapshotMarker(repoDir, await snapshotIsProvenComplete(repoDir, extraFiles, writtenParts));
|
|
14305
14423
|
await import_isomorphic_git.default.commit({
|
|
14306
14424
|
fs: import_fs4.default,
|
|
14307
14425
|
dir: repoDir,
|
|
@@ -14357,41 +14475,35 @@ async function commitChange(docPath, xmlContent, message, extraFiles, deletedFil
|
|
|
14357
14475
|
} catch (err) {
|
|
14358
14476
|
if (err?.code === HISTORY_QUOTA_CODE) throw err;
|
|
14359
14477
|
}
|
|
14360
|
-
await writeAndAdd(repoDir, "document.xml", xmlContent);
|
|
14361
14478
|
if (extraFiles) {
|
|
14362
|
-
for (const
|
|
14363
|
-
await writeAndAdd(repoDir, name2, content);
|
|
14364
|
-
}
|
|
14479
|
+
for (const name2 of Object.keys(extraFiles)) if (!isTraversalName(name2) && isTrackedPackageFile(name2)) historyPathFor(name2);
|
|
14365
14480
|
}
|
|
14481
|
+
await writeAndAdd(repoDir, "document.xml", xmlContent);
|
|
14482
|
+
const writtenParts = await writeSnapshotParts(repoDir, extraFiles);
|
|
14366
14483
|
try {
|
|
14367
14484
|
const toDelete = /* @__PURE__ */ new Set();
|
|
14368
14485
|
if (deletedFiles) {
|
|
14369
14486
|
for (const name2 of deletedFiles) {
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
toDelete.add(rel);
|
|
14487
|
+
if (!isTrackedPackageFile(name2) || isTraversalName(name2)) continue;
|
|
14488
|
+
toDelete.add(historyPathFor(name2));
|
|
14373
14489
|
}
|
|
14374
14490
|
}
|
|
14375
14491
|
if (extraFiles) {
|
|
14376
|
-
const newTracked =
|
|
14377
|
-
Object.keys(extraFiles).map((k) => toRepoRelative(k))
|
|
14378
|
-
);
|
|
14492
|
+
const newTracked = writtenParts;
|
|
14379
14493
|
try {
|
|
14380
14494
|
const tracked = await import_isomorphic_git.default.listFiles({ fs: import_fs4.default, dir: repoDir });
|
|
14381
14495
|
for (const t of tracked) {
|
|
14382
14496
|
const rel = toRepoRelative(t);
|
|
14383
|
-
if (rel
|
|
14384
|
-
if (isTrackedPackageFile(rel) && !newTracked.has(rel)) {
|
|
14497
|
+
if (isRepoPartPath(rel) && !newTracked.has(rel)) {
|
|
14385
14498
|
toDelete.add(rel);
|
|
14386
14499
|
}
|
|
14387
14500
|
}
|
|
14388
14501
|
} catch {
|
|
14389
14502
|
}
|
|
14390
14503
|
try {
|
|
14391
|
-
const onDisk = await
|
|
14392
|
-
for (const rel of onDisk
|
|
14393
|
-
if (rel
|
|
14394
|
-
if (isTrackedPackageFile(rel) && !newTracked.has(rel)) {
|
|
14504
|
+
const onDisk = await listRepoPaths(repoDir);
|
|
14505
|
+
for (const rel of onDisk) {
|
|
14506
|
+
if (isRepoPartPath(rel) && !newTracked.has(rel)) {
|
|
14395
14507
|
toDelete.add(rel);
|
|
14396
14508
|
}
|
|
14397
14509
|
}
|
|
@@ -14399,7 +14511,7 @@ async function commitChange(docPath, xmlContent, message, extraFiles, deletedFil
|
|
|
14399
14511
|
}
|
|
14400
14512
|
}
|
|
14401
14513
|
for (const rel of toDelete) {
|
|
14402
|
-
if (
|
|
14514
|
+
if (isTraversalName(rel)) continue;
|
|
14403
14515
|
const fullPath = import_path3.default.join(repoDir, rel);
|
|
14404
14516
|
try {
|
|
14405
14517
|
await import_promises2.default.unlink(fullPath);
|
|
@@ -14412,6 +14524,7 @@ async function commitChange(docPath, xmlContent, message, extraFiles, deletedFil
|
|
|
14412
14524
|
}
|
|
14413
14525
|
} catch {
|
|
14414
14526
|
}
|
|
14527
|
+
await applySnapshotMarker(repoDir, await snapshotIsProvenComplete(repoDir, extraFiles, writtenParts));
|
|
14415
14528
|
const sha = await import_isomorphic_git.default.commit({
|
|
14416
14529
|
fs: import_fs4.default,
|
|
14417
14530
|
dir: repoDir,
|
|
@@ -14463,8 +14576,8 @@ async function getHistory(docPath) {
|
|
|
14463
14576
|
});
|
|
14464
14577
|
return entries;
|
|
14465
14578
|
}
|
|
14466
|
-
async function
|
|
14467
|
-
const out =
|
|
14579
|
+
async function listRepoPaths(repoDir) {
|
|
14580
|
+
const out = [];
|
|
14468
14581
|
async function walk(dir, relBase) {
|
|
14469
14582
|
let entries = [];
|
|
14470
14583
|
try {
|
|
@@ -14474,21 +14587,18 @@ async function collectRepoFiles(repoDir) {
|
|
|
14474
14587
|
}
|
|
14475
14588
|
for (const entry of entries) {
|
|
14476
14589
|
if (entry.name === ".git") continue;
|
|
14477
|
-
const full = import_path3.default.join(dir, entry.name);
|
|
14478
14590
|
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
14479
|
-
if (entry.isDirectory())
|
|
14480
|
-
|
|
14481
|
-
} else if (entry.isFile()) {
|
|
14482
|
-
try {
|
|
14483
|
-
out.set(rel, await import_promises2.default.readFile(full));
|
|
14484
|
-
} catch {
|
|
14485
|
-
}
|
|
14486
|
-
}
|
|
14591
|
+
if (entry.isDirectory()) await walk(import_path3.default.join(dir, entry.name), rel);
|
|
14592
|
+
else if (entry.isFile()) out.push(rel);
|
|
14487
14593
|
}
|
|
14488
14594
|
}
|
|
14489
14595
|
await walk(repoDir, "");
|
|
14490
14596
|
return out;
|
|
14491
14597
|
}
|
|
14598
|
+
function trackedPredicateFor(target) {
|
|
14599
|
+
return target.complete ? isTrackedPackageFile : isLegacyTrackedPackageFile;
|
|
14600
|
+
}
|
|
14601
|
+
var INCOMPLETE_SNAPSHOT_WARNING = "This commit was recorded by an older ASTra version and predates complete snapshots: parts it never recorded (for example comment metadata, document properties, custom XML) keep their current content in the file, so the restore is not exact: those parts can be left without the content-type or relationship entries they had before. Restores to versions saved from now on are exact.";
|
|
14492
14602
|
async function checkoutCommit(docPath, commitOid) {
|
|
14493
14603
|
if (typeof commitOid !== "string" || commitOid.length === 0) {
|
|
14494
14604
|
throw new Error(`Cannot checkout: commit ${commitOid} not found in history.`);
|
|
@@ -14552,8 +14662,18 @@ async function checkoutCommit(docPath, commitOid) {
|
|
|
14552
14662
|
);
|
|
14553
14663
|
}
|
|
14554
14664
|
const xml = Buffer.from(docBlob.blob).toString("utf8");
|
|
14665
|
+
let complete = false;
|
|
14666
|
+
const markerOid = blobs.get(SNAPSHOT_FORMAT_MARKER);
|
|
14667
|
+
if (markerOid) {
|
|
14668
|
+
try {
|
|
14669
|
+
const marker = await import_isomorphic_git.default.readBlob({ fs: import_fs4.default, dir: repoDir, oid: markerOid });
|
|
14670
|
+
complete = isCompleteSnapshotMarker(marker.blob);
|
|
14671
|
+
} catch {
|
|
14672
|
+
complete = false;
|
|
14673
|
+
}
|
|
14674
|
+
}
|
|
14555
14675
|
const trackedEntries = [...blobs.entries()].filter(
|
|
14556
|
-
([rel]) => rel
|
|
14676
|
+
([rel]) => isRepoPartPath(rel) && (complete || isLegacyTrackedPackageFile(entryNameFor(rel)))
|
|
14557
14677
|
);
|
|
14558
14678
|
const extraBlobs = await Promise.all(
|
|
14559
14679
|
trackedEntries.map(async ([rel, blobOid]) => {
|
|
@@ -14566,11 +14686,11 @@ async function checkoutCommit(docPath, commitOid) {
|
|
|
14566
14686
|
`Checkout failed: cannot read blob '${rel}' (${blobOid}) at commit ${commitOid}: ${cause}`
|
|
14567
14687
|
);
|
|
14568
14688
|
}
|
|
14569
|
-
return [rel, Buffer.from(blobResult.blob)];
|
|
14689
|
+
return [entryNameFor(rel), Buffer.from(blobResult.blob)];
|
|
14570
14690
|
})
|
|
14571
14691
|
);
|
|
14572
14692
|
const extra = new Map(extraBlobs);
|
|
14573
|
-
return { xml, extra };
|
|
14693
|
+
return { xml, extra, complete };
|
|
14574
14694
|
}
|
|
14575
14695
|
async function commitStateIfDiffersFromHead(docPath, xmlContent, message, extraFiles = {}, author) {
|
|
14576
14696
|
try {
|
|
@@ -40718,7 +40838,7 @@ function summarizeStyleGuide(guide) {
|
|
|
40718
40838
|
// package.json
|
|
40719
40839
|
var package_default = {
|
|
40720
40840
|
name: "@attentionform/astra",
|
|
40721
|
-
version: "0.4.
|
|
40841
|
+
version: "0.4.2",
|
|
40722
40842
|
description: "Command-line tool for OOXML (.docx) document engineering",
|
|
40723
40843
|
main: "dist/cli.js",
|
|
40724
40844
|
types: "dist/packages/cli/src/cli.d.ts",
|
|
@@ -40762,7 +40882,7 @@ var package_default = {
|
|
|
40762
40882
|
},
|
|
40763
40883
|
dependencies: {
|
|
40764
40884
|
"@attentionform/astra-engine": "^0.4.0",
|
|
40765
|
-
"@attentionform/ooxml-validator": "^0.4.
|
|
40885
|
+
"@attentionform/ooxml-validator": "^0.4.2",
|
|
40766
40886
|
"@xmldom/xmldom": "0.8.15",
|
|
40767
40887
|
"adm-zip": "0.6.1",
|
|
40768
40888
|
"isomorphic-git": "^1.25.0",
|
|
@@ -41538,10 +41658,13 @@ function commandHelpText(cmd) {
|
|
|
41538
41658
|
" working file was modified since the last recorded commit (E_CONFLICT: a revert would",
|
|
41539
41659
|
" overwrite changes that were never recorded). The restored package is written only after",
|
|
41540
41660
|
" it passes the OOXML validation gate; a commit failure restores the pre-command bytes.",
|
|
41541
|
-
"
|
|
41542
|
-
" word/{
|
|
41543
|
-
"
|
|
41544
|
-
"
|
|
41661
|
+
"Every package part is recorded and restored (document.xml, [Content_Types].xml, styles, comments",
|
|
41662
|
+
" and their metadata, word/{media,charts,embeddings,theme}/, docProps/, custom XML, ...); a part",
|
|
41663
|
+
" the commit does not have is removed. Commits recorded by ASTra versions before 0.4.2 hold only",
|
|
41664
|
+
" document.xml, [Content_Types].xml, word/{styles,numbering,settings,comments,footnotes,endnotes}.xml,",
|
|
41665
|
+
" header/footer parts and their rels, the document rels, and word/{media,charts,embeddings,theme}/;",
|
|
41666
|
+
" restoring from one prints a warning, and parts outside that set (e.g. docProps/) keep the",
|
|
41667
|
+
" working-file bytes.",
|
|
41545
41668
|
"Exit 0 ok \xB7 1 conflict/validation/IO failure (nothing written) \xB7 2 bad args/absent sidecar."
|
|
41546
41669
|
].join("\n"),
|
|
41547
41670
|
redo: [
|
|
@@ -41601,10 +41724,10 @@ function sidecarGitState(file) {
|
|
|
41601
41724
|
function hasHistorySidecar(file) {
|
|
41602
41725
|
return sidecarGitState(file) === "repo";
|
|
41603
41726
|
}
|
|
41604
|
-
function trackedExtras(snapshot3) {
|
|
41727
|
+
function trackedExtras(snapshot3, isTracked = isTrackedPackageFile) {
|
|
41605
41728
|
const out = /* @__PURE__ */ new Map();
|
|
41606
41729
|
for (const [name2, buf] of snapshot3) {
|
|
41607
|
-
if (name2 !== "word/document.xml" &&
|
|
41730
|
+
if (name2 !== "word/document.xml" && isTracked(name2)) out.set(name2, buf);
|
|
41608
41731
|
}
|
|
41609
41732
|
return out;
|
|
41610
41733
|
}
|
|
@@ -45363,7 +45486,7 @@ async function divergedFiles(file, parser2, commitOid) {
|
|
|
45363
45486
|
const recorded = await checkoutCommit(file, commitOid);
|
|
45364
45487
|
const snapshot3 = parser2.capturePackageSnapshot();
|
|
45365
45488
|
const liveXml = snapshot3.get("word/document.xml")?.toString("utf8") ?? "";
|
|
45366
|
-
return packageDiff(liveXml, trackedExtras(snapshot3), recorded.xml, recorded.extra);
|
|
45489
|
+
return packageDiff(liveXml, trackedExtras(snapshot3, trackedPredicateFor(recorded)), recorded.xml, recorded.extra);
|
|
45367
45490
|
}
|
|
45368
45491
|
function sidecarWarning(commits) {
|
|
45369
45492
|
const carried = commits.warning;
|
|
@@ -45458,12 +45581,13 @@ function parseDocStepsArgs(out, cmd, args) {
|
|
|
45458
45581
|
async function restoreAndCommit(opts) {
|
|
45459
45582
|
const { cmd, file, parser: parser2, prevBytes, target, targetOid, commitMessage } = opts;
|
|
45460
45583
|
const snapshot3 = parser2.capturePackageSnapshot();
|
|
45584
|
+
const isTrackedForTarget = trackedPredicateFor(target);
|
|
45461
45585
|
for (const key of [...snapshot3.keys()]) {
|
|
45462
|
-
if (key !== "word/document.xml" &&
|
|
45586
|
+
if (key !== "word/document.xml" && isTrackedForTarget(key)) snapshot3.delete(key);
|
|
45463
45587
|
}
|
|
45464
45588
|
for (const [name2, data] of target.extra) snapshot3.set(name2, data);
|
|
45465
45589
|
snapshot3.set("word/document.xml", Buffer.from(target.xml, "utf8"));
|
|
45466
|
-
parser2.restorePackageSnapshot(snapshot3);
|
|
45590
|
+
parser2.restorePackageSnapshot(snapshot3, { registerCoreProperties: !target.complete });
|
|
45467
45591
|
const buf = parser2.rezip();
|
|
45468
45592
|
const candidate = await validateDocxBuffer(buf, { maxErrors: BASELINE_MAX_ERRORS });
|
|
45469
45593
|
if (!candidate.ok && judgeAgainstBaseline(candidate, buildBaseline(candidate)).kind !== "tolerated") {
|
|
@@ -45635,7 +45759,7 @@ async function cmdPreviewRevert(out, args) {
|
|
|
45635
45759
|
const target = await checkoutCommit(file, targetOid);
|
|
45636
45760
|
const snapshot3 = parser2.capturePackageSnapshot();
|
|
45637
45761
|
const liveXml = snapshot3.get("word/document.xml")?.toString("utf8") ?? "";
|
|
45638
|
-
const affectedFiles = packageDiff(liveXml, trackedExtras(snapshot3), target.xml, target.extra);
|
|
45762
|
+
const affectedFiles = packageDiff(liveXml, trackedExtras(snapshot3, trackedPredicateFor(target)), target.xml, target.extra);
|
|
45639
45763
|
const changed = await divergedFiles(file, parser2, commits[0].oid);
|
|
45640
45764
|
const warning = sidecarWarning(commits);
|
|
45641
45765
|
const head = commits[0].oid;
|
|
@@ -45653,6 +45777,7 @@ async function cmdPreviewRevert(out, args) {
|
|
|
45653
45777
|
changed.length > 0 ? ` working file: DIVERGED from head ${shortOid(head)} \u2014 a real revert would refuse E_CONFLICT (changed: ${shownList(changed)})` : ` working file: clean at head ${shortOid(head)} \u2014 a real revert would proceed`
|
|
45654
45778
|
);
|
|
45655
45779
|
if (warning) printStderr(`${CLI_NAME}: warning: ${warning}`);
|
|
45780
|
+
if (!target.complete) printStderr(`${CLI_NAME}: warning: ${INCOMPLETE_SNAPSHOT_WARNING}`);
|
|
45656
45781
|
printJson(out, {
|
|
45657
45782
|
ok: true,
|
|
45658
45783
|
command: "preview-revert",
|
|
@@ -45666,7 +45791,9 @@ async function cmdPreviewRevert(out, args) {
|
|
|
45666
45791
|
totalCommits: commits.length,
|
|
45667
45792
|
diverged: changed.length > 0,
|
|
45668
45793
|
divergedFiles: changed,
|
|
45669
|
-
warning
|
|
45794
|
+
warning,
|
|
45795
|
+
snapshotComplete: target.complete,
|
|
45796
|
+
...target.complete ? {} : { restoreWarning: INCOMPLETE_SNAPSHOT_WARNING }
|
|
45670
45797
|
});
|
|
45671
45798
|
return EXIT_OK;
|
|
45672
45799
|
} catch (err) {
|
|
@@ -45699,6 +45826,7 @@ async function cmdRevert(out, args) {
|
|
|
45699
45826
|
out,
|
|
45700
45827
|
`REVERT OK ${file}: restored ${shortOid(targetOid)} "${subject}" (steps=${steps}); new commit ${shortOid(result.commitOid)}`
|
|
45701
45828
|
);
|
|
45829
|
+
if (!target.complete) printStderr(`${CLI_NAME}: warning: ${INCOMPLETE_SNAPSHOT_WARNING}`);
|
|
45702
45830
|
printJson(out, {
|
|
45703
45831
|
ok: true,
|
|
45704
45832
|
command: "revert",
|
|
@@ -45706,7 +45834,9 @@ async function cmdRevert(out, args) {
|
|
|
45706
45834
|
steps,
|
|
45707
45835
|
revertedToCommit: targetOid,
|
|
45708
45836
|
commitOid: result.commitOid,
|
|
45709
|
-
commitMessage: result.commitMessage
|
|
45837
|
+
commitMessage: result.commitMessage,
|
|
45838
|
+
snapshotComplete: target.complete,
|
|
45839
|
+
...target.complete ? {} : { restoreWarning: INCOMPLETE_SNAPSHOT_WARNING }
|
|
45710
45840
|
});
|
|
45711
45841
|
return EXIT_OK;
|
|
45712
45842
|
} catch (err) {
|
|
@@ -45752,13 +45882,16 @@ async function cmdRedo(out, args) {
|
|
|
45752
45882
|
out,
|
|
45753
45883
|
`REDO OK ${file}: re-applied the state replaced by revert ${shortOid(headOid)}; new commit ${shortOid(result.commitOid)}`
|
|
45754
45884
|
);
|
|
45885
|
+
if (!target.complete) printStderr(`${CLI_NAME}: warning: ${INCOMPLETE_SNAPSHOT_WARNING}`);
|
|
45755
45886
|
printJson(out, {
|
|
45756
45887
|
ok: true,
|
|
45757
45888
|
command: "redo",
|
|
45758
45889
|
file,
|
|
45759
45890
|
restoredCommit: targetOid,
|
|
45760
45891
|
commitOid: result.commitOid,
|
|
45761
|
-
commitMessage: result.commitMessage
|
|
45892
|
+
commitMessage: result.commitMessage,
|
|
45893
|
+
snapshotComplete: target.complete,
|
|
45894
|
+
...target.complete ? {} : { restoreWarning: INCOMPLETE_SNAPSHOT_WARNING }
|
|
45762
45895
|
});
|
|
45763
45896
|
return EXIT_OK;
|
|
45764
45897
|
} catch (err) {
|
|
@@ -13,8 +13,12 @@ export type SidecarGitState = 'repo' | 'stray' | 'absent';
|
|
|
13
13
|
export declare function sidecarGitState(file: string): SidecarGitState;
|
|
14
14
|
/** Is a valid sidecar chain present for this document? */
|
|
15
15
|
export declare function hasHistorySidecar(file: string): boolean;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Tracked package files (everything but document.xml) from a live snapshot. `isTracked` selects the
|
|
18
|
+
* rule: the default is the complete-snapshot rule; pass trackedPredicateFor(commit) to compare with a
|
|
19
|
+
* commit recorded by an older ASTra version.
|
|
20
|
+
*/
|
|
21
|
+
export declare function trackedExtras(snapshot: Map<string, Buffer>, isTracked?: (name: string) => boolean): Map<string, Buffer>;
|
|
18
22
|
export declare function extrasRecord(extras: Map<string, Buffer>): Record<string, Buffer>;
|
|
19
23
|
export type BatchRevisionResult = {
|
|
20
24
|
recorded: true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attentionform/astra",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Command-line tool for OOXML (.docx) document engineering",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"types": "dist/packages/cli/src/cli.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@attentionform/astra-engine": "^0.4.0",
|
|
47
|
-
"@attentionform/ooxml-validator": "^0.4.
|
|
47
|
+
"@attentionform/ooxml-validator": "^0.4.2",
|
|
48
48
|
"@xmldom/xmldom": "0.8.15",
|
|
49
49
|
"adm-zip": "0.6.1",
|
|
50
50
|
"isomorphic-git": "^1.25.0",
|