@intentic/fileq 1.317.0 → 1.319.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/NOTICE +208 -0
- package/README.md +39 -12
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +19 -7
- package/dist/app.js.map +1 -1
- package/dist/commands/check.command.d.ts +3 -0
- package/dist/commands/check.command.d.ts.map +1 -0
- package/dist/commands/check.command.js +80 -0
- package/dist/commands/check.command.js.map +1 -0
- package/dist/commands/derive.command.js +1 -1
- package/dist/commands/derive.command.js.map +1 -1
- package/dist/commands/read.command.d.ts.map +1 -1
- package/dist/commands/read.command.js +26 -45
- package/dist/commands/read.command.js.map +1 -1
- package/dist/commands/render.command.d.ts +3 -0
- package/dist/commands/render.command.d.ts.map +1 -0
- package/dist/commands/render.command.js +67 -0
- package/dist/commands/render.command.js.map +1 -0
- package/dist/lib/check/check.d.ts +15 -0
- package/dist/lib/check/check.d.ts.map +1 -0
- package/dist/lib/check/check.js +35 -0
- package/dist/lib/check/check.js.map +1 -0
- package/dist/lib/check/docx.d.ts +3 -0
- package/dist/lib/check/docx.d.ts.map +1 -0
- package/dist/lib/check/docx.js +329 -0
- package/dist/lib/check/docx.js.map +1 -0
- package/dist/lib/check/finding.d.ts +20 -0
- package/dist/lib/check/finding.d.ts.map +1 -0
- package/dist/lib/check/finding.js +27 -0
- package/dist/lib/check/finding.js.map +1 -0
- package/dist/lib/check/package.d.ts +25 -0
- package/dist/lib/check/package.d.ts.map +1 -0
- package/dist/lib/check/package.js +139 -0
- package/dist/lib/check/package.js.map +1 -0
- package/dist/lib/check/pdf.d.ts +3 -0
- package/dist/lib/check/pdf.d.ts.map +1 -0
- package/dist/lib/check/pdf.js +160 -0
- package/dist/lib/check/pdf.js.map +1 -0
- package/dist/lib/check/pptx.d.ts +3 -0
- package/dist/lib/check/pptx.d.ts.map +1 -0
- package/dist/lib/check/pptx.js +497 -0
- package/dist/lib/check/pptx.js.map +1 -0
- package/dist/lib/check/text-fit.d.ts +18 -0
- package/dist/lib/check/text-fit.d.ts.map +1 -0
- package/dist/lib/check/text-fit.js +69 -0
- package/dist/lib/check/text-fit.js.map +1 -0
- package/dist/lib/check/xlsx.d.ts +3 -0
- package/dist/lib/check/xlsx.d.ts.map +1 -0
- package/dist/lib/check/xlsx.js +131 -0
- package/dist/lib/check/xlsx.js.map +1 -0
- package/dist/lib/check/xml-tree.d.ts +15 -0
- package/dist/lib/check/xml-tree.d.ts.map +1 -0
- package/dist/lib/check/xml-tree.js +131 -0
- package/dist/lib/check/xml-tree.js.map +1 -0
- package/dist/lib/content-cache.d.ts +12 -0
- package/dist/lib/content-cache.d.ts.map +1 -0
- package/dist/lib/content-cache.js +33 -0
- package/dist/lib/content-cache.js.map +1 -0
- package/dist/lib/derive.d.ts +10 -0
- package/dist/lib/derive.d.ts.map +1 -1
- package/dist/lib/derive.js +37 -4
- package/dist/lib/derive.js.map +1 -1
- package/dist/lib/derivers/image.d.ts.map +1 -1
- package/dist/lib/derivers/image.js.map +1 -1
- package/dist/lib/formats.d.ts +1 -0
- package/dist/lib/formats.d.ts.map +1 -1
- package/dist/lib/formats.js +1 -0
- package/dist/lib/formats.js.map +1 -1
- package/dist/lib/render/convert.d.ts +11 -0
- package/dist/lib/render/convert.d.ts.map +1 -0
- package/dist/lib/render/convert.js +107 -0
- package/dist/lib/render/convert.js.map +1 -0
- package/dist/lib/render/pages.d.ts +11 -0
- package/dist/lib/render/pages.d.ts.map +1 -0
- package/dist/lib/render/pages.js +43 -0
- package/dist/lib/render/pages.js.map +1 -0
- package/dist/lib/render/render.d.ts +29 -0
- package/dist/lib/render/render.d.ts.map +1 -0
- package/dist/lib/render/render.js +142 -0
- package/dist/lib/render/render.js.map +1 -0
- package/dist/lib/sidecar.d.ts +11 -2
- package/dist/lib/sidecar.d.ts.map +1 -1
- package/dist/lib/sidecar.js +28 -16
- package/dist/lib/sidecar.js.map +1 -1
- package/dist/skill-authoring.d.ts +2 -0
- package/dist/skill-authoring.d.ts.map +1 -0
- package/dist/skill-authoring.js +20 -0
- package/dist/skill-authoring.js.map +1 -0
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +32 -8
- package/dist/skill.js.map +1 -1
- package/dist/testing.d.ts +67 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +151 -0
- package/dist/testing.js.map +1 -1
- package/package.json +21 -8
- package/src/app.ts +19 -7
- package/src/cli.integration.test.ts +115 -30
- package/src/commands/check.command.ts +97 -0
- package/src/commands/derive.command.ts +1 -1
- package/src/commands/read.command.ts +30 -52
- package/src/commands/render.command.ts +80 -0
- package/src/lib/archives.ts +1 -1
- package/src/lib/check/check.test.ts +446 -0
- package/src/lib/check/check.ts +49 -0
- package/src/lib/check/docx.ts +434 -0
- package/src/lib/check/finding.ts +63 -0
- package/src/lib/check/package.ts +199 -0
- package/src/lib/check/pdf.ts +228 -0
- package/src/lib/check/pptx.ts +641 -0
- package/src/lib/check/text-fit.ts +105 -0
- package/src/lib/check/xlsx.ts +163 -0
- package/src/lib/check/xml-tree.ts +186 -0
- package/src/lib/content-cache.ts +58 -0
- package/src/lib/derive.integration.test.ts +198 -0
- package/src/lib/derive.ts +66 -7
- package/src/lib/derivers/archive.ts +1 -1
- package/src/lib/derivers/image.ts +2 -1
- package/src/lib/derivers/load-cost.test.ts +1 -1
- package/src/lib/derivers/pdf.ts +1 -1
- package/src/lib/derivers/xlsx.ts +1 -1
- package/src/lib/formats.ts +6 -0
- package/src/lib/render/convert.ts +133 -0
- package/src/lib/render/pages.test.ts +38 -0
- package/src/lib/render/pages.ts +58 -0
- package/src/lib/render/render.ts +198 -0
- package/src/lib/sidecar.ts +42 -22
- package/src/render.integration.test.ts +217 -0
- package/src/skill-authoring.ts +27 -0
- package/src/skill.ts +36 -8
- package/src/testing.ts +287 -0
- package/dist/commands/sweep.command.d.ts +0 -3
- package/dist/commands/sweep.command.d.ts.map +0 -1
- package/dist/commands/sweep.command.js +0 -41
- package/dist/commands/sweep.command.js.map +0 -1
- package/dist/lib/flags.d.ts +0 -2
- package/dist/lib/flags.d.ts.map +0 -1
- package/dist/lib/flags.js +0 -8
- package/dist/lib/flags.js.map +0 -1
- package/dist/lib/sweep.d.ts +0 -7
- package/dist/lib/sweep.d.ts.map +0 -1
- package/dist/lib/sweep.js +0 -56
- package/dist/lib/sweep.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -3
- package/dist/version.js.map +0 -1
- package/src/commands/sweep.command.ts +0 -48
- package/src/lib/sweep.ts +0 -72
package/src/testing.ts
CHANGED
|
@@ -331,3 +331,290 @@ export const gzipBytes = (name: string, content: Uint8Array | string): Uint8Arra
|
|
|
331
331
|
/** A 1×1 PNG (no EXIF — dimensions are what image derivation reads off it). */
|
|
332
332
|
export const pngBytes = (): Uint8Array =>
|
|
333
333
|
Uint8Array.from(Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "base64"));
|
|
334
|
+
|
|
335
|
+
/* ---- documents to check: small OOXML and PDF files with one known defect each, built from their parts ---------- */
|
|
336
|
+
|
|
337
|
+
const EMU = 914_400;
|
|
338
|
+
const NS_P = 'xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"';
|
|
339
|
+
const REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
340
|
+
|
|
341
|
+
export interface FixtureRel {
|
|
342
|
+
readonly type: string;
|
|
343
|
+
readonly target: string;
|
|
344
|
+
readonly external?: boolean;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const relsXml = (rels: Readonly<Record<string, FixtureRel>>): string =>
|
|
348
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
349
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${Object.entries(rels)
|
|
350
|
+
.map(([id, rel]) => `<Relationship Id="${id}" Type="${REL_TYPE}/${rel.type}" Target="${xmlEscape(rel.target)}"${rel.external === true ? ' TargetMode="External"' : ""}/>`)
|
|
351
|
+
.join("")}</Relationships>`;
|
|
352
|
+
|
|
353
|
+
/** Where a shape sits, in inches from the slide's top left. */
|
|
354
|
+
export interface Frame {
|
|
355
|
+
readonly x: number;
|
|
356
|
+
readonly y: number;
|
|
357
|
+
readonly w: number;
|
|
358
|
+
readonly h: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const xfrm = (frame: Frame, tag = "a:xfrm"): string =>
|
|
362
|
+
`<${tag}><a:off x="${Math.round(frame.x * EMU)}" y="${Math.round(frame.y * EMU)}"/><a:ext cx="${Math.round(frame.w * EMU)}" cy="${Math.round(frame.h * EMU)}"/></${tag}>`;
|
|
363
|
+
|
|
364
|
+
export interface TextBoxOptions {
|
|
365
|
+
/** Point size of every run; unset inherits (18 pt for a plain text box). */
|
|
366
|
+
readonly size?: number;
|
|
367
|
+
/** A placeholder of this type (and idx); a placeholder with no frame inherits its layout's. */
|
|
368
|
+
readonly ph?: { readonly type?: string; readonly idx?: number };
|
|
369
|
+
readonly autofit?: "shrink" | "grow";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let drawingId = 10;
|
|
373
|
+
|
|
374
|
+
/** A text shape: one paragraph per line; `frame` undefined leaves the geometry to the layout (placeholders only). */
|
|
375
|
+
export const textBox = (name: string, frame: Frame | undefined, lines: readonly string[], options: TextBoxOptions = {}): string => {
|
|
376
|
+
drawingId += 1;
|
|
377
|
+
const ph = options.ph === undefined ? "" : `<p:ph${options.ph.type === undefined ? "" : ` type="${options.ph.type}"`}${options.ph.idx === undefined ? "" : ` idx="${options.ph.idx}"`}/>`;
|
|
378
|
+
const fit = options.autofit === "shrink" ? "<a:normAutofit/>" : options.autofit === "grow" ? "<a:spAutoFit/>" : "";
|
|
379
|
+
const size = options.size === undefined ? "" : ` sz="${options.size * 100}"`;
|
|
380
|
+
const paragraphs = lines.length === 0 ? "<a:p/>" : lines.map((line) => `<a:p><a:r><a:rPr lang="en-US"${size}/><a:t>${xmlEscape(line)}</a:t></a:r></a:p>`).join("");
|
|
381
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${drawingId}" name="${xmlEscape(name)}"/><p:cNvSpPr/><p:nvPr>${ph}</p:nvPr></p:nvSpPr><p:spPr>${frame === undefined ? "" : xfrm(frame)}</p:spPr><p:txBody><a:bodyPr wrap="square">${fit}</a:bodyPr><a:lstStyle/>${paragraphs}</p:txBody></p:sp>`;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/** A picture drawing its image from relationship `rId`, optionally cropped (fractions of 100000 per side). */
|
|
385
|
+
export const pictureFrame = (name: string, frame: Frame, rId: string, crop?: { readonly l?: number; readonly r?: number; readonly t?: number; readonly b?: number }): string => {
|
|
386
|
+
drawingId += 1;
|
|
387
|
+
const src = crop === undefined ? "" : `<a:srcRect l="${crop.l ?? 0}" r="${crop.r ?? 0}" t="${crop.t ?? 0}" b="${crop.b ?? 0}"/>`;
|
|
388
|
+
return `<p:pic><p:nvPicPr><p:cNvPr id="${drawingId}" name="${xmlEscape(name)}"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="${rId}"/>${src}<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr>${xfrm(frame)}<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** A table frame with one row per entry, each row `height` inches tall and its cells in `size`-point text. */
|
|
392
|
+
export const tableFrame = (name: string, frame: Frame, rows: readonly (readonly string[])[], height: number, size = 18): string => {
|
|
393
|
+
drawingId += 1;
|
|
394
|
+
const columns = Math.max(1, ...rows.map((row) => row.length));
|
|
395
|
+
const columnWidth = Math.round((frame.w * EMU) / columns);
|
|
396
|
+
const cells = (row: readonly string[]): string =>
|
|
397
|
+
row.map((cell) => `<a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" sz="${size * 100}"/><a:t>${xmlEscape(cell)}</a:t></a:r></a:p></a:txBody><a:tcPr/></a:tc>`).join("");
|
|
398
|
+
return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${drawingId}" name="${xmlEscape(name)}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>${xfrm(frame, "p:xfrm")}<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl><a:tblGrid>${Array.from({ length: columns }, () => `<a:gridCol w="${columnWidth}"/>`).join("")}</a:tblGrid>${rows.map((row) => `<a:tr h="${Math.round(height * EMU)}">${cells(row)}</a:tr>`).join("")}</a:tbl></a:graphicData></a:graphic></p:graphicFrame>`;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export interface DeckSlide {
|
|
402
|
+
/** The spTree's drawings, as XML (textBox, pictureFrame, tableFrame, or by hand). */
|
|
403
|
+
readonly drawings: readonly string[];
|
|
404
|
+
/** Relationships beyond the layout, which every slide gets as rId1. */
|
|
405
|
+
readonly rels?: Readonly<Record<string, FixtureRel>>;
|
|
406
|
+
readonly hidden?: boolean;
|
|
407
|
+
/** The part's file number, when a test needs parts numbered out of deck order. */
|
|
408
|
+
readonly partNumber?: number;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export interface DeckOptions {
|
|
412
|
+
readonly slides: readonly DeckSlide[];
|
|
413
|
+
/** Media parts by name under ppt/media/. */
|
|
414
|
+
readonly media?: Readonly<Record<string, Uint8Array>>;
|
|
415
|
+
/** Slide size in inches; 13.333 × 7.5 (16:9) by default. */
|
|
416
|
+
readonly width?: number;
|
|
417
|
+
readonly height?: number;
|
|
418
|
+
/** Leave png out of [Content_Types].xml, the damage a hand-zipped deck carries. */
|
|
419
|
+
readonly untypedMedia?: boolean;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// A master with a title and a body placeholder and the text styles they inherit (44 pt titles, 28 pt body), and one
|
|
423
|
+
// "Title and Content" layout whose placeholders carry no frame of their own, so a slide's geometry comes from the master.
|
|
424
|
+
const MASTER = `<?xml version="1.0" encoding="UTF-8"?>
|
|
425
|
+
<p:sldMaster ${NS_P}><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>
|
|
426
|
+
<p:sp><p:nvSpPr><p:cNvPr id="2" name="Title Placeholder 1"/><p:cNvSpPr/><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:spPr>${xfrm({ x: 0.92, y: 0.4, w: 11.5, h: 1.45 })}</p:spPr><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>Click to edit Master title style</a:t></a:r></a:p></p:txBody></p:sp>
|
|
427
|
+
<p:sp><p:nvSpPr><p:cNvPr id="3" name="Text Placeholder 2"/><p:cNvSpPr/><p:nvPr><p:ph type="body" idx="1"/></p:nvPr></p:nvSpPr><p:spPr>${xfrm({ x: 0.92, y: 2, w: 11.5, h: 4.75 })}</p:spPr><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>Click to edit Master text styles</a:t></a:r></a:p></p:txBody></p:sp>
|
|
428
|
+
</p:spTree></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/><p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
|
|
429
|
+
<p:txStyles><p:titleStyle><a:lvl1pPr><a:defRPr sz="4400"/></a:lvl1pPr></p:titleStyle><p:bodyStyle><a:lvl1pPr><a:defRPr sz="2800"/></a:lvl1pPr><a:lvl2pPr><a:defRPr sz="2400"/></a:lvl2pPr></p:bodyStyle><p:otherStyle/></p:txStyles></p:sldMaster>`;
|
|
430
|
+
|
|
431
|
+
const LAYOUT = `<?xml version="1.0" encoding="UTF-8"?>
|
|
432
|
+
<p:sldLayout ${NS_P} type="obj"><p:cSld name="Title and Content"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>
|
|
433
|
+
<p:sp><p:nvSpPr><p:cNvPr id="2" name="Title 1"/><p:cNvSpPr/><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>Click to edit Master title style</a:t></a:r></a:p></p:txBody></p:sp>
|
|
434
|
+
<p:sp><p:nvSpPr><p:cNvPr id="3" name="Content Placeholder 2"/><p:cNvSpPr/><p:nvPr><p:ph idx="1"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>Click to edit Master text styles</a:t></a:r></a:p></p:txBody></p:sp>
|
|
435
|
+
</p:spTree></p:cSld></p:sldLayout>`;
|
|
436
|
+
|
|
437
|
+
/** A PowerPoint deck with a real slide list, master and layout, one slide part per entry of `slides`. */
|
|
438
|
+
export const deckBytes = (options: DeckOptions): Uint8Array => {
|
|
439
|
+
const width = Math.round((options.width ?? 13.333) * EMU);
|
|
440
|
+
const height = Math.round((options.height ?? 7.5) * EMU);
|
|
441
|
+
const numbers = options.slides.map((slide, index) => slide.partNumber ?? index + 1);
|
|
442
|
+
const types = [
|
|
443
|
+
'<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>',
|
|
444
|
+
'<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>',
|
|
445
|
+
'<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>',
|
|
446
|
+
...numbers.map((number) => `<Override PartName="/ppt/slides/slide${number}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`),
|
|
447
|
+
options.untypedMedia === true ? "" : '<Default Extension="png" ContentType="image/png"/>',
|
|
448
|
+
].join("");
|
|
449
|
+
const files = new Map<string, Uint8Array>([
|
|
450
|
+
["[Content_Types].xml", strToU8(CONTENT_TYPES(types))],
|
|
451
|
+
["_rels/.rels", strToU8(RELS("ppt/presentation.xml"))],
|
|
452
|
+
["ppt/presentation.xml", strToU8(
|
|
453
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
454
|
+
<p:presentation ${NS_P}><p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst><p:sldIdLst>${numbers.map((number, index) => `<p:sldId id="${256 + index}" r:id="rId${number + 1}"/>`).join("")}</p:sldIdLst><p:sldSz cx="${width}" cy="${height}"/><p:notesSz cx="6858000" cy="9144000"/></p:presentation>`,
|
|
455
|
+
)],
|
|
456
|
+
["ppt/_rels/presentation.xml.rels", strToU8(
|
|
457
|
+
relsXml({
|
|
458
|
+
rId1: { type: "slideMaster", target: "slideMasters/slideMaster1.xml" },
|
|
459
|
+
...Object.fromEntries(numbers.map((number) => [`rId${number + 1}`, { type: "slide", target: `slides/slide${number}.xml` }])),
|
|
460
|
+
}),
|
|
461
|
+
)],
|
|
462
|
+
["ppt/slideMasters/slideMaster1.xml", strToU8(MASTER)],
|
|
463
|
+
["ppt/slideMasters/_rels/slideMaster1.xml.rels", strToU8(relsXml({ rId1: { type: "slideLayout", target: "../slideLayouts/slideLayout1.xml" } }))],
|
|
464
|
+
["ppt/slideLayouts/slideLayout1.xml", strToU8(LAYOUT)],
|
|
465
|
+
["ppt/slideLayouts/_rels/slideLayout1.xml.rels", strToU8(relsXml({ rId1: { type: "slideMaster", target: "../slideMasters/slideMaster1.xml" } }))],
|
|
466
|
+
]);
|
|
467
|
+
options.slides.forEach((slide, index) => {
|
|
468
|
+
const number = numbers[index] ?? index + 1;
|
|
469
|
+
files.set(`ppt/slides/slide${number}.xml`, strToU8(
|
|
470
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
471
|
+
<p:sld ${NS_P}${slide.hidden === true ? ' show="0"' : ""}><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>${slide.drawings.join("")}</p:spTree></p:cSld></p:sld>`,
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
files.set(`ppt/slides/_rels/slide${number}.xml.rels`, strToU8(relsXml({ rId1: { type: "slideLayout", target: "../slideLayouts/slideLayout1.xml" }, ...slide.rels })));
|
|
475
|
+
});
|
|
476
|
+
for (const [name, bytes] of Object.entries(options.media ?? {})) {
|
|
477
|
+
files.set(`ppt/media/${name}`, bytes);
|
|
478
|
+
}
|
|
479
|
+
return zipSync(Object.fromEntries(files));
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/** A PNG header claiming `width` × `height` pixels: all an aspect-ratio check reads of it. */
|
|
483
|
+
export const pngOfSize = (width: number, height: number): Uint8Array => {
|
|
484
|
+
const png = pngBytes().slice();
|
|
485
|
+
const view = new DataView(png.buffer, png.byteOffset, png.byteLength);
|
|
486
|
+
view.setUint32(16, width);
|
|
487
|
+
view.setUint32(20, height);
|
|
488
|
+
return png;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const NS_W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"';
|
|
492
|
+
|
|
493
|
+
/** A paragraph: plain, or in a style by id. */
|
|
494
|
+
export const para = (text: string, style?: string): string =>
|
|
495
|
+
`<w:p>${style === undefined ? "" : `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>`}${text === "" ? "" : `<w:r><w:t xml:space="preserve">${xmlEscape(text)}</w:t></w:r>`}</w:p>`;
|
|
496
|
+
|
|
497
|
+
/** An inline picture `width` inches wide drawing relationship `rId`, in a paragraph of its own. */
|
|
498
|
+
export const inlinePicture = (rId: string, width: number, height: number): string =>
|
|
499
|
+
`<w:p><w:r><w:drawing><wp:inline><wp:extent cx="${Math.round(width * EMU)}" cy="${Math.round(height * EMU)}"/><wp:docPr id="1" name="Picture 1"/><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:blipFill><a:blip r:embed="${rId}"/></pic:blipFill></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p>`;
|
|
500
|
+
|
|
501
|
+
/** A complex field: begin, instruction, separate, the saved result (none when undefined), end. */
|
|
502
|
+
export const field = (instruction: string, result: string | undefined): string =>
|
|
503
|
+
`<w:p><w:r><w:fldChar w:fldCharType="begin"/></w:r><w:r><w:instrText xml:space="preserve"> ${xmlEscape(instruction)} </w:instrText></w:r>${result === undefined ? "" : `<w:r><w:fldChar w:fldCharType="separate"/></w:r><w:r><w:t>${xmlEscape(result)}</w:t></w:r>`}<w:r><w:fldChar w:fldCharType="end"/></w:r></w:p>`;
|
|
504
|
+
|
|
505
|
+
export interface WordOptions {
|
|
506
|
+
/** The body's paragraphs and tables, as XML (para, field, inlinePicture, or by hand). */
|
|
507
|
+
readonly body: readonly string[];
|
|
508
|
+
readonly rels?: Readonly<Record<string, FixtureRel>>;
|
|
509
|
+
readonly media?: Readonly<Record<string, Uint8Array>>;
|
|
510
|
+
/** Comment texts, written as word/comments.xml. */
|
|
511
|
+
readonly comments?: readonly string[];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Heading styles under ids that are not their names, the way a localised Word template writes them: the check must
|
|
515
|
+
// read the style's name, not guess from its id.
|
|
516
|
+
const STYLES = `<?xml version="1.0" encoding="UTF-8"?>
|
|
517
|
+
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style><w:style w:type="paragraph" w:styleId="berschrift1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/></w:style><w:style w:type="paragraph" w:styleId="berschrift2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/></w:style><w:style w:type="paragraph" w:styleId="Callout"><w:name w:val="Callout"/><w:basedOn w:val="berschrift2"/></w:style></w:styles>`;
|
|
518
|
+
|
|
519
|
+
/** A Word document with styles (headings under localised ids), a letter-size section, and optional comments and media. */
|
|
520
|
+
export const wordBytes = (options: WordOptions): Uint8Array => {
|
|
521
|
+
const commentRels = options.comments === undefined ? {} : { rIdComments: { type: "comments", target: "comments.xml" } };
|
|
522
|
+
const files = new Map<string, Uint8Array>([
|
|
523
|
+
["[Content_Types].xml", strToU8(
|
|
524
|
+
CONTENT_TYPES(
|
|
525
|
+
[
|
|
526
|
+
'<Default Extension="png" ContentType="image/png"/>',
|
|
527
|
+
'<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>',
|
|
528
|
+
'<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>',
|
|
529
|
+
options.comments === undefined ? "" : '<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>',
|
|
530
|
+
].join(""),
|
|
531
|
+
),
|
|
532
|
+
)],
|
|
533
|
+
["_rels/.rels", strToU8(RELS("word/document.xml"))],
|
|
534
|
+
["word/styles.xml", strToU8(STYLES)],
|
|
535
|
+
["word/document.xml", strToU8(
|
|
536
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
537
|
+
<w:document ${NS_W}><w:body>${options.body.join("")}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:gutter="0"/></w:sectPr></w:body></w:document>`,
|
|
538
|
+
)],
|
|
539
|
+
]);
|
|
540
|
+
if (options.comments !== undefined) {
|
|
541
|
+
files.set("word/comments.xml", strToU8(
|
|
542
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
543
|
+
<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">${options.comments.map((text, index) => `<w:comment w:id="${index}" w:author="Reviewer"><w:p><w:r><w:t>${xmlEscape(text)}</w:t></w:r></w:p></w:comment>`).join("")}</w:comments>`,
|
|
544
|
+
),
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
files.set("word/_rels/document.xml.rels", strToU8(relsXml({ rIdStyles: { type: "styles", target: "styles.xml" }, ...options.rels, ...commentRels })));
|
|
548
|
+
for (const [name, bytes] of Object.entries(options.media ?? {})) {
|
|
549
|
+
files.set(`word/media/${name}`, bytes);
|
|
550
|
+
}
|
|
551
|
+
return zipSync(Object.fromEntries(files));
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export interface SheetFixture {
|
|
555
|
+
readonly name: string;
|
|
556
|
+
/** The sheetData's rows, as XML: `<row r="1"><c r="A1"><v>1</v></c></row>`. */
|
|
557
|
+
readonly rows: string;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** An Excel workbook, one worksheet per entry, with optional defined names and calculation settings. */
|
|
561
|
+
export const workbookBytes = (sheets: readonly SheetFixture[], options: { readonly definedNames?: string; readonly calcPr?: string } = {}): Uint8Array => {
|
|
562
|
+
const files = new Map<string, Uint8Array>([
|
|
563
|
+
["[Content_Types].xml", strToU8(
|
|
564
|
+
CONTENT_TYPES(
|
|
565
|
+
[
|
|
566
|
+
'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>',
|
|
567
|
+
...sheets.map((_, index) => `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`),
|
|
568
|
+
].join(""),
|
|
569
|
+
),
|
|
570
|
+
)],
|
|
571
|
+
["_rels/.rels", strToU8(RELS("xl/workbook.xml"))],
|
|
572
|
+
["xl/workbook.xml", strToU8(
|
|
573
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
574
|
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>${sheets.map((sheet, index) => `<sheet name="${xmlEscape(sheet.name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`).join("")}</sheets>${options.definedNames === undefined ? "" : `<definedNames>${options.definedNames}</definedNames>`}${options.calcPr ?? ""}</workbook>`,
|
|
575
|
+
)],
|
|
576
|
+
["xl/_rels/workbook.xml.rels", strToU8(relsXml(Object.fromEntries(sheets.map((_, index) => [`rId${index + 1}`, { type: "worksheet", target: `worksheets/sheet${index + 1}.xml` }]))))],
|
|
577
|
+
]);
|
|
578
|
+
sheets.forEach((sheet, index) => {
|
|
579
|
+
files.set(`xl/worksheets/sheet${index + 1}.xml`, strToU8(
|
|
580
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
581
|
+
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${sheet.rows}</sheetData></worksheet>`,
|
|
582
|
+
),
|
|
583
|
+
);
|
|
584
|
+
});
|
|
585
|
+
return zipSync(Object.fromEntries(files));
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
export interface PdfPageFixture {
|
|
589
|
+
/** The page's content stream, e.g. `BT /F1 12 Tf 72 720 Td (Hello) Tj ET`; empty draws nothing. */
|
|
590
|
+
readonly content: string;
|
|
591
|
+
/** The BaseFont /F1 names; Helvetica (one of the fourteen every reader has) by default. */
|
|
592
|
+
readonly font?: string;
|
|
593
|
+
/** Extra page dictionary entries, e.g. `/Annots [...]`. */
|
|
594
|
+
readonly extra?: string;
|
|
595
|
+
/** Width and height in points; US Letter by default. */
|
|
596
|
+
readonly size?: readonly [number, number];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** A PDF with one page per entry, honest xref offsets, and no font embedded (a font the reader lacks is substituted). */
|
|
600
|
+
export const pdfPagesBytes = (pages: readonly PdfPageFixture[]): Uint8Array => {
|
|
601
|
+
const objects: string[] = ["", ""];
|
|
602
|
+
const add = (body: string): number => objects.push(body);
|
|
603
|
+
const kids = pages.map((page) => {
|
|
604
|
+
const font = add(`<< /Type /Font /Subtype /${page.font === undefined ? "Type1" : "TrueType"} /BaseFont /${page.font ?? "Helvetica"} >>`);
|
|
605
|
+
const content = add(`<< /Length ${page.content.length} >>\nstream\n${page.content}\nendstream`);
|
|
606
|
+
const [width, height] = page.size ?? [612, 792];
|
|
607
|
+
return add(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${width} ${height}] /Contents ${content} 0 R /Resources << /Font << /F1 ${font} 0 R >> >> ${page.extra ?? ""} >>`);
|
|
608
|
+
});
|
|
609
|
+
objects[0] = "<< /Type /Catalog /Pages 2 0 R >>";
|
|
610
|
+
objects[1] = `<< /Type /Pages /Kids [${kids.map((kid) => `${kid} 0 R`).join(" ")}] /Count ${kids.length} >>`;
|
|
611
|
+
let out = "%PDF-1.4\n";
|
|
612
|
+
const offsets = objects.map((body, index) => {
|
|
613
|
+
const offset = out.length;
|
|
614
|
+
out += `${index + 1} 0 obj\n${body}\nendobj\n`;
|
|
615
|
+
return offset;
|
|
616
|
+
});
|
|
617
|
+
const xref = out.length;
|
|
618
|
+
out += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n${offsets.map((offset) => `${String(offset).padStart(10, "0")} 00000 n `).join("\n")}\ntrailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
|
|
619
|
+
return strToU8(out);
|
|
620
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sweep.command.d.ts","sourceRoot":"","sources":["../../src/commands/sweep.command.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAUlE,eAAO,MAAM,YAAY,iDAoCvB,CAAC"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { buildCommand } from "@stricli/core";
|
|
2
|
-
import { sweep } from "../lib/sweep.js";
|
|
3
|
-
import { workspaceRoot } from "../lib/env.js";
|
|
4
|
-
import { outcomeLine } from "./derive.command.js";
|
|
5
|
-
export const sweepCommand = buildCommand({
|
|
6
|
-
docs: { brief: "Converge every derivable workspace file's sidecar; prune shadows of deleted files" },
|
|
7
|
-
parameters: {
|
|
8
|
-
flags: {
|
|
9
|
-
json: { kind: "boolean", default: false, brief: "Machine-readable summary on stdout" },
|
|
10
|
-
quiet: { kind: "boolean", default: false, brief: "Totals only, no per-file lines" },
|
|
11
|
-
},
|
|
12
|
-
positional: { kind: "tuple", parameters: [] },
|
|
13
|
-
},
|
|
14
|
-
async func(flags) {
|
|
15
|
-
const root = workspaceRoot();
|
|
16
|
-
if (root === undefined) {
|
|
17
|
-
this.process.stdout.write("fileq: sweep needs a workspace (WORKSPACE_ROOT is not set)\n");
|
|
18
|
-
process.exitCode = 2;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const result = await sweep(root, (outcome) => {
|
|
22
|
-
if (!flags.quiet && !flags.json && outcome.kind !== "fresh") {
|
|
23
|
-
this.process.stdout.write(`${outcomeLine(outcome)}\n`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
const counts = { derived: 0, fresh: 0, removed: 0, skipped: 0 };
|
|
27
|
-
for (const outcome of result.outcomes) {
|
|
28
|
-
counts[outcome.kind] += 1;
|
|
29
|
-
}
|
|
30
|
-
if (flags.json) {
|
|
31
|
-
this.process.stdout.write(`${JSON.stringify({ ...counts, pruned: result.pruned.length })}\n`);
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
for (const relPath of result.pruned) {
|
|
35
|
-
this.process.stdout.write(`pruned ${relPath} (source gone)\n`);
|
|
36
|
-
}
|
|
37
|
-
this.process.stdout.write(`sweep: ${counts.derived} derived, ${counts.fresh} fresh, ${counts.removed + result.pruned.length} removed, ${counts.skipped} skipped\n`);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
//# sourceMappingURL=sweep.command.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sweep.command.js","sourceRoot":"","sources":["../../src/commands/sweep.command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAuB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAOlD,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC;IACrC,IAAI,EAAE,EAAE,KAAK,EAAE,mFAAmF,EAAE;IACpG,UAAU,EAAE;QACR,KAAK,EAAE;YACH,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oCAAoC,EAAE;YACtF,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE;SACtF;QACD,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD,KAAK,CAAC,IAAI,CAAuB,KAAiB;QAC9C,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;YAC1F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACX,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;YACzC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC1D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAChE,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QAClG,CAAC;aAAM,CAAC;YACJ,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,kBAAkB,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CACrB,UAAU,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,KAAK,WAAW,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,aAAa,MAAM,CAAC,OAAO,YAAY,CAC3I,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAC,CAAC"}
|
package/dist/lib/flags.d.ts
DELETED
package/dist/lib/flags.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../../src/lib/flags.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,QAAS,MAAM,KAAG,MAM1C,CAAC"}
|
package/dist/lib/flags.js
DELETED
package/dist/lib/flags.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/lib/flags.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAU,EAAE;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,GAAG,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC"}
|
package/dist/lib/sweep.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type Outcome } from "./derive.js";
|
|
2
|
-
export interface SweepResult {
|
|
3
|
-
readonly outcomes: Outcome[];
|
|
4
|
-
readonly pruned: string[];
|
|
5
|
-
}
|
|
6
|
-
export declare const sweep: (workspaceRoot: string, onOutcome?: (outcome: Outcome) => void) => Promise<SweepResult>;
|
|
7
|
-
//# sourceMappingURL=sweep.d.ts.map
|
package/dist/lib/sweep.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/lib/sweep.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAyC1D,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IAE7B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,eAAO,MAAM,KAAK,kBAAyB,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,OAAO,CAAC,WAAW,CAoB9G,CAAC"}
|
package/dist/lib/sweep.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { readdir, stat } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { IGNORED_DIRS, isAgentWorktreePath, REFERENCE_DIR } from "@intentic/workspace-ignore";
|
|
4
|
-
import { STATE_DIR } from "@intentic/constants";
|
|
5
|
-
import { ensureSidecar } from "./derive.js";
|
|
6
|
-
import { isCandidatePath } from "./formats.js";
|
|
7
|
-
import { DERIVED_DIR, removeSidecar } from "./sidecar.js";
|
|
8
|
-
const skipDir = (relPath, name, childRel) => IGNORED_DIRS.has(name) || name === STATE_DIR || (relPath === "" && name === REFERENCE_DIR) || isAgentWorktreePath(childRel);
|
|
9
|
-
const walk = async (root, relPath, found) => {
|
|
10
|
-
const entries = await readdir(join(root, relPath), { withFileTypes: true }).catch(() => []);
|
|
11
|
-
for (const entry of entries) {
|
|
12
|
-
const childRel = relPath === "" ? entry.name : `${relPath}/${entry.name}`;
|
|
13
|
-
if (entry.isDirectory()) {
|
|
14
|
-
if (!skipDir(relPath, entry.name, childRel)) {
|
|
15
|
-
await walk(root, childRel, found);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
else if (entry.isFile() && isCandidatePath(entry.name)) {
|
|
19
|
-
found.push(childRel);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
const walkSidecars = async (root, relPath, found) => {
|
|
24
|
-
const entries = await readdir(join(root, DERIVED_DIR, relPath), { withFileTypes: true }).catch(() => []);
|
|
25
|
-
for (const entry of entries) {
|
|
26
|
-
const childRel = relPath === "" ? entry.name : `${relPath}/${entry.name}`;
|
|
27
|
-
if (entry.isDirectory()) {
|
|
28
|
-
await walkSidecars(root, childRel, found);
|
|
29
|
-
}
|
|
30
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
31
|
-
found.push(childRel.slice(0, -3));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
export const sweep = async (workspaceRoot, onOutcome) => {
|
|
36
|
-
const candidates = [];
|
|
37
|
-
await walk(workspaceRoot, "", candidates);
|
|
38
|
-
const outcomes = [];
|
|
39
|
-
for (const relPath of candidates) {
|
|
40
|
-
const outcome = await ensureSidecar(workspaceRoot, join(workspaceRoot, relPath));
|
|
41
|
-
outcomes.push(outcome);
|
|
42
|
-
onOutcome?.(outcome);
|
|
43
|
-
}
|
|
44
|
-
const shadowed = [];
|
|
45
|
-
await walkSidecars(workspaceRoot, "", shadowed);
|
|
46
|
-
const alive = new Set(candidates);
|
|
47
|
-
const pruned = [];
|
|
48
|
-
for (const relPath of shadowed) {
|
|
49
|
-
if (!alive.has(relPath) && (await stat(join(workspaceRoot, relPath)).catch(() => undefined)) === undefined) {
|
|
50
|
-
await removeSidecar(workspaceRoot, relPath);
|
|
51
|
-
pruned.push(relPath);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return { outcomes, pruned };
|
|
55
|
-
};
|
|
56
|
-
//# sourceMappingURL=sweep.js.map
|
package/dist/lib/sweep.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sweep.js","sourceRoot":"","sources":["../../src/lib/sweep.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
DELETED
package/dist/version.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,EAAE,MAA2F,CAAC"}
|
package/dist/version.js
DELETED
package/dist/version.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,MAAM,CAAC,MAAM,OAAO,GAAY,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/* `fileq sweep`: the whole workspace converged in one pass — every derivable file shadowed, every orphaned shadow pruned. */
|
|
2
|
-
import { buildCommand, type CommandContext } from "@stricli/core";
|
|
3
|
-
import { sweep } from "../lib/sweep.js";
|
|
4
|
-
import { workspaceRoot } from "../lib/env.js";
|
|
5
|
-
import { outcomeLine } from "./derive.command.js";
|
|
6
|
-
|
|
7
|
-
interface SweepFlags {
|
|
8
|
-
readonly json: boolean;
|
|
9
|
-
readonly quiet: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const sweepCommand = buildCommand({
|
|
13
|
-
docs: { brief: "Converge every derivable workspace file's sidecar; prune shadows of deleted files" },
|
|
14
|
-
parameters: {
|
|
15
|
-
flags: {
|
|
16
|
-
json: { kind: "boolean", default: false, brief: "Machine-readable summary on stdout" },
|
|
17
|
-
quiet: { kind: "boolean", default: false, brief: "Totals only, no per-file lines" },
|
|
18
|
-
},
|
|
19
|
-
positional: { kind: "tuple", parameters: [] },
|
|
20
|
-
},
|
|
21
|
-
async func(this: CommandContext, flags: SweepFlags) {
|
|
22
|
-
const root = workspaceRoot();
|
|
23
|
-
if (root === undefined) {
|
|
24
|
-
this.process.stdout.write("fileq: sweep needs a workspace (WORKSPACE_ROOT is not set)\n");
|
|
25
|
-
process.exitCode = 2;
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const result = await sweep(root, (outcome) => {
|
|
29
|
-
if (!flags.quiet && !flags.json && outcome.kind !== "fresh") {
|
|
30
|
-
this.process.stdout.write(`${outcomeLine(outcome)}\n`);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
const counts = { derived: 0, fresh: 0, removed: 0, skipped: 0 };
|
|
34
|
-
for (const outcome of result.outcomes) {
|
|
35
|
-
counts[outcome.kind] += 1;
|
|
36
|
-
}
|
|
37
|
-
if (flags.json) {
|
|
38
|
-
this.process.stdout.write(`${JSON.stringify({ ...counts, pruned: result.pruned.length })}\n`);
|
|
39
|
-
} else {
|
|
40
|
-
for (const relPath of result.pruned) {
|
|
41
|
-
this.process.stdout.write(`pruned ${relPath} (source gone)\n`);
|
|
42
|
-
}
|
|
43
|
-
this.process.stdout.write(
|
|
44
|
-
`sweep: ${counts.derived} derived, ${counts.fresh} fresh, ${counts.removed + result.pruned.length} removed, ${counts.skipped} skipped\n`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
});
|
package/src/lib/sweep.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { readdir, stat } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { IGNORED_DIRS, isAgentWorktreePath, REFERENCE_DIR } from "@intentic/workspace-ignore";
|
|
4
|
-
import { STATE_DIR } from "@intentic/constants";
|
|
5
|
-
import { ensureSidecar, type Outcome } from "./derive.js";
|
|
6
|
-
import { isCandidatePath } from "./formats.js";
|
|
7
|
-
import { DERIVED_DIR, removeSidecar } from "./sidecar.js";
|
|
8
|
-
|
|
9
|
-
// Whole-workspace pass: converges every derivable file and removes every orphaned shadow, making the sidecar tree a
|
|
10
|
-
// statement about the workspace as it is now, not as the watcher's incremental view has it.
|
|
11
|
-
// Sequential on purpose, since it shares the box with the agent it's shadowing; parallel parses would cost that
|
|
12
|
-
// argument.
|
|
13
|
-
// Skips what the daemon's watcher skips (machine subtrees, the state dir, the reference shelf), so the two never
|
|
14
|
-
// disagree about what's workspace.
|
|
15
|
-
|
|
16
|
-
const skipDir = (relPath: string, name: string, childRel: string): boolean =>
|
|
17
|
-
IGNORED_DIRS.has(name) || name === STATE_DIR || (relPath === "" && name === REFERENCE_DIR) || isAgentWorktreePath(childRel);
|
|
18
|
-
|
|
19
|
-
const walk = async (root: string, relPath: string, found: string[]): Promise<void> => {
|
|
20
|
-
const entries = await readdir(join(root, relPath), { withFileTypes: true }).catch(() => []);
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
const childRel = relPath === "" ? entry.name : `${relPath}/${entry.name}`;
|
|
23
|
-
if (entry.isDirectory()) {
|
|
24
|
-
if (!skipDir(relPath, entry.name, childRel)) {
|
|
25
|
-
await walk(root, childRel, found);
|
|
26
|
-
}
|
|
27
|
-
} else if (entry.isFile() && isCandidatePath(entry.name)) {
|
|
28
|
-
found.push(childRel);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Shadow tree's own walk, for orphan pruning: every `<rel>.md` under derived/ names the source it shadows.
|
|
34
|
-
const walkSidecars = async (root: string, relPath: string, found: string[]): Promise<void> => {
|
|
35
|
-
const entries = await readdir(join(root, DERIVED_DIR, relPath), { withFileTypes: true }).catch(() => []);
|
|
36
|
-
for (const entry of entries) {
|
|
37
|
-
const childRel = relPath === "" ? entry.name : `${relPath}/${entry.name}`;
|
|
38
|
-
if (entry.isDirectory()) {
|
|
39
|
-
await walkSidecars(root, childRel, found);
|
|
40
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
41
|
-
found.push(childRel.slice(0, -3));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export interface SweepResult {
|
|
47
|
-
readonly outcomes: Outcome[];
|
|
48
|
-
/** Sources of shadows whose file vanished since they were derived, removed by this sweep. */
|
|
49
|
-
readonly pruned: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const sweep = async (workspaceRoot: string, onOutcome?: (outcome: Outcome) => void): Promise<SweepResult> => {
|
|
53
|
-
const candidates: string[] = [];
|
|
54
|
-
await walk(workspaceRoot, "", candidates);
|
|
55
|
-
const outcomes: Outcome[] = [];
|
|
56
|
-
for (const relPath of candidates) {
|
|
57
|
-
const outcome = await ensureSidecar(workspaceRoot, join(workspaceRoot, relPath));
|
|
58
|
-
outcomes.push(outcome);
|
|
59
|
-
onOutcome?.(outcome);
|
|
60
|
-
}
|
|
61
|
-
const shadowed: string[] = [];
|
|
62
|
-
await walkSidecars(workspaceRoot, "", shadowed);
|
|
63
|
-
const alive = new Set(candidates);
|
|
64
|
-
const pruned: string[] = [];
|
|
65
|
-
for (const relPath of shadowed) {
|
|
66
|
-
if (!alive.has(relPath) && (await stat(join(workspaceRoot, relPath)).catch(() => undefined)) === undefined) {
|
|
67
|
-
await removeSidecar(workspaceRoot, relPath);
|
|
68
|
-
pruned.push(relPath);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { outcomes, pruned };
|
|
72
|
-
};
|