@mintlify/cli 4.0.1802 → 4.0.1804

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.
@@ -6661,7 +6661,10 @@ function serializeCommentThreadIds(ids) {
6661
6661
  }
6662
6662
 
6663
6663
  // ../editor/dist/converter/utils/static-html-source.js
6664
- var STATIC_HTML_TYPES = /* @__PURE__ */ new Set([BLOCK_TYPES.staticHTML, BLOCK_TYPES.inlineStaticHTML]);
6664
+ var STATIC_HTML_TYPES = /* @__PURE__ */ new Set([
6665
+ BLOCK_TYPES.staticHTML,
6666
+ BLOCK_TYPES.inlineStaticHTML
6667
+ ]);
6665
6668
  function keepsStaticHtmlSource(tagName, hasStaticHtmlParent) {
6666
6669
  return !hasStaticHtmlParent || tagName === "svg";
6667
6670
  }
@@ -28472,21 +28475,58 @@ function renderTooltip(state, node2, renderInline2) {
28472
28475
  state.write(`<${BLOCK_TYPES.tooltip}${attrs2 ? ` ${attrs2}` : ""} />`);
28473
28476
  }
28474
28477
  }
28475
- function renderStaticHTML(state, node2, renderContent2) {
28478
+ var sourceTexts = /* @__PURE__ */ new WeakMap();
28479
+ function parsedText(node2, source) {
28480
+ try {
28481
+ let text2;
28482
+ mdxToPm(source, { schema: node2.type.schema }).doc.descendants((child) => {
28483
+ if (text2 === void 0 && STATIC_HTML_TYPES.has(child.type.name))
28484
+ text2 = child.textContent;
28485
+ return text2 === void 0;
28486
+ });
28487
+ return text2;
28488
+ } catch (_a2) {
28489
+ return void 0;
28490
+ }
28491
+ }
28492
+ function sourceText(node2, mdxJsxNode, source) {
28493
+ if (!sourceTexts.has(mdxJsxNode))
28494
+ sourceTexts.set(mdxJsxNode, parsedText(node2, source));
28495
+ return sourceTexts.get(mdxJsxNode);
28496
+ }
28497
+ function textDiffersFromSource(node2, mdxJsxNode, source) {
28498
+ if (node2.childCount === 0 && !node2.inlineContent)
28499
+ return false;
28500
+ const text2 = sourceText(node2, mdxJsxNode, source);
28501
+ return text2 !== void 0 && text2 !== node2.textContent;
28502
+ }
28503
+ function sourceOpeningTag(mdxJsxNode) {
28504
+ return stringifyMdxNode([Object.assign(Object.assign({}, mdxJsxNode), { children: [], data: void 0 })]).trim().replace(/^<|\s*\/>$/g, "");
28505
+ }
28506
+ function renderStaticHTML(state, node2, renderContent2, renderInline2) {
28476
28507
  var _a2;
28477
28508
  const inline = state.inTable || node2.type.name === BLOCK_TYPES.inlineStaticHTML;
28478
28509
  const escapeTablePipes = (value) => state.escapesPipes ? value.replace(/\|/g, "&#124;") : value;
28510
+ const inlineSource = (value) => escapeTablePipes(value.replace(/\s*\n\s*/g, " ").trim());
28479
28511
  const mdxJsxNode = node2.attrs.mdxJsxNode;
28512
+ let sourceTag;
28480
28513
  if (mdxJsxNode) {
28481
28514
  try {
28482
28515
  const str = stringifyMdxNode([mdxJsxNode]);
28483
- if (inline) {
28484
- state.write(escapeTablePipes(str.replace(/\s*\n\s*/g, " ").trim()));
28485
- } else {
28486
- state.write(str);
28487
- state.closeBlock();
28516
+ if (!textDiffersFromSource(node2, mdxJsxNode, str)) {
28517
+ if (inline) {
28518
+ state.write(inlineSource(str));
28519
+ } else {
28520
+ state.write(str);
28521
+ state.closeBlock();
28522
+ }
28523
+ return;
28524
+ }
28525
+ if (mdxJsxNode.name) {
28526
+ sourceTag = sourceOpeningTag(mdxJsxNode);
28527
+ if (inline)
28528
+ sourceTag = inlineSource(sourceTag);
28488
28529
  }
28489
- return;
28490
28530
  } catch (_b) {
28491
28531
  }
28492
28532
  }
@@ -28499,13 +28539,16 @@ function renderStaticHTML(state, node2, renderContent2) {
28499
28539
  }).filter(Boolean);
28500
28540
  const expressionProps = getExpressionAttrStrings(attributes);
28501
28541
  const attrs2 = [...namedAttrs, ...expressionProps].join(" ");
28502
- const tag2 = attrs2 ? `${tagName} ${attrs2}` : tagName;
28542
+ const tag2 = sourceTag !== null && sourceTag !== void 0 ? sourceTag : attrs2 ? `${tagName} ${attrs2}` : tagName;
28503
28543
  if (node2.childCount > 0) {
28504
28544
  state.write(`<${tag2}>`);
28505
28545
  if (!inline)
28506
28546
  state.ensureNewLine();
28507
- renderContent2(state, node2);
28508
- state.write(`</${tagName}>`);
28547
+ if (node2.inlineContent)
28548
+ renderInline2(state, node2);
28549
+ else
28550
+ renderContent2(state, node2);
28551
+ state.write(`</${sourceTag ? mdxJsxNode.name : tagName}>`);
28509
28552
  } else {
28510
28553
  state.write(`<${tag2} />`);
28511
28554
  }
@@ -29116,7 +29159,7 @@ function renderNode(state, node2, _parent, _index) {
29116
29159
  break;
29117
29160
  case BLOCK_TYPES.staticHTML:
29118
29161
  case BLOCK_TYPES.inlineStaticHTML:
29119
- renderStaticHTML(state, node2, renderContent);
29162
+ renderStaticHTML(state, node2, renderContent, boundRenderInline);
29120
29163
  break;
29121
29164
  case BLOCK_TYPES.unknownTag:
29122
29165
  case BLOCK_TYPES.inlineUnknownTag:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1802",
3
+ "version": "4.0.1804",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -87,7 +87,7 @@
87
87
  "keytar": "7.9.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@mintlify/editor": "0.0.619",
90
+ "@mintlify/editor": "0.0.620",
91
91
  "@mintlify/ts-config": "2.0.2",
92
92
  "@tsconfig/recommended": "1.0.2",
93
93
  "@types/detect-port": "1.3.2",
@@ -106,5 +106,5 @@
106
106
  "vitest": "2.1.9",
107
107
  "vitest-mock-process": "1.0.4"
108
108
  },
109
- "gitHead": "5c3ece93a21155964942953f6e889233ace06eaa"
109
+ "gitHead": "97f5b1f90612968ac5d9a8782bbf6e15d0690252"
110
110
  }
@@ -10,6 +10,7 @@ const readySchema = z.object({
10
10
  remote: z.url(),
11
11
  branch: z.literal('main'),
12
12
  expiresAt: z.iso.datetime(),
13
+ orgSlug: z.string().min(1).optional(),
13
14
  });
14
15
  const existingSchema = z.discriminatedUnion('status', [
15
16
  readySchema.extend({ code: z.literal('deployment_already_exists') }),
@@ -43,7 +44,12 @@ const trustedReposSchema = z.object({
43
44
 
44
45
  export type AgentSite = z.infer<typeof readySchema>;
45
46
  export type AgentWriteToken = z.infer<typeof writeTokenSchema>;
46
- export type AgentSiteSettings = { subdomain: string; orgName?: string };
47
+ export type AgentProjectType = 'site' | 'workspace';
48
+ export type AgentSiteSettings = {
49
+ subdomain: string;
50
+ orgName?: string;
51
+ projectType?: AgentProjectType;
52
+ };
47
53
 
48
54
  export async function provisionAgentSite(
49
55
  apiUrl: string,
package/src/cli.tsx CHANGED
@@ -22,10 +22,11 @@ import { comingSoon } from './comingSoon.js';
22
22
  import { setTelemetryEnabled } from './config.js';
23
23
  import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
24
24
  import { API_URL } from './constants.js';
25
+ import { pushBaseCheckout } from './deploy-base.js';
25
26
  import { deployFromGithubActions, isGithubActions } from './deploy-github-actions.js';
26
27
  import { formatGithubSetup, setupGithubDeployment } from './deploy-github-setup.js';
27
28
  import { DEPLOY_HELP, deploymentInstructions } from './deploy-instructions.js';
28
- import { formatDeployment, prepareAgentDeployment } from './deploy.js';
29
+ import { agentEditorUrl, formatDeployment, prepareAgentDeployment } from './deploy.js';
29
30
  import { FEEDBACK_CATEGORIES, feedback } from './feedback.js';
30
31
  import { formatHandler } from './format.js';
31
32
  import {
@@ -235,13 +236,29 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
235
236
  default: false,
236
237
  description: 'With --github, overwrite an existing .github/workflows/mintlify.yml',
237
238
  })
239
+ .option('base', {
240
+ type: 'boolean',
241
+ description:
242
+ 'Provision a base, edited in the Mintlify editor, instead of a docs site',
243
+ })
244
+ .option('push', {
245
+ type: 'string',
246
+ description: 'With --base, rebase this clone onto main and push it to the base',
247
+ })
248
+ .implies('push', 'base')
249
+ .conflicts('push', 'email')
238
250
  .conflicts('github', 'email')
251
+ .conflicts('base', 'github')
239
252
  .example('$0 deploy --subdomain my-docs', 'Provision a site without signing in')
240
253
  .example(
241
254
  '$0 deploy --email you@example.com',
242
255
  'Get repository access and a claim link for the current site'
243
256
  )
244
257
  .example('$0 deploy --new --subdomain another-site', 'Create another anonymous site')
258
+ .example(
259
+ '$0 deploy --base --subdomain my-base',
260
+ 'Provision a base and get its editor link'
261
+ )
245
262
  .example(
246
263
  '$0 deploy --github --subdomain my-docs',
247
264
  'Deploy the site from GitHub Actions on every push to the default branch'
@@ -288,12 +305,21 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
288
305
  subdomain: argv.subdomain,
289
306
  orgName: argv.orgName,
290
307
  newSite: argv.new,
308
+ base: argv.base,
291
309
  },
292
310
  (message) => {
293
311
  process.stderr.write(`Warning: ${message}\n`);
294
312
  },
295
313
  (message) => process.stderr.write(`${message}\n`)
296
314
  );
315
+ if (argv.push && result.status === 'ready') {
316
+ await pushBaseCheckout(argv.push, result.write);
317
+ process.stdout.write(
318
+ `Pushed ${argv.push} to the base.\nEditor: ${agentEditorUrl(result.site)}\n`
319
+ );
320
+ await terminate(0);
321
+ return;
322
+ }
297
323
  process.stdout.write(`${formatDeployment(result, API_URL)}\n`);
298
324
  let claimUrl: string | undefined;
299
325
  if (argv.email && result.status === 'ready') {
@@ -0,0 +1,18 @@
1
+ import type { AgentWriteToken } from './agent-deployment.js';
2
+ import { git } from './deploy-github-actions.js';
3
+
4
+ export async function pushBaseCheckout(checkout: string, write: AgentWriteToken): Promise<void> {
5
+ const env = {
6
+ ...process.env,
7
+ GIT_CONFIG_COUNT: '1',
8
+ GIT_CONFIG_KEY_0: `http.${write.remote}.extraHeader`,
9
+ GIT_CONFIG_VALUE_0: `Authorization: Bearer ${write.token}`,
10
+ };
11
+ await git(['fetch', write.remote, write.branch], checkout, env);
12
+ await git(['rebase', 'FETCH_HEAD'], checkout, env).catch(() => {
13
+ throw new Error(
14
+ `Could not rebase ${checkout} onto the latest ${write.branch}. Resolve the conflicts, run git rebase --continue, then rerun mint deploy --base --push ${checkout}.`
15
+ );
16
+ });
17
+ await git(['push', write.remote, `HEAD:refs/heads/${write.branch}`], checkout, env);
18
+ }
@@ -44,7 +44,7 @@ async function getGithubOidcToken(env: NodeJS.ProcessEnv, audience: string): Pro
44
44
  return parsed.data.value;
45
45
  }
46
46
 
47
- function git(args: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<string> {
47
+ export function git(args: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<string> {
48
48
  return new Promise((resolve, reject) => {
49
49
  const child = spawn('git', args, { cwd, env, stdio: ['ignore', 'pipe', 'inherit'] });
50
50
  let out = '';
@@ -9,6 +9,7 @@ export const DEPLOY_HELP = [
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
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
+ 'Pass --base to provision a base instead of a docs site. The output has the claim steps, a clone command, and the editor link; there is no site URL. Commit in the clone, then run mint deploy --base --push <clone-directory> to publish. --base cannot be combined with --github.',
12
13
  '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.',
13
14
  ].join('\n\n');
14
15
 
@@ -17,8 +18,38 @@ export const CLAIM_INSTRUCTIONS = [
17
18
  '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
  ];
19
20
 
21
+ const baseClaimStep = (claimUrl?: string) =>
22
+ claimUrl
23
+ ? [
24
+ `3. Claim the base last. Send the user this link: ${claimUrl}`,
25
+ ' They sign in and get a code. When they give it to you, run: mint claim --code <code>',
26
+ ]
27
+ : [
28
+ '3. Claim the base last. Ask the user for their email (never invent one), then run:',
29
+ ' mint deploy --base --email <their-email>',
30
+ ' Send the user the claim link it prints. They sign in and get a code. When they give it to you, run: mint claim --code <code>',
31
+ ];
32
+
33
+ function baseInstructions(result: Extract<DeployResult, { status: 'ready' }>, claimUrl?: string) {
34
+ const { site, write } = result;
35
+ const checkout = site.subdomain;
36
+ return [
37
+ '\nNext steps:',
38
+ `1. Clone the base into ./${checkout}:`,
39
+ ` git -c http.${write.remote}.extraHeader="Authorization: Bearer ${write.token}" clone ${write.remote} ${checkout}`,
40
+ ` The clone command works until ${write.tokenExpiresAt}. Run mint deploy --base again for a fresh one.`,
41
+ `2. Commit your changes in ./${checkout}, then publish them:`,
42
+ ` mint deploy --base --push ${checkout}`,
43
+ ' This rebases onto the latest main and pushes. Do not run git push yourself.',
44
+ ...baseClaimStep(claimUrl),
45
+ ' After the claim, the user opens the base at the editor link above and edits it there. The CLI can no longer push to it.',
46
+ 'Run every mint command from this directory (or repeat --directory).',
47
+ ].join('\n');
48
+ }
49
+
20
50
  export function deploymentInstructions(result: DeployResult, claimUrl?: string): string {
21
51
  if (result.status !== 'ready') return '';
52
+ if (result.projectType === 'workspace') return baseInstructions(result, claimUrl);
22
53
  return [
23
54
  '\nNext steps:',
24
55
  '1. Clone the hosted repository into a separate directory. Do not replace the user’s existing Git remote.',
package/src/deploy.ts CHANGED
@@ -14,10 +14,12 @@ import {
14
14
  import {
15
15
  getAgentWriteToken,
16
16
  provisionAgentSite,
17
+ type AgentProjectType,
17
18
  type AgentSite,
18
19
  type AgentWriteToken,
19
20
  } from './agent-deployment.js';
20
21
  import { getAgentSiteMetadata, setAgentSiteMetadata } from './config.js';
22
+ import { DASHBOARD_URL } from './constants.js';
21
23
  import { trackEvent } from './telemetry/track.js';
22
24
 
23
25
  export type DeployOptions = {
@@ -26,12 +28,14 @@ export type DeployOptions = {
26
28
  subdomain?: string;
27
29
  orgName?: string;
28
30
  newSite?: boolean;
31
+ base?: boolean;
29
32
  };
30
33
 
31
34
  export type DeployResult =
32
35
  | { status: 'claimed' }
33
36
  | {
34
37
  status: 'ready';
38
+ projectType: AgentProjectType;
35
39
  site: AgentSite;
36
40
  write: AgentWriteToken;
37
41
  created: boolean;
@@ -61,6 +65,7 @@ export async function prepareAgentDeployment(
61
65
  const subdomain = options.subdomain ?? (options.newSite ? undefined : previous?.subdomain);
62
66
  if (!subdomain) throw new Error('Choose a site name with --subdomain <name>.');
63
67
  const discovery = await discoverAgentAuth(apiUrl);
68
+ const projectType: AgentProjectType = options.base ? 'workspace' : 'site';
64
69
 
65
70
  const save = (credentials: AgentCredentials) =>
66
71
  saveAgentCredentials(apiUrl, directory, credentials);
@@ -77,10 +82,11 @@ export async function prepareAgentDeployment(
77
82
  if (identity.registrationId !== credentials.registrationId)
78
83
  throw new Error('Agent identity does not match the saved registration.');
79
84
  if (identity.identityType === 'user_bound') return { status: 'claimed' };
80
- progress('Provisioning the site and repository...');
85
+ progress(`Provisioning the ${options.base ? 'base' : 'site'} and repository...`);
81
86
  const site = await provisionAgentSite(apiUrl, authenticated.accessToken, {
82
87
  subdomain,
83
88
  orgName: options.orgName,
89
+ ...(options.base && { projectType }),
84
90
  });
85
91
  if (site.status === 'expired') {
86
92
  warn('The previous anonymous site expired. Creating a new registration and site.');
@@ -103,7 +109,14 @@ export async function prepareAgentDeployment(
103
109
  const write = await getAgentWriteToken(apiUrl, authenticated.accessToken);
104
110
  if (write.remote !== site.remote)
105
111
  throw new Error('Repository credentials do not match the provisioned site.');
106
- return { status: 'ready', site, write, created: site.created, credentials: authenticated };
112
+ return {
113
+ status: 'ready',
114
+ projectType,
115
+ site,
116
+ write,
117
+ created: site.created,
118
+ credentials: authenticated,
119
+ };
107
120
  }
108
121
  throw new Error('The new anonymous site is already expired. Please retry later.');
109
122
  }
@@ -116,10 +129,28 @@ export function agentSiteUrl(apiUrl: string, subdomain: string): string {
116
129
  return new URL(`https://${subdomain}.${domain}`).toString();
117
130
  }
118
131
 
132
+ export function agentEditorUrl(site: Pick<AgentSite, 'subdomain' | 'orgSlug'>): string {
133
+ return site.orgSlug
134
+ ? new URL(`/${site.orgSlug}/${site.subdomain}/editor`, DASHBOARD_URL).toString()
135
+ : new URL(DASHBOARD_URL).toString();
136
+ }
137
+
119
138
  export function formatDeployment(result: DeployResult, apiUrl: string): string {
120
139
  if (result.status === 'claimed')
121
140
  return 'This site has been claimed. Manage it in the Mintlify dashboard. Use mint deploy --new --subdomain <name> to create another site.';
122
141
  const { site, write } = result;
142
+ const access = [
143
+ `Repository: ${site.remote}`,
144
+ `Branch: ${site.branch}`,
145
+ `Write token: ${write.token}`,
146
+ `Write token expires: ${write.tokenExpiresAt}`,
147
+ ];
148
+ if (result.projectType === 'workspace')
149
+ return [
150
+ result.created ? 'Base provisioned.' : 'Using the existing anonymous base.',
151
+ `Editor: ${agentEditorUrl(site)}`,
152
+ `Expires: ${site.expiresAt}. Claim it before then or it is deleted.`,
153
+ ].join('\n');
123
154
  const siteUrl = agentSiteUrl(apiUrl, site.subdomain);
124
155
  return [
125
156
  result.created
@@ -127,10 +158,7 @@ export function formatDeployment(result: DeployResult, apiUrl: string): string {
127
158
  : 'Using the existing anonymous repository; push any changes to build the site.',
128
159
  `Site: ${siteUrl}`,
129
160
  `Expires: ${site.expiresAt}`,
130
- `Repository: ${site.remote}`,
131
- `Branch: ${site.branch}`,
132
- `Write token: ${write.token}`,
133
- `Write token expires: ${write.tokenExpiresAt}`,
161
+ ...access,
134
162
  'Commit and push your documentation to this repository to start the build.',
135
163
  'The site link becomes available after the push and build complete.',
136
164
  ].join('\n');