import type ts from "typescript"; import type { PackageJson } from "@arcgis/components-build-utils"; import type { CopyDocDefinitions } from "./extractors/copyDoc.js"; import type { ApiExtractor } from "./ApiExtractor.js"; import type { TypeScriptConfigResult } from "../vite/typeScript.js"; import type { ApiModule } from "../apiJson.js"; import type { ApiExtractorResult } from "./types.js"; export interface ApiExtractorConfig { readonly context?: ApiExtractorContextConfig; readonly types?: ApiExtractorTypesConfig; readonly documentation?: ApiExtractorDocumentationConfig; readonly verify?: ApiExtractorVerifyConfig; readonly watch?: ApiExtractorWatchConfig; /** * API Extractor is strict about ensuring public APIs only reference types * that were in turn explicitly marked as public. This avoids unintentionally * exposing private details. For packages in the process of transition to this * stricter syntax, you can temporarily disable the strictness. * * Under strict:false, the following checks are relaxed: * - Web components, and their `@property()`, `@method()`, and events are * implicitly public. The types referenced by them are also implicitly * public. This behavior is suppressed if `@private` JSDoc tag is present. * - If public function/method doesn't have explicit `@param` for each * parameter, an error is suppressed. * * @default // false in Lumina web component projects (temporary). true otherwise. */ readonly strict?: boolean; /** * This function will be called after the api.json is created but before it is * used to generate any output files. * * You can mutate the api.json to affect the types, lazy component metadata * and documentation. * * @param result */ afterApiJsonCreate?(result: Pick): void; } /** * Extraction context. Tells what to do and how. If running inside Vite, most * of these options will be set automatically. */ export interface ApiExtractorContextConfig { /** * The path for emitting .d.ts files * * @default // "" (do not emit) if running standalone. "dist/" if running in Vite. * @example "dist/" */ readonly dtsEmitPath?: string; /** * Whether to delete any existing .d.ts files in dtsEmitPath before emitting * new ones. * * @default config.dtsEmitPath !== undefined && config.environment === "production" */ readonly emptyDtsEmitPath?: boolean; /** * The path for emitting api.json. * * @default "" // (do not emit) * @example "dist/docs/api.json" */ readonly apiJsonEmitPath?: string; /** * The environment in which the extractor is running. * * @default // "production" if running standalone. Inherits Vite environment if running in Vite. */ readonly environment?: "development" | "production"; /** * The path to the root of the project to extract. * * @default // process.cwd() if running standalone. Vite's root if running in Vite. */ readonly cwd?: string; /** * The cwd-relative folder inside which all public API lives. Extractor will * look at public declarations only inside this folder. All JSDoc links should * be relative to this folder. The path must not start with ./ and must use * / over \ as path separators. * * @default "src/" */ readonly basePath?: string; /** * The project's package.json dependencies. * This will be used to verify that public declarations only depend on * packages that are declared as runtime dependencies in the package.json. * * @default // Will be retrieved from the nearest package.json */ readonly packageJson?: Pick; /** * Customize where the log messages are sent. * Defaults to console.log and console.error. * * @default // If running in Vite, will use Vite's configured logger. Otherwise, uses console. */ readonly logger?: { info: (message: string) => void; error: (message: string) => void; }; /** * API Extractor automatically discovers files that contain `@public`. Not * having to manually maintain a list of entries saves labor. * * As a performance optimization, you can exclude some directories from * `@public` file discovery. This should be a flat list of folders inside * basePath without slashes or nested paths. * * @example ["tests"] */ readonly excludedDirectories?: Set; /** * A flag to enable the bare minimal extraction in a Lumina web component * project. This powers lazy-loading in the Lumina development server. * It skips resolving types, does not use type checker, and only extracts * web components. * * @default false */ readonly isLuminaMinimalExtraction?: boolean; /** * To produce lazy-loading metadata, Lumina sets this flag to make api.json * include private components as well. Such private components are filtered * out before the api.json is written, and never appear in .d.ts files. * * @default false */ readonly includeLuminaPrivateComponents?: boolean; /** * @deprecated * @default false */ readonly supportApplyMixinsSyntax?: boolean; } /** Options that primarily impact type checking and the types output. */ export interface ApiExtractorTypesConfig { /** * If type checker is used for extraction, fewer type annotations will be * required. * For example, given `property = myHelper()`, without type checking, the * public type will look like `property: ReturnType`. However, * this is not very pretty in the docs, and extractor will error if `myHelper` * is not public. For such cases, you either need to provide an explicit type * annotation (`property: MyType = myHelper()`) or enable type checked * extraction, and the type will be inferred by the type checker. * * Type checked extraction is slower. However, if you are type checking your * project anyway as part of build, you can deduplicate that work by enabling * type checked extraction and the ApiExtractorTypesConfig.fullTypeCheck * option. * * Example performance impact: * If doing full type check, typed extraction is practically free: * * ```log * [@arcgis/api-extractor] Type checked in 0.911s * [@arcgis/api-extractor] Extracted 5 \@public files in 0.008s * ``` * * Otherwise, on-the-fly type checking adds some overhead, but still cheaper * than full type check: * * ```log * [@arcgis/api-extractor] Extracted 5 \@public files in 0.248s * ``` * * The impact of typed extraction is negligible on most projects. However, on * larger codebases, especially with many non-public files it can make a 10x * difference. * * @default // false if running standalone. true if running in Vite. */ readonly typeCheckedExtraction?: boolean; /** * Do a full type check of the codebase. This is equivalent of running * `npx tsc`. It type checks even non-public APIs, thus is slower than * `typeCheckedExtraction`, but if you are type checking as part of build * anyway, this option deduplicates work by removing the need for you to run * type checking separately. * * @default // false if running standalone or in storybook. true if running in Vite. */ readonly fullTypeCheck?: boolean; /** * If ApiExtractorTypesConfig.fullTypeCheck is enabled, this callback will * be invoked if any errors were console logged by TypeScript. * * @param diagnostics */ afterDiagnostic?(diagnostics: readonly ts.Diagnostic[]): void; /** * A callback for getting reference to the TypeScript Program and TypeScript * host instances created by the compiler. * * When running in Vite, this will only be called during build or Storybook * dev server, as TypeScript program is not created when running in serve mode. * * @param program * @param host */ typeScriptInstanceCreated?(program: ts.Program, host: ts.CompilerHost): void; /** * Path to the tsconfig.json file. * This is used if ApiExtractorTypesConfig.typeScriptConfig is not provided. * * @default "tsconfig.json" */ readonly typeScriptConfigPath?: string; /** * A resolved config returned by [loadTypeScriptConfig()](https://developers.arcgis.com/javascript/latest/references/api-extractor/vite/typeScript/#loadTypeScriptConfig). * Useful if you already resolved the config for other purposes and wish to * deduplicate the work. */ readonly typeScriptConfig?: TypeScriptConfigResult; /** * TypeScript compiler options to override the tsconfig.json settings. * Keep these minimal to ensure extractor reports same errors as your IDE and * standalone `npx tsc` */ readonly compilerOptions?: ts.CompilerOptions; /** Allows to modify the content of .d.ts files before they are emitted. */ readonly declarationTextTransformers?: DeclarationTextTransformer[]; readonly typeReplacements?: TypeReplacements; /** * Produce .d.ts files even if dtsEmitPath is not set. If dtsEmitPath is not * set, the produced .d.ts will not be written to disk but will be accessible * inside [ApiExtractorResult](https://developers.arcgis.com/javascript/latest/references/api-extractor/extractor/types/#ApiExtractorResult). * * This is useful if you need in-memory access to the .d.ts files or wish to * write them yourself. */ readonly forceProduceDts?: boolean; } /** * @param original - The original declaration file. * @param apiModule - The ApiModule corresponding to the declaration file. * @param extractor - The ApiExtractor instance. * @returns The transformed declaration file, or false to skip emitting this file. */ export type DeclarationTextTransformer = (original: DeclarationFile, apiModule: ApiModule, extractor: ApiExtractor) => DeclarationFile | false; export interface DeclarationFile { /** Absolute file path */ filePath: string; content: string; } /** * Privately, arcgis-js-api has interfaces that mirror some of the most widely * used types. They are in the process of refactoring out many of them. Until * that is complete, this hardcoded table is used to replace the usages of these * interfaces with their concrete types. * * Keep this list minimal. Type replacements have pitfalls: * - In a .ts file there is no indication that a given type will be replaced. * It can be surprising why some types are replaced and some are not. * - The replaced type is not equivalent (LayerUnion=>Layer). These do affect * type checking in some cases. * - If the name we are replacing with is already present in the current scope, * there will be a collision. Detecting such collisions is tricky - extractor * does it minimally. * * @see https://devtopia.esri.com/WebGIS/arcgis-js-api/discussions/60843 * @see https://devtopia.esri.com/WebGIS/arcgis-js-api/issues/69911 * @see https://devtopia.esri.com/WebGIS/arcgis-js-api/issues/73395 */ export type TypeReplacements = Record | undefined>; /** Options that primarily impact the api.json and the JSDocs in .d.ts files. */ export interface ApiExtractorDocumentationConfig { readonly copyDocDefinitions?: CopyDocDefinitions; readonly noInheritMembers?: NoInheritMembers; /** * Whether to omit \@internal APIs from api.json. * This can only be disabled if not emitting .d.ts files. * Otherwise, you can manually filter out nodes that have * .docsTags[].name==="internal" in the api.json. * * @default false */ readonly omitInternal?: boolean; /** * Get a prefix for a public-facing URL for each component story. * * @see https://webgis.esri.com/references/support-packages/storybook * @example * Set this to "https://developers.arcgis.com/javascript/latest/storybook/map-components/index.html". * This will produce URLs like * "https://developers.arcgis.com/javascript/latest/storybook/map-components/index.html?path=/story/arcgis-area-measurement-2d--demo&singleStory=true" */ readonly publicStoryUrlPrefix?: string | false; /** * Provide a URL to a page that documents the component. * This URL will be visible in VS Code and IntelliJ when hovering over a * component tag in an .html file * * @example (tagName) => `https://developers.arcgis.com/javascript/latest/references/map-components/components/${tagName}/` */ readonly getComponentDocsUrl?: false | ((tagName: string, className: string) => string | undefined); /** * Provide a URL to a page that documents the component. * This URL will be visible in VS Code and IntelliJ when hovering over a * component tag in an .html file * * If you wish to provide multiple URLs, use "getPublicStoryUrl" instead. * * @example (tagName) => `https://developers.arcgis.com/javascript/latest/storybook/map-components/?path=/story/${tagName}--demo&singleStory=true` */ readonly getComponentDemoUrl?: false | ((tagName: string, className: string) => string | undefined); /** * Host name of the documentation site for the current environment. * * @default // "next.gha.afd.arcgis.com" if context.environment is "development". "developers.arcgis.com" otherwise */ readonly host?: string; /** * Host name of the documentation site that is targeting the non-current environment. * Any references to this hostname in the code will be replaced by * [ApiExtractorDocumentationConfig](https://developers.arcgis.com/javascript/latest/references/api-extractor/extractor/config/#ApiExtractorDocumentationConfig-host). * * Use cases: * - In the source code, link to internal docs only. This gives more up to date * docs and avoids 404 errors for pages that are not yet part of public * release. * - In production builds, the URLs are replaced with the public docs hostname. * * @default // "developers.arcgis.com" if context.environment is "development". "next.gha.afd.arcgis.com" otherwise */ readonly alternativeHost?: string; /** * The path inside the * [ApiExtractorDocumentationConfig](https://developers.arcgis.com/javascript/latest/references/api-extractor/extractor/config/#ApiExtractorDocumentationConfig-host) that is used * as the base of all documentation reference pages. Make sure this starts and * ends with "/". * * @default `/javascript/latest/references/${packageJson.name.split("/").at(-1)}/` */ readonly basePath?: string; } export interface ApiExtractorVerifyConfig { /** * Run a type checker on the emitted types to verify correctness. * * This is slower, but it catches many kinds of issues. * Because emitted types strip non-public APIs, this option catches when * public type accesses non-public properties (in this["test"], or * Pick). It also catches if a class or interface is extended * incorrectly. * * If your users don't have skipLibCheck:true enabled, they will see the same * errors as what this option catches, thus this check is valuable. * * It is recommended to run this as part of CI or as a manual pre-release check. * * @default false */ readonly typeCheckTypes?: boolean; /** * If there are type errors in types of dependencies you do not control, * you can silence such errors by returning false. Ideally, you should also * report such errors to the upstream dependency so that these errors don't * affect your users. * * @param diagnostic * @example * ```ts * filterTypeCheckDiagnostic: (diagnostic) => * !diagnostic.file?.fileName?.endsWith("node_modules/vite/dist/node/index.d.ts"), * ``` */ filterTypeCheckDiagnostic?(diagnostic: ts.Diagnostic): boolean; /** * @deprecated REFACTOR: Drop this flag in 5.1 * @default false */ readonly detectBrokenLinks?: boolean; } /** * Don't include these members in each subclass to keep api reference pages cleaner. * These has no types impact since their inheritance is still done by TypeScript. * * The structure maps module path to the list of class members inside that module. */ export type NoInheritMembers = Readonly>; export interface ApiExtractorWatchConfig { /** * A callback that is invoked when any file change is detected. * This schedules an extractor rerun. The callback is called with a promise * that resolves with the result of the rerun. If the change that triggered * the rerun didn't impact api.json, the promise resolves with `undefined`. * * @param promise */ onUpdate?(promise: Promise): Promise | void; /** * When saving multiple files in VSCode or doing Git operations, many file * changes may trigger in a short time. To avoid excessive re-runs, we * wait a few milliseconds before starting the re-run. If another file is * changed during the wait, the wait restarts. * * @default // 50 if using type-checked extraction or full-type-check, 20 otherwise */ readonly debounceTime?: number; /** * Whether to clear the console output between re-runs. * * @default // true if running standalone. false if running in Vite */ readonly clearScreen?: boolean; /** * Whether it is safe to skip writing .d.ts for unchanged files between re-runs. * * @default * // true if ApiExtractorContextConfig.emptyDtsEmitPath is false * // and not running inside Vite or Vite's build.emptyOutDir is false. */ readonly skipWritingUnchanged?: boolean; } /** * On each re-run in watch mode, a new api.json is produced. Unchanged * ApiModules are copied as is by reference. Changed modules are re-created, * without mutating the old api.json. */ export interface ApiExtractorWatchResult extends ApiExtractorResult { /** * The names of modules that were directly or indirectly affected by the * changes to any watched files. */ readonly changedModules: readonly { readonly old: ApiModule; readonly new: ApiModule; }[]; /** This is empty on the first watch run. */ readonly addedModules: readonly ApiModule[]; /** > Rename is treated as removed + added. */ readonly removedModules: readonly ApiModule[]; } /** An ApiExtractor config with all default values filled in. */ export interface ResolvedApiExtractorConfig extends MarkPropertiesNonNullable { readonly isResolvedConfig: true; readonly context: MarkPropertiesNonNullable; readonly documentation: MarkPropertiesNonNullable; readonly types: MarkPropertiesNonNullable; readonly verify: MarkPropertiesNonNullable; readonly watch: MarkPropertiesNonNullable; } /** @experimental */ export type MarkPropertiesNonNullable = { readonly [K in keyof T]-?: NonNullable; }; /** * Find and load an ApiExtractor config file. * * @param cwd - If configFilePath was not provided, will load * `api-extractor.config.ts` from this folder. * @param configFilePath - Relative or absolute path to the config file. * By default, will load `api-extractor.config.ts` from the cwd * @returns The loaded config file. If configFilePath is provided but does not * exist, throws. Otherwise, if no config exists, returns an empty config. */ export function loadApiExtractorConfig(cwd?: string, configFilePath?: string): Promise; /** * @param baseConfig * @param overrideConfig */ export function mergeApiExtractorConfigs(baseConfig: ResolvedApiExtractorConfig, overrideConfig: ApiExtractorConfig): ResolvedApiExtractorConfig; /** * @param baseConfig * @param overrideConfig */ export function mergeApiExtractorConfigs(baseConfig: ApiExtractorConfig, overrideConfig: ResolvedApiExtractorConfig): ResolvedApiExtractorConfig; /** * @param baseConfig * @param overrideConfig */ export function mergeApiExtractorConfigs(baseConfig: ApiExtractorConfig, overrideConfig: ApiExtractorConfig): ApiExtractorConfig;