@writechoice/mint-cli 0.0.14 → 0.0.16

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/bin/cli.js CHANGED
@@ -135,6 +135,42 @@ fix
135
135
  await fixCodeblocks(mergedOptions);
136
136
  });
137
137
 
138
+ // Fix inlineimages subcommand
139
+ fix
140
+ .command("inlineimages")
141
+ .description("Convert inline images to <InlineImage> components in MDX files")
142
+ .option("-f, --file <path>", "Fix a single MDX file directly")
143
+ .option("-d, --dir <path>", "Fix MDX files in a specific directory")
144
+ .option("--dry-run", "Preview changes without writing files")
145
+ .option("--quiet", "Suppress terminal output")
146
+ .action(async (options) => {
147
+ const { loadConfig, mergeInlineImagesConfig } = await import("../src/utils/config.js");
148
+ const { fixInlineImages } = await import("../src/commands/fix/inlineimages.js");
149
+
150
+ const config = loadConfig();
151
+ const mergedOptions = mergeInlineImagesConfig(options, config);
152
+ mergedOptions.verbose = !mergedOptions.quiet;
153
+ await fixInlineImages(mergedOptions);
154
+ });
155
+
156
+ // Fix images subcommand
157
+ fix
158
+ .command("images")
159
+ .description("Wrap standalone images in <Frame> components in MDX files")
160
+ .option("-f, --file <path>", "Fix a single MDX file directly")
161
+ .option("-d, --dir <path>", "Fix MDX files in a specific directory")
162
+ .option("--dry-run", "Preview changes without writing files")
163
+ .option("--quiet", "Suppress terminal output")
164
+ .action(async (options) => {
165
+ const { loadConfig, mergeImagesConfig } = await import("../src/utils/config.js");
166
+ const { fixImages } = await import("../src/commands/fix/images.js");
167
+
168
+ const config = loadConfig();
169
+ const mergedOptions = mergeImagesConfig(options, config);
170
+ mergedOptions.verbose = !mergedOptions.quiet;
171
+ await fixImages(mergedOptions);
172
+ });
173
+
138
174
  // Config command
139
175
  program
140
176
  .command("config")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writechoice/mint-cli",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "CLI tool for Mintlify documentation validation and utilities",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -11,6 +11,7 @@
11
11
  "scripts": {
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
13
  "dev": "node bin/cli.js",
14
+ "release": "bash publish.sh",
14
15
  "postinstall": "echo \"\\n⚠️ Don't forget to install Playwright browsers:\\n npx playwright install chromium\\n\""
15
16
  },
