@intentic/fileq 1.317.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/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +43 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/derive.command.d.ts +5 -0
- package/dist/commands/derive.command.d.ts.map +1 -0
- package/dist/commands/derive.command.js +53 -0
- package/dist/commands/derive.command.js.map +1 -0
- package/dist/commands/git-attributes.command.d.ts +3 -0
- package/dist/commands/git-attributes.command.d.ts.map +1 -0
- package/dist/commands/git-attributes.command.js +12 -0
- package/dist/commands/git-attributes.command.js.map +1 -0
- package/dist/commands/read.command.d.ts +3 -0
- package/dist/commands/read.command.d.ts.map +1 -0
- package/dist/commands/read.command.js +126 -0
- package/dist/commands/read.command.js.map +1 -0
- package/dist/commands/sweep.command.d.ts +3 -0
- package/dist/commands/sweep.command.d.ts.map +1 -0
- package/dist/commands/sweep.command.js +41 -0
- package/dist/commands/sweep.command.js.map +1 -0
- package/dist/lib/archives.d.ts +18 -0
- package/dist/lib/archives.d.ts.map +1 -0
- package/dist/lib/archives.js +133 -0
- package/dist/lib/archives.js.map +1 -0
- package/dist/lib/derive.d.ts +32 -0
- package/dist/lib/derive.d.ts.map +1 -0
- package/dist/lib/derive.js +99 -0
- package/dist/lib/derive.js.map +1 -0
- package/dist/lib/derivers/archive.d.ts +3 -0
- package/dist/lib/derivers/archive.d.ts.map +1 -0
- package/dist/lib/derivers/archive.js +132 -0
- package/dist/lib/derivers/archive.js.map +1 -0
- package/dist/lib/derivers/deriver.d.ts +13 -0
- package/dist/lib/derivers/deriver.d.ts.map +1 -0
- package/dist/lib/derivers/deriver.js +8 -0
- package/dist/lib/derivers/deriver.js.map +1 -0
- package/dist/lib/derivers/docx.d.ts +6 -0
- package/dist/lib/derivers/docx.d.ts.map +1 -0
- package/dist/lib/derivers/docx.js +29 -0
- package/dist/lib/derivers/docx.js.map +1 -0
- package/dist/lib/derivers/epub.d.ts +3 -0
- package/dist/lib/derivers/epub.d.ts.map +1 -0
- package/dist/lib/derivers/epub.js +102 -0
- package/dist/lib/derivers/epub.js.map +1 -0
- package/dist/lib/derivers/html.d.ts +3 -0
- package/dist/lib/derivers/html.d.ts.map +1 -0
- package/dist/lib/derivers/html.js +18 -0
- package/dist/lib/derivers/html.js.map +1 -0
- package/dist/lib/derivers/image.d.ts +3 -0
- package/dist/lib/derivers/image.d.ts.map +1 -0
- package/dist/lib/derivers/image.js +42 -0
- package/dist/lib/derivers/image.js.map +1 -0
- package/dist/lib/derivers/ipynb.d.ts +3 -0
- package/dist/lib/derivers/ipynb.d.ts.map +1 -0
- package/dist/lib/derivers/ipynb.js +75 -0
- package/dist/lib/derivers/ipynb.js.map +1 -0
- package/dist/lib/derivers/media.d.ts +3 -0
- package/dist/lib/derivers/media.d.ts.map +1 -0
- package/dist/lib/derivers/media.js +45 -0
- package/dist/lib/derivers/media.js.map +1 -0
- package/dist/lib/derivers/odp.d.ts +3 -0
- package/dist/lib/derivers/odp.d.ts.map +1 -0
- package/dist/lib/derivers/odp.js +41 -0
- package/dist/lib/derivers/odp.js.map +1 -0
- package/dist/lib/derivers/ods.d.ts +3 -0
- package/dist/lib/derivers/ods.d.ts.map +1 -0
- package/dist/lib/derivers/ods.js +86 -0
- package/dist/lib/derivers/ods.js.map +1 -0
- package/dist/lib/derivers/odt.d.ts +4 -0
- package/dist/lib/derivers/odt.d.ts.map +1 -0
- package/dist/lib/derivers/odt.js +77 -0
- package/dist/lib/derivers/odt.js.map +1 -0
- package/dist/lib/derivers/pdf-furniture.d.ts +7 -0
- package/dist/lib/derivers/pdf-furniture.d.ts.map +1 -0
- package/dist/lib/derivers/pdf-furniture.js +76 -0
- package/dist/lib/derivers/pdf-furniture.js.map +1 -0
- package/dist/lib/derivers/pdf.d.ts +4 -0
- package/dist/lib/derivers/pdf.d.ts.map +1 -0
- package/dist/lib/derivers/pdf.js +79 -0
- package/dist/lib/derivers/pdf.js.map +1 -0
- package/dist/lib/derivers/pptx.d.ts +4 -0
- package/dist/lib/derivers/pptx.d.ts.map +1 -0
- package/dist/lib/derivers/pptx.js +51 -0
- package/dist/lib/derivers/pptx.js.map +1 -0
- package/dist/lib/derivers/profile.d.ts +3 -0
- package/dist/lib/derivers/profile.d.ts.map +1 -0
- package/dist/lib/derivers/profile.js +115 -0
- package/dist/lib/derivers/profile.js.map +1 -0
- package/dist/lib/derivers/rtf.d.ts +4 -0
- package/dist/lib/derivers/rtf.d.ts.map +1 -0
- package/dist/lib/derivers/rtf.js +187 -0
- package/dist/lib/derivers/rtf.js.map +1 -0
- package/dist/lib/derivers/xlsx.d.ts +3 -0
- package/dist/lib/derivers/xlsx.d.ts.map +1 -0
- package/dist/lib/derivers/xlsx.js +68 -0
- package/dist/lib/derivers/xlsx.js.map +1 -0
- package/dist/lib/env.d.ts +2 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +5 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/flags.d.ts +2 -0
- package/dist/lib/flags.d.ts.map +1 -0
- package/dist/lib/flags.js +8 -0
- package/dist/lib/flags.js.map +1 -0
- package/dist/lib/formats.d.ts +6 -0
- package/dist/lib/formats.d.ts.map +1 -0
- package/dist/lib/formats.js +110 -0
- package/dist/lib/formats.js.map +1 -0
- package/dist/lib/sidecar.d.ts +30 -0
- package/dist/lib/sidecar.d.ts.map +1 -0
- package/dist/lib/sidecar.js +108 -0
- package/dist/lib/sidecar.js.map +1 -0
- package/dist/lib/sweep.d.ts +7 -0
- package/dist/lib/sweep.d.ts.map +1 -0
- package/dist/lib/sweep.js +56 -0
- package/dist/lib/sweep.js.map +1 -0
- package/dist/lib/tools.d.ts +2 -0
- package/dist/lib/tools.d.ts.map +1 -0
- package/dist/lib/tools.js +15 -0
- package/dist/lib/tools.js.map +1 -0
- package/dist/lib/xml.d.ts +3 -0
- package/dist/lib/xml.d.ts.map +1 -0
- package/dist/lib/xml.js +16 -0
- package/dist/lib/xml.js.map +1 -0
- package/dist/skill.d.ts +5 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +75 -0
- package/dist/skill.js.map +1 -0
- package/dist/testing.d.ts +25 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +220 -0
- package/dist/testing.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +89 -0
- package/src/app.ts +48 -0
- package/src/cli.integration.test.ts +200 -0
- package/src/cli.ts +6 -0
- package/src/commands/derive.command.ts +59 -0
- package/src/commands/git-attributes.command.ts +13 -0
- package/src/commands/read.command.ts +158 -0
- package/src/commands/sweep.command.ts +48 -0
- package/src/derivers.integration.test.ts +415 -0
- package/src/lib/archives.test.ts +72 -0
- package/src/lib/archives.ts +209 -0
- package/src/lib/derive.ts +132 -0
- package/src/lib/derivers/archive.ts +168 -0
- package/src/lib/derivers/deriver.ts +33 -0
- package/src/lib/derivers/docx.test.ts +38 -0
- package/src/lib/derivers/docx.ts +44 -0
- package/src/lib/derivers/epub.ts +130 -0
- package/src/lib/derivers/html.ts +21 -0
- package/src/lib/derivers/image.ts +54 -0
- package/src/lib/derivers/ipynb.ts +113 -0
- package/src/lib/derivers/load-cost.test.ts +44 -0
- package/src/lib/derivers/media.ts +51 -0
- package/src/lib/derivers/odp.ts +52 -0
- package/src/lib/derivers/ods.ts +104 -0
- package/src/lib/derivers/odt.ts +101 -0
- package/src/lib/derivers/pdf-furniture.test.ts +60 -0
- package/src/lib/derivers/pdf-furniture.ts +121 -0
- package/src/lib/derivers/pdf.ts +102 -0
- package/src/lib/derivers/pptx.ts +61 -0
- package/src/lib/derivers/profile.ts +172 -0
- package/src/lib/derivers/rtf.ts +233 -0
- package/src/lib/derivers/xlsx.ts +82 -0
- package/src/lib/env.ts +7 -0
- package/src/lib/formats.test.ts +50 -0
- package/src/lib/formats.ts +141 -0
- package/src/lib/sidecar.test.ts +60 -0
- package/src/lib/sidecar.ts +156 -0
- package/src/lib/sweep.ts +72 -0
- package/src/lib/tools.ts +16 -0
- package/src/lib/xml.ts +20 -0
- package/src/skill.ts +90 -0
- package/src/testing.ts +333 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* `fileq derive` derives metadata from one input file. */
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { buildCommand, type CommandContext } from "@stricli/core";
|
|
4
|
+
import { ensureSidecar, type Outcome } from "../lib/derive.js";
|
|
5
|
+
import { workspaceRoot } from "../lib/env.js";
|
|
6
|
+
|
|
7
|
+
interface DeriveFlags {
|
|
8
|
+
readonly json: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const outcomeLine = (outcome: Outcome): string => {
|
|
12
|
+
switch (outcome.kind) {
|
|
13
|
+
case "derived":
|
|
14
|
+
return `derived ${outcome.relPath} -> ${outcome.sidecarPath} (${outcome.tokens} tokens)`;
|
|
15
|
+
case "fresh":
|
|
16
|
+
return `fresh ${outcome.relPath}`;
|
|
17
|
+
case "removed":
|
|
18
|
+
return `removed ${outcome.relPath} (source gone)`;
|
|
19
|
+
case "skipped":
|
|
20
|
+
return `skipped ${outcome.relPath}: ${outcome.reason}`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const deriveCommand = buildCommand({
|
|
25
|
+
docs: { brief: "Converge sidecars for the named files (derive stale, remove orphaned, skip fresh)" },
|
|
26
|
+
parameters: {
|
|
27
|
+
flags: {
|
|
28
|
+
json: { kind: "boolean", default: false, brief: "One JSON object per line" },
|
|
29
|
+
},
|
|
30
|
+
positional: { kind: "array", parameter: { parse: String, brief: "Files to converge, workspace-root-relative (the watcher's own namespace) or absolute", placeholder: "file" } },
|
|
31
|
+
},
|
|
32
|
+
async func(this: CommandContext, flags: DeriveFlags, ...files: string[]) {
|
|
33
|
+
const root = workspaceRoot();
|
|
34
|
+
if (root === undefined) {
|
|
35
|
+
this.process.stdout.write("fileq: derive needs a workspace (WORKSPACE_ROOT is not set); use `fileq read` for one-off files\n");
|
|
36
|
+
process.exitCode = 2;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (files.length === 0) {
|
|
40
|
+
this.process.stdout.write("fileq: derive takes file paths (or run `fileq sweep` for the whole workspace)\n");
|
|
41
|
+
process.exitCode = 2;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let useful = 0;
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const outcome = await ensureSidecar(root, resolve(root, file));
|
|
47
|
+
if (outcome.kind !== "skipped") {
|
|
48
|
+
useful += 1;
|
|
49
|
+
}
|
|
50
|
+
if (flags.json) {
|
|
51
|
+
this.process.stdout.write(`${JSON.stringify(outcome.kind === "derived" ? { ...outcome, doc: undefined, body: undefined } : outcome)}\n`);
|
|
52
|
+
} else {
|
|
53
|
+
this.process.stdout.write(`${outcomeLine(outcome)}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// grep's convention: 0 something derivable was converged, 1 nothing here was fileq's business.
|
|
57
|
+
process.exitCode = useful > 0 ? 0 : 1;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* `fileq git-attributes`: the gitattributes lines that make git's own diffs read documents through fileq (textconv). */
|
|
2
|
+
import { buildCommand, type CommandContext } from "@stricli/core";
|
|
3
|
+
import { gitAttributeLines } from "../lib/formats.js";
|
|
4
|
+
|
|
5
|
+
export const gitAttributesCommand = buildCommand({
|
|
6
|
+
docs: {
|
|
7
|
+
brief: "Print gitattributes marking every derivable format `diff=fileq`, for a `diff.fileq.textconv` of `fileq read --plain`",
|
|
8
|
+
},
|
|
9
|
+
parameters: { positional: { kind: "tuple", parameters: [] } },
|
|
10
|
+
func(this: CommandContext) {
|
|
11
|
+
this.process.stdout.write(`${gitAttributeLines().join("\n")}\n`);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* `fileq read <file>` (also the default command): one file as markdown — a capsule line saying what happened, the content up to a token budget; `--plain` is the body alone, whole, for a program (git's textconv) rather than an agent. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { toolOutDir } from "@intentic/agent-cli/env";
|
|
6
|
+
import { countParser } from "@intentic/agent-cli/flags";
|
|
7
|
+
import { capsule, clip } from "@intentic/agent-cli/output";
|
|
8
|
+
import { estimateTokens } from "@intentic/base/format";
|
|
9
|
+
import { buildCommand, type CommandContext } from "@stricli/core";
|
|
10
|
+
import { errorMessage } from "@intentic/base/errors";
|
|
11
|
+
import { deriverStamp, neutralizeDoc, type DerivedDoc } from "../lib/derivers/deriver.js";
|
|
12
|
+
import { detectFormat } from "../lib/formats.js";
|
|
13
|
+
import { DERIVERS, ensureSidecar, type Outcome } from "../lib/derive.js";
|
|
14
|
+
import { workspaceRoot } from "../lib/env.js";
|
|
15
|
+
|
|
16
|
+
interface ReadFlags {
|
|
17
|
+
readonly budget: number;
|
|
18
|
+
readonly json: boolean;
|
|
19
|
+
readonly plain: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const readCommand = buildCommand({
|
|
23
|
+
docs: { brief: "One file as clean markdown: budgeted on stdout, whole in its sidecar" },
|
|
24
|
+
parameters: {
|
|
25
|
+
flags: {
|
|
26
|
+
budget: { kind: "parsed", parse: countParser, default: "4000", brief: "Max stdout tokens; 0 prints only the capsule" },
|
|
27
|
+
json: { kind: "boolean", default: false, brief: "Machine-readable result on stdout" },
|
|
28
|
+
plain: { kind: "boolean", default: false, brief: "The markdown alone, whole and unbudgeted; nothing saved for a file outside the workspace (git textconv)" },
|
|
29
|
+
},
|
|
30
|
+
positional: {
|
|
31
|
+
kind: "tuple",
|
|
32
|
+
parameters: [{ parse: String, brief: "The file to read (relative paths resolve against the cwd)", placeholder: "file" }],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
async func(this: CommandContext, flags: ReadFlags, file: string) {
|
|
36
|
+
const absPath = resolve(file);
|
|
37
|
+
const root = workspaceRoot();
|
|
38
|
+
const save = !flags.plain;
|
|
39
|
+
const result = root === undefined ? await readOutsideWorkspace(absPath, save) : await readInWorkspace(root, absPath, save);
|
|
40
|
+
if (result === undefined) {
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (flags.plain) {
|
|
45
|
+
this.process.stdout.write(result.body === "" ? "" : `${result.body}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (flags.json) {
|
|
49
|
+
this.process.stdout.write(
|
|
50
|
+
`${JSON.stringify({ file: absPath, format: result.format, deriver: result.deriver, tokens: result.tokens, path: result.savedPath, source: result.source, notes: result.notes })}\n`,
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const fields = [result.title ?? basename(absPath), result.format, `${result.tokens} tokens`, result.source];
|
|
55
|
+
this.process.stdout.write(capsule("fileq", fields, result.notes));
|
|
56
|
+
this.process.stdout.write(`saved: ${result.savedPath}\n`);
|
|
57
|
+
if (flags.budget > 0 && result.body !== "") {
|
|
58
|
+
this.process.stdout.write("---\n");
|
|
59
|
+
this.process.stdout.write(withFinalNewline(clip(result.body, flags.budget, result.savedPath, "document")));
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
interface ReadResult {
|
|
65
|
+
readonly format: string;
|
|
66
|
+
// The reader's stamp (`docx v1`), the same one a sidecar's front matter names, so a caller can keep it beside the text.
|
|
67
|
+
readonly deriver: string;
|
|
68
|
+
readonly body: string;
|
|
69
|
+
readonly tokens: number;
|
|
70
|
+
readonly savedPath: string;
|
|
71
|
+
readonly source: "derived" | "fresh";
|
|
72
|
+
readonly title?: string | undefined;
|
|
73
|
+
readonly notes: string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const readInWorkspace = async (root: string, absPath: string, save: boolean): Promise<ReadResult | undefined> => {
|
|
77
|
+
const outcome = await ensureSidecar(root, absPath);
|
|
78
|
+
if (outcome.kind === "skipped" && outcome.reason === "outside-workspace") {
|
|
79
|
+
return readOutsideWorkspace(absPath, save);
|
|
80
|
+
}
|
|
81
|
+
return fromOutcome(outcome);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const fromOutcome = (outcome: Outcome): ReadResult | undefined => {
|
|
85
|
+
switch (outcome.kind) {
|
|
86
|
+
case "derived":
|
|
87
|
+
return {
|
|
88
|
+
format: outcome.format,
|
|
89
|
+
deriver: deriverStamp(DERIVERS[outcome.format]),
|
|
90
|
+
body: outcome.body,
|
|
91
|
+
tokens: outcome.tokens,
|
|
92
|
+
savedPath: outcome.sidecarPath,
|
|
93
|
+
source: "derived",
|
|
94
|
+
title: outcome.doc.title,
|
|
95
|
+
notes: outcome.doc.notes,
|
|
96
|
+
};
|
|
97
|
+
case "fresh":
|
|
98
|
+
return {
|
|
99
|
+
format: outcome.format,
|
|
100
|
+
deriver: deriverStamp(DERIVERS[outcome.format]),
|
|
101
|
+
body: outcome.body,
|
|
102
|
+
tokens: outcome.tokens,
|
|
103
|
+
savedPath: outcome.sidecarPath,
|
|
104
|
+
source: "fresh",
|
|
105
|
+
notes: [],
|
|
106
|
+
};
|
|
107
|
+
case "removed":
|
|
108
|
+
case "skipped": {
|
|
109
|
+
const reason = outcome.kind === "removed" ? "missing" : outcome.reason;
|
|
110
|
+
process.stdout.write(`fileq: cannot read ${outcome.relPath}: ${reason}\n`);
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/* Outside a workspace there is no sidecar tree; derive in memory and save the whole thing under the XDG, unless the
|
|
117
|
+
caller wants only the text (`--plain`: git hands textconv a temp file per blob, and a saved copy of each would pile up). */
|
|
118
|
+
const readOutsideWorkspace = async (absPath: string, save: boolean): Promise<ReadResult | undefined> => {
|
|
119
|
+
const format = await detectFormat(absPath).catch(() => undefined);
|
|
120
|
+
if (format === undefined) {
|
|
121
|
+
process.stdout.write(`fileq: cannot read ${absPath}: unsupported or missing\n`);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
// The same outcome a corrupt file gets inside a workspace (ensureSidecar's loud skip): a notebook that is
|
|
125
|
+
// not JSON, a docx that is not a zip, answers with the reason and exit 1, never a stack trace.
|
|
126
|
+
let doc: DerivedDoc;
|
|
127
|
+
try {
|
|
128
|
+
doc = neutralizeDoc(await DERIVERS[format].derive(absPath));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
process.stdout.write(`fileq: cannot read ${absPath}: derive-failed (${format}): ${errorMessage(error).split("\n")[0]}\n`);
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const outDir = toolOutDir("fileq");
|
|
134
|
+
const hash = createHash("sha256").update(absPath).digest("hex").slice(0, 8);
|
|
135
|
+
const savedPath = join(
|
|
136
|
+
outDir,
|
|
137
|
+
`${basename(absPath)
|
|
138
|
+
.toLowerCase()
|
|
139
|
+
.replaceAll(/[^a-z0-9.]+/g, "-")}-${hash}.md`,
|
|
140
|
+
);
|
|
141
|
+
if (save) {
|
|
142
|
+
await mkdir(outDir, { recursive: true });
|
|
143
|
+
await writeFile(savedPath, `${doc.markdown}\n`);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
format,
|
|
147
|
+
deriver: deriverStamp(DERIVERS[format]),
|
|
148
|
+
body: doc.markdown,
|
|
149
|
+
tokens: estimateTokens(doc.markdown),
|
|
150
|
+
savedPath,
|
|
151
|
+
source: "derived",
|
|
152
|
+
title: doc.title,
|
|
153
|
+
notes: doc.notes,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// A document's last line is content, and a sidecar body need not end in a newline; a clipped one already does.
|
|
158
|
+
const withFinalNewline = (text: string): string => (text.endsWith("\n") ? text : `${text}\n`);
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
});
|