@mintlify/cli 4.0.1724 → 4.0.1726

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.
@@ -6572,6 +6572,25 @@ function serializeCommentThreadIds(ids) {
6572
6572
  return unique.length > 0 ? unique.join(" ") : null;
6573
6573
  }
6574
6574
 
6575
+ // ../editor/dist/converter/utils/static-html-source.js
6576
+ var STATIC_HTML_TYPES = /* @__PURE__ */ new Set([BLOCK_TYPES.staticHTML, BLOCK_TYPES.inlineStaticHTML]);
6577
+ function keepsStaticHtmlSource(tagName, hasStaticHtmlParent) {
6578
+ return !hasStaticHtmlParent || tagName === "svg";
6579
+ }
6580
+ function findRedundantStaticHtmlSources(doc) {
6581
+ const positions = [];
6582
+ const visit5 = (node, pos, coveredBySource) => {
6583
+ const isStaticHtml = STATIC_HTML_TYPES.has(node.type.name);
6584
+ const hasSource = isStaticHtml && node.attrs.mdxJsxNode != null;
6585
+ if (hasSource && coveredBySource && !keepsStaticHtmlSource(node.attrs.tagName, true)) {
6586
+ positions.push(pos);
6587
+ }
6588
+ node.forEach((child, offset2) => visit5(child, pos + 1 + offset2, coveredBySource || hasSource));
6589
+ };
6590
+ doc.forEach((child, offset2) => visit5(child, offset2, false));
6591
+ return positions;
6592
+ }
6593
+
6575
6594
  // ../editor/dist/converter/parse/parse-utils.js
6576
6595
  import { parse as parseYaml } from "yaml";
6577
6596
  function parseFrontmatterYaml(value) {
@@ -6678,7 +6697,7 @@ function normalizeBlockContent(nodes2, schema) {
6678
6697
  return normalized;
6679
6698
  }
6680
6699
  function staticHtmlSource(node, ctx) {
6681
- return ctx.hasStaticHtmlParent && node.name !== "svg" ? null : node;
6700
+ return keepsStaticHtmlSource(node.name, ctx.hasStaticHtmlParent === true) ? node : null;
6682
6701
  }
6683
6702
 
6684
6703
  // ../editor/dist/converter/parse/mdast-to-pm.js
@@ -17706,6 +17725,7 @@ export {
17706
17725
  extractFrontmatter,
17707
17726
  extractText,
17708
17727
  filterFrontmatterRecord,
17728
+ findRedundantStaticHtmlSources,
17709
17729
  formatFrontmatterValue,
17710
17730
  formatLineRanges,
17711
17731
  getDiffMarkerComment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1724",
3
+ "version": "4.0.1726",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -82,7 +82,7 @@
82
82
  "keytar": "7.9.0"
83
83
  },
84
84
  "devDependencies": {
85
- "@mintlify/editor": "0.0.570",
85
+ "@mintlify/editor": "0.0.571",
86
86
  "@mintlify/ts-config": "2.0.2",
87
87
  "@tsconfig/recommended": "1.0.2",
88
88
  "@types/detect-port": "1.3.2",
@@ -101,5 +101,5 @@
101
101
  "vitest": "2.1.9",
102
102
  "vitest-mock-process": "1.0.4"
103
103
  },
104
- "gitHead": "ebc025616bce1aefff4fe95ab5a3802bd359e403"
104
+ "gitHead": "c26b060f6eca8e35927539c90f73fe6ff44aa220"
105
105
  }
@@ -34,6 +34,13 @@ const writeTokenSchema = z.object({
34
34
  tokenExpiresAt: z.iso.datetime(),
35
35
  });
36
36
 
37
+ const trustedReposSchema = z.object({
38
+ subdomain: z.string().min(1),
39
+ trustedRepos: z.array(
40
+ z.object({ repositoryId: z.string(), repository: z.string(), branch: z.string() })
41
+ ),
42
+ });
43
+
37
44
  export type AgentSite = z.infer<typeof readySchema>;
