Skip to content

Commit

Permalink
Migrate WordPress block asset files from PHP to JSON format (#1656)
Browse files Browse the repository at this point in the history
* Improve unzipping compat

Signed-off-by: Joe Fusco <[email protected]>

* Fix liniting issues

Signed-off-by: Joe Fusco <[email protected]>

* Fix liniting issues

Signed-off-by: Joe Fusco <[email protected]>

* Fix linting issues

Signed-off-by: Joe Fusco <[email protected]>

* Clean up unused functions & tests

Signed-off-by: Joe Fusco <[email protected]>

* Add test coverage

Signed-off-by: Joe Fusco <[email protected]>

* Match parent class

Signed-off-by: Joe Fusco <[email protected]>

* Fix PSR-4 warning

Signed-off-by: Joe Fusco <[email protected]>

* Improve block tests

Signed-off-by: Joe Fusco <[email protected]>

* Attempt to fix failing test config

Signed-off-by: Joe Fusco <[email protected]>

* failing tests

Signed-off-by: Joe Fusco <[email protected]>

* Update tests

Signed-off-by: Joe Fusco <[email protected]>

* Fix refactoring issues

Signed-off-by: Joe Fusco <[email protected]>

* Explicitly require patchwork

Signed-off-by: Joe Fusco <[email protected]>

* Require patchwork before anything else

Signed-off-by: Joe Fusco <[email protected]>

* Correct path to Patchwork

Signed-off-by: Joe Fusco <[email protected]>

* Attempt to resolve failing E2E tests

Signed-off-by: Joe Fusco <[email protected]>

* Scope requiring patchwork to it’s relevant class

Signed-off-by: Joe Fusco <[email protected]>

* Revert "Scope requiring patchwork to it’s relevant class"

This reverts commit 9b3371a.

* Avoid polluting codeception env with Patchwork

Signed-off-by: Joe Fusco <[email protected]>

* Conditionally load patchwork package via env

Signed-off-by: Joe Fusco <[email protected]>

* Only update antecedent/patchwork in lockfile

Signed-off-by: Joe Fusco <[email protected]>

* Clean up refactor

Signed-off-by: Joe Fusco <[email protected]>

* Use our own directories method

Signed-off-by: Joe Fusco <[email protected]>

* Update tests to reflect function argument changes

Signed-off-by: Joe Fusco <[email protected]>

* Clean up

Signed-off-by: Joe Fusco <[email protected]>

* Revert "Clean up"

This reverts commit 7352acc.

* Prevent .php inclusion as faust handles dependencies

Signed-off-by: Joe Fusco <[email protected]>

* Update block-support example deps

Signed-off-by: Joe Fusco <[email protected]>

* Ensure --webpack-no-externals is used to remove generated php

Signed-off-by: Joe Fusco <[email protected]>

* leave wp deps alone

Signed-off-by: Joe Fusco <[email protected]>

* Remove problematic CLI flag

Signed-off-by: Joe Fusco <[email protected]>

* Handle PHP block files in the CLI w/ tests

Signed-off-by: Joe Fusco <[email protected]>

* Resolve linting errors

Signed-off-by: Joe Fusco <[email protected]>

* Clean up logging

Signed-off-by: Joe Fusco <[email protected]>

* Resolve phpcs issue

Signed-off-by: Joe Fusco <[email protected]>

* Resolve phpcs issue

Signed-off-by: Joe Fusco <[email protected]>

* Fix asset loading

Signed-off-by: Joe Fusco <[email protected]>

* Sync lockfile

Signed-off-by: Joe Fusco <[email protected]>

* Resolve linting issues

Signed-off-by: Joe Fusco <[email protected]>

* Fix linting warnings

Signed-off-by: Joe Fusco <[email protected]>

* Update lockfile

Signed-off-by: Joe Fusco <[email protected]>

---------

Signed-off-by: Joe Fusco <[email protected]>
  • Loading branch information
josephfusco authored Dec 19, 2023
1 parent fd0edd5 commit 205fb09
Show file tree
Hide file tree
Showing 14 changed files with 603 additions and 183 deletions.
5 changes: 5 additions & 0 deletions .changeset/slow-mayflies-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@faustwp/wordpress-plugin': patch
---

Improved plugin's process for handling blockset file uploads by leveraging WordPress' native [unzip_file](https://developer.wordpress.org/reference/functions/unzip_file/) function.
125 changes: 99 additions & 26 deletions packages/faustwp-cli/src/blockset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ import archiver from 'archiver';
import { spawnSync } from 'child_process';

import { getWpUrl, getWpSecret, hasYarn } from './utils/index.js';
import { infoLog } from './stdout/index.js';
import { debugLog } from './stdout/index.js';

// File paths used throughout the blockset process
export const ROOT_DIR = process.cwd();
export const FAUST_DIR = path.join(ROOT_DIR, '.faust');
export const FAUST_BUILD_DIR = path.join(FAUST_DIR, 'build');
export const BLOCKS_DIR = path.join(FAUST_DIR, 'blocks');
export const MANIFEST_PATH = path.join(BLOCKS_DIR, 'manifest.json');

const ROOT_DIR = process.cwd();
const FAUST_DIR = path.join(ROOT_DIR, '.faust');
const FAUST_BUILD_DIR = path.join(FAUST_DIR, 'build');
const BLOCKS_DIR = path.join(FAUST_DIR, 'blocks');
const MANIFEST_PATH = path.join(BLOCKS_DIR, 'manifest.json');
const IGNORE_NODE_MODULES = '**/node_modules/**';
const FAUST_BLOCKS_SRC_DIR = 'wp-blocks';

// Ensure required directories exist
// Ensure that the required directories for block processing exist
fs.ensureDirSync(BLOCKS_DIR);

/**
* Represents the structure of the manifest file.
*/
export type Manifest = {
blocks: any[];
timestamp: string;
Expand All @@ -30,10 +35,54 @@ const manifest: Manifest = {
timestamp: new Date().toISOString(),
};

/**
* Interface representing the structure of the parsed PHP asset file.
*/
export interface PhpAsset {
dependencies?: string[];
version?: string;
}

/**
* Parses a PHP asset file content and converts it into a JSON object.
*
* @param {string} phpContent - The content of the PHP asset file.
* @returns {PhpAsset} - A JSON object representing the parsed content.
*/
export function parsePhpAssetFile(phpContent: string): PhpAsset {
const jsonObject: PhpAsset = {};

// Match the PHP array structure
const matches = /return\s+array\(([^;]+)\);/.exec(phpContent);
if (!matches || matches.length < 2) {
console.error('Error: Unable to parse PHP file.');
return {};
}

// Extract dependencies if present
const dependenciesMatch = matches[1].match(
/'dependencies'\s*=>\s*array\(([^)]+)\)/,
);
if (dependenciesMatch) {
jsonObject.dependencies = dependenciesMatch[1]
.split(',')
.map((dep) => dep.trim().replace(/'/g, ''));
}

// Extract version if present
const versionMatch = matches[1].match(/'version'\s*=>\s*'([^']+)'/);
if (versionMatch) {
const [, version] = versionMatch; // destructures versionMatch and skips the first element (which is the full match of the regex), directly assigning the second element (the captured group) to the variable version.
jsonObject.version = version;
}

return jsonObject;
}

/**
* Fetches paths to all block.json files while ignoring node_modules.
*
* @returns {Promise<string[]>} An array of paths to block.json files.
* @returns {Promise<string[]>} - An array of paths to block.json files.
*/
export async function fetchBlockFiles(): Promise<string[]> {
return glob(`${FAUST_BUILD_DIR}/**/block.json`, {
Expand All @@ -42,32 +91,56 @@ export async function fetchBlockFiles(): Promise<string[]> {
}

/**
* Processes each block.json file, copying its directory and updating the manifest.
* Processes each block.json file by copying its directory, updating the manifest,
* and handling PHP files.
*
* @param {string[]} files - An array of paths to block.json files.
* @returns {Promise<void>}
*/
export async function processBlockFiles(files: string[]): Promise<void> {
await fs.emptyDir(BLOCKS_DIR);
// Use Promise.all and map instead of for...of loop
await Promise.all(
files.map(async (filePath) => {
const blockDir = path.dirname(filePath);
const blockName = path.basename(blockDir);
const destDir = path.join(BLOCKS_DIR, blockName);

await fs.copy(blockDir, destDir);

const blockJson = await fs.readJson(filePath);
manifest.blocks.push(blockJson);
}),
);

const fileProcessingPromises = files.map(async (filePath) => {
const blockDir = path.dirname(filePath);
const blockName = path.basename(blockDir);
const destDir = path.join(BLOCKS_DIR, blockName);

await fs.copy(blockDir, destDir);

if (path.extname(filePath) === '.json') {
try {
const blockJson = await fs.readJson(filePath);
manifest.blocks.push(blockJson);
} catch (error) {
console.error(`Error reading JSON file: ${filePath}`, error);
}
}

// Handle PHP asset file
const phpAssetPath = path.join(blockDir, 'index.asset.php');
if (await fs.pathExists(phpAssetPath)) {
const phpContent = await fs.readFile(phpAssetPath, 'utf8');
const assetData = parsePhpAssetFile(phpContent);
await fs.writeJson(path.join(destDir, 'index.asset.json'), assetData, {
spaces: 2,
});
await fs.remove(phpAssetPath);
}

// Remove any other PHP files
const phpFiles = await glob(`${destDir}/**/*.php`, {
ignore: IGNORE_NODE_MODULES,
});
await Promise.all(phpFiles.map((file) => fs.remove(file)));
});

await Promise.all(fileProcessingPromises);
}

/**
* Creates a ZIP archive of the blocks.
*
* @returns {Promise<string>} Path to the created ZIP archive.
* @returns {Promise<string>} - Path to the created ZIP archive.
*/
export async function createZipArchive(): Promise<string> {
const zipPath = path.join(FAUST_DIR, 'blocks.zip');
Expand Down Expand Up @@ -114,7 +187,7 @@ export async function uploadToWordPress(zipPath: string): Promise<void> {
}

try {
infoLog('WordPress:', await response.json());
console.log(await response.json());
} catch (jsonError) {
if (jsonError instanceof Error) {
throw new Error('Error parsing response from WordPress.');
Expand All @@ -133,12 +206,12 @@ export async function uploadToWordPress(zipPath: string): Promise<void> {
}

/**
* Compiles the blocks and places them into the faust build dir.
* Compiles the blocks and places them into the FAUST build directory.
*
* @returns {Promise<void>}
*/
export async function compileBlocks(): Promise<void> {
infoLog(`Faust: Compiling Blocks into ${FAUST_BUILD_DIR}`);
debugLog(`Faust: Compiling Blocks into ${FAUST_BUILD_DIR}`);
await fs.emptyDir(FAUST_BUILD_DIR);
const command = hasYarn() ? 'yarn' : 'npm';
let args = ['exec', 'wp-scripts', 'start', '--package=@wordpress/scripts'];
Expand Down
129 changes: 128 additions & 1 deletion packages/faustwp-cli/tests/blockset/blockset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ jest.mock('../../src/utils/hasYarn.js', () => ({
hasYarn: jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false),
}));

import { compileBlocks } from '../../src/blockset';
import {
compileBlocks,
parsePhpAssetFile,
processBlockFiles,
BLOCKS_DIR,
FAUST_DIR
} from '../../src/blockset';
import fs from 'fs-extra';
import path from 'path';

const spawnSyncMock = spawnSync as unknown as jest.Mock<
Partial<SpawnSyncReturns<string[]>>
Expand Down Expand Up @@ -62,4 +70,123 @@ describe('blockset command', () => {
);
});
});

describe('PHP file processing', () => {
const mockSourceDir = path.join(__dirname, 'mockSourceDir');
const mockPhpFilePath = path.join(mockSourceDir, 'index.asset.php');

const mockPhpContent = `
<?php
return array(
'dependencies' => array(
'react',
'wp-block-editor',
'wp-blocks',
'wp-i18n',
'wp-components',
'wp-hooks'
),
'version' => '00000000000000001234'
);
`;

const expectedJson = {
dependencies: [
'react',
'wp-block-editor',
'wp-blocks',
'wp-i18n',
'wp-components',
'wp-hooks',
],
version: '00000000000000001234',
};

beforeAll(async () => {
await fs.ensureDir(mockSourceDir);
await fs.writeFile(mockPhpFilePath, mockPhpContent);
});

afterAll(async () => {
await fs.remove(mockSourceDir);
await fs.remove(FAUST_DIR);
});

it('should convert PHP file to JSON and remove the original PHP file', async () => {
await processBlockFiles([mockPhpFilePath]);

// Use the BLOCKS_DIR for locating the JSON file
const blockName = path.basename(path.dirname(mockPhpFilePath));
const jsonFilePath = path.join(BLOCKS_DIR, blockName, 'index.asset.json');
expect(await fs.pathExists(jsonFilePath)).toBeTruthy();

// Check JSON file content
const jsonContent = await fs.readJson(jsonFilePath);
expect(jsonContent).toEqual(expectedJson);

// Check PHP file removal
expect(await fs.pathExists(mockPhpFilePath)).toBeFalsy();
});
});

// Test with correctly formatted PHP content
it('correctly parses valid PHP content', () => {
const validPhpContent = `
<?php
return array(
'dependencies' => array(
'react',
'wp-block-editor'
),
'version' => '1.0.0'
);
`;
const expectedJson = {
dependencies: ['react', 'wp-block-editor'],
version: '1.0.0'
};
expect(parsePhpAssetFile(validPhpContent)).toEqual(expectedJson);
});

it('returns an empty object for invalid PHP content', () => {
const invalidPhpContent = `<?php echo "Not a valid asset file"; ?>`;
expect(parsePhpAssetFile(invalidPhpContent)).toEqual({});
});

it('returns an empty object for empty PHP content', () => {
const emptyPhpContent = '';
expect(parsePhpAssetFile(emptyPhpContent)).toEqual({});
});

it('handles missing dependencies', () => {
const missingDependencies = `
<?php
return array(
'version' => '1.0.0'
);
`;
expect(parsePhpAssetFile(missingDependencies)).toEqual({ version: '1.0.0' });
});

it('handles missing version', () => {
const missingVersion = `
<?php
return array(
'dependencies' => array('react')
);
`;
expect(parsePhpAssetFile(missingVersion)).toEqual({ dependencies: ['react'] });
});

it('parses content with extra whitespace and different formatting', () => {
const formattedPhpContent = `
<?php
return array( 'dependencies' => array( 'react', 'wp-editor' ), 'version' => '2.0.0' );
`;
const expectedJson = {
dependencies: ['react', 'wp-editor'],
version: '2.0.0'
};
expect(parsePhpAssetFile(formattedPhpContent)).toEqual(expectedJson);
});
});
1 change: 1 addition & 0 deletions plugins/faustwp/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "project",
"minimum-stability": "stable",
"require-dev": {
"antecedent/patchwork": "^2.1",
"brain/monkey": "^2.6",
"codeception/codeception": "^4.1",
"codeception/module-asserts": "^1.0",
Expand Down
14 changes: 7 additions & 7 deletions plugins/faustwp/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 205fb09

Please sign in to comment.