@mintlify/cli 4.0.1023 → 4.0.1024

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.
@@ -0,0 +1,153 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { select, input, editor } from '@inquirer/prompts';
12
+ import { addLog, addLogs, SuccessLog } from '@mintlify/previewing';
13
+ import fse from 'fs-extra';
14
+ import { Text } from 'ink';
15
+ import path from 'path';
16
+ import { CMD_EXEC_PATH } from './constants.js';
17
+ export function slugify(name) {
18
+ return name
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, '-')
21
+ .replace(/^-|-$/g, '');
22
+ }
23
+ const CRON_FIELD = /^(\*|(\*\/\d+)|(\d+(-\d+)?(,\d+(-\d+)?)*))$/;
24
+ export function isValidCron(expr) {
25
+ const fields = expr.trim().split(/\s+/);
26
+ if (fields.length !== 5)
27
+ return false;
28
+ return fields.every((f) => CRON_FIELD.test(f));
29
+ }
30
+ function escapeYaml(value) {
31
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
32
+ }
33
+ export function buildFrontmatter(config) {
34
+ var _a;
35
+ const lines = [];
36
+ lines.push(`name: "${escapeYaml(config.name)}"`);
37
+ if (config.triggerType === 'push') {
38
+ lines.push('on:');
39
+ lines.push(' push:');
40
+ if (config.triggerRepos) {
41
+ for (const repo of config.triggerRepos) {
42
+ lines.push(` - repo: "${escapeYaml(repo)}"`);
43
+ }
44
+ }
45
+ }
46
+ else {
47
+ lines.push('on:');
48
+ lines.push(` cron: "${escapeYaml((_a = config.cronExpression) !== null && _a !== void 0 ? _a : '')}"`);
49
+ }
50
+ if (config.contextRepos && config.contextRepos.length > 0) {
51
+ lines.push('context:');
52
+ for (const repo of config.contextRepos) {
53
+ lines.push(` - repo: "${escapeYaml(repo)}"`);
54
+ }
55
+ }
56
+ if (config.automerge) {
57
+ lines.push('automerge: true');
58
+ }
59
+ return `---\n${lines.join('\n')}\n---`;
60
+ }
61
+ const sendUsageMessageForAI = () => {
62
+ addLogs(_jsx(Text, { children: "Agent Detected - sending AI friendly prompt" }), _jsx(Text, { children: '<system-message>' }), _jsx(Text, { children: "Example usage of `mintlify workflow`. This command is interactive and creates a workflow file in `.mintlify/workflows/`." }), _jsx(Text, { children: "Workflow files are Markdown files with YAML frontmatter. Instead of running this command, you can directly create a `.mintlify/workflows/your-workflow.md` file." }), _jsx(Text, { children: "Frontmatter fields: name (string), on.cron (string) or on.push with repo list, context (array of objects with repo key), automerge (boolean, only when true)." }), _jsx(Text, { children: "The Markdown body contains the agent instructions/prompt." }), _jsx(Text, { children: '</system-message>' }));
63
+ };
64
+ export function addWorkflow() {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ const docsJsonPath = path.join(CMD_EXEC_PATH, 'docs.json');
67
+ if (!(yield fse.pathExists(docsJsonPath))) {
68
+ throw new Error('docs.json not found in the current directory. Please run this command from your docs repository root.');
69
+ }
70
+ const isInteractive = process.stdin.isTTY;
71
+ const isClaudeCode = process.env.CLAUDECODE === '1';
72
+ const isAI = !isInteractive || isClaudeCode;
73
+ if (isAI) {
74
+ sendUsageMessageForAI();
75
+ return;
76
+ }
77
+ const workflowName = yield input({
78
+ message: 'Workflow name',
79
+ default: 'Update changelog',
80
+ });
81
+ if (!workflowName.trim()) {
82
+ throw new Error('Workflow name cannot be empty.');
83
+ }
84
+ const slug = slugify(workflowName);
85
+ if (!slug) {
86
+ throw new Error('Workflow name must contain at least one alphanumeric character.');
87
+ }
88
+ const triggerType = yield select({
89
+ message: 'Trigger type',
90
+ choices: [
91
+ { name: 'Cron (scheduled)', value: 'cron' },
92
+ { name: 'Push (on push to repo)', value: 'push' },
93
+ ],
94
+ });
95
+ let cronExpression;
96
+ let triggerRepos = [];
97
+ if (triggerType === 'cron') {
98
+ cronExpression = yield input({
99
+ message: 'Cron expression',
100
+ default: '0 9 * * 1',
101
+ validate: (value) => isValidCron(value) ||
102
+ 'Invalid cron expression. Expected 5 fields: minute hour day-of-month month day-of-week (e.g. 0 9 * * 1).',
103
+ });
104
+ }
105
+ else {
106
+ const triggerReposInput = yield input({
107
+ message: 'Trigger repos (comma-separated, e.g. your-org/your-docs)',
108
+ default: '',
109
+ });
110
+ triggerRepos = triggerReposInput
111
+ .split(',')
112
+ .map((r) => r.trim())
113
+ .filter(Boolean);
114
+ }
115
+ const contextReposInput = yield input({
116
+ message: 'Context repos (comma-separated, e.g. your-org/your-product, optional)',
117
+ default: '',
118
+ });
119
+ const contextRepos = contextReposInput
120
+ .split(',')
121
+ .map((r) => r.trim())
122
+ .filter(Boolean);
123
+ const automergeChoice = yield select({
124
+ message: 'Enable automerge?',
125
+ choices: [
126
+ { name: 'Yes', value: 'yes' },
127
+ { name: 'No', value: 'no' },
128
+ ],
129
+ });
130
+ const instructions = yield editor({
131
+ message: 'Agent instructions (opens your default editor)',
132
+ default: '# Agent Instructions\n\nDescribe what this workflow should do.\n\nFor example:\n- Update the changelog based on recent commits\n- Summarize recent PRs\n',
133
+ });
134
+ const frontmatter = buildFrontmatter({
135
+ name: workflowName,
136
+ triggerType,
137
+ cronExpression,
138
+ triggerRepos,
139
+ contextRepos: contextRepos.length > 0 ? contextRepos : undefined,
140
+ automerge: automergeChoice === 'yes',
141
+ });
142
+ const filename = slug + '.md';
143
+ const workflowDir = path.join(CMD_EXEC_PATH, '.mintlify', 'workflows');
144
+ const filePath = path.join(workflowDir, filename);
145
+ const relativePath = path.relative(CMD_EXEC_PATH, filePath);
146
+ yield fse.ensureDir(workflowDir);
147
+ if (yield fse.pathExists(filePath)) {
148
+ throw new Error(`A workflow already exists at ${relativePath}. Please choose a different name or delete the existing file.`);
149
+ }
150
+ yield fse.writeFile(filePath, frontmatter + '\n\n' + instructions.trim() + '\n');
151
+ addLog(_jsx(SuccessLog, { message: `Workflow created at ${relativePath}` }));
152
+ });
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1023",
3
+ "version": "4.0.1024",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -87,5 +87,5 @@
87
87
  "vitest": "2.0.4",
