Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions __tests__/variables/index.test.tsx
Comment thread
RAYMOND-LUO marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ export const Hello = () => <p>{user.name}</p>;

expect(screen.getByText('Owlbert')).toBeVisible();
});

Comment thread
RAYMOND-LUO marked this conversation as resolved.
it('supports structured user variables', () => {
const md = '{user.keys.length}';
const Content = execute(md, {}, { variables: { user: { keys: [{ apiKey: 'rdme_123' }] } } });

render(<Content />);

expect(screen.getByText('1')).toBeVisible();
});
});
3 changes: 2 additions & 1 deletion processor/plugin/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { h } from 'hastscript';

import { mdx, plain } from '../../lib';
import { STANDARD_HTML_TAGS } from '../../utils/common-html-words';
import { flattenUserVariables } from '../../utils/user';
import { hasNamedExport } from '../utils';

const HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
Expand Down Expand Up @@ -149,7 +150,7 @@ const getDepth = (el: HastHeading) => {
const flattenVariables = (variables?: Variables): Record<string, string> => {
if (!variables) return {};
return {
...variables.user,
...flattenUserVariables(variables.user),
...Object.fromEntries(
(variables.defaults || []).filter(d => !(d.name in variables.user)).map(d => [d.name, d.default]),
),
Expand Down
4 changes: 3 additions & 1 deletion processor/transform/mdxish/variables-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { Plugin } from 'unified';
import { MDX_VARIABLE_REGEXP, VARIABLE_REGEXP } from '@readme/variable';
import { visit } from 'unist-util-visit';

import { flattenUserVariables } from '../../../utils/user';

interface Options {
variables?: Variables;
}
Expand All @@ -18,7 +20,7 @@ function flattenVariables(variables?: Variables): Record<string, string> {

return {
...Object.fromEntries((variables.defaults || []).map(d => [d.name, d.default])),
...variables.user,
...flattenUserVariables(variables.user),
};
}

Expand Down
2 changes: 1 addition & 1 deletion types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ interface TocList extends Element {

interface Variables {
defaults: { default: string; name: string }[];
user: Record<string, string>;
user: Record<string, unknown>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes sense but just be careful since this type gets used in several places including the main readme repo 🙏 Hopefully it doesn't break

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea good point - funny enough it looks like the readme repo imports Variables from packages/iso/src/types/rdmd.ts and not directly from this package, and that type already declares user: Record<string, unknown>. So widening here brings @readme/markdown in line with what readme repo already expects.

At least to my understanding, the only direct consumer that needed updating is mdx-renderer (handled in my other pr)

}

interface TocListItem extends Element {
Expand Down
23 changes: 22 additions & 1 deletion utils/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,30 @@ interface Default {

export interface Variables {
defaults: Default[];
user: Record<string, string>;
user: Record<string, unknown>;
}

/**
* Coerce a user variable value to a string for substitution into markdown text.
* Non-string values (arrays, objects, numbers) are stringified via JSON or `String()`
* so that `<<var>>` syntax doesn't produce `[object Object]` for structured data like
* JWT `keys`.
*/
const stringifyVariableValue = (value: unknown): string => {
if (typeof value === 'string') return value;
if (value == null) return '';
if (typeof value === 'object') return JSON.stringify(value) ?? '';
return String(value);
};

/**
* Flatten `variables.user` into a string-keyed string-valued record by coercing
* each value. Used by markdown substitution paths that need a plain
* `Record<string, string>` lookup.
*/
export const flattenUserVariables = (user: Record<string, unknown>): Record<string, string> =>
Object.fromEntries(Object.entries(user).map(([name, value]) => [name, stringifyVariableValue(value)]));

const User = (variables?: Variables) => {
const { user = {}, defaults = [] } = variables || {};

Expand Down