@gitbook/react-openapi 1.5.2 → 1.5.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 1.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b4a021a: Add heredoc support for cURL JSON body
8
+ - a512c90: Re-arrange OpenAPI Scopes for OAuth2
9
+ - df1966d: Bump Scalar
10
+ - b45feaf: Disable OpenAPI "Try it" when no servers are defined
11
+ - 10995e0: Use NPM Trusted publishing for publishing the package.
12
+ - f9f8011: Add alt text support to card covers
13
+ - 8ce7322: Add OpenAPI servers selection
14
+ - 2c3066e: Improve OAuth2 scopes handling in OpenAPI
15
+ - Updated dependencies [df1966d]
16
+ - Updated dependencies [10995e0]
17
+ - Updated dependencies [10995e0]
18
+ - @gitbook/openapi-parser@3.0.5
19
+ - @gitbook/expr@1.2.4
20
+
3
21
  ## 1.5.2
4
22
 
5
23
  ### Patch Changes
@@ -3,10 +3,10 @@
3
3
 
4
4
  import { Section, SectionBody, SectionHeader, SectionHeaderContent } from "./StaticSection.js";
5
5
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
- import { useDisclosureState } from "./node_modules/react-stately/dist/import.js";
7
6
  import clsx from "classnames";
8
7
  import { useRef } from "react";
9
8
  import { mergeProps, useButton, useDisclosure, useFocusRing } from "react-aria";
9
+ import { useDisclosureState } from "react-stately";
10
10
 
11
11
  //#region src/InteractiveSection.tsx
12
12
  /**
@@ -37,7 +37,7 @@ function InteractiveSection(props) {
37
37
  <div className={clsx("openapi-section-header-controls", `${className}-header-controls`)} onClick={(event) => {
38
38
  event.stopPropagation();
39
39
  }}>
40
- {tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onSelectionChange={() => {
40
+ {tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onChange={() => {
41
41
  state.expand();
42
42
  }} icon={selectIcon} placement="bottom end">
43
43
  {tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
@@ -132,10 +132,10 @@ function OpenAPICodeSampleFooter(props) {
132
132
  const hideTryItPanel = data["x-hideTryItPanel"] || data.operation["x-hideTryItPanel"];
133
133
  const hasMultipleMediaTypes = renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0);
134
134
  if (hideTryItPanel && !hasMultipleMediaTypes) return null;
135
- if (!validateHttpMethod(method)) return null;
135
+ if (!validateHttpMethod(method) || !hasMultipleMediaTypes && servers.length === 0) return null;
136
136
  return <div className="openapi-codesample-footer">
137
137
  {hasMultipleMediaTypes ? <OpenAPIMediaTypeExamplesSelector method={data.method} path={data.path} renderers={renderers} selectIcon={context.icons.chevronDown} blockKey={context.blockKey} /> : <span />}
138
- {!hideTryItPanel && <ScalarApiButton context={getOpenAPIClientContext(context)} method={method} path={path} securities={securities} servers={servers} specUrl={specUrl} />}
138
+ {!hideTryItPanel && servers.length > 0 && <ScalarApiButton context={getOpenAPIClientContext(context)} method={method} path={path} securities={securities} servers={servers} specUrl={specUrl} />}
139
139
  </div>;
140
140
  }
141
141
  /**
@@ -3,7 +3,6 @@
3
3
 
4
4
  import { createStateKey } from "./utils.js";
5
5
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
- import clsx from "classnames";
7
6
 
8
7
  //#region src/OpenAPICodeSampleInteractive.tsx
9
8
  function OpenAPIMediaTypeExamplesSelector(props) {
@@ -24,10 +23,7 @@ function MediaTypeSelector(props) {
24
23
  key: renderer.mediaType,
25
24
  label: renderer.mediaType
26
25
  }));
27
- return <OpenAPISelect className={clsx("openapi-select")} items={renderers.map((renderer) => ({
28
- key: renderer.mediaType,
29
- label: renderer.mediaType
30
- }))} icon={selectIcon} stateKey={stateKey} placement="bottom start">
26
+ return <OpenAPISelect items={items} icon={selectIcon} stateKey={stateKey} placement="bottom start">
31
27
  {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
32
28
  {item.label}
33
29
  </OpenAPISelectItem>)}
@@ -2,8 +2,10 @@
2
2
 
3
3
 
4
4
  import { t } from "./translate.js";
5
+ import { OpenAPITooltip } from "./OpenAPITooltip.js";
6
+ import clsx from "classnames";
5
7
  import { useState } from "react";
6
- import { Button, Tooltip, TooltipTrigger } from "react-aria-components";
8
+ import { Button } from "react-aria-components";
7
9
 
8
10
  //#region src/OpenAPICopyButton.tsx
9
11
  function OpenAPICopyButton(props) {
@@ -21,18 +23,21 @@ function OpenAPICopyButton(props) {
21
23
  }, 2e3);
22
24
  });
23
25
  };
24
- return <TooltipTrigger isOpen={isOpen} onOpenChange={setIsOpen} isDisabled={!withTooltip} closeDelay={200} delay={200}>
26
+ return <OpenAPITooltip isDisabled={!withTooltip} isOpen={isOpen} onOpenChange={setIsOpen}>
25
27
  <Button type="button" preventFocusOnPress onPress={(e) => {
26
28
  handleCopy();
27
29
  onPress?.(e);
28
- }} className={`openapi-copy-button ${className}`} {...props}>
30
+ }} className={clsx("openapi-copy-button", className)} {...props}>
29
31
  {children}
30
32
  </Button>
31
33
 
32
- <Tooltip isOpen={isOpen} onOpenChange={setIsOpen} placement="top" offset={4} className="openapi-tooltip">
33
- {copied ? t(context.translation, "copied") : label || t(context.translation, "copy_to_clipboard")}
34
- </Tooltip>
35
- </TooltipTrigger>;
34
+ <OpenAPITooltip.Content isOpen={isOpen} onOpenChange={setIsOpen}>
35
+ {copied ? <>
36
+ {context.icons.check}
37
+ {t(context.translation, "copied")}
38
+ </> : label || t(context.translation, "copy_to_clipboard")}
39
+ </OpenAPITooltip.Content>
40
+ </OpenAPITooltip>;
36
41
  }
37
42
 
38
43
  //#endregion
@@ -10,19 +10,17 @@ import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
10
10
  * Display an interactive OpenAPI disclosure.
11
11
  */
