Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 bin/build-plugin-zip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ npm ci
status "Generating build... 👷‍♀️"
npm run build -- --skip-types

# Only including public icons when building for WordPress Core.
#
# This runs before creating the archive but after the build so icon collection validation passes as expected. Plugin
# builds keep the full library.
if [ "$IS_WORDPRESS_CORE" = "true" ]; then
status "Pruning non-public icons for WordPress Core... ✂️"
node packages/icons/lib/prune-non-public-icons.cjs
Comment thread
desrosj marked this conversation as resolved.
Resolved
fi

# Generate the plugin zip file.
status "Creating archive... 🎁"
zip --recurse-paths --no-dir-entries \
Expand Down
7 changes: 6 additions & 1 deletion packages/icons/lib/generate-manifest-php.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
const path = require( 'path' );
const { readFile, writeFile } = require( 'fs' ).promises;

/**
* Internal dependencies
*/
const { getPublicIcons } = require( './get-public-icons.cjs' );

const MANIFEST_JSON_PATH = path.join( __dirname, '..', 'src', 'manifest.json' );
const MANIFEST_PHP_PATH = path.join( __dirname, '..', 'src', 'manifest.php' );

Expand Down Expand Up @@ -64,7 +69,7 @@ ${ phpEntries.join( '\n' ) }
async function generateManifestPHP() {
const manifestJson = await readFile( MANIFEST_JSON_PATH, 'utf8' );
const manifest = JSON.parse( manifestJson );
const publicIcons = manifest.filter( ( item ) => item.public === true );
const publicIcons = getPublicIcons( manifest );
const phpHeader = `<?php
// This file is automatically generated. Do not edit directly.
if ( ! defined( 'ABSPATH' ) ) {
Expand Down
13 changes: 13 additions & 0 deletions packages/icons/lib/get-public-icons.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Filters the list of registered icons marked as public.
*
* Public icons are those explicitly marked `public: true` in the manifest file.
*
* @param {Array<Object>} manifest Parsed manifest.json array.
* @return {Array<Object>} Icons with `public === true`.
*/
function getPublicIcons( manifest ) {
return manifest.filter( ( item ) => item.public === true );
}
Comment thread
desrosj marked this conversation as resolved.
Resolved

module.exports = { getPublicIcons };
62 changes: 62 additions & 0 deletions packages/icons/lib/prune-non-public-icons.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* External dependencies
*/
const path = require( 'path' );
const { readFile, readdir, unlink } = require( 'fs' ).promises;

/**
* Internal dependencies
*/
const { getPublicIcons } = require( './get-public-icons.cjs' );

const MANIFEST_JSON_PATH = path.join( __dirname, '..', 'src', 'manifest.json' );
const LIBRARY_DIR = path.join( __dirname, '..', 'src', 'library' );

/**
* Deletes every non-public SVG in the icon library.
*
* Non-public icons are those not explicitly marked as `public: true` in the manifest. Must run after icon collection
* validation, which expects the manifest and library to match 1:1.
*
* @param {Object} [options] Options.
* @param {string} [options.libraryDir] Directory containing the SVG files.
* @param {string} [options.manifestPath] Path to manifest.json.
* @return {Promise<{pruned: number, retained: number}>} Prune summary.
*/
async function pruneNonPublicIcons( {
libraryDir = LIBRARY_DIR,
manifestPath = MANIFEST_JSON_PATH,
} = {} ) {
const manifest = JSON.parse( await readFile( manifestPath, 'utf8' ) );
const publicFiles = new Set(
getPublicIcons( manifest ).map( ( item ) =>
path.basename( item.filePath )
)
);

const svgFiles = ( await readdir( libraryDir ) ).filter( ( file ) =>
file.endsWith( '.svg' )
);

let pruned = 0;
for ( const file of svgFiles ) {
if ( ! publicFiles.has( file ) ) {
await unlink( path.join( libraryDir, file ) );
Comment thread
desrosj marked this conversation as resolved.
Resolved
pruned++;
}
}

const retained = svgFiles.length - pruned;
// eslint-disable-next-line no-console
console.log(
`Pruned ${ pruned } non-public icon(s); ${ retained } retained.`
);

return { pruned, retained };
}

module.exports = { pruneNonPublicIcons };

if ( module === require.main ) {
pruneNonPublicIcons();
}
26 changes: 26 additions & 0 deletions packages/icons/lib/test/get-public-icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Internal dependencies
*/
import { getPublicIcons } from '../get-public-icons.cjs';

describe( 'getPublicIcons', () => {
it( 'returns only entries marked public: true', () => {
const manifest = [
{ slug: 'a', public: true },
{ slug: 'b' },
{ slug: 'c', public: false },
{ slug: 'd', public: true },
];

expect( getPublicIcons( manifest ).map( ( i ) => i.slug ) ).toEqual( [
'a',
'd',
] );
} );

it( 'returns an empty array when no entry is public', () => {
expect( getPublicIcons( [ { slug: 'a' }, { slug: 'b' } ] ) ).toEqual(
[]
);
} );
} );
70 changes: 70 additions & 0 deletions packages/icons/lib/test/prune-non-public-icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* External dependencies
*/
import { mkdtemp, mkdir, writeFile, readdir, rm } from 'fs/promises';
import os from 'os';
import path from 'path';

/**
* Internal dependencies
*/
import { pruneNonPublicIcons } from '../prune-non-public-icons.cjs';

describe( 'pruneNonPublicIcons', () => {
let workDir;
let libraryDir;
let manifestPath;

beforeEach( async () => {
workDir = await mkdtemp( path.join( os.tmpdir(), 'icons-prune-' ) );
libraryDir = path.join( workDir, 'library' );
manifestPath = path.join( workDir, 'manifest.json' );
await mkdir( libraryDir );

for ( const name of [ 'public-a', 'public-b', 'private-c' ] ) {
await writeFile(
path.join( libraryDir, `${ name }.svg` ),
'<svg/>'
);
}

const manifest = [
{
slug: 'public-a',
label: 'A',
filePath: 'library/public-a.svg',
public: true,
},
{
slug: 'public-b',
label: 'B',
filePath: 'library/public-b.svg',
public: true,
},
{
slug: 'private-c',
label: 'C',
filePath: 'library/private-c.svg',
},
];
await writeFile( manifestPath, JSON.stringify( manifest ) );
} );

afterEach( async () => {
await rm( workDir, { recursive: true, force: true } );
} );

it( 'deletes non-public SVGs and keeps public ones', async () => {
const result = await pruneNonPublicIcons( {
libraryDir,
manifestPath,
} );

const remaining = ( await readdir( libraryDir ) ).sort();
expect( remaining ).toEqual( [ 'public-a.svg', 'public-b.svg' ] );
expect( result ).toEqual( { pruned: 1, retained: 2 } );
expect( console ).toHaveLoggedWith(
'Pruned 1 non-public icon(s); 2 retained.'
);
} );
} );
Loading