@bendyline/squisq-cli 2.3.3 → 2.4.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/README.md CHANGED
@@ -121,6 +121,29 @@ squisq validate ./my-folder --strict
121
121
 
122
122
  Diagnostics are reported at three severities — `error`, `warning`, and `info` (info is counted and shown separately). Exit codes depend on **errors** only: `0` clean, warnings-only, or info-only; `1` errors (or any warning with `--strict`); `2` input unreadable.
123
123
 
124
+ ### `squisq transform <input>`
125
+
126
+ Apply one-time markdown **source** transforms to a `.md` file, in the order given: `unwrap` (remove forced line wrapping so each paragraph is one line — hard breaks kept), `wrap` (hard-wrap paragraph prose at a column width on word boundaries — code, tables, and headings untouched), and `cleanup` (canonical house-style normalization: bullets, emphasis, headings, table padding, spacing; annotations and frontmatter preserved). Not to be confused with the `--transform <style>` slideshow-style flag on `convert`/`video` — this command rewrites the markdown text itself.
127
+
128
+ The transformed markdown goes to stdout by default (status messages go to stderr, so it pipes cleanly).
129
+
130
+ ```bash
131
+ squisq transform doc.md --ops unwrap
132
+ squisq transform doc.md --ops unwrap,cleanup > cleaned.md
133
+ squisq transform doc.md --ops wrap --width 100 --in-place
134
+ squisq transform doc.md --ops cleanup -o cleaned.md
135
+ ```
136
+
137
+ | Option | Description |
138
+ | -------------- | -------------------------------------------------------------------------- |
139
+ | `--ops <list>` | Comma-separated transforms, applied in order (`unwrap`, `wrap`, `cleanup`) |
140
+ | `--width <n>` | Column width for `wrap` (20–500, default 80) |
141
+ | `-o <file>` | Write the result to a file (refuses to overwrite without `--overwrite`) |
142
+ | `--in-place` | Rewrite the input file (conflicts with `-o`) |
143
+ | `--overwrite` | Allow `-o` to replace an existing file |
144
+
145
+ Transforms run in strict mode: each one reparses its output and structurally compares it against the input document; if equivalence cannot be proven, the command exits `1` without emitting anything. Exit codes: `0` success, `1` transform/output failure, `2` input unreadable.
146
+
124
147
  ## Input Formats
125
148
 
126
149
  All commands accept:
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import "./chunk-GGVASRPG.js";
12
12
 
13
13
  // src/index.ts
14
14
  import { createRequire } from "module";
15
- import { Command } from "commander";
15
+ import { Command as Command2 } from "commander";
16
16
 
17
17
  // src/commands/convert.ts
18
18
  import { mkdir } from "fs/promises";
@@ -627,6 +627,91 @@ async function runDoctor(runtime = defaultDoctorRuntime) {
627
627
  return allPresent;
628
628
  }
629
629
 