12
12
  function OpenAPIDisclosure(props) {
13
- const { icon, header, label, children, className } = props;
14
- const [isExpanded, setIsExpanded] = useState(false);
13
+ const { icon, header, label, children, className, defaultExpanded = false } = props;
14
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
15
15
  return <Disclosure className={clsx("openapi-disclosure", className)} isExpanded={isExpanded} onExpandedChange={setIsExpanded}>
16
16
  <Button slot="trigger" className="openapi-disclosure-trigger" style={({ isFocusVisible }) => ({ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" })}>
17
17
  {header}
18
18
  <div className="openapi-disclosure-trigger-label">
19
- <span>{typeof label === "function" ? label(isExpanded) : label}</span>
19
+ {label ? <span>{typeof label === "function" ? label(isExpanded) : label}</span> : null}
20
20
  {icon}
21
21
  </div>
22
22
  </Button>
23
- <DisclosurePanel className="openapi-disclosure-panel">
24
- {isExpanded ? children : null}
25
- </DisclosurePanel>
23
+ {isExpanded ? <DisclosurePanel className="openapi-disclosure-panel">{children}</DisclosurePanel> : null}
26
24
  </Disclosure>;
27
25
  }
28
26
 
@@ -2,24 +2,38 @@
2
2
 
3
3
 
4
4
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
5
- import { useDisclosureGroupState, useDisclosureState } from "./node_modules/react-stately/dist/import.js";
5
+ import { getOrCreateDisclosureStoreByKey } from "./getOrCreateDisclosureStoreByKey.js";
6
+ import clsx from "classnames";
6
7
  import { createContext, useContext, useRef } from "react";
8
+ import { useStore } from "zustand";
7
9
  import { mergeProps, useButton, useDisclosure, useFocusRing, useId as useId$1 } from "react-aria";
10
+ import { useDisclosureGroupState, useDisclosureState } from "react-stately";
8
11
 
9
12
  //#region src/OpenAPIDisclosureGroup.tsx
10
13
  const DisclosureGroupStateContext = createContext(null);
14
+ function useDisclosureGroupStore(stateKey = "disclosure-group", initialKeys) {
15
+ return useStore(getOrCreateDisclosureStoreByKey(stateKey, initialKeys));
16
+ }
11
17
  /**
12
18
  * Display an interactive OpenAPI disclosure group.
13
19
  */
14
20
  function OpenAPIDisclosureGroup(props) {
15
- const { icon, groups, selectStateKey, selectIcon } = props;
16
- const state = useDisclosureGroupState(props);
21
+ const { icon, groups, selectStateKey, stateKey, selectIcon, className, expandedKeys, defaultExpandedKeys, onExpandedChange } = props;
22
+ const { expandedKeys: storeExpandedKeys, setExpandedKeys } = useDisclosureGroupStore(stateKey, expandedKeys || defaultExpandedKeys ? new Set(expandedKeys || defaultExpandedKeys) : void 0);
23
+ const state = useDisclosureGroupState({
24
+ ...props,
25
+ expandedKeys: storeExpandedKeys,
26
+ onExpandedChange: (keys) => {
27
+ setExpandedKeys(keys);
28
+ onExpandedChange?.(keys);
29
+ }
30
+ });
17
31
  return <DisclosureGroupStateContext.Provider value={state}>
18
- {groups.map((group) => <DisclosureItem selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
32
+ {groups.map((group) => <DisclosureItem className={className} selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
19
33
  </DisclosureGroupStateContext.Provider>;
20
34
  }
21
35
  function DisclosureItem(props) {
22
- const { icon, group, selectStateKey, selectIcon } = props;
36
+ const { icon, group, selectStateKey, selectIcon, className } = props;
23
37
  const defaultId = useId$1();
24
38
  const id = group.key || defaultId;
25
39
  const groupState = useContext(DisclosureGroupStateContext);
@@ -42,7 +56,7 @@ function DisclosureItem(props) {
42
56
  const { isFocusVisible, focusProps } = useFocusRing();
43
57
  const store = useSelectState(selectStateKey, group.tabs?.[0]?.key || "");
44
58
  const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
45
- return <div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
59
+ return <div className={clsx("openapi-disclosure-group", className)} aria-expanded={state.isExpanded}>
46
60
  <div slot="trigger" ref={triggerRef} {...mergeProps(buttonProps, focusProps)} aria-disabled={isDisabled} style={{ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500)/0.4)" : "none" }} className="openapi-disclosure-group-trigger">
47
61
  <div className="openapi-disclosure-group-icon">
48
62
  {icon || <svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
@@ -54,7 +68,7 @@ function DisclosureItem(props) {
54
68
  {group.label}
55
69
 
56
70
  {group.tabs ? <div className="openapi-disclosure-group-mediatype" onClick={(e) => e.stopPropagation()}>
57
- {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onSelectionChange={() => {
71
+ {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onChange={() => {
58
72
  state.expand();
59
73
  }} items={group.tabs} placement="bottom end">
60
74
  {group.tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
@@ -1,50 +1,24 @@
1
- import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
2
1
  import { getOpenAPIClientContext } from "./context.js";
2
+ import { OpenAPIPathItem } from "./OpenAPIPathItem.js";
3
+ import { formatPath } from "./formatPath.js";
3
4
  import { getDefaultServerURL } from "./util/server.js";
5
+ import { OpenAPIPathMultipleServers } from "./OpenAPIPathMultipleServers.js";
4
6
 
5
7
  //#region src/OpenAPIPath.tsx
6
8
  /**
7
9
  * Display the path of an operation.
8
10
  */
9
11
  function OpenAPIPath(props) {
10
- const { data, context, withServer = true, canCopy = true } = props;
11
- const { method, path, operation } = data;
12
- const server = getDefaultServerURL(data.servers);
12
+ const { data, withServer = true, context } = props;
13
+ const { path } = data;
14
+ const clientContext = getOpenAPIClientContext(context);
15
+ if (withServer && data.servers.length > 1) return <OpenAPIPathMultipleServers {...props} context={clientContext} />;
13
16
  const formattedPath = formatPath(path);
14
- const element = (() => {
15
- return <>
16
- {withServer ? <span className="openapi-path-server">{server}</span> : null}
17
- {formattedPath}
18
- </>;
19
- })();
20
- return <div className="openapi-path">
21
- <div className={`openapi-method openapi-method-${method}`}>{method}</div>
22
-
23
- <OpenAPICopyButton value={`${withServer ? server : ""}${path}`} className="openapi-path-title" data-deprecated={operation.deprecated} isDisabled={!canCopy} context={getOpenAPIClientContext(context)}>
24
- {element}
25
- </OpenAPICopyButton>
26
- </div>;
27
- }
28
- /**
29
- * Format the path by wrapping placeholders in <span> tags.
30
- */
31
- function formatPath(path) {
32
- const regex = /\{\s*(\w+)\s*\}|:\w+/g;
33
- const parts = [];
34
- let lastIndex = 0;
35
- path.replace(regex, (match, _, offset) => {
36
- if (offset > lastIndex) parts.push(path.slice(lastIndex, offset));
37
- parts.push(<span key={`offset-${offset}`} className="openapi-path-variable">
38
- {match}
39
- </span>);
40
- lastIndex = offset + match.length;
41
- return match;
42
- });
43
- if (lastIndex < path.length) parts.push(path.slice(lastIndex));
44
- return parts.map((part, index) => {
45
- if (typeof part === "string") return <span key={`part-${index}`}>{part}</span>;
46
- return part;
47
- });
17
+ const defaultServer = getDefaultServerURL(data.servers);
18
+ return <OpenAPIPathItem {...props} value={`${defaultServer}${path}`} context={clientContext}>
19
+ {withServer ? <span className="openapi-path-server">{defaultServer}</span> : null}
20
+ {formattedPath}
21
+ </OpenAPIPathItem>;
48
22
  }
49
23
 
50
24
  //#endregion
@@ -0,0 +1,22 @@
1
+ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
2
+
3
+ //#region src/OpenAPIPathItem.tsx
4
+ function OpenAPIPathItem(props) {
5
+ const { value, canCopy = true, context, children, data, copyType = "children" } = props;
6
+ const { operation, method } = data;
7
+ const title = <span className="openapi-path-title">{children}</span>;
8
+ return <div className="openapi-path">
9
+ <div className={`openapi-method openapi-method-${method}`}>{method}</div>
10
+ {canCopy && value ? copyType === "children" ? <OpenAPICopyButton value={value} data-deprecated={operation.deprecated} isDisabled={!canCopy} context={context} className="openapi-path-copy-button">
11
+ {title}
12
+ </OpenAPICopyButton> : <>
13
+ {title}
14
+ <OpenAPICopyButton value={value} data-deprecated={operation.deprecated} isDisabled={!canCopy} context={context} className="openapi-path-copy-button openapi-path-copy-button-icon">
15
+ {context.icons.copy}
16
+ </OpenAPICopyButton>
17
+ </> : title}
18
+ </div>;
19
+ }
20
+
21
+ //#endregion
22
+ export { OpenAPIPathItem };
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+
4
+ import { OpenAPITooltip } from "./OpenAPITooltip.js";
5
+ import { createStateKey } from "./utils.js";
6
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
7
+ import { OpenAPIPathItem } from "./OpenAPIPathItem.js";
8
+ import { formatPath } from "./formatPath.js";
9
+ import { getDefaultServerURL } from "./util/server.js";
10
+ import { Text } from "react-aria-components";
11
+
12
+ //#region src/OpenAPIPathMultipleServers.tsx
13
+ const serversStateKey = createStateKey("servers");
14
+ /**
15
+ * Display the path of an operation.
16
+ */
17
+ function OpenAPIPathMultipleServers(props) {
18
+ const { data, withServer = true, context } = props;
19
+ const { path, servers } = data;
20
+ const defaultServer = getDefaultServerURL(servers);
21
+ const { key, setKey } = useSelectState(serversStateKey, defaultServer);
22
+ const formattedPath = formatPath(path);
23
+ const items = servers.filter((server) => !!server.url).map((server) => ({
24
+ key: server.url,
25
+ label: server.url,
26
+ description: server.description
27
+ }));
28
+ return <OpenAPIPathItem copyType="button" {...props} value={`${withServer ? key : ""}${path}`} context={context}>
29
+ {withServer ? <OpenAPITooltip>
30
+ <OpenAPISelect className="openapi-select openapi-select-unstyled" items={items} stateKey={serversStateKey} placement="bottom start" icon={context.icons.chevronDown} defaultValue={defaultServer} onChange={setKey}>
31
+ {items.map((item) => <OpenAPISelectItem textValue={item.label} key={item.key} id={item.key} value={item} className="openapi-select-item-column">
32
+ <Text slot="label">{item.label}</Text>
33
+ {item.description ? <Text slot="description">{item.description}</Text> : null}
34
+ </OpenAPISelectItem>)}
35
+ </OpenAPISelect>
36
+ <OpenAPITooltip.Content>Click to select a server</OpenAPITooltip.Content>
37
+ </OpenAPITooltip> : null}
38
+ {formattedPath}
39
+ </OpenAPIPathItem>;
40
+ }
41
+
42
+ //#endregion
43
+ export { OpenAPIPathMultipleServers };
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+
4
+ import { t } from "./translate.js";
5
+ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
6
+ import { useSelectState } from "./OpenAPISelect.js";
7
+ import { OpenAPIDisclosureGroup } from "./OpenAPIDisclosureGroup.js";
8
+
9
+ //#region src/OpenAPIRequiredScopes.tsx
10
+ /**
11
+ * Present securities authorization that can be used for this operation.
12
+ */
13
+ function OpenAPIRequiredScopes(props) {
14
+ const { securities, stateKey, context } = props;
15
+ const { key: selectedKey } = useSelectState(stateKey, securities[0]?.key);
16
+ const selectedSecurity = securities.find((security) => security.key === selectedKey);
17
+ if (!selectedSecurity) return null;
18
+ const scopes = selectedSecurity.schemes.flatMap((scheme) => {
19
+ return scheme.scopes ?? [];
20
+ });
21
+ if (!scopes.length) return null;
22
+ return <OpenAPIDisclosureGroup className="openapi-required-scopes" icon={context.icons.chevronRight} stateKey="required-scopes" defaultExpandedKeys={["required-scopes"]} groups={[{
23
+ key: "required-scopes",
24
+ label: <div className="openapi-required-scopes-header">
25
+ {context.icons.lock}
26
+ <span>{t(context.translation, "required_scopes")}</span>
27
+ </div>,
28
+ tabs: [{
29
+ key: "scopes",
30
+ label: "",
31
+ body: <OpenAPISchemaScopes scopes={scopes} context={context} />
32
+ }]
33
+ }]} />;
34
+ }
35
+ function OpenAPISchemaScopes(props) {
36
+ const { scopes, context, isOAuth2 } = props;
37
+ return <div className="openapi-securities-scopes openapi-markdown">
38
+ <div className="openapi-required-scopes-description">
39
+ {t(context.translation, isOAuth2 ? "available_scopes" : "required_scopes_description")}
40
+ </div>
41
+ <ul>
42
+ {scopes.map((scope) => <OpenAPIScopeItem key={scope[0]} scope={scope} context={context} />)}
43
+ </ul>
44
+ </div>;
45
+ }
46
+ /**
47
+ * Display a scope item. Either a key-value pair or a single string.
48
+ */
49
+ function OpenAPIScopeItem(props) {
50
+ const { scope, context } = props;
51
+ return <li>
52
+ <OpenAPIScopeItemKey name={scope[0]} context={context} />
53
+ {scope[1] ? <span>: {scope[1]}</span> : null}
54
+ </li>;
55
+ }
56
+ /**
57
+ * Displays the scope name within a copyable button.
58
+ */
59
+ function OpenAPIScopeItemKey(props) {
60
+ const { name, context } = props;
61
+ return <OpenAPICopyButton value={name} context={context} withTooltip>
62
+ <code>{name}</code>
63
+ </OpenAPICopyButton>;
64
+ }
65
+
66
+ //#endregion
67
+ export { OpenAPIRequiredScopes, OpenAPISchemaScopes };
@@ -4,6 +4,7 @@ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
4
4
  import { OpenAPISchemaName } from "./OpenAPISchemaName.js";
5
5
  import { createStateKey, extractOperationSecurityInfo, resolveDescription } from "./utils.js";
6
6
  import { InteractiveSection } from "./InteractiveSection.js";
7
+ import { OpenAPIRequiredScopes, OpenAPISchemaScopes } from "./OpenAPIRequiredScopes.js";
7
8
  import { Fragment } from "react";
8
9
 
9
10
  //#region src/OpenAPISecurities.tsx
@@ -17,20 +18,23 @@ function OpenAPISecurities(props) {
17
18
  securityRequirement,
18
19
  securities
19
20
  });
20
- return <InteractiveSection header={t(context.translation, "authorizations")} stateKey={createStateKey("securities", context.blockKey)} toggeable defaultOpened={false} toggleIcon={context.icons.chevronRight} selectIcon={context.icons.chevronDown} className="openapi-securities" tabs={tabsData.map(({ key, label, schemes }) => ({
21
+ const stateKey = createStateKey("securities", context.blockKey);
22
+ return <>
23
+ <OpenAPIRequiredScopes context={context} stateKey={stateKey} securities={tabsData} />
24
+ <InteractiveSection header={t(context.translation, "authorizations")} stateKey={stateKey} toggleIcon={context.icons.chevronRight} selectIcon={context.icons.chevronDown} className="openapi-securities" tabs={tabsData.map(({ key, label, schemes }) => ({
21
25
  key,
22
26
  label,
23
27
  body: <div className="openapi-schema">
24
- {schemes.map((security, index) => {
25
- const description = resolveDescription(security);
28
+ {schemes.map((security, index) => {
29
+ const description = security.type !== "oauth2" ? resolveDescription(security) : void 0;
26
30
  return <div key={`${key}-${index}`} className="openapi-schema-presentation">
27
- {getLabelForType(security, context)}
28
- {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
29
- {security.scopes?.length ? <OpenAPISchemaScopes scopes={security.scopes} context={context} /> : null}
30
- </div>;
31
+ {getLabelForType(security, context)}
32
+ {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
33
+ </div>;
31
34
  })}
32
- </div>
33
- }))} />;
35
+ </div>
36
+ }))} />
37
+ </>;
34
38
  }
35
39
  function getLabelForType(security, context) {
36
40
  switch (security.type) {
@@ -61,11 +65,12 @@ function OpenAPISchemaOAuth2Flows(props) {
61
65
  function OpenAPISchemaOAuth2Item(props) {
62
66
  const { flow, context, security, name } = props;
63
67
  if (!flow) return null;
64
- const scopes = flow.scopes ? Object.entries(flow.scopes) : [];
68
+ const scopes = !security.scopes?.length && flow.scopes ? Object.entries(flow.scopes) : [];
69
+ const description = resolveDescription(security);
65
70
  return <div>
66
71
  <OpenAPISchemaName context={context} propertyName="OAuth2" type={name} required={security.required} />
67
72
  <div className="openapi-securities-oauth-content openapi-markdown">
68
- {security.description ? <Markdown source={security.description} /> : null}
73
+ {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
69
74
  {"authorizationUrl" in flow && flow.authorizationUrl ? <span>
70
75
  Authorization URL:{" "}
71
76
  <OpenAPICopyButton value={flow.authorizationUrl} context={context} className="openapi-securities-url" withTooltip>
@@ -84,41 +89,10 @@ function OpenAPISchemaOAuth2Item(props) {
84
89
  {flow.refreshUrl}
85
90
  </OpenAPICopyButton>
86
91
  </span> : null}
87
- {scopes.length ? <OpenAPISchemaScopes scopes={scopes} context={context} /> : null}
92
+ {scopes.length ? <OpenAPISchemaScopes scopes={scopes} context={context} isOAuth2 /> : null}
88
93
  </div>
89
94
  </div>;
90
95
  }
91
- /**
92
- * Render a list of available scopes.
93
- */
94
- function OpenAPISchemaScopes(props) {
95
- const { scopes, context } = props;
96
- return <div className="openapi-securities-scopes openapi-markdown">
97
- <span>{t(context.translation, "required_scopes")}: </span>
98
- <ul>
99
- {scopes.map((scope) => <OpenAPIScopeItem key={scope[0]} scope={scope} context={context} />)}
100
- </ul>
101
- </div>;
102
- }
103
- /**
104
- * Display a scope item. Either a key-value pair or a single string.
105
- */
106
- function OpenAPIScopeItem(props) {
107
- const { scope, context } = props;
108
- return <li>
109
- <OpenAPIScopeItemKey name={scope[0]} context={context} />
110
- {scope[1] ? `: ${scope[1]}` : null}
111
- </li>;
112
- }
113
- /**
114
- * Displays the scope name within a copyable button.
115
- */
116
- function OpenAPIScopeItemKey(props) {
117
- const { name, context } = props;
118
- return <OpenAPICopyButton value={name} context={context} withTooltip>
119
- <code>{name}</code>
120
- </OpenAPICopyButton>;
121
- }
122
96
 
123
97
  //#endregion
124
98
  export { OpenAPISecurities };
@@ -16,16 +16,16 @@ function useSelectState(stateKey = "select-state", initialKey = "default") {
16
16
  };
17
17
  }
18
18
  function OpenAPISelect(props) {
19
- const { icon = "▼", items, children, className, placement, stateKey, selectedKey, onSelectionChange } = props;
20
- const state = useSelectState(stateKey, items[0]?.key);
19
+ const { icon, items, children, className, placement, stateKey, value, onChange, defaultValue } = props;
20
+ const state = useSelectState(stateKey, defaultValue ?? items[0]?.key);
21
21
  const selected = items.find((item) => item.key === state.key) || items[0];
22
- return <Select aria-label="OpenAPI Select" {...props} value={selectedKey || selected?.key} onChange={(key) => {
23
- onSelectionChange?.(key);
22
+ return <Select aria-label="OpenAPI Select" {...props} value={value ?? selected?.key} onChange={(key) => {
23
+ onChange?.(key);
24
24
  state.setKey(key);
25
25
  }} className={clsx("openapi-select", className)}>
26
26
  <Button>
27
27
  <SelectValue />
28
- {icon}
28
+ {icon !== null ? icon || "▼" : null}
29
29
  </Button>
30
30
  <Popover placement={placement} className="openapi-select-popover">
31
31
  <ListBox className="openapi-select-listbox" items={items}>
@@ -38,7 +38,7 @@ function OpenAPISelectItem(props) {
38
38
  return <ListBoxItem {...props} className={({ isFocused, isSelected }) => clsx("openapi-select-item", {
39
39
  "openapi-select-item-focused": isFocused,
40
40
  "openapi-select-item-selected": isSelected
41
- })} />;
41
+ }, props.className)} />;
42
42
  }
43
43
 
44
44
  //#endregion
@@ -36,7 +36,7 @@ function groupParameters(parameters, context) {
36
36
  const key = parameter.in;
37
37
  const label = getParameterGroupName(parameter.in, context);
38
38
  const group = groups.find((group$1) => group$1.key === key);
39
- if (group) group.parameters.push(parameter);
39
+ if (group) group.parameters = [...group.parameters, parameter];
40
40
  else groups.push({
41
41
  key,
42
42
  label,
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+
4
+ import clsx from "classnames";
5
+ import { Tooltip, TooltipTrigger } from "react-aria-components";
6
+
7
+ //#region src/OpenAPITooltip.tsx
8
+ function OpenAPITooltip(props) {
9
+ const { children,...rest } = props;
10
+ return <TooltipTrigger {...rest} closeDelay={200} delay={200}>
11
+ {children}
12
+ </TooltipTrigger>;
13
+ }
14
+ function OpenAPITooltipContent(props) {
15
+ const { children, placement = "top", offset = 4, className,...rest } = props;
16
+ return <Tooltip {...rest} placement={placement} offset={offset} className={clsx("openapi-tooltip", className)}>
17
+ {children}
18
+ </Tooltip>;
19
+ }
20
+ OpenAPITooltip.Content = OpenAPITooltipContent;
21
+
22
+ //#endregion
23
+ export { OpenAPITooltip };
@@ -47,12 +47,15 @@ function ScalarModal(props) {
47
47
  url: specUrl,
48
48
  ...prefillConfig
49
49
  }} initialRequest={{
50
- method,
50
+ method: toScalarHttpMethod(method),
51
51
  path
52
52
  }}>
53
53
  <ScalarModalController method={method} path={path} controllerRef={controllerRef} />
54
54
  </ApiClientModalProvider>;
55
55
  }
56
+ function toScalarHttpMethod(method) {
57
+ return method.toUpperCase();
58
+ }
56
59
  function ScalarModalController(props) {
57
60
  const { method, path, controllerRef } = props;
58
61
  const openScalarClient = useApiClientModal()?.open;
@@ -60,7 +63,7 @@ function ScalarModalController(props) {
60
63
  const openClient = useMemo(() => {
61
64
  if (openScalarClient) return () => {
62
65
  openScalarClient({
63
- method,
66
+ method: toScalarHttpMethod(method),
64
67
  path,
65
68
  _source: "gitbook"
66
69
  });
@@ -33,7 +33,6 @@ ${headerString}${bodyString}`;
33
33
  label: "cURL",
34
34
  syntax: "bash",
35
35
  generate: ({ method, url: { origin, path }, headers, body }) => {
36
- const separator = " \\\n";
37
36
  const lines = ["curl -L"];
38
37
  if (method.toUpperCase() !== "GET") lines.push(`--request ${method.toUpperCase()}`);
39
38
  lines.push(`--url '${origin}${path}'`);
@@ -49,7 +48,7 @@ ${headerString}${bodyString}`;
49
48
  });
50
49
  if (body) if (Array.isArray(body)) lines.push(...body);
51
50
  else lines.push(body);
52
- return lines.map((line, index) => index > 0 ? indent(line, 2) : line).join(separator);
51
+ return buildHeredoc(lines);
53
52
  }
54
53
  },
55
54
  {
@@ -138,7 +137,15 @@ const BodyGenerators = {
138
137
  headersCopy["Content-Type"] = "application/json";
139
138
  } else if (isPDF(contentType)) body = `--data-binary '@${String(body)}'`;
140
139
  else if (isYAML(contentType)) body = `--data-binary $'${yaml.dump(body).replace(/'/g, "").replace(/\\n/g, "\n")}'`;
141
- else body = `--data '${stringifyOpenAPI(body, null, 2).replace(/\\n/g, "\n")}'`;
140
+ else {
141
+ const jsonString = stringifyOpenAPI(body, null, 2).replace(/\\n/g, "\n");
142
+ if (jsonString.includes("'")) body = [
143
+ "--data @- <<'EOF'",
144
+ ...jsonString.split("\n"),
145
+ "EOF"
146
+ ];
147
+ else body = `--data '${jsonString}'`;
148
+ }
142
149
  return {
143
150
  body,
144
151
  headers: headersCopy
@@ -270,6 +277,29 @@ function convertBodyToXML(body) {
270
277
  }
271
278
  return json2xml(body).replace(/"/g, "").replace(/\\n/g, "\n").replace(/\\t/g, " ");
272
279
  }
280
+ /**
281
+ * Builds a heredoc string from an array of lines
282
+ */
283
+ function buildHeredoc(lines) {
284
+ const separator = " \\\n";
285
+ let result = "";
286
+ let inHeredoc = false;
287
+ for (let i = 0; i < lines.length; i++) {
288
+ const line = lines[i];
289
+ if (!line) continue;
290
+ const isHeredocStart = line.includes("<<'EOF'");
291
+ const isHeredocEnd = inHeredoc && line === "EOF";
292
+ if (isHeredocStart) {
293
+ inHeredoc = true;
294
+ result += `${i > 0 ? indent(line, 2) : line}\n`;
295
+ } else if (isHeredocEnd) {
296
+ inHeredoc = false;
297
+ result += line;
298
+ } else if (inHeredoc) result += `${indent(line, 2)}\n`;
299
+ else result += `${i > 0 ? indent(line, 2) : line}${i < lines.length - 1 ? separator : ""}`;
300
+ }
301
+ return result;
302
+ }
273
303
 
274
304
  //#endregion
275
305
  export { codeSampleGenerators, parseHostAndPath };
package/dist/context.d.ts CHANGED
@@ -14,6 +14,9 @@ interface OpenAPIClientContext {
14
14
  chevronDown: React.ReactNode;
15
15
  chevronRight: React.ReactNode;
16
16
  plus: React.ReactNode;
17
+ copy: React.ReactNode;
18
+ check: React.ReactNode;
19
+ lock: React.ReactNode;
17
20
  };
18
21
  /**
19
22
  * Force all sections to be opened by default.
@@ -0,0 +1,25 @@
1
+ //#region src/formatPath.tsx
2
+ /**
3
+ * Format the path by wrapping placeholders in <span> tags.
4
+ */
5
+ function formatPath(path) {
6
+ const regex = /\{\s*(\w+)\s*\}|:\w+/g;
7
+ const parts = [];
8
+ let lastIndex = 0;
9
+ path.replace(regex, (match, _, offset) => {
10
+ if (offset > lastIndex) parts.push(path.slice(lastIndex, offset));
11
+ parts.push(<span key={`offset-${offset}`} className="openapi-path-variable">
12
+ {match}
13
+ </span>);
14
+ lastIndex = offset + match.length;
15
+ return match;
16
+ });
17
+ if (lastIndex < path.length) parts.push(path.slice(lastIndex));
18
+ return parts.map((part, index) => {
19
+ if (typeof part === "string") return <span key={`part-${index}`}>{part}</span>;
20
+ return part;
21
+ });
22
+ }
23
+
24
+ //#endregion
25
+ export { formatPath };
@@ -0,0 +1,31 @@
1
+ import { createStore } from "zustand";
2
+
3
+ //#region src/getOrCreateDisclosureStoreByKey.ts
4
+ const createDisclosureStore = (initialKeys) => {
5
+ return createStore()((set) => ({
6
+ expandedKeys: initialKeys ? new Set(initialKeys) : /* @__PURE__ */ new Set(),
7
+ setExpandedKeys: (keys) => {
8
+ set(() => ({ expandedKeys: keys }));
9
+ },
10
+ toggleKey: (key) => {
11
+ set((state) => {
12
+ const newKeys = new Set(state.expandedKeys);
13
+ if (newKeys.has(key)) newKeys.delete(key);
14
+ else newKeys.add(key);
15
+ return { expandedKeys: newKeys };
16
+ });
17
+ }
18
+ }));
19
+ };
20
+ const defaultDisclosureStores = /* @__PURE__ */ new Map();
21
+ const createDisclosureStoreFactory = (stores) => {
22
+ return (storeKey, initialKeys) => {
23
+ if (!stores.has(storeKey)) stores.set(storeKey, createDisclosureStore(initialKeys));
24
+ if (!stores.get(storeKey)) throw new Error(`Failed to get or create store for key: ${storeKey}`);
25
+ return stores.get(storeKey);
26
+ };
27
+ };
28
+ const getOrCreateDisclosureStoreByKey = createDisclosureStoreFactory(defaultDisclosureStores);
29
+
30
+ //#endregion
31
+ export { getOrCreateDisclosureStoreByKey };
@@ -88,8 +88,11 @@ function flattenSecurities(security) {
88
88
  * Resolve the scopes for a security scheme.
89
89
  */
90
90
  function resolveSecurityScopes({ securityScheme, operationScopes }) {
91
- if (!securityScheme || checkIsReference(securityScheme) || isOAuthSecurityScheme(securityScheme)) return null;
92
- return operationScopes?.map((scope) => [scope, void 0]) || [];
91
+ if (!operationScopes?.length || !securityScheme || checkIsReference(securityScheme)) return null;
92
+ if (isOAuthSecurityScheme(securityScheme)) return (securityScheme.flows ? Object.entries(securityScheme.flows) : []).flatMap(([_, flow]) => {
93
+ return Object.entries(flow.scopes ?? {}).filter(([scope]) => operationScopes.includes(scope));
94
+ });
95
+ return operationScopes.map((scope) => [scope, void 0]);
93
96
  }
94
97
  /**
95
98
  * Check if a security scheme is an OAuth or OpenID Connect security scheme.
package/dist/translate.js CHANGED
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { isValidElement } from "react";
2
2
 
3
3
  //#region src/translate.tsx
4
4
  /**
@@ -35,7 +35,7 @@ function reactToString(el) {
35
35
  if (typeof el === "string" || typeof el === "number" || typeof el === "boolean") return `${el}`;
36
36
  if (el === null || el === void 0) return "";
37
37
  if (Array.isArray(el)) return el.map(reactToString).join("");
38
- if (typeof el === "object" && "props" in el) return el.props.children.map(reactToString).join("");
38
+ if (isValidElement(el)) return el.props.children.map(reactToString).join("");
39
39
  throw new Error(`Unsupported type ${typeof el}`);
40
40
  }
41
41
 
@@ -38,6 +38,8 @@ const de = {
38
38
  hide: "Verstecke ${1}",
39
39
  available_items: "Verfügbare Elemente",
40
40
  required_scopes: "Erforderliche Scopes",
41
+ required_scopes_description: "Dieser Endpunkt erfordert die folgenden Scopes:",
42
+ available_scopes: "Verfügbare Scopes:",
41
43
  properties: "Eigenschaften",
42
44
  or: "oder",
43
45
  and: "und",
@@ -38,6 +38,8 @@ declare const en: {
38
38
  hide: string;
39
39
  available_items: string;
40
40
  required_scopes: string;
41
+ required_scopes_description: string;
42
+ available_scopes: string;
41
43
  possible_values: string;
42
44
  properties: string;
43
45
  or: string;
@@ -38,6 +38,8 @@ const en = {
38
38
  hide: "Hide ${1}",
39
39
  available_items: "Available items",
40
40
  required_scopes: "Required scopes",
41
+ required_scopes_description: "This endpoint requires the following scopes:",
42
+ available_scopes: "Available scopes:",
41
43
  possible_values: "Possible values",
42
44
  properties: "Properties",
43
45
  or: "or",
@@ -38,6 +38,8 @@ const es = {
38
38
  hide: "Ocultar ${1}",
39
39
  available_items: "Elementos disponibles",
40
40
  required_scopes: "Scopes requeridos",
41
+ required_scopes_description: "Este endpoint requiere los siguientes scopes:",
42
+ available_scopes: "Scopes disponibles:",
41
43
  properties: "Propiedades",
42
44
  or: "o",
43
45
  and: "y",
@@ -38,6 +38,8 @@ const fr = {
38
38
  hide: "Masquer ${1}",
39
39
  available_items: "Éléments disponibles",
40
40
  required_scopes: "Scopes requis",
41
+ required_scopes_description: "Cet endpoint nécessite les scopes suivants:",
42
+ available_scopes: "Scopes disponibles:",
41
43
  properties: "Propriétés",
42
44
  or: "ou",
43
45
  and: "et",
@@ -41,6 +41,8 @@ declare const translations: {
41
41
  hide: string;
42
42
  available_items: string;
43
43
  required_scopes: string;
44
+ required_scopes_description: string;
45
+ available_scopes: string;
44
46
  possible_values: string;
45
47
  properties: string;
46
48
  or: string;
@@ -85,6 +87,8 @@ declare const translations: {
85
87
  hide: string;
86
88
  available_items: string;
87
89
  required_scopes: string;
90
+ required_scopes_description: string;
91
+ available_scopes: string;
88
92
  properties: string;
89
93
  or: string;
90
94
  and: string;
@@ -129,6 +133,8 @@ declare const translations: {
129
133
  hide: string;
130
134
  available_items: string;
131
135
  required_scopes: string;
136
+ required_scopes_description: string;
137
+ available_scopes: string;
132
138
  properties: string;
133
139
  or: string;
134
140
  and: string;
@@ -173,6 +179,8 @@ declare const translations: {
173
179
  hide: string;
174
180
  available_items: string;
175
181
  required_scopes: string;
182
+ required_scopes_description: string;
183
+ available_scopes: string;
176
184
  properties: string;
177
185
  or: string;
178
186
  and: string;
@@ -217,6 +225,8 @@ declare const translations: {
217
225
  hide: string;
218
226
  available_items: string;
219
227
  required_scopes: string;
228
+ required_scopes_description: string;
229
+ available_scopes: string;
220
230
  properties: string;
221
231
  or: string;
222
232
  and: string;
@@ -261,6 +271,8 @@ declare const translations: {
261
271
  hide: string;
262
272
  available_items: string;
263
273
  required_scopes: string;
274
+ required_scopes_description: string;
275
+ available_scopes: string;
264
276
  properties: string;
265
277
  or: string;
266
278
  and: string;
@@ -305,6 +317,8 @@ declare const translations: {
305
317
  hide: string;
306
318
  available_items: string;
307
319
  required_scopes: string;
320
+ required_scopes_description: string;
321
+ available_scopes: string;
308
322
  properties: string;
309
323
  or: string;
310
324
  and: string;
@@ -349,6 +363,8 @@ declare const translations: {
349
363
  hide: string;
350
364
  available_items: string;
351
365
  required_scopes: string;
366
+ required_scopes_description: string;
367
+ available_scopes: string;
352
368
  properties: string;
353
369
  or: string;
354
370
  and: string;
@@ -393,6 +409,8 @@ declare const translations: {
393
409
  hide: string;
394
410
  available_items: string;
395
411
  required_scopes: string;
412
+ required_scopes_description: string;
413
+ available_scopes: string;
396
414
  properties: string;
397
415
  or: string;
398
416
  and: string;
@@ -38,6 +38,8 @@ const ja = {
38
38
  hide: "${1}を非表示",
39
39
  available_items: "利用可能なアイテム",
40
40
  required_scopes: "必須スコープ",
41
+ required_scopes_description: "このエンドポイントには次のスコープが必要です:",
42
+ available_scopes: "利用可能なスコープ:",
41
43
  properties: "プロパティ",
42
44
  or: "または",
43
45
  and: "かつ",
@@ -38,6 +38,8 @@ const nl = {
38
38
  hide: "Verberg ${1}",
39
39
  available_items: "Beschikbare items",
40
40
  required_scopes: "Vereiste scopes",
41
+ required_scopes_description: "Dit endpoint vereist de volgende scopes:",
42
+ available_scopes: "Beschikbare scopes:",
41
43
  properties: "Eigenschappen",
42
44
  or: "of",
43
45
  and: "en",
@@ -38,6 +38,8 @@ const no = {
38
38
  hide: "Skjul ${1}",
39
39
  available_items: "Tilgjengelige elementer",
40
40
  required_scopes: "Påkrevde scopes",
41
+ required_scopes_description: "Dette endepunktet krever følgende scopes:",
42
+ available_scopes: "Tilgjengelige scopes:",
41
43
  properties: "Egenskaper",
42
44
  or: "eller",
43
45
  and: "og",
@@ -38,6 +38,8 @@ const pt_br = {
38
38
  hide: "Ocultar ${1}",
39
39
  available_items: "Itens disponíveis",
40
40
  required_scopes: "Scopes obrigatórios",
41
+ required_scopes_description: "Este endpoint requer os seguintes scopes:",
42
+ available_scopes: "Scopes disponíveis:",
41
43
  properties: "Propriedades",
42
44
  or: "ou",
43
45
  and: "e",
@@ -38,6 +38,8 @@ const zh = {
38
38
  hide: "隐藏${1}",
39
39
  available_items: "可用项",
40
40
  required_scopes: "必需范围",
41
+ required_scopes_description: "此端点需要以下范围:",
42
+ available_scopes: "可用范围:",
41
43
  properties: "属性",
42
44
  or: "或",
43
45
  and: "和",
package/dist/types.d.ts CHANGED
@@ -6,6 +6,7 @@ type OpenAPIServerVariableWithCustomProperties = OpenAPIV3.ServerVariableObject
6
6
  * OpenAPI ServerObject type extended to provide x-gitbook prefill custom properties at the variable level.
7
7
  */
8
8
  type OpenAPIServerWithCustomProperties = Omit<OpenAPIV3.ServerObject, 'variables'> & {
9
+ name?: string;
9
10
  variables?: {
10
11
  [variable: string]: OpenAPIServerVariableWithCustomProperties;
11
12
  };
package/package.json CHANGED
@@ -7,31 +7,40 @@
7
7
  "default": "./dist/index.js"
8
8
  }
9
9
  },
10
- "version": "1.5.2",
10
+ "version": "1.5.3",
11
11
  "sideEffects": false,
12
12
  "dependencies": {
13
- "@gitbook/expr": "1.2.3",
14
- "@gitbook/openapi-parser": "3.0.4",
15
- "@scalar/api-client-react": "^1.3.16",
16
- "@scalar/oas-utils": "^0.2.130",
17
- "@scalar/types": "^0.1.9",
13
+ "@gitbook/expr": "1.2.4",
14
+ "@gitbook/openapi-parser": "3.0.5",
15
+ "@scalar/api-client-react": "^1.3.46",
16
+ "@scalar/oas-utils": "^0.6.3",
17
+ "@scalar/types": "^0.4.0",
18
18
  "classnames": "^2.5.1",
19
19
  "flatted": "^3.2.9",
20
20
  "json-xml-parse": "^1.3.0",
21
21
  "react-aria-components": "^1.13.0",
22
22
  "react-aria": "^3.44.0",
23
- "usehooks-ts": "^3.1.0",
23
+ "react-stately": "^3.42.0",
24
+ "usehooks-ts": "^3.1.1",
24
25
  "zustand": "^5.0.3",
25
26
  "js-yaml": "^4.1.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/js-yaml": "^4.0.9",
30
+ "@types/react": "^19.0.0",
31
+ "@types/react-dom": "^19.0.0",
29
32
  "bun-types": "^1.1.20",
30
33
  "react": "^19.0.0",
31
34
  "react-dom": "^19.0.0",
32
35
  "tsdown": "^0.15.6",
33
36
  "typescript": "^5.5.3"
34
37
  },
38
+ "overrides": {
39
+ "@types/react": "catalog:",
40
+ "@types/react-dom": "catalog:",
41
+ "react": "catalog:",
42
+ "react-dom": "catalog:"
43
+ },
35
44
  "peerDependencies": {
36
45
  "react": "*",
37
46
  "react-dom": "*"
@@ -41,11 +50,15 @@
41
50
  "typecheck": "tsc --noEmit",
42
51
  "unit": "bun test",
43
52
  "dev": "bun run build -- --watch ./src",
44
- "clean": "rm -rf ./dist"
53
+ "clean": "rm -rf ./dist",
54
+ "publish-to-npm": "../../scripts/publish-if-new.sh"
45
55
  },
46
56
  "files": ["dist", "README.md", "CHANGELOG.md"],
47
57
  "publishConfig": {
48
58
  "access": "public",
49
59
  "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "repository": {
62
+ "url": "https://github.com/GitbookIO/gitbook"
50
63
  }
51
64
  }