Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate WordPress block asset files from PHP to JSON format #1656

Merged
merged 45 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8bd1f16
Improve unzipping compat
josephfusco Nov 15, 2023
0b20557
Fix liniting issues
josephfusco Nov 15, 2023
dba8bfb
Fix liniting issues
josephfusco Nov 15, 2023
edaf855
Fix linting issues
josephfusco Nov 15, 2023
8fa6d13
Clean up unused functions & tests
josephfusco Nov 16, 2023
2fccd87
Add test coverage
josephfusco Nov 16, 2023
ff54a08
Match parent class
josephfusco Nov 16, 2023
afc7efc
Fix PSR-4 warning
josephfusco Nov 16, 2023
463d8a3
Improve block tests
josephfusco Nov 16, 2023
1c5fc5f
Attempt to fix failing test config
josephfusco Nov 16, 2023
db48bb9
failing tests
josephfusco Nov 16, 2023
8091a3a
Update tests
josephfusco Nov 16, 2023
c1d73f7
Fix refactoring issues
josephfusco Nov 16, 2023
01a2974
Explicitly require patchwork
josephfusco Nov 16, 2023
dd101ce
Require patchwork before anything else
josephfusco Nov 16, 2023
ff29ebc
Correct path to Patchwork
josephfusco Nov 16, 2023
bb04e39
Attempt to resolve failing E2E tests
josephfusco Nov 16, 2023
9b3371a
Scope requiring patchwork to it’s relevant class
josephfusco Nov 16, 2023
a3c525d
Revert "Scope requiring patchwork to it’s relevant class"
josephfusco Nov 16, 2023
33358a7
Avoid polluting codeception env with Patchwork
josephfusco Nov 16, 2023
d08068e
Conditionally load patchwork package via env
josephfusco Nov 16, 2023
fba8da2
Only update antecedent/patchwork in lockfile
josephfusco Nov 16, 2023
de8b873
Clean up refactor
josephfusco Nov 16, 2023
f20b5f4
Use our own directories method
josephfusco Nov 16, 2023
10c1415
Update tests to reflect function argument changes
josephfusco Nov 16, 2023
7352acc
Clean up
josephfusco Nov 17, 2023
cfd6db9
Revert "Clean up"
josephfusco Nov 17, 2023
653a8fa
Merge branch 'canary' into fix/blockset-issue-1633
josephfusco Nov 28, 2023
01118f5
Prevent .php inclusion as faust handles dependencies
josephfusco Nov 28, 2023
e572b44
Update block-support example deps
josephfusco Nov 28, 2023
efd388b
Ensure --webpack-no-externals is used to remove generated php
josephfusco Nov 28, 2023
cc4bfa0
leave wp deps alone
josephfusco Nov 28, 2023
6e70451
Remove problematic CLI flag
josephfusco Nov 29, 2023
93f9085
Handle PHP block files in the CLI w/ tests
josephfusco Nov 29, 2023
cada1d6
Resolve linting errors
josephfusco Nov 29, 2023
d4a3ba0
Clean up logging
josephfusco Nov 30, 2023
abcf8c9
Resolve phpcs issue
josephfusco Nov 30, 2023
9484a73
Resolve phpcs issue
josephfusco Nov 30, 2023
fbeadba
Fix asset loading
josephfusco Nov 30, 2023
f4e54f2
Merge branch 'canary' into fix/blockset-issue-1633
josephfusco Dec 1, 2023
c9894ab
Sync lockfile
josephfusco Dec 1, 2023
5cd554d
Resolve linting issues
josephfusco Dec 1, 2023
3633917
Merge branch 'canary' into fix/blockset-issue-1633
josephfusco Dec 6, 2023
44a254c
Fix linting warnings
josephfusco Dec 13, 2023
42e0f85
Update lockfile
josephfusco Dec 13, 2023
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/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.
16 changes: 8 additions & 8 deletions package-lock.json

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

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);
});
});
Loading
Loading