npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@p-buddy/parkdown

v0.0.37

Published

`parkdown` allows you to include other file's content within your markdown using a link with no text (i.e. `[](<url>)`), where `<url>` corresponds to either: - a local file, e.g. `[](./other.md)` or `[](../root.ts)` - **_COMING SOON_**: An external li

Downloads

493

Readme

parkdown (p↓)

parkdown allows you to include other file's content within your markdown using a link with no text (i.e. [](<url>)), where <url> corresponds to either:

  • a local file, e.g. [](./other.md) or [](../root.ts)
  • COMING SOON: An external link

Markdown renderers shouldn't display these links, but parkdown can process and populate them. Also, your editor hopefully makes these links easy to navigate to, improving productivity.

Collectively, parkdown enables your documentation to behave a little more like code, and for your code to have a rightful place in your documentation.

Invocation

Invoke parkdown's functionality with either the cli or via the processMarkdownIncludes export:

CLI

The following commands are all equivalent:

npx @p-buddy/parkdown --file ./README.md
npx @p-buddy/parkdown -f README.md
npx @p-buddy/parkdown # defaults to processing inclusions in the 'README.md' file of the current working directory

populateMarkdownIncludes export

Authoring

You author inclusions in your markdown files using a link with no text i.e. [](<url>), where <url> points to some local or remote text resource (e.g../other.md, https://example.com/remote.md).

These links can be rendered either inline or block, depending on how you author them.

Inline

Inline inclusions occur when your text-less link has 1 or more siblings (meaning it's not the only node in a paragraph).

CAUTION: parkdown will not protect you from authoring inline inclusions that create invalid markdown (and thus should instead be block inclusions).

There are two equivalent ways to author inline inclusions, single-line or multi-line, and which you choose depends solely on how you want your raw markdown to look (it will not affect the rendered output).

Single-line

What you write:

Before... [](<url>) ...After

What is rendered (before processing, same as Multi-line):

Before... ...After

What your markdown file contains (after processing):

Before... [](<url>) <!-- p↓ Begin -->
...Included Content...
...Included Content... <!-- p↓ End --> ...After

What is rendered (after processing, same as Multi-line):

Before... ...Included Content... ...Included Content... ...After

Multi-line

What you write:

Before... 
[](<url>)
...After

What is rendered (before processing, same as Single-line):

Before... ...After

What your markdown file contains (after processing):

Before... 
[](<url>) <!-- p↓ Begin -->
...Included Content...
...Included Content... <!-- p↓ End --> 
...After

What is rendered (after processing, same as Single-line):

Before... ...Included Content... ...Included Content... ...After

Block

Block inclusions occur when your "empty" link is the only node in a paragraph (at least before being populated). This is likely the most common way to author inclusions.

What you write:

Before...

[](<url>)

...After

What is rendered (before processing):

Before...

...After

What your markdown file contains (after processing):

Before...

[](<url>)
<!-- p↓ Begin  -->
...Included Content...
...Included Content...
<!-- p↓ End  -->

...After

What is rendered (after processing):

Before...

...Included Content... ...Included Content...

...After

Query parameters

You can pass query parameters to your inclusion links to control how their content is processed and included within your markdown.

Processing Order

const params = new URLSearchParams(query);
const entries = (key: string) => {
  const values = Array.from(params.entries())
    .filter(([k]) => k === key)
    .map(([_, v]) => v);
  return values.length >= 1 ? values.join(",") : undefined;
};
const regions = entries("region")?.split(COMMA_NOT_IN_PARENTHESIS);
const skip = params.has("skip");
const headingModfiier = params.get("heading") ?? 0;
const inlineOverride = params.has("inline");
const wraps = params.get("wrap")?.split(COMMA_NOT_IN_PARENTHESIS);

region

Modify content from the included file based on regions designated by comments.

Comments that act as region boundaries will be identified by their specifiers, and are expected to come in pairs / bookend the desired region, like so:

/** some-specifier */
... code to find ...
/** some-specifier */

NOTE: Because comments are expected to come in consecutive pairs, nesting regions that are retrieved with the same specifier will NOT work as expected (since the nesting will not be respected).

Identifiers will be searched for within the text of a comment, split by spaces (i.e. /* some-specifier */ has a single identifier, but /* some specifier */ can be identified by either some or specifier).

It is generally BEST PRACTICE to include a parkdown prefix in your comment text, as all parkdown-prefixed comments will be stripped as a post-processing step. Otherwise, non-prefixed comment boundaries will be left in the included content, regardless of how they are processed.

The supported prefixes are:

const prefixes = [
  "(pd)",
  "(p↓)",
  "(parkdown)",
  "pd:",
  "p↓:",
  "parkdown:",
]

Below is the currently supported API for the region query parameter, where each defined method signature can be invoked as a value for the region parameter, for example:

  • [](<url>?region=method(argument))

If no value(s) are included (e.g. [](<url>?region)), then simply all comments that contain parkdown: or p↓: will be stripped (as is done as a post-processing step for all other region functionality).

NOTE ON API USAGE: As you can see from the included examples, each invocation of an API method looks like a less strict (more quirky) version of a typical javascript function invocation.

Please see the full explanation to learn more and/or if the below is confusing.

const definitions = [
  /**
   * Include all content between comments that INCLUDE the specified ids.
   *
   * This is similar to `extract`, but instead of removing all other content, it appends the specified regions.
   *
   * @param id The id of the comment to include.
   * @param 0 An optional additional id to include.
   * @param 1 An optional additional id to include.
   * @param 2 An optional additional id to include.
   * @param 3 An optional additional id to include.
   * @param 4 An optional additional id to include.
   * @param 5 An optional additional id to include.
   * @param 6 An optional additional id to include.
   * @param 7 An optional additional id to include.
   * @param 8 An optional additional id to include.
   * @param 9 An optional additional id to include.
   * @param 10 An optional additional id to include.
   * @example [](<url>?region=include(specifier))
   * @example [](<url>?region=include(specifier,other-specifier,some-other-specifier))
   */
  "include(id: string, 0?: string, 1?: string, 2?: string, 3?: string, 4?: string, 5?: string, 6?: string, 7?: string, 8?: string, 9?: string, 10?: string)",

  /**
   * Extract regions from the retrieved content between comments that INCLUDE the specified ids.
   *
   * NOTE: This method is deprecated in favor of `include`, which has more intuitive behavior.
   * `extract` effectively behaves destructively, removing all content outside of the specified regions,
   * thus you can't chain multiple `extract` calls to build up content from different regions
   * (like you can with `include`).
   *
   * @deprecated Use `include` instead (usage is more intuitive).
   * @param id The id of the comment to extract.
   * @param 0 An optional additional id to include.
   * @param 1 An optional additional id to include.
   * @param 2 An optional additional id to include.
   * @param 3 An optional additional id to include.
   * @param 4 An optional additional id to include.
   * @param 5 An optional additional id to include.
   * @param 6 An optional additional id to include.
   * @param 7 An optional additional id to include.
   * @param 8 An optional additional id to include.
   * @param 9 An optional additional id to include.
   * @param 10 An optional additional id to include.
   * @example [](<url>?region=extract(specifier))
   * @example [](<url>?region=extract(specifier,other-specifier,some-other-specifier))
   */
  "extract(id: string, 0?: string, 1?: string, 2?: string)",

  /**
   * Remove regions from the retrieved content between comments that INCLUDE the specified ids.
   * @param id The id of the comment to remove.
   * @param 0 An optional additional id to remove.
   * @param 1 An optional additional id to remove.
   * @param 2 An optional additional id to remove.
   * @example [](<url>?region=remove(specifier))
   * @example [](<url>?region=remove(specifier,other-specifier,some-other-specifier))
   */
  "remove(id: string, 0?: string, 1?: string, 2?: string)",

  /**
   * Replace regions from the retrieved content between comments that INCLUDE the specified ids.
   * @param id The id of the comment to replace.
   * @param with The replacement content (if ommitted, the content of the detected comment will be used).
   * @param space The space character to use between words in the replacement content (defaults to `-`).
   * @example [](<url>?region=replace(specifier))
   * @example [](<url>?region=replace(specifier,new-content))
   * @example [](<url>?region=replace(specifier,new_content,_)
   */
  "replace(id: string, with?: string, space?: string)",

  /**
   * Remap the content (similiar to `string.replaceAll`) within a specified comment region.
   * @param id The id of the comment regions to act on.
   * @param from The content to replace.
   * @param to The content to replace with.
   * @param space The space character to use between words in the content to replace (defaults to `-`).
   * @example [](<url>?region=remap(specifier,hello-world,hello-universe))
   * @example [](<url>?region=remap(specifier,hello_world,hello_universe,_)
   */
  "remap(id?: string, from: string, to?: string, space?: string)",

  /**
   * Make the content of the region a single line (where all whitespace characters, including newlines, are converted to a single space).
   * @param id The id of the comment regions to act on.
   * @example [](<url>?region=single-line(specifier))
   */
  "single-line(id: string, includeBoundaries?: boolean)",

  /**
   * Trim the whitespace surrounding the comment boundaries of the region.
   * @param id The id of the comment region to act on.
   * @param inside Whether to trim the whitespace within the comment region. Defaults to `true`.
   * @param outside Whether to trim the whitespace outside the comment region. Defaults to `true`.
   * @example [](<url>?region=trim(specifier))
   * @example [](<url>?region=trim(specifier,false))
   * @example [](<url>?region=trim(specifier,,false))
   * @example [](<url>?region=trim(specifier,false,false))
   */
  "trim(id: string, inside?: boolean, outside?: boolean)",

  /**
   * Trim the whitespace surrounding the starting comment boundary of the specified region.
   * @param id The id of the comment region to act on.
   * @param left Whether to trim the whitespace to the left of the comment region. Defaults to `true`.
   * @param right Whether to trim the whitespace to the right of the comment region. Defaults to `true`.
   * @example [](<url>?region=trim-start(specifier))
   * @example [](<url>?region=trim-start(specifier,false))
   */
  "trim-start(id: string, left?: boolean, right?: boolean)",

  /**
   * Trim the whitespace surrounding the ending comment boundary of the specified region.
   * @param id The id of the comment region to act on.
   * @param left Whether to trim the whitespace to the left of the comment region. Defaults to `true`.
   * @param right Whether to trim the whitespace to the right of the comment region. Defaults to `true`.
   * @example [](<url>?region=trim-end(specifier))
   * @example [](<url>?region=trim-end(specifier,false))
   */
  "trim-end(id: string, left?: boolean, right?: boolean)",

  /**
   * Splice the retrieved content at the starting comment boundary of the specified region.
   * @param id The id of the comment region to act on.
   * @param deleteCount The number of characters to delete at either the beginning or end of the comment boundary.
   * Specifying a number greater than or equal to 0 indicates the action should be taken at the end of the comment boundary (i.e to the right of the comment).
   * Specifying undefined or a number less than 0 indicates the action should be taken at the beginning of the comment boundary (i.e to the left of the comment).
   * @param insert The content to insert.
   * @param space The space character to use between words in the content to insert (defaults to `-`).
   *
   * **NOTE:** Content within comments will not be acted upon.
   */
  "splice-start(id: string, deleteCount?: number, insert?: string, space?: string)",

  /**
   * Splice the retrieved content at the ending comment boundary of the specified region.
   * @param id The id of the comment region to act on.
   * @param deleteCount The number of characters to delete at either the beginning or end of the comment boundary.
   * Specifying a number greater than or equal to 0 indicates the action should be taken at the end of the comment boundary (i.e to the right of the comment).
   * Specifying undefined or a number less than 0 indicates the action should be taken at the beginning of the comment boundary (i.e to the left of the comment).
   * @param insert The content to insert.
   * @param space The space character to use between words in the content to insert (defaults to `-`).
   *
   * **NOTE:** Content within comments will not be acted upon.
   */
  "splice-end(id: string, deleteCount?: number, insert?: string, space?: string)",

  /**
   * If included at the end of a query, parkdown comments will not be removed from the content after processing.
   * Helpful when trying to determine fine-grained edits (e.g. trimming, splicing, etc.).
   */
  "debug()",
]

skip

Skip the default processing behavior for the given type of file.

* ...
  content = recursivelyPopulateInclusions(
    content,
    * ...
  );
} else if (/^(js|ts)x?|svelte$/i.test(extension))
  content = wrap(
    content,
    "code",
    * ...
  );
[](<url>?skip)

heading

Modify the heading depth applied to included content. By default, the headings of the included content are adjusted to be one-level below their parent heading (i.e. the heading the included content falls under).

You might commonly see [](<url>?heading=-1) used to ensure that the included content's heading level is the same as it's parent heading.

<!-- to-be-included.md -->
# Included Heading
<!-- README.md -->
# Heading

[](./to-be-included.md)

When README.md is processed, it will be transformed into the following:

# Heading

## Included Heading

Where the included content's heading has been modified to be one-level below the parent heading (i.e. it is converted from an h1 / # heading to a h2 / ## heading — h1 + 1 = h2).

If we instead wanted the included heading to remain a h1 / # heading, we'd make use of the heading query parameter with a value of -1 (since h1 + 1 - 1 = h1), like so:

<!-- README.md -->
# Heading

[](./to-be-included.md?heading=-1)

which would result in the following:

# Heading

# Included Heading

inline (Advanced)

Force a replacement target to be treated as inline content.

wrap

Wrap the content of the included file in a specific kind of element.

Below is the currently supported API for the wrap query parameter, where each defined method signature can be invoked as a value for the wrap parameter, for example:

  • [](<url>?wrap=code)
  • [](<url>?wrap=dropdown(hello-world))

NOTE ON API USAGE: As you can see from the included examples, each invocation of an API method looks like a less strict (more quirky) version of a typical javascript function invocation.

Please see the full explanation to learn more and/or if the below is confusing.

const definitions = [
  /**
   * Wraps the content in a markdown-formatted code block.
   * @param lang The language of the code block (defaults to the file extension).
   * @param meta Additional metadata to include in the top line of the code block (i.e. to the right of the `lang`).
   * @example [](<url>?wrap=code)
   * @example [](<url>?wrap=code())
   * @example [](<url>?wrap=code(ts))
   * @example [](<url>?wrap=code(,some-meta))
   */
  "code(lang?: string, meta?: string)",

  /**
   * Wraps the content in a markdown-formatted blockquote 
   * (using the `>` character if the content is a single line, 
   * or the `<blockquote>` tag if the content is a multi-line block).
   * @example [](<url>?wrap=quote)
   * @example [](<url>?wrap=quote())
   * @example [](<url>?wrap=quote(,))
   */
  "quote()",

  /**
   * Wraps the content in a markdown-formatted dropdown (using the `<details>` and `<summary>` tags).
   * @param summary The summary text of the dropdown.
   * @param open Whether the dropdown should be open by default.
   * @param space The space character to use between words in the summary (defaults to `-`).
   * @example [](<url>?wrap=dropdown(hello-world))
   * @example [](<url>?wrap=dropdown('hello,-world',true))
   * @example [](<url>?wrap=dropdown(hello_world,,_)) 
   */
  "dropdown(summary: string, open?: boolean, space?: string)",

]

Query Parameters with Function-like APIs

Some query parameters have more complex APIs, which are defined by a collection of typescript function singatures (limited to only string, boolean, and number arguments), like:

const definitions = [
  "method(arg1: string, arg2: boolean, arg3?: number)",
  "otherMethod(arg1: string, arg2?: boolean, arg3?: number)"
]

These APIs are utilized when setting the value of the specific query parameter with a function-like invocation syntax, such as:

[](<url>?example=method(hello-world,true))

As you can see from the example above, we're relaxing some constraints on typical function invocations (like the need to wrap string arguments in quotes), while also imposing some additional constraints (like not using any spaces) to ensure the links are valid markdown and the URLs are safe.

The goal is to make it as painless as possible to author links that are valid markdown, valid URLs, and easy to read and write.

Please note the following:

  • Methods that take no arguments can be invoked without parentheses (e.g. [](<url>?example=method)).
  • String arguments do not need to be wrapped in quotes (e.g. [](<url>?example=method(some-string))), and they CANNOT be wrapped in double quotes (see more below).
  • You cannot use spaces within a string argument or anywhere else in the query (as this would violate the markdown link syntax). For arguments that reasonably could include spaces, there should be an optional space argument that defaults to -, so that any usage of the space character will be converted to a space (e.g. hello-world becomes hello world).
  • Characters that are reserved or unsafe in URLs can be included by using the below remapping, where you'll write the corresponding key wrapped in the applicable space character (see the above bullet point, defaults to -). For example, if you want to use a /, you'd instead write -slash- (or with whatever you specify as your space character instead of -).

const urlCharacters = {
  reserved: {
    ["semi"]: ";",
    ["slash"]: "/",
    ["question"]: "?",
    ["colon"]: ":",
    ["at"]: "@",
    ["equal"]: "=",
    ["and"]: "&",
  },
  unsafe: {
    ["quote"]: "\"",
    ["angle"]: "<",
    ["unangle"]: ">",
    ["hash"]: "#",
    ["percent"]: "%",
    ["curly"]: "{",
    ["uncurly"]: "}",
    ["pipe"]: "|",
    ["back"]: "\\",
    ["carrot"]: "^",
    ["tilde"]: "~",
    ["square"]: "[",
    ["unsquare"]: "]",
    ["tick"]: "`",
    ["line"]: "\n",
  }
}
  • If a method takes a string argument, and you want to include a comma within that argument, you must wrap it in one or more single quotes (e.g.hello,-world should be specified as 'hello,-world').
  • String arguments wrapped in a single set of single quotes will automatically have the quotes removed when the query is parsed (e.g. the argument included in [](<url>?example=method('hello,world')) will parse to hello,world).
  • If you want single quotes preserved in the parsed output, use two single quotes in a row (e.g. [](<url>?example=method(''single-quoted''))).
  • You cannot use double quotes within a string argument (as they are not a URL safe character). To include a double-quote in the parsed output, use three single quotes in a row (e.g. [](<url>?example=method('''double-quoted'''))). Or use the remapping described above, like [](<url>?example=method(-quote-double-quoted-quote-)).
  • Optional arguments can be completely ommitted (for example if a method took 3 optional arguments, and you only wanted to provide the third, you could do the following: [](<url>?example=method(,,your-third-argument))).
  • Overall, text meant to be displayed will be sanitized in the following manner (unless otherwise noted):

type Replacement = [from: RegExp | string, to: string];

const replacements: Record<string, Replacement[]> = {
  static: [
    [`'''`, `"`],
    [`''`, `'`],
    [/parkdown:\s+/g, ``],
    [/p↓:\s+/g, ``],
  ],
  url: [
    ...(Object.entries(urlCharacters.unsafe)),
    ...(Object.entries(urlCharacters.reserved))
  ]
};

const applyReplacement = (accumulator: string, [from, to]: Replacement) =>
  accumulator.replaceAll(from, to);

export const sanitize = (content: string, space: string = DEFAULT_SPACE) => {
  const sanitized = replacements.static.reduce(applyReplacement, content)
  return replacements.url
    .map(([key, to]) => ([space + key + space, to] satisfies Replacement))
    .reduce(applyReplacement, sanitized)
    .replaceAll(space, " ");
}

Removing populated inclusions

Sometimes you may want to remove populated inclusions from your markdown file, since they can make things more difficult to read during authoring. You can do this either using the cli or via the removePopulatedInclusions export:

CLI (removing populated inclusions)

The following commands are all equivalent:

npx @p-buddy/parkdown --file ./README.md --depopulate --no-inclusions
npx @p-buddy/parkdown -f README.md -d --ni # Notice the double-dash (--) on 'ni'
npx @p-buddy/parkdown -d --ni # defaults to processing the 'README.md' file of the current working directory

The following commands will lead to the same result, but since --no-inclusions (--ni) is not specified, there will be some wasted work as inclusions will be processed and then removed.

npx @p-buddy/parkdown --file ./README.md --depopulate
npx @p-buddy/parkdown -f README.md -d
npx @p-buddy/parkdown -d # defaults to processing the 'README.md' file of the current working directory

depopulateMarkdownIncludes export