88
88
  "vitest-mock-process": "1.0.4"
89
89
  },
90
- "gitHead": "0f79fec28aa549e0ab75193dd90407148608e963"
90
+ "gitHead": "7095433b39d9eaebefa9e96357796b9b84cf511e"
91
91
  }
package/src/cli.tsx CHANGED
@@ -39,6 +39,7 @@ import { mdxLinter } from './mdxLinter.js';
39
39
  import { migrateMdx } from './migrateMdx.js';
40
40
  import { scrapeSite, scrapePage, scrapeOpenApi } from './scrape.js';
41
41
  import { update } from './update.js';
42
+ import { addWorkflow } from './workflow.js';
42
43
 
43
44
  export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
44
45
  render(<Logs />);
@@ -457,6 +458,22 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
457
458
  }
458
459
  }
459
460
  )
461
+ .command(
462
+ 'workflow',
463
+ 'Add a workflow to your documentation repository',
464
+ () => undefined,
465
+ async () => {
466
+ try {
467
+ await addWorkflow();
468
+ await terminate(0);
469
+ } catch (error) {
470
+ addLog(
471
+ <ErrorLog message={error instanceof Error ? error.message : 'error occurred'} />
472
+ );
473
+ await terminate(1);
474
+ }
475
+ }
476
+ )
460
477
  .command('scrape', 'Scrape documentation from external sites', (yargs) =>
461
478
  yargs
462
479
  .command(
@@ -0,0 +1,195 @@
1
+ import { select, input, editor } from '@inquirer/prompts';
2
+ import { addLog, addLogs, SuccessLog } from '@mintlify/previewing';
3
+ import fse from 'fs-extra';
4
+ import { Text } from 'ink';
5
+ import path from 'path';
6
+
7
+ import { CMD_EXEC_PATH } from './constants.js';
8
+
9
+ export function slugify(name: string): string {
10
+ return name
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, '-')
13
+ .replace(/^-|-$/g, '');
14
+ }
15
+
16
+ const CRON_FIELD = /^(\*|(\*\/\d+)|(\d+(-\d+)?(,\d+(-\d+)?)*))$/;
17
+
18
+ export function isValidCron(expr: string): boolean {
19
+ const fields = expr.trim().split(/\s+/);
20
+ if (fields.length !== 5) return false;
21
+ return fields.every((f) => CRON_FIELD.test(f));
22
+ }
23
+
24
+ function escapeYaml(value: string): string {
25
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
26
+ }
27
+
28
+ export function buildFrontmatter(config: {
29
+ name: string;
30
+ triggerType: string;
31
+ cronExpression?: string;
32
+ triggerRepos?: string[];
33
+ contextRepos?: string[];
34
+ automerge: boolean;
35
+ }): string {
36
+ const lines: string[] = [];
37
+ lines.push(`name: "${escapeYaml(config.name)}"`);
38
+
39
+ if (config.triggerType === 'push') {
40
+ lines.push('on:');
41
+ lines.push(' push:');
42
+ if (config.triggerRepos) {
43
+ for (const repo of config.triggerRepos) {
44
+ lines.push(` - repo: "${escapeYaml(repo)}"`);
45
+ }
46
+ }
47
+ } else {
48
+ lines.push('on:');
49
+ lines.push(` cron: "${escapeYaml(config.cronExpression ?? '')}"`);
50
+ }
51
+
52
+ if (config.contextRepos && config.contextRepos.length > 0) {
53
+ lines.push('context:');
54
+ for (const repo of config.contextRepos) {
55
+ lines.push(` - repo: "${escapeYaml(repo)}"`);
56
+ }
57
+ }
58
+
59
+ if (config.automerge) {
60
+ lines.push('automerge: true');
61
+ }
62
+
63
+ return `---\n${lines.join('\n')}\n---`;
64
+ }
65
+
66
+ const sendUsageMessageForAI = () => {
67
+ addLogs(
68
+ <Text>Agent Detected - sending AI friendly prompt</Text>,
69
+ <Text>{'<system-message>'}</Text>,
70
+ <Text>
71
+ Example usage of `mintlify workflow`. This command is interactive and creates a workflow file
72
+ in `.mintlify/workflows/`.
73
+ </Text>,
74
+ <Text>
75
+ Workflow files are Markdown files with YAML frontmatter. Instead of running this command, you
76
+ can directly create a `.mintlify/workflows/your-workflow.md` file.
77
+ </Text>,
78
+ <Text>
79
+ Frontmatter fields: name (string), on.cron (string) or on.push with repo list, context (array
80
+ of objects with repo key), automerge (boolean, only when true).
81
+ </Text>,
82
+ <Text>The Markdown body contains the agent instructions/prompt.</Text>,
83
+ <Text>{'</system-message>'}</Text>
84
+ );
85
+ };
86
+
87
+ export async function addWorkflow(): Promise<void> {
88
+ const docsJsonPath = path.join(CMD_EXEC_PATH, 'docs.json');
89
+ if (!(await fse.pathExists(docsJsonPath))) {
90
+ throw new Error(
91
+ 'docs.json not found in the current directory. Please run this command from your docs repository root.'
92
+ );
93
+ }
94
+
95
+ const isInteractive = process.stdin.isTTY;
96
+ const isClaudeCode = process.env.CLAUDECODE === '1';
97
+ const isAI = !isInteractive || isClaudeCode;
98
+
99
+ if (isAI) {
100
+ sendUsageMessageForAI();
101
+ return;
102
+ }
103
+
104
+ const workflowName = await input({
105
+ message: 'Workflow name',
106
+ default: 'Update changelog',
107
+ });
108
+
109
+ if (!workflowName.trim()) {
110
+ throw new Error('Workflow name cannot be empty.');
111
+ }
112
+
113
+ const slug = slugify(workflowName);
114
+ if (!slug) {
115
+ throw new Error('Workflow name must contain at least one alphanumeric character.');
116
+ }
117
+
118
+ const triggerType = await select({
119
+ message: 'Trigger type',
120
+ choices: [
121
+ { name: 'Cron (scheduled)', value: 'cron' },
122
+ { name: 'Push (on push to repo)', value: 'push' },
123
+ ],
124
+ });
125
+
126
+ let cronExpression: string | undefined;
127
+ let triggerRepos: string[] = [];
128
+
129
+ if (triggerType === 'cron') {
130
+ cronExpression = await input({
131
+ message: 'Cron expression',
132
+ default: '0 9 * * 1',
133
+ validate: (value) =>
134
+ isValidCron(value) ||
135
+ 'Invalid cron expression. Expected 5 fields: minute hour day-of-month month day-of-week (e.g. 0 9 * * 1).',
136
+ });
137
+ } else {
138
+ const triggerReposInput = await input({
139
+ message: 'Trigger repos (comma-separated, e.g. your-org/your-docs)',
140
+ default: '',
141
+ });
142
+ triggerRepos = triggerReposInput
143
+ .split(',')
144
+ .map((r) => r.trim())
145
+ .filter(Boolean);
146
+ }
147
+
148
+ const contextReposInput = await input({
149
+ message: 'Context repos (comma-separated, e.g. your-org/your-product, optional)',
150
+ default: '',
151
+ });
152
+ const contextRepos = contextReposInput
153
+ .split(',')
154
+ .map((r) => r.trim())
155
+ .filter(Boolean);
156
+
157
+ const automergeChoice = await select({
158
+ message: 'Enable automerge?',
159
+ choices: [
160
+ { name: 'Yes', value: 'yes' },
161
+ { name: 'No', value: 'no' },
162
+ ],
163
+ });
164
+
165
+ const instructions = await editor({
166
+ message: 'Agent instructions (opens your default editor)',
167
+ default:
168
+ '# Agent Instructions\n\nDescribe what this workflow should do.\n\nFor example:\n- Update the changelog based on recent commits\n- Summarize recent PRs\n',
169
+ });
170
+
171
+ const frontmatter = buildFrontmatter({
172
+ name: workflowName,
173
+ triggerType,
174
+ cronExpression,
175
+ triggerRepos,
176
+ contextRepos: contextRepos.length > 0 ? contextRepos : undefined,
177
+ automerge: automergeChoice === 'yes',
178
+ });
179
+
180
+ const filename = slug + '.md';
181
+ const workflowDir = path.join(CMD_EXEC_PATH, '.mintlify', 'workflows');
182
+ const filePath = path.join(workflowDir, filename);
183
+ const relativePath = path.relative(CMD_EXEC_PATH, filePath);
184
+
185
+ await fse.ensureDir(workflowDir);
186
+
187
+ if (await fse.pathExists(filePath)) {
188
+ throw new Error(
189
+ `A workflow already exists at ${relativePath}. Please choose a different name or delete the existing file.`
190
+ );
191
+ }
192
+
193
+ await fse.writeFile(filePath, frontmatter + '\n\n' + instructions.trim() + '\n');
194
+ addLog(<SuccessLog message={`Workflow created at ${relativePath}`} />);
195
+ }