16
17
  "keywords": [
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Fix Images Tool
3
+ *
4
+ * Wraps standalone images in MDX files with <Frame> components.
5
+ * Handles both Markdown images (![alt](url)) and HTML <img> tags.
6
+ *
7
+ * Skips images that are already inside:
8
+ * - <Frame> blocks
9
+ * - Fenced code blocks
10
+ * - Markdown tables
11
+ * - HTML tables
12
+ */
13
+
14
+ import { existsSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
15
+ import { join, relative, resolve } from "path";
16
+ import chalk from "chalk";
17
+
18
+ const EXCLUDED_DIRS = ["node_modules", ".git"];
19
+
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // Protection patterns (tokenized so images inside them are never touched)
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ // Existing <Frame>...</Frame> blocks (case-insensitive, with optional attributes)
25
+ const FRAME_RE = /<Frame(?:\s[^>]*)?>[\s\S]*?<\/Frame>/gi;
26
+
27
+ // HTML tables
28
+ const HTML_TABLE_RE = /<table(?:\s[^>]*)?>[\s\S]*?<\/table>/gi;
29
+
30
+ // Markdown tables: one or more consecutive lines starting with |
31
+ const MD_TABLE_RE = /^(?:\|[^\n]*\n)+(?:\|[^\n]*)?/gm;
32
+
33
+ // Fenced code blocks (backtick or tilde, 3+)
34
+ const FENCE_RE = /^[ \t]*(`{3,}|~{3,})[ \t]*[^\n]*\n[\s\S]*?\n[ \t]*\1[ \t]*$/gm;
35
+
36
+ // ─────────────────────────────────────────────────────────────────────────────
37
+ // Image patterns (applied after protection)
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+
40
+ // Standalone Markdown image on its own line: ![alt](url)
41
+ const MD_IMAGE_RE = /^([ \t]*)(!\[[^\]\n]*\]\([^\)\n]+\))[ \t]*$/gm;
42
+
43
+ // Standalone HTML <img> tag on its own line
44
+ const HTML_IMG_RE = /^([ \t]*)(<img\b[^>\n]*\/?>)[ \t]*$/gm;
45
+
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+ // Tokenizer helpers
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+
50
+ function tokenize(pattern, text, tag) {
51
+ const stash = [];
52
+ const result = text.replace(pattern, (match) => {
53
+ const idx = stash.length;
54
+ stash.push(match);
55
+ return `\x00${tag}${idx}\x00`;
56
+ });
57
+ return { text: result, stash };
58
+ }
59
+
60
+ function detokenize(text, tag, stash) {
61
+ const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62
+ return text.replace(new RegExp(`\x00${escapedTag}(\\d+)\x00`, "g"), (_, idx) => {
63
+ return stash[parseInt(idx, 10)];
64
+ });
65
+ }
66
+
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+ // Core processing
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+
71
+ /**
72
+ * Processes MDX content and wraps standalone images in <Frame> components.
73
+ * Returns { newContent, count }.
74
+ */
75
+ function processContent(content) {
76
+ let text = content;
77
+
78
+ // 1. Protect existing <Frame> blocks
79
+ const frameResult = tokenize(FRAME_RE, text, "FRAME");
80
+ text = frameResult.text;
81
+
82
+ // 2. Protect HTML tables
83
+ const htmlTableResult = tokenize(HTML_TABLE_RE, text, "HTMLTABLE");
84
+ text = htmlTableResult.text;
85
+
86
+ // 3. Protect Markdown tables
87
+ const mdTableResult = tokenize(MD_TABLE_RE, text, "MDTABLE");
88
+ text = mdTableResult.text;
89
+
90
+ // 4. Protect fenced code blocks
91
+ const fenceResult = tokenize(FENCE_RE, text, "FENCE");
92
+ text = fenceResult.text;
93
+
94
+ // 5. Wrap standalone images
95
+ let count = 0;
96
+
97
+ text = text.replace(MD_IMAGE_RE, (match, indent, image) => {
98
+ count++;
99
+ return `${indent}<Frame>\n${indent}${image}\n${indent}</Frame>`;
100
+ });
101
+
102
+ text = text.replace(HTML_IMG_RE, (match, indent, tag) => {
103
+ count++;
104
+ return `${indent}<Frame>\n${indent}${tag}\n${indent}</Frame>`;
105
+ });
106
+
107
+ // 6. Restore all protected regions (reverse order)
108
+ text = detokenize(text, "FENCE", fenceResult.stash);
109
+ text = detokenize(text, "MDTABLE", mdTableResult.stash);
110
+ text = detokenize(text, "HTMLTABLE", htmlTableResult.stash);
111
+ text = detokenize(text, "FRAME", frameResult.stash);
112
+
113
+ return { newContent: text, count };
114
+ }
115
+
116
+ // ─────────────────────────────────────────────────────────────────────────────
117
+ // File discovery
118
+ // ─────────────────────────────────────────────────────────────────────────────
119
+
120
+ function findMdxFiles(repoRoot, directory = null, file = null) {
121
+ if (file) {
122
+ const fullPath = resolve(repoRoot, file);
123
+ return existsSync(fullPath) ? [fullPath] : [];
124
+ }
125
+
126
+ const searchDirs = directory ? [resolve(repoRoot, directory)] : [repoRoot];
127
+ const mdxFiles = [];
128
+
129
+ function walkDirectory(dir) {
130
+ const dirName = dir.split("/").pop();
131
+ if (EXCLUDED_DIRS.includes(dirName)) return;
132
+
133
+ try {
134
+ const entries = readdirSync(dir);
135
+ for (const entry of entries) {
136
+ const fullPath = join(dir, entry);
137
+ const stat = statSync(fullPath);
138
+ if (stat.isDirectory()) {
139
+ walkDirectory(fullPath);
140
+ } else if (stat.isFile() && entry.endsWith(".mdx")) {
141
+ mdxFiles.push(fullPath);
142
+ }
143
+ }
144
+ } catch (error) {
145
+ console.error(`Error reading directory ${dir}: ${error.message}`);
146
+ }
147
+ }
148
+
149
+ for (const dir of searchDirs) {
150
+ if (existsSync(dir)) walkDirectory(dir);
151
+ }
152
+
153
+ return mdxFiles.sort();
154
+ }
155
+
156
+ // ─────────────────────────────────────────────────────────────────────────────
157
+ // Main export
158
+ // ─────────────────────────────────────────────────────────────────────────────
159
+
160
+ export async function fixImages(options) {
161
+ const repoRoot = process.cwd();
162
+
163
+ if (!options.quiet) {
164
+ console.log(chalk.bold("\n🖼️ Image Frame Fixer\n"));
165
+ }
166
+
167
+ const files = findMdxFiles(repoRoot, options.dir, options.file);
168
+
169
+ if (files.length === 0) {
170
+ console.error(chalk.red("✗ No MDX files found."));
171
+ process.exit(1);
172
+ }
173
+
174
+ if (!options.quiet) {
175
+ console.log(`Found ${files.length} MDX file(s) to process\n`);
176
+ if (options.dryRun) {
177
+ console.log(chalk.yellow("Dry run — no files will be written\n"));
178
+ }
179
+ }
180
+
181
+ const results = {};
182
+ let totalImages = 0;
183
+
184
+ for (const filePath of files) {
185
+ const content = readFileSync(filePath, "utf-8");
186
+ const { newContent, count } = processContent(content);
187
+
188
+ if (count > 0) {
189
+ const relPath = relative(repoRoot, filePath);
190
+ results[relPath] = count;
191
+ totalImages += count;
192
+
193
+ if (options.verbose) {
194
+ console.log(`${chalk.cyan(relPath)}: wrapped ${count} image(s)`);
195
+ }
196
+
197
+ if (!options.dryRun) {
198
+ writeFileSync(filePath, newContent, "utf-8");
199
+ }
200
+ }
201
+ }
202
+
203
+ // Summary
204
+ if (!options.quiet) {
205
+ const fileCount = Object.keys(results).length;
206
+
207
+ if (fileCount > 0) {
208
+ const verb = options.dryRun ? "Would wrap" : "Wrapped";
209
+ console.log(chalk.green(`\n✓ ${verb} ${totalImages} image(s) in ${fileCount} file(s)`));
210
+
211
+ if (!options.verbose) {
212
+ for (const [filePath, count] of Object.entries(results)) {
213
+ console.log(` ${chalk.cyan(filePath)}: ${count} image(s)`);
214
+ }
215
+ }
216
+ } else {
217
+ console.log(chalk.yellow("⚠️ No unwrapped images found."));
218
+ }
219
+ }
220
+ }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Fix Inline Images Tool
3
+ *
4
+ * Converts images that appear inline within text lines to <InlineImage> components.
5
+ * Handles both Markdown images (![alt](url)) and HTML <img> tags.
6
+ *
7
+ * - Inline image (has other text on the line) → <InlineImage src="url" />
8
+ * - Standalone image (alone on its line) → left unchanged (use fix images)
9
+ *
10
+ * Also adds the required import at the top of the file (after frontmatter):
11
+ * import { InlineImage } from "/snippets/InlineImage.jsx";
12
+ *
13
+ * Skips images inside:
14
+ * - Fenced code blocks
15
+ * - Inline code spans
16
+ * - Markdown tables
17
+ * - HTML tables
18
+ * - <Frame> blocks
19
+ */
20
+
21
+ import { existsSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
22
+ import { join, relative, resolve } from "path";
23
+ import chalk from "chalk";
24
+
25
+ const EXCLUDED_DIRS = ["node_modules", ".git"];
26
+
27
+ const IMPORT_LINE = 'import { InlineImage } from "/snippets/InlineImage.jsx";';
28
+
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+ // Protection patterns (multi-line regions tokenized before line processing)
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+
33
+ const FRAME_RE = /<Frame(?:\s[^>]*)?>[\s\S]*?<\/Frame>/gi;
34
+ const HTML_TABLE_RE = /<table(?:\s[^>]*)?>[\s\S]*?<\/table>/gi;
35
+ const MD_TABLE_RE = /^(?:\|[^\n]*\n)+(?:\|[^\n]*)?/gm;
36
+ const FENCE_RE = /^[ \t]*(`{3,}|~{3,})[ \t]*[^\n]*\n[\s\S]*?\n[ \t]*\1[ \t]*$/gm;
37
+
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ // Tokenizer helpers
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ function tokenize(pattern, text, tag) {
43
+ const stash = [];
44
+ const result = text.replace(pattern, (match) => {
45
+ const idx = stash.length;
46
+ stash.push(match);
47
+ return `\x00${tag}${idx}\x00`;
48
+ });
49
+ return { text: result, stash };
50
+ }
51
+
52
+ function detokenize(text, tag, stash) {
53
+ const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
54
+ return text.replace(new RegExp(`\x00${escapedTag}(\\d+)\x00`, "g"), (_, idx) => {
55
+ return stash[parseInt(idx, 10)];
56
+ });
57
+ }
58
+
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+ // Import injection
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+
63
+ /**
64
+ * Returns the index immediately after the frontmatter closing ---, or -1 if none.
65
+ */
66
+ function findFrontmatterEnd(content) {
67
+ if (!content.startsWith("---")) return -1;
68
+ const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
69
+ if (!match) return -1;
70
+ return match[0].length;
71
+ }
72
+
73
+ /**
74
+ * Ensures the InlineImage import is present in the file.
75
+ * Inserts after frontmatter (if any) with an empty line below.
76
+ */
77
+ function ensureImport(content) {
78
+ if (content.includes(IMPORT_LINE)) return content;
79
+
80
+ const fmEnd = findFrontmatterEnd(content);
81
+
82
+ if (fmEnd === -1) {
83
+ // No frontmatter — insert at top
84
+ return IMPORT_LINE + "\n\n" + content;
85
+ }
86
+
87
+ const before = content.slice(0, fmEnd);
88
+ const after = content.slice(fmEnd).replace(/^\n+/, ""); // normalise blank lines
89
+ return before + "\n" + IMPORT_LINE + "\n\n" + after;
90
+ }
91
+
92
+ // ─────────────────────────────────────────────────────────────────────────────
93
+ // Per-line processing
94
+ // ─────────────────────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Processes a single line: replaces inline images (those sharing the line with text).
98
+ * Protects inline code spans so images inside backticks are not touched.
99
+ * Returns { line, count }.
100
+ */
101
+ function processLine(line) {
102
+ // Quick check — skip lines with no image syntax at all
103
+ if (!line.includes("![") && !/<img\b/i.test(line)) {
104
+ return { line, count: 0 };
105
+ }
106
+
107
+ // Protect inline code spans within this line
108
+ const inlineCodeResult = tokenize(/`[^`\n]+`/g, line, "ICODE");
109
+ let text = inlineCodeResult.text;
110
+
111
+ const hasMdImage = /!\[[^\]\n]*\]\([^\)\n]+\)/.test(text);
112
+ const hasHtmlImg = /<img\b[^>\n]*\/?>/i.test(text);
113
+
114
+ if (!hasMdImage && !hasHtmlImg) {
115
+ return { line, count: 0 };
116
+ }
117
+
118
+ // Strip all images to check whether there's other text content on the line
119
+ const withoutImages = text
120
+ .replace(/!\[[^\]\n]*\]\([^\)\n]+\)/g, "")
121
+ .replace(/<img\b[^>\n]*\/?>/gi, "")
122
+ .trim();
123
+
124
+ if (withoutImages === "") {
125
+ // Line contains only images (standalone) — leave for fix images command
126
+ return { line, count: 0 };
127
+ }
128
+
129
+ // ── Replace inline markdown images ──────────────────────────────────────────
130
+ // Negative lookbehind (?<!\[) prevents matching linked images [![alt](url)](link)
131
+ let count = 0;
132
+ text = text.replace(/(?<!\[)!\[([^\]\n]*)\]\(([^\)\n]+)\)/g, (match, alt, src) => {
133
+ count++;
134
+ const altProp = alt.trim() ? ` alt="${alt.trim()}"` : "";
135
+ return `<InlineImage src="${src}"${altProp} />`;
136
+ });
137
+
138
+ // ── Replace inline HTML <img> tags ──────────────────────────────────────────
139
+ // Rename <img ...> / <img ... /> to <InlineImage ... />, preserving all attributes
140
+ text = text.replace(/<img\b([^>]*?)(\s*\/?)>/gi, (match, attrs) => {
141
+ if (!attrs.includes("src")) return match; // no src — leave as-is
142
+ count++;
143
+ const cleanAttrs = attrs.trimEnd().replace(/\/$/, "").trimEnd();
144
+ return `<InlineImage${cleanAttrs} />`;
145
+ });
146
+
147
+ // Restore inline code spans
148
+ text = detokenize(text, "ICODE", inlineCodeResult.stash);
149
+
150
+ return { line: text, count };
151
+ }
152
+
153
+ // ─────────────────────────────────────────────────────────────────────────────
154
+ // Core processing
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+
157
+ /**
158
+ * Processes MDX content: replaces inline images and injects the import.
159
+ * Returns { newContent, count }.
160
+ */
161
+ function processContent(content) {
162
+ let text = content;
163
+
164
+ // 1. Protect multi-line regions
165
+ const frameResult = tokenize(FRAME_RE, text, "FRAME");
166
+ text = frameResult.text;
167
+
168
+ const htmlTableResult = tokenize(HTML_TABLE_RE, text, "HTMLTABLE");
169
+ text = htmlTableResult.text;
170
+
171
+ const mdTableResult = tokenize(MD_TABLE_RE, text, "MDTABLE");
172
+ text = mdTableResult.text;
173
+
174
+ const fenceResult = tokenize(FENCE_RE, text, "FENCE");
175
+ text = fenceResult.text;
176
+
177
+ // 2. Process line by line
178
+ let totalCount = 0;
179
+ const lines = text.split("\n");
180
+ const processedLines = lines.map((line) => {
181
+ const { line: newLine, count } = processLine(line);
182
+ totalCount += count;
183
+ return newLine;
184
+ });
185
+ text = processedLines.join("\n");
186
+
187
+ // 3. Restore protected regions
188
+ text = detokenize(text, "FENCE", fenceResult.stash);
189
+ text = detokenize(text, "MDTABLE", mdTableResult.stash);
190
+ text = detokenize(text, "HTMLTABLE", htmlTableResult.stash);
191
+ text = detokenize(text, "FRAME", frameResult.stash);
192
+
193
+ // 4. Add import if any replacements were made
194
+ if (totalCount > 0) {
195
+ text = ensureImport(text);
196
+ }
197
+
198
+ return { newContent: text, count: totalCount };
199
+ }
200
+
201
+ // ─────────────────────────────────────────────────────────────────────────────
202
+ // File discovery
203
+ // ─────────────────────────────────────────────────────────────────────────────
204
+
205
+ function findMdxFiles(repoRoot, directory = null, file = null) {
206
+ if (file) {
207
+ const fullPath = resolve(repoRoot, file);
208
+ return existsSync(fullPath) ? [fullPath] : [];
209
+ }
210
+
211
+ const searchDirs = directory ? [resolve(repoRoot, directory)] : [repoRoot];
212
+ const mdxFiles = [];
213
+
214
+ function walkDirectory(dir) {
215
+ const dirName = dir.split("/").pop();
216
+ if (EXCLUDED_DIRS.includes(dirName)) return;
217
+
218
+ try {
219
+ const entries = readdirSync(dir);
220
+ for (const entry of entries) {
221
+ const fullPath = join(dir, entry);
222
+ const stat = statSync(fullPath);
223
+ if (stat.isDirectory()) {
224
+ walkDirectory(fullPath);
225
+ } else if (stat.isFile() && entry.endsWith(".mdx")) {
226
+ mdxFiles.push(fullPath);
227
+ }
228
+ }
229
+ } catch (error) {
230
+ console.error(`Error reading directory ${dir}: ${error.message}`);
231
+ }
232
+ }
233
+
234
+ for (const dir of searchDirs) {
235
+ if (existsSync(dir)) walkDirectory(dir);
236
+ }
237
+
238
+ return mdxFiles.sort();
239
+ }
240
+
241
+ // ─────────────────────────────────────────────────────────────────────────────
242
+ // Main export
243
+ // ─────────────────────────────────────────────────────────────────────────────
244
+
245
+ export async function fixInlineImages(options) {
246
+ const repoRoot = process.cwd();
247
+
248
+ if (!options.quiet) {
249
+ console.log(chalk.bold("\n🖼️ Inline Image Fixer\n"));
250
+ }
251
+
252
+ const files = findMdxFiles(repoRoot, options.dir, options.file);
253
+
254
+ if (files.length === 0) {
255
+ console.error(chalk.red("✗ No MDX files found."));
256
+ process.exit(1);
257
+ }
258
+
259
+ if (!options.quiet) {
260
+ console.log(`Found ${files.length} MDX file(s) to process\n`);
261
+ if (options.dryRun) {
262
+ console.log(chalk.yellow("Dry run — no files will be written\n"));
263
+ }
264
+ }
265
+
266
+ const results = {};
267
+ let totalImages = 0;
268
+
269
+ for (const filePath of files) {
270
+ const content = readFileSync(filePath, "utf-8");
271
+ const { newContent, count } = processContent(content);
272
+
273
+ if (count > 0) {
274
+ const relPath = relative(repoRoot, filePath);
275
+ results[relPath] = count;
276
+ totalImages += count;
277
+
278
+ if (options.verbose) {
279
+ console.log(`${chalk.cyan(relPath)}: converted ${count} inline image(s)`);
280
+ }
281
+
282
+ if (!options.dryRun) {
283
+ writeFileSync(filePath, newContent, "utf-8");
284
+ }
285
+ }
286
+ }
287
+
288
+ // Summary
289
+ if (!options.quiet) {
290
+ const fileCount = Object.keys(results).length;
291
+
292
+ if (fileCount > 0) {
293
+ const verb = options.dryRun ? "Would convert" : "Converted";
294
+ console.log(chalk.green(`\n✓ ${verb} ${totalImages} inline image(s) in ${fileCount} file(s)`));
295
+
296
+ if (!options.verbose) {
297
+ for (const [filePath, count] of Object.entries(results)) {
298
+ console.log(` ${chalk.cyan(filePath)}: ${count} inline image(s)`);
299
+ }
300
+ }
301
+ } else {
302
+ console.log(chalk.yellow("⚠️ No inline images found."));
303
+ }
304
+ }
305
+ }
@@ -141,6 +141,44 @@ export function mergeCodeblocksConfig(options, config) {
141
141
  };
142
142
  }
143
143
 
144
+ /**
145
+ * Merges config file with CLI options for the inlineimages command
146
+ * CLI options take precedence over config file
147
+ *
148
+ * @param {Object} options - CLI options
149
+ * @param {Object|null} config - Loaded config object
150
+ * @returns {Object} Merged options
151
+ */
152
+ export function mergeInlineImagesConfig(options, config) {
153
+ const imgConfig = config?.inlineimages || {};
154
+
155
+ return {
156
+ file: options.file || imgConfig.file || null,
157
+ dir: options.dir || imgConfig.dir || null,
158
+ dryRun: options.dryRun !== undefined ? options.dryRun : (imgConfig["dry-run"] ?? false),
159
+ quiet: options.quiet !== undefined ? options.quiet : (imgConfig.quiet ?? false),
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Merges config file with CLI options for the images command
165
+ * CLI options take precedence over config file
166
+ *
167
+ * @param {Object} options - CLI options
168
+ * @param {Object|null} config - Loaded config object
169
+ * @returns {Object} Merged options
170
+ */
171
+ export function mergeImagesConfig(options, config) {
172
+ const imgConfig = config?.images || {};
173
+
174
+ return {
175
+ file: options.file || imgConfig.file || null,
176
+ dir: options.dir || imgConfig.dir || null,
177
+ dryRun: options.dryRun !== undefined ? options.dryRun : (imgConfig["dry-run"] ?? false),
178
+ quiet: options.quiet !== undefined ? options.quiet : (imgConfig.quiet ?? false),
179
+ };
180
+ }
181
+
144
182
  /**
145
183
  * Validates that required fields are present
146
184
  * @param {string|undefined} baseUrl - Base URL