630
+ // src/commands/transform.ts
631
+ import { mkdir as mkdir3, readFile } from "fs/promises";
632
+ import { dirname as dirname4, resolve as resolve4 } from "path";
633
+ import { Option as Option2 } from "commander";
634
+ import {
635
+ DEFAULT_WRAP_WIDTH,
636
+ MARKDOWN_SOURCE_TRANSFORMS,
637
+ applyMarkdownSourceTransform
638
+ } from "@bendyline/squisq/markdown";
639
+ var VALID_OPS = MARKDOWN_SOURCE_TRANSFORMS.map((t) => t.id);
640
+ function registerTransformCommand(program2) {
641
+ program2.command("transform").description("Apply one-time markdown source transforms (unwrap, wrap, cleanup) to a document").argument("<input>", "Path to a markdown (.md) file").requiredOption(
642
+ "--ops <list>",
643
+ `Comma-separated transforms, applied in order. Valid: ${VALID_OPS.join(", ")}`
644
+ ).option(
645
+ "--width <n>",
646
+ "Column width for the wrap transform (20\u2013500)",
647
+ String(DEFAULT_WRAP_WIDTH)
648
+ ).addOption(
649
+ new Option2("-o, --output <file>", "Write the result to a file (default: stdout)").conflicts(
650
+ "inPlace"
651
+ )
652
+ ).addOption(
653
+ new Option2("--in-place", "Rewrite the input file with the result").conflicts("output")
654
+ ).option("--overwrite", "Replace an existing --output file (default: refuse and exit non-zero)").action(async (inputPath, opts) => {
655
+ try {
656
+ process.exitCode = await runTransform(inputPath, opts);
657
+ } catch (err) {
658
+ const message = err instanceof Error ? err.message : String(err);
659
+ console.error(`Error: ${message}`);
660
+ process.exitCode = 1;
661
+ }
662
+ });
663
+ }
664
+ async function runTransform(inputPath, opts) {
665
+ const resolvedInput = resolve4(inputPath);
666
+ let source;
667
+ try {
668
+ source = await readFile(resolvedInput, { encoding: "utf-8" });
669
+ } catch (err) {
670
+ const message = err instanceof Error ? err.message : String(err);
671
+ console.error(`Error: could not read input: ${message}`);
672
+ return 2;
673
+ }
674
+ const ids = opts.ops.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
675
+ if (ids.length === 0) {
676
+ console.error(`Error: --ops needs at least one transform (valid: ${VALID_OPS.join(", ")})`);
677
+ return 1;
678
+ }
679
+ for (const id of ids) {
680
+ if (!VALID_OPS.includes(id)) {
681
+ console.error(`Error: unknown transform "${id}" (valid: ${VALID_OPS.join(", ")})`);
682
+ return 1;
683
+ }
684
+ }
685
+ const width = Number.parseInt(opts.width, 10);
686
+ if (Number.isNaN(width)) {
687
+ console.error(`Error: --width must be a number (got "${opts.width}")`);
688
+ return 1;
689
+ }
690
+ let result = source;
691
+ const applied = [];
692
+ for (const id of ids) {
693
+ const step = applyMarkdownSourceTransform(id, result, { width, strict: true });
694
+ result = step.output;
695
+ applied.push(step.changed ? id : `${id} (no changes)`);
696
+ }
697
+ const summary = applied.join(", ");
698
+ const bytes = Buffer.from(result, "utf-8");
699
+ if (opts.inPlace) {
700
+ await writeFileGuarded(resolvedInput, bytes, true);
701
+ console.error(`\u2713 ${summary} \u2192 ${resolvedInput}`);
702
+ } else if (opts.output) {
703
+ const resolvedOutput = resolve4(opts.output);
704
+ await assertOutputsWritable([resolvedOutput], opts.overwrite);
705
+ await mkdir3(dirname4(resolvedOutput), { recursive: true });
706
+ await writeFileGuarded(resolvedOutput, bytes, opts.overwrite);
707
+ console.error(`\u2713 ${summary} \u2192 ${resolvedOutput}`);
708
+ } else {
709
+ process.stdout.write(result);
710
+ console.error(`\u2713 ${summary}`);
711
+ }
712
+ return 0;
713
+ }
714
+
630
715
  // src/index.ts
631
716
  var require2 = createRequire(import.meta.url);
632
717
  var { version } = require2("../package.json");
@@ -635,10 +720,11 @@ if (process.stderr.isTTY) {
635
720
  `\x1B[36m{[\x1B[0m \x1B[1msquiggly square\x1B[0m \x1B[2m\u2014\x1B[0m \x1B[1msquisq\x1B[0m \x1B[2m\u2014\x1B[0m \x1B[33mv${version}\x1B[0m \x1B[36m]}\x1B[0m`
636
721
  );
637
722
  }
638
- var program = new Command();
723
+ var program = new Command2();
639
724
  program.name("squisq").description("Squisq CLI \u2014 convert and process markdown-based documents").version(version);
640
725
  registerConvertCommand(program);
641
726
  registerVideoCommand(program);
642
727
  registerValidateCommand(program);
643
728
  registerDoctorCommand(program);
729
+ registerTransformCommand(program);
644
730
  program.parse();