Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/fancy-keys-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@patternfly/pfe-tools": minor
---

Accept `tagPrefix` as an array of strings, supporting repos with multiple element prefixes
2 changes: 1 addition & 1 deletion .pfe.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"renderTitleInOverview": false,
"tagPrefix": "pf-v5"
"tagPrefix": ["pf-v5", "pf-v6"]
}
4 changes: 2 additions & 2 deletions tools/pfe-tools/11ty/DocsPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PfeConfig } from '../config.js';
import { getPfeConfig } from '../config.js';
import { getPfeConfig, matchPrefix } from '../config.js';
import { Manifest } from '../custom-elements-manifest/lib/Manifest.js';

import slugify from 'slugify';
Expand All @@ -26,7 +26,7 @@ export class DocsPage {
this.docsTemplatePath = options?.docsTemplatePath;
this.summary = this.manifest.getSummary(this.tagName);
this.description = this.manifest.getDescription(this.tagName);
const prefix = `${config.tagPrefix.replace(/-$/, '')}-`;
const prefix = matchPrefix(this.tagName, config);
const aliased = config.aliases[this.tagName] ?? this.tagName.replace(prefix, '');
this.title = options?.title ?? Manifest.prettyTag(this.tagName, config.aliases, prefix);
this.slug = slugify(aliased, { strict: true, lower: true });
Expand Down
14 changes: 2 additions & 12 deletions tools/pfe-tools/11ty/plugins/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import type { PfeConfig } from '@patternfly/pfe-tools/config';
import type { Manifest } from '@patternfly/pfe-tools/custom-elements-manifest/lib/Manifest.js';

export interface DemoRecord {
title: string;
tagName: string;
tagPrefix: string;
primaryElementName: string;
manifest: Manifest;
slug: string;
filePath: string;
permalink: string;
url: string;
}
export type { DemoRecord } from '@patternfly/pfe-tools/custom-elements-manifest/lib/Manifest.js';
import type { DemoRecord } from '@patternfly/pfe-tools/custom-elements-manifest/lib/Manifest.js';

export interface PluginOptions extends PfeConfig {
/** list of extra demo records not included in the custom-elements-manifest. Default [] */
Expand Down
75 changes: 75 additions & 0 deletions tools/pfe-tools/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';

import { getPrefixes, matchPrefix, deslugify } from './config.js';

describe('getPrefixes', function() {
it('should return array from single string', function() {
assert.deepStrictEqual(getPrefixes({ tagPrefix: 'pf' }), ['pf']);
});

it('should return array as-is', function() {
assert.deepStrictEqual(getPrefixes({ tagPrefix: ['pf-v5', 'pf-v6'] }), ['pf-v5', 'pf-v6']);
});

it('should filter empty strings', function() {
assert.deepStrictEqual(getPrefixes({ tagPrefix: ['pf-v5', '', 'pf-v6'] }), ['pf-v5', 'pf-v6']);
});

it('should throw when tagPrefix is undefined', function() {
assert.throws(() => getPrefixes({ tagPrefix: undefined }), {
message: 'tagPrefix must contain at least one non-empty prefix',
});
});

it('should throw when tagPrefix is empty array', function() {
assert.throws(() => getPrefixes({ tagPrefix: [] }), {
message: 'tagPrefix must contain at least one non-empty prefix',
});
});

it('should throw when all entries are empty strings', function() {
assert.throws(() => getPrefixes({ tagPrefix: ['', ''] }), {
message: 'tagPrefix must contain at least one non-empty prefix',
});
});
});

describe('matchPrefix', function() {
it('should match pf-v5 prefix', function() {
assert.strictEqual(matchPrefix('pf-v5-button', { tagPrefix: ['pf-v5', 'pf-v6'] }), 'pf-v5-');
});

it('should match pf-v6 prefix', function() {
assert.strictEqual(matchPrefix('pf-v6-card', { tagPrefix: ['pf-v5', 'pf-v6'] }), 'pf-v6-');
});

it('should fall back to first prefix when no match', function() {
assert.strictEqual(matchPrefix('custom-element', { tagPrefix: ['pf-v5', 'pf-v6'] }), 'pf-v5-');
});

it('should work with single string prefix', function() {
assert.strictEqual(matchPrefix('pf-button', { tagPrefix: 'pf' }), 'pf-');
});

it('should strip trailing dash from config before adding canonical dash', function() {
assert.strictEqual(matchPrefix('pf-v5-button', { tagPrefix: 'pf-v5-' }), 'pf-v5-');
});
});

describe('deslugify', function() {
it('should return prefixed slug when slug already has a configured prefix', function() {
const result = deslugify('pf-v5-button');
assert.strictEqual(result, 'pf-v5-button');
});

it('should return prefixed slug for pf-v6 prefix', function() {
const result = deslugify('pf-v6-card');
assert.strictEqual(result, 'pf-v6-card');
});

it('should prepend first prefix when slug has no prefix', function() {
const result = deslugify('button');
assert.strictEqual(result, 'pf-v5-button');
});
});
29 changes: 26 additions & 3 deletions tools/pfe-tools/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export interface PfeConfig {
sourceControlURLPrefix?: string ;
/** absolute URL prefix for demos, with trailing slash. Default 'https://patternflyelements.org/' */
demoURLPrefix?: string ;
/** custom elements namespace. Default 'pf' */
tagPrefix?: string;
/** custom elements namespace. Default 'pf'. Accepts an array for repos with multiple prefixes. */
tagPrefix?: string | string[];
/** Dev Server site options */
site?: SiteOptions;
}
Expand Down Expand Up @@ -78,6 +78,27 @@ export function getPfeConfig(rootDir: string = process.cwd()): Required<PfeConfi
};
}

