Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/generator-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ export class MarkdownGeneratorFactory implements GeneratorFactory {
return new MarkdownTopicIndex(topic, commands, topicMeta, this.outputDir);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public createCommand(
topic: string,
subtopic: string | null,
_subtopic: string | null,
command: CommandClass,
commandMeta: Record<string, unknown>
): WritableWithFilename {
return new MarkdownCommand(topic, subtopic, command, commandMeta, this.outputDir);
return new MarkdownCommand(topic, command, commandMeta, this.outputDir);
}
}
36 changes: 14 additions & 22 deletions src/markdown/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@

import { join } from 'node:path';
import { asString, Dictionary, ensureObject, ensureString } from '@salesforce/ts-types';
import { CommandClass, CommandParameterData, punctuate, replaceConfigVariables } from '../utils.js';
import {
CommandClass,
CommandParameterData,
commandFileBase,
escapeAngleBrackets,
punctuate,
replaceConfigVariables,
stateLabel,
} from '../utils.js';
import { buildCommandParameters, FlagInfo, formatParagraphs, readBinary } from '../ditamap/command-helpers.js';
import { MarkdownBase } from './markdown-base.js';

Expand All @@ -40,19 +48,11 @@ export class MarkdownCommand extends MarkdownBase {

public constructor(
topic: string,
subtopic: string | null,
command: CommandClass,
commandMeta: Record<string, unknown> = {},
outputDir: string
) {
const commandWithUnderscores = ensureString(command.id).replace(/:/g, '_');
// If the command ID has no subtopic (e.g. "doctor"), its filename would collide with the topic
// index file (cli_reference_doctor.md), so append _command to disambiguate.
const isTopicLevelCommand = !ensureString(command.id).includes(':');
const baseName = isTopicLevelCommand
? `cli_reference_${commandWithUnderscores}_command`
: `cli_reference_${commandWithUnderscores}`;
const filename = MarkdownBase.file(baseName);
const filename = MarkdownBase.file(commandFileBase(ensureString(command.id)));
super(filename, outputDir);
this.destination = join(outputDir, topic, filename);

Expand Down Expand Up @@ -102,12 +102,12 @@ export class MarkdownCommand extends MarkdownBase {
lines.push('<!-- prettier-ignore-start -->');
lines.push('');

const stateLabel = resolveStateLabel(this.state, this.deprecated);
lines.push(`# ${this.commandName}${stateLabel ? ` (${stateLabel})` : ''}`);
const label = stateLabel(this.state, this.deprecated);
lines.push(`# ${this.commandName}${label ? ` (${label})` : ''}`);
lines.push('');

if (this.summary) {
lines.push(this.summary);
lines.push(escapeAngleBrackets(this.summary));
lines.push('');
}

Expand Down Expand Up @@ -180,7 +180,7 @@ export class MarkdownCommand extends MarkdownBase {

function escapeForMarkdown(text: string): string {
// Escape HTML entities for markdown safety
let result = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
let result = escapeAngleBrackets(text);

// Normalize whitespace: collapse multiple spaces/tabs/newlines to single space
result = result.replace(/\s+/g, ' ');
Expand Down Expand Up @@ -226,14 +226,6 @@ function convertBulletListsToHtml(paragraphs: string[]): string[] {
return result;
}

function resolveStateLabel(state: unknown, deprecated: boolean): string | null {
if (deprecated) return 'Deprecated';
if (state === 'beta') return 'Beta';
if (state === 'preview') return 'Developer Preview';
if (state === 'closedPilot' || state === 'openPilot') return 'Pilot';
return null;
}

function resolveDisclaimer(
commandName: string,
state: unknown,
Expand Down
1 change: 0 additions & 1 deletion src/markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ export { MarkdownBase } from './markdown-base.js';
export { MarkdownCommand } from './command.js';
export { MarkdownCliReference } from './cli-reference.js';
export { MarkdownRootIndex } from './root-index.js';
export { MarkdownTopicCommands } from './topic-commands.js';
export { MarkdownTopicIndex } from './topic-index.js';
export { MarkdownToc } from './toc.js';
23 changes: 4 additions & 19 deletions src/markdown/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@
*/

import { TocTopicEntry } from '../generator-factory.js';
import { commandLinkTarget, stateLabel } from '../utils.js';
import { MarkdownBase } from './markdown-base.js';

const STATE_LABELS: Record<string, string> = {
beta: 'Beta',
preview: 'Developer Preview',
closedPilot: 'Closed Pilot',
openPilot: 'Open Pilot',
deprecated: 'Deprecated',
};

export class MarkdownToc extends MarkdownBase {
public constructor(private topicEntries: TocTopicEntry[], outputDir: string) {
super('sfclireference-toc.yml', outputDir);
Expand Down Expand Up @@ -52,18 +45,10 @@ export class MarkdownToc extends MarkdownBase {
lines.push(` link: ${topic}/cli_reference_${topic}.md`);
lines.push(' topics:');
for (const { id, state, deprecated } of [...commandIds].sort((a, b) => a.id.localeCompare(b.id))) {
const commandWithUnderscores = id.replace(/:/g, '_');
const commandWithSpaces = id.replace(/:/g, ' ');
const stateLabel = deprecated
? ' (Deprecated)'
: state && STATE_LABELS[state]
? ` (${STATE_LABELS[state]})`
: '';
const isTopicLevelCommand = !id.includes(':');
const linkTarget = isTopicLevelCommand
? `cli_reference_${commandWithUnderscores}_command.md`
: `cli_reference_${commandWithUnderscores}.md`;
lines.push(` - title: ${commandWithSpaces}${stateLabel}`);
const label = stateLabel(state, deprecated);
const linkTarget = commandLinkTarget(id);
lines.push(` - title: ${commandWithSpaces}${label ? ` (${label})` : ''}`);
lines.push(` link: ${topic}/${linkTarget}`);
}
}
Expand Down
39 changes: 0 additions & 39 deletions src/markdown/topic-commands.ts

This file was deleted.

27 changes: 6 additions & 21 deletions src/markdown/topic-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,9 @@
*/

import { join } from 'node:path';
import { CommandClass, punctuate, SfTopic } from '../utils.js';
import { CommandClass, commandLinkTarget, escapeAngleBrackets, punctuate, SfTopic, stateLabel } from '../utils.js';
import { MarkdownBase } from './markdown-base.js';

function resolveStateLabel(command: CommandClass): string | null {
const deprecated = Boolean(command.deprecated);
const state = command.state;

if (deprecated) return 'Deprecated';
if (state === 'beta') return 'Beta';
if (state === 'preview') return 'Developer Preview';
if (state === 'closedPilot' || state === 'openPilot') return 'Pilot';
return null;
}

export class MarkdownTopicIndex extends MarkdownBase {
public constructor(
private topic: string,
Expand All @@ -48,24 +37,20 @@ export class MarkdownTopicIndex extends MarkdownBase {
lines.push(`# ${this.topic} Commands`);
lines.push('');
if (this.topicMeta.description) {
lines.push(this.topicMeta.description);
lines.push(escapeAngleBrackets(this.topicMeta.description));
lines.push('');
}
const sortedCommands = [...this.commands].sort((a, b) => a.id.localeCompare(b.id));
for (const command of sortedCommands) {
const id = command.id;
const commandWithUnderscores = id.replace(/:/g, '_');
const commandWithSpaces = id.replace(/:/g, ' ');
const isTopicLevelCommand = !id.includes(':');
const linkTarget = isTopicLevelCommand
? `cli_reference_${commandWithUnderscores}_command.md`
: `cli_reference_${commandWithUnderscores}.md`;
const stateLabel = resolveStateLabel(command);
const commandDisplay = stateLabel ? `${commandWithSpaces} (${stateLabel})` : commandWithSpaces;
const linkTarget = commandLinkTarget(id);
const label = stateLabel(command.state, Boolean(command.deprecated));
const commandDisplay = label ? `${commandWithSpaces} (${label})` : commandWithSpaces;
const summary = punctuate(command.summary);
if (summary) {
lines.push(`- **[${commandDisplay}](./${linkTarget})**<br>`);
lines.push(` ${summary}`);
lines.push(` ${escapeAngleBrackets(summary)}`);
} else {
lines.push(`- **[${commandDisplay}](./${linkTarget})**`);
}
Expand Down
38 changes: 38 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,44 @@ export function punctuate(description?: string): string | undefined {
export const replaceConfigVariables = (text: string, bin: string, id: string): string =>
text.replace(/<%= config.bin %>/g, bin ?? 'unknown').replace(/<%= command.id %>/g, id);

/**
* Converts the `<` and `>` characters to their HTML entity equivalents (`&lt;` and `&gt;`)
* so they render as literal characters instead of being interpreted as HTML tags.
*/
export function escapeAngleBrackets(text: string): string {
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

/**
* Resolves a command's state (and deprecation) to the label shown next to its name, or `null`
* when there's nothing to show. Both pilot states render as "Pilot".
*/
export function stateLabel(state: unknown, deprecated = false): string | null {
if (deprecated) return 'Deprecated';
if (state === 'beta') return 'Beta';
if (state === 'preview') return 'Developer Preview';
if (state === 'closedPilot' || state === 'openPilot') return 'Pilot';
return null;
}

/**
* Returns the base filename (without extension) for a command's reference page.
*
* A command ID with no subtopic (e.g. "doctor") would collide with the topic index file
* (cli_reference_doctor.md), so its page is suffixed with `_command` to disambiguate.
*/
export function commandFileBase(id: string): string {
const commandWithUnderscores = id.replace(/:/g, '_');
return id.includes(':')
? `cli_reference_${commandWithUnderscores}`
: `cli_reference_${commandWithUnderscores}_command`;
}

/** Returns the relative Markdown filename to link to a command's reference page. */
export function commandLinkTarget(id: string): string {
return `${commandFileBase(id)}.md`;
}

export type CliMeta = {
binary: string;
topicSeparator?: string;
Expand Down
Loading