38
45
  export type AgentWriteToken = z.infer<typeof writeTokenSchema>;
39
46
  export type AgentSiteSettings = { subdomain: string; orgName?: string };
@@ -73,3 +80,31 @@ export async function getAgentWriteToken(
73
80
  writeTokenSchema
74
81
  );
75
82
  }
83
+
84
+ export async function getGithubActionsWriteToken(
85
+ apiUrl: string,
86
+ oidcToken: string,
87
+ subdomain: string
88
+ ): Promise<AgentWriteToken> {
89
+ return agentResponse(
90
+ await agentRequest(`${apiUrl}/api/agent/deployments/github-actions/token`, {
91
+ method: 'POST',
92
+ headers: { Authorization: `Bearer ${oidcToken}`, 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({ subdomain }),
94
+ }),
95
+ writeTokenSchema
96
+ );
97
+ }
98
+
99
+ export type TrustedRepo = { repositoryId: string; repository: string; branch: string };
100
+
101
+ export async function trustGithubRepo(apiUrl: string, accessToken: string, repo: TrustedRepo) {
102
+ return agentResponse(
103
+ await agentRequest(`${apiUrl}/api/agent/deployments/trusted-repos`, {
104
+ method: 'POST',
105
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
106
+ body: JSON.stringify(repo),
107
+ }),
108
+ trustedReposSchema
109
+ );
110
+ }
package/src/cli.tsx CHANGED
@@ -24,6 +24,8 @@ import { comingSoon } from './comingSoon.js';
24
24
  import { setTelemetryEnabled } from './config.js';
25
25
  import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
26
26
  import { API_URL } from './constants.js';
27
+ import { deployFromGithubActions, isGithubActions } from './deploy-github-actions.js';
28
+ import { formatGithubSetup, setupGithubDeployment } from './deploy-github-setup.js';
27
29
  import { DEPLOY_HELP, deploymentInstructions } from './deploy-instructions.js';
28
30
  import { formatDeployment, prepareAgentDeployment } from './deploy.js';
29
31
  import { formatHandler } from './format.js';
@@ -175,15 +177,63 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
175
177
  type: 'string',
176
178
  description: 'User email for an optional claim link',
177
179
  })
180
+ .option('github', {
181
+ type: 'boolean',
182
+ default: false,
183
+ description:
184
+ 'Trust this GitHub repository to deploy the site and write a GitHub Actions workflow',
185
+ })
186
+ .option('force', {
187
+ type: 'boolean',
188
+ default: false,
189
+ description: 'With --github, overwrite an existing .github/workflows/mintlify.yml',
190
+ })
191
+ .conflicts('github', 'email')
178
192
  .example('$0 deploy --subdomain my-docs', 'Provision a site without signing in')
179
193
  .example(
180
194
  '$0 deploy --email you@example.com',
181
195
  'Get repository access and a claim link for the current site'
182
196
  )
183
197
  .example('$0 deploy --new --subdomain another-site', 'Create another anonymous site')
198
+ .example(
199
+ '$0 deploy --github --subdomain my-docs',
200
+ 'Deploy the site from GitHub Actions on every push to the default branch'
201
+ )
202
+ .example(
203
+ '$0 deploy --subdomain my-docs',
204
+ 'In GitHub Actions with id-token: write, push the current commit to a trusted site'
205
+ )
184
206
  .epilog(DEPLOY_HELP),
185
207
  async (argv) => {
186
208
  try {
209
+ if (isGithubActions()) {
210
+ const siteUrl = await deployFromGithubActions(
211
+ { apiUrl: API_URL, directory: argv.directory, subdomain: argv.subdomain },
212
+ (message) => process.stderr.write(`${message}\n`)
213
+ );
214
+ process.stdout.write(
215
+ `Pushed to ${argv.subdomain}; the build starts now.\nSite: ${siteUrl}\n`
216
+ );
217
+ await terminate(0);
218
+ return;
219
+ }
220
+ if (argv.github) {
221
+ const setup = await setupGithubDeployment(
222
+ {
223
+ apiUrl: API_URL,
224
+ directory: argv.directory,
225
+ subdomain: argv.subdomain,
226
+ orgName: argv.orgName,
227
+ newSite: argv.new,
228
+ force: argv.force,
229
+ },
230
+ (message) => process.stderr.write(`Warning: ${message}\n`),
231
+ (message) => process.stderr.write(`${message}\n`)
232
+ );
233
+ process.stdout.write(`${formatGithubSetup(setup)}\n`);
234
+ await terminate(0);
235
+ return;
236
+ }
187
237
  const result = await prepareAgentDeployment(
188
238
  {
189
239
  apiUrl: API_URL,
@@ -0,0 +1,139 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+
5
+ import { getGithubActionsWriteToken, type AgentWriteToken } from './agent-deployment.js';
6
+ import { AgentApiError } from './agent-http.js';
7
+ import { agentSiteUrl } from './deploy.js';
8
+
9
+ const oidcSchema = z.object({ value: z.string().min(1) });
10
+
11
+ export type GithubActionsDeployOptions = {
12
+ apiUrl: string;
13
+ directory: string;
14
+ subdomain?: string;
15
+ };
16
+
17
+ export function isGithubActions(env: NodeJS.ProcessEnv = process.env): boolean {
18
+ return env.GITHUB_ACTIONS === 'true';
19
+ }
20
+
21
+ async function getGithubOidcToken(env: NodeJS.ProcessEnv, audience: string): Promise<string> {
22
+ const requestUrl = env.ACTIONS_ID_TOKEN_REQUEST_URL;
23
+ const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
24
+ if (!requestUrl || !requestToken) {
25
+ throw new Error(
26
+ 'GitHub Actions did not provide an OIDC token. Add `permissions: id-token: write` to the workflow or job.'
27
+ );
28
+ }
29
+ let response: Response;
30
+ try {
31
+ response = await fetch(`${requestUrl}&audience=${encodeURIComponent(audience)}`, {
32
+ headers: { Authorization: `Bearer ${requestToken}` },
33
+ signal: AbortSignal.timeout(30_000),
34
+ });
35
+ } catch {
36
+ throw new Error('Could not request a GitHub Actions OIDC token. Retry the workflow.');
37
+ }
38
+ const parsed = oidcSchema.safeParse(await response.json().catch(() => null));
39
+ if (!response.ok || !parsed.success) {
40
+ throw new Error(
41
+ `Could not get a GitHub Actions OIDC token (HTTP ${response.status}). Check that the workflow has \`permissions: id-token: write\`.`
42
+ );
43
+ }
44
+ return parsed.data.value;
45
+ }
46
+
47
+ function git(args: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<string> {
48
+ return new Promise((resolve, reject) => {
49
+ const child = spawn('git', args, { cwd, env, stdio: ['ignore', 'pipe', 'inherit'] });
50
+ let out = '';
51
+ child.stdout.on('data', (chunk: Buffer) => (out += chunk.toString()));
52
+ child.on('error', reject);
53
+ child.on('close', (code) =>
54
+ code === 0 ? resolve(out.trim()) : reject(new Error(`git ${args[0]} failed`))
55
+ );
56
+ });
57
+ }
58
+
59
+ async function commitDirectory(directory: string, env: NodeJS.ProcessEnv): Promise<string> {
60
+ const prefix = await git(['rev-parse', '--show-prefix'], directory, env);
61
+ const tree = await git(['rev-parse', `HEAD:${prefix}`], directory, env);
62
+ const [name = '', email = '', date = '', sha = '', subject = ''] = (
63
+ await git(['log', '-1', '--format=%an%n%ae%n%aI%n%H%n%s'], directory, env)
64
+ ).split('\n');
65
+ const repository = env.GITHUB_REPOSITORY ? `${env.GITHUB_REPOSITORY}@` : '';
66
+ return git(
67
+ ['commit-tree', tree, '-m', subject, '-m', `Deployed from ${repository}${sha}`],
68
+ directory,
69
+ {
70
+ ...env,
71
+ GIT_AUTHOR_NAME: name,
72
+ GIT_AUTHOR_EMAIL: email,
73
+ GIT_AUTHOR_DATE: date,
74
+ GIT_COMMITTER_NAME: name,
75
+ GIT_COMMITTER_EMAIL: email,
76
+ }
77
+ );
78
+ }
79
+
80
+ function pushCommit(
81
+ directory: string,
82
+ commit: string,
83
+ write: AgentWriteToken,
84
+ env: NodeJS.ProcessEnv
85
+ ): Promise<void> {
86
+ const index = Number(env.GIT_CONFIG_COUNT) || 0;
87
+ const credentials = Buffer.from(`x-access-token:${write.token}`).toString('base64');
88
+ return new Promise((resolve, reject) => {
89
+ const child = spawn(
90
+ 'git',
91
+ ['push', '--force', write.remote, `${commit}:refs/heads/${write.branch}`],
92
+ {
93
+ cwd: directory,
94
+ stdio: ['ignore', 'inherit', 'inherit'],
95
+ env: {
96
+ ...env,
97
+ GIT_CONFIG_COUNT: String(index + 1),
98
+ [`GIT_CONFIG_KEY_${index}`]: `http.${write.remote}.extraHeader`,
99
+ [`GIT_CONFIG_VALUE_${index}`]: `Authorization: Basic ${credentials}`,
100
+ },
101
+ }
102
+ );
103
+ const failed = () => reject(new Error('git push to the site repository failed.'));
104
+ child.on('error', failed);
105
+ child.on('close', (code) => (code === 0 ? resolve() : failed()));
106
+ });
107
+ }
108
+
109
+ export async function deployFromGithubActions(
110
+ options: GithubActionsDeployOptions,
111
+ progress: (message: string) => void = () => {},
112
+ env: NodeJS.ProcessEnv = process.env
113
+ ): Promise<string> {
114
+ const { apiUrl, subdomain } = options;
115
+ if (!subdomain) throw new Error('Pass the site name with --subdomain <name>.');
116
+ const directory = path.resolve(options.directory);
117
+ const commit = await commitDirectory(directory, env).catch(() => {
118
+ throw new Error(
119
+ `Could not read ${options.directory} from the checked-out commit. Run mint deploy after actions/checkout, with --directory inside the repository.`
120
+ );
121
+ });
122
+ progress('Requesting a GitHub Actions identity token...');
123
+ const oidcToken = await getGithubOidcToken(env, apiUrl);
124
+ progress('Getting repository write access...');
125
+ let write: AgentWriteToken;
126
+ try {
127
+ write = await getGithubActionsWriteToken(apiUrl, oidcToken, subdomain);
128
+ } catch (error) {
129
+ if (error instanceof AgentApiError && error.status === 403) {
130
+ throw new Error(
131
+ `This repository/branch isn't trusted to deploy ${subdomain}. Run \`mint deploy --github\` locally from this repository to trust it.`
132
+ );
133
+ }
134
+ throw error;
135
+ }
136
+ progress('Pushing to the site repository...');
137
+ await pushCommit(directory, commit, write, env);
138
+ return agentSiteUrl(apiUrl, subdomain);
139
+ }
@@ -0,0 +1,189 @@
1
+ import { execFile } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { z } from 'zod';
5
+
6
+ import { trustGithubRepo } from './agent-deployment.js';
7
+ import { CLAIM_INSTRUCTIONS } from './deploy-instructions.js';
8
+ import { agentSiteUrl, prepareAgentDeployment, type DeployOptions } from './deploy.js';
9
+
10
+ const githubRepoSchema = z.object({
11
+ id: z.number().int().positive(),
12
+ full_name: z.string().regex(/^[\w.-]+\/[\w.-]+$/),
13
+ default_branch: z.string().min(1),
14
+ });
15
+
16
+ export type GithubRepo = z.infer<typeof githubRepoSchema>;
17
+
18
+ export const GITHUB_WORKFLOW_PATH = path.join('.github', 'workflows', 'mintlify.yml');
19
+
20
+ function run(command: string, args: string[], cwd: string): Promise<string> {
21
+ return new Promise((resolve, reject) => {
22
+ execFile(command, args, { cwd }, (error, stdout) =>
23
+ error ? reject(error) : resolve(String(stdout).trim())
24
+ );
25
+ });
26
+ }
27
+
28
+ export function parseGithubRemote(remote: string): { owner: string; repo: string } | null {
29
+ const match = remote
30
+ .trim()
31
+ .match(
32
+ /^(?:https:\/\/(?:[^@/]+@)?github\.com\/|ssh:\/\/git@github\.com(?::\d+)?\/|git@github\.com:)([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/
33
+ );
34
+ return match?.[1] && match[2] ? { owner: match[1], repo: match[2] } : null;
35
+ }
36
+
37
+ export function githubWorkflow(branch: string, subdomain: string, docsPath = ''): string {
38
+ const directory = /^[\w./-]+$/.test(docsPath) ? docsPath : JSON.stringify(docsPath);
39
+ return [
40
+ 'name: Deploy to Mintlify',
41
+ 'on:',
42
+ ' push:',
43
+ ` branches: [${JSON.stringify(branch)}]`,
44
+ ...(docsPath
45
+ ? [` paths: [${JSON.stringify(`${docsPath}/**`)}, ".github/workflows/mintlify.yml"]`]
46
+ : []),
47
+ 'permissions:',
48
+ ' id-token: write',
49
+ ' contents: read',
50
+ 'concurrency:',
51
+ ' group: mintlify-deploy',
52
+ ' cancel-in-progress: true',
53
+ 'jobs:',
54
+ ' deploy:',
55
+ ' runs-on: ubuntu-latest',
56
+ ' steps:',
57
+ ' - uses: actions/checkout@v4',
58
+ ` - run: npx mint@latest deploy --subdomain ${subdomain}${docsPath ? ` --directory ${directory}` : ''}`,
59
+ '',
60
+ ].join('\n');
61
+ }
62
+
63
+ async function readFileIfExists(file: string): Promise<string | undefined> {
64
+ return fs.readFile(file, 'utf8').catch((error: NodeJS.ErrnoException) => {
65
+ if (error.code === 'ENOENT') return undefined;
66
+ throw error;
67
+ });
68
+ }
69
+
70
+ export async function writeGithubWorkflow(
71
+ root: string,
72
+ contents: string,
73
+ force = false
74
+ ): Promise<'created' | 'unchanged' | 'updated'> {
75
+ const file = path.join(root, GITHUB_WORKFLOW_PATH);
76
+ const existing = await readFileIfExists(file);
77
+ if (existing === contents) return 'unchanged';
78
+ if (existing !== undefined && !force) {
79
+ throw new Error(
80
+ `${GITHUB_WORKFLOW_PATH} already exists with different contents. Pass --force to overwrite it.`
81
+ );
82
+ }
83
+ await fs.mkdir(path.dirname(file), { recursive: true });
84
+ await fs.writeFile(file, contents);
85
+ return existing === undefined ? 'created' : 'updated';
86
+ }
87
+
88
+ export async function findGitRoot(directory: string): Promise<string> {
89
+ try {
90
+ return await run('git', ['rev-parse', '--show-toplevel'], directory);
91
+ } catch {
92
+ throw new Error('--github requires a Git repository. Run it inside your docs repository.');
93
+ }
94
+ }
95
+
96
+ export async function resolveGithubRepo(root: string): Promise<GithubRepo> {
97
+ const remote = await run('git', ['remote', 'get-url', 'origin'], root).catch(() => {
98
+ throw new Error('This repository has no origin remote. Add a github.com origin and retry.');
99
+ });
100
+ const parsed = parseGithubRemote(remote);
101
+ if (!parsed) throw new Error(`The origin remote (${remote}) is not a github.com repository.`);
102
+ const output = await run(
103
+ 'gh',
104
+ [
105
+ 'api',
106
+ `repos/${parsed.owner}/${parsed.repo}`,
107
+ '--jq',
108
+ '{id: .id, full_name: .full_name, default_branch: .default_branch}',
109
+ ],
110
+ root
111
+ ).catch((error: NodeJS.ErrnoException) => {
112
+ if (error.code === 'ENOENT')
113
+ throw new Error('The GitHub CLI (gh) is required. Install it and run `gh auth login`.');
114
+ throw new Error(
115
+ `Could not read ${parsed.owner}/${parsed.repo} with gh. Run \`gh auth login\` with access to the repository and retry.`
116
+ );
117
+ });
118
+ try {
119
+ return githubRepoSchema.parse(JSON.parse(output));
120
+ } catch {
121
+ throw new Error('gh returned an unexpected repository response.');
122
+ }
123
+ }
124
+
125
+ export type GithubSetupResult = {
126
+ siteUrl: string;
127
+ subdomain: string;
128
+ repository: string;
129
+ branch: string;
130
+ workflowPath: string;
131
+ workflow: 'created' | 'unchanged' | 'updated';
132
+ };
133
+
134
+ export async function setupGithubDeployment(
135
+ options: DeployOptions & { force?: boolean },
136
+ warn: (message: string) => void,
137
+ progress: (message: string) => void = () => {}
138
+ ): Promise<GithubSetupResult> {
139
+ const directory = path.resolve(options.directory);
140
+ const root = await findGitRoot(directory);
141
+ const [realRoot, realDirectory] = await Promise.all([fs.realpath(root), fs.realpath(directory)]);
142
+ const docsPath = path.relative(realRoot, realDirectory).split(path.sep).join('/');
143
+ if (!(await readFileIfExists(path.join(directory, 'docs.json')))) {
144
+ throw new Error(`No docs.json in ${directory}. Pass the docs folder with --directory.`);
145
+ }
146
+ progress('Resolving the GitHub repository...');
147
+ const repo = await resolveGithubRepo(root);
148
+ const result = await prepareAgentDeployment(options, warn, progress);
149
+ if (result.status === 'claimed') {
150
+ throw new Error(
151
+ 'This site has been claimed. Setting up GitHub deploys for claimed sites is not supported yet; connect the repository from the Mintlify dashboard.'
152
+ );
153
+ }
154
+ const accessToken = result.credentials.accessToken;
155
+ if (!accessToken) throw new Error('Agent authentication did not return an access token.');
156
+ progress(`Trusting ${repo.full_name}@${repo.default_branch} to deploy...`);
157
+ await trustGithubRepo(options.apiUrl, accessToken, {
158
+ repositoryId: String(repo.id),
159
+ repository: repo.full_name,
160
+ branch: repo.default_branch,
161
+ });
162
+ const workflow = await writeGithubWorkflow(
163
+ root,
164
+ githubWorkflow(repo.default_branch, result.site.subdomain, docsPath),
165
+ options.force
166
+ );
167
+ return {
168
+ siteUrl: agentSiteUrl(options.apiUrl, result.site.subdomain),
169
+ subdomain: result.site.subdomain,
170
+ repository: repo.full_name,
171
+ branch: repo.default_branch,
172
+ workflowPath: GITHUB_WORKFLOW_PATH,
173
+ workflow,
174
+ };
175
+ }
176
+
177
+ export function formatGithubSetup(result: GithubSetupResult): string {
178
+ return [
179
+ `Site: ${result.siteUrl}`,
180
+ `Trusted repository: ${result.repository} (branch ${result.branch})`,
181
+ `Workflow: ${result.workflowPath} (${result.workflow})`,
182
+ '',
183
+ 'Next steps:',
184
+ `1. Commit and push ${result.workflowPath} to ${result.branch}.`,
185
+ `2. Every push to ${result.branch} then deploys ${result.subdomain} through GitHub Actions. No secrets are stored in the repository.`,
186
+ '',
187
+ ...CLAIM_INSTRUCTIONS,
188
+ ].join('\n');
189
+ }
@@ -8,8 +8,15 @@ export const DEPLOY_HELP = [
8
8
  'Pass --email to obtain a claim link for the user. If it is absent, ask the user for their email and rerun with --email from the same directory. Never invent an email address.',
9
9
  'To claim an existing site without provisioning, run mint claim --email <email>. Show the link to the user and ask them to return the displayed code. Then run mint claim --code <code>. Use the original documentation directory for both commands, or pass --directory.',
10
10
  'The output contains a short-lived write token. Do not paste it into public logs or save it in a Git remote.',
11
+ 'Pass --github from the root of a GitHub repository to deploy on every push: it trusts the repository (via the gh CLI and the origin remote) to deploy the site and writes .github/workflows/mintlify.yml. Commit and push that workflow. Pass --directory to deploy a docs subdirectory of the repository as the site root. Use --force to overwrite a different existing workflow.',
12
+ 'In GitHub Actions (GITHUB_ACTIONS=true), no agent credentials are used: the workflow needs `permissions: id-token: write`, --subdomain is required, and the current commit is force-pushed to the site from a trusted repository and branch.',
11
13
  ].join('\n\n');
12
14
 
15
+ export const CLAIM_INSTRUCTIONS = [
16
+ 'Ask the user for the email address they want to use to claim this site. Never invent an email address.',
17
+ 'After they provide it, run mint deploy --email <their-email> from the same documentation directory (or repeat --directory). This reuses the site and returns its claim link.',
18
+ ];
19
+
13
20
  export function deploymentInstructions(result: DeployResult, claimUrl?: string): string {
14
21
  if (result.status !== 'ready') return '';
15
22
  return [
@@ -33,9 +40,6 @@ export function deploymentInstructions(result: DeployResult, claimUrl?: string):
33
40
  'Show this link to the user. Ask them to open it, sign in, and return the displayed code. Do not sign in on their behalf.',
34
41
  'After the user returns the code, run mint claim --code <code> from the same documentation directory (or repeat --directory). The CLI completes the claim using its saved credentials.',
35
42
  ]
36
- : [
37
- 'Ask the user for the email address they want to use to claim this site. Never invent an email address.',
38
- 'After they provide it, run mint deploy --email <their-email> from the same documentation directory (or repeat --directory). This reuses the site and returns its claim link.',
39
- ]),
43
+ : CLAIM_INSTRUCTIONS),
40
44
  ].join('\n');
41
45
  }
package/src/deploy.ts CHANGED
@@ -105,15 +105,19 @@ export async function prepareAgentDeployment(
105
105
  throw new Error('The new anonymous site is already expired. Please retry later.');
106
106
  }
107
107
 
108
- export function formatDeployment(result: DeployResult, apiUrl: string): string {
109
- if (result.status === 'claimed')
110
- return 'This site has been claimed. Manage it in the Mintlify dashboard. Use mint deploy --new --subdomain <name> to create another site.';
111
- const { site, write } = result;
108
+ export function agentSiteUrl(apiUrl: string, subdomain: string): string {
112
109
  const domain =
113
110
  new URL(apiUrl).hostname === 'leaves-dev.mintlify.com'
114
111
  ? 'mint-dev.mintlify.me'
115
112
  : 'mintlify.site';
116
- const siteUrl = new URL(`https://${site.subdomain}.${domain}`).toString();
113
+ return new URL(`https://${subdomain}.${domain}`).toString();
114
+ }
115
+
116
+ export function formatDeployment(result: DeployResult, apiUrl: string): string {
117
+ if (result.status === 'claimed')
118
+ return 'This site has been claimed. Manage it in the Mintlify dashboard. Use mint deploy --new --subdomain <name> to create another site.';
119
+ const { site, write } = result;
120
+ const siteUrl = agentSiteUrl(apiUrl, site.subdomain);
117
121
  return [
118
122
  result.created
119
123
  ? 'Repository provisioned; push required to build the site.'