/**
* Normalizes tagPrefix config to a non-empty array of prefixes (without trailing dash).
* Filters empty strings. Throws if result is empty.
*/
export function getPrefixes(config: Pick<PfeConfig, 'tagPrefix'>): string[] {
const prefixes = [config.tagPrefix].flat().filter((p): p is string => !!p);
if (!prefixes.length) {
throw new Error('tagPrefix must contain at least one non-empty prefix');
}
return prefixes;
}

/**
* Returns the prefix that matches the given tag name (with trailing dash),
* or falls back to the first configured prefix.
*/
export function matchPrefix(tagName: string, config: Pick<PfeConfig, 'tagPrefix'>): string {
const prefixes = getPrefixes(config).map(p => `${p.replace(/-$/, '')}-`);
return prefixes.find(p => tagName.startsWith(p)) ?? prefixes[0];
}

const slugsConfigMap = new Map<string, { config: PfeConfig; slugs: Map<string, string> }>();
const reverseSlugifyObject = ([k, v]: [string, string]): [string, string] =>
[slugify(v, { lower: true }), k];
Expand All @@ -102,6 +123,8 @@ export function deslugify(
rootDir: string = process.cwd(),
): string {
const { slugs, config } = getSlugsMap(rootDir);
const prefixedSlug = (slug.startsWith(`${config.tagPrefix}-`)) ? slug : `${config.tagPrefix}-${slug}`;
const prefixes = getPrefixes(config);
const hasPrefix = prefixes.some(p => slug.startsWith(`${p}-`));
const prefixedSlug = hasPrefix ? slug : `${prefixes[0]}-${slug}`;
return slugs.get(slug) ?? prefixedSlug;
}
4 changes: 2 additions & 2 deletions tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { readFileSync } from 'node:fs';

import { getAllPackages } from './get-all-packages.js';
import slugify from 'slugify';
import { deslugify } from '@patternfly/pfe-tools/config.js';
import { deslugify, matchPrefix } from '@patternfly/pfe-tools/config.js';

type PredicateFn = (x: unknown) => boolean;

Expand Down Expand Up @@ -324,7 +324,7 @@ export class Manifest {
const [last = ''] = filePath.split(path.sep).reverse();
const filename = last.replace('.html', '');
const isMainElementDemo = filename === 'index';
const prefix = `${options.tagPrefix.replace(/-$/, '')}-`;
const prefix = matchPrefix(tagName, options);
const title = isMainElementDemo ? prettyTag(tagName, options.aliases, prefix)
: last
.replace(/(?:^|[-/\s])\w/g, x => x.toUpperCase())
Expand Down
8 changes: 5 additions & 3 deletions tools/pfe-tools/test/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { junitReporter } from '@web/test-runner-junit-reporter';
import { a11ySnapshotPlugin } from '@web/test-runner-commands/plugins';

import { pfeDevServerConfig, type PfeDevServerConfigOptions } from '../dev-server/config.js';
import { getPfeConfig } from '../config.js';
import { getPfeConfig, getPrefixes } from '../config.js';

export interface PfeTestRunnerConfigOptions extends PfeDevServerConfigOptions {
files?: string[];
Expand Down Expand Up @@ -50,7 +50,9 @@ const exists = async (path: string | URL) => {
export function pfeTestRunnerConfig(opts: PfeTestRunnerConfigOptions): TestRunnerConfig {
const { open, ...devServerConfig } = pfeDevServerConfig({ ...opts, loadDemo: false });

const { elementsDir, tagPrefix } = getPfeConfig();
const config = getPfeConfig();
const { elementsDir } = config;
const tagPrefixes = getPrefixes(config);

const configuredReporter = opts.reporter ?? 'default';

Expand Down Expand Up @@ -121,7 +123,7 @@ export function pfeTestRunnerConfig(opts: PfeTestRunnerConfigOptions): TestRunne
*/
async function(ctx, next) {
if (ctx.path.endsWith('.js')
&& ctx.path.startsWith(`/${elementsDir}/${tagPrefix}-`)
&& tagPrefixes.some(p => ctx.path.startsWith(`/${elementsDir}/${p}-`))
&& await exists(`./${ctx.path}`.replace('.js', '.ts').replace('//', '/'))) {
ctx.redirect(ctx.path.replace('.js', '.ts'));
} else {
Expand Down
2 changes: 1 addition & 1 deletion web-test-runner.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getPatternflyIconNodemodulesImports } from '@patternfly/pfe-tools/dev-s
export default pfeTestRunnerConfig({
// workaround for https://github.com/evanw/esbuild/issues/3019
tsconfig: 'tsconfig.esbuild.json',
files: ['!tools/create-element/templates/**/*'],
files: ['!tools/create-element/templates/**/*', '!tools/pfe-tools/*.spec.ts'],
reporter: process.env.CI ? 'summary' : 'default',
importMapOptions: {
providers: {
Expand Down
Loading