WooCommerce Docs: Improve Manifest Structure, Extend Frontmatter Support (#39214)

This commit is contained in:
Sam Seay 2023-07-17 16:46:23 +08:00 committed by GitHub
parent 66cee083d4
commit 69e9acaba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 548 additions and 133 deletions

View File

@ -14,7 +14,8 @@
"require-dev": {
"woocommerce/woocommerce-sniffs": "^0.1.3",
"phpunit/phpunit": "^9.6",
"yoast/phpunit-polyfills": "^2.0"
"yoast/phpunit-polyfills": "^2.0",
"php-stubs/wordpress-tests-stubs": "^6.2"
},
"config": {
"allow-plugins": {

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c3e8ed8878c0f36e0b6be7c78d35663f",
"content-hash": "061f99e607a2e8a793f0231a294cc761",
"packages": [
{
"name": "dflydev/dot-access-data",
@ -1030,6 +1030,46 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "php-stubs/wordpress-tests-stubs",
"version": "v6.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-stubs/wordpress-tests-stubs.git",
"reference": "175f395c814d9f52ebd2c1c64069a3b01ef764e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-stubs/wordpress-tests-stubs/zipball/175f395c814d9f52ebd2c1c64069a3b01ef764e8",
"reference": "175f395c814d9f52ebd2c1c64069a3b01ef764e8",
"shasum": ""
},
"require-dev": {
"php": "~7.3 || ~8.0",
"php-stubs/generator": "^0.8.0"
},
"suggest": {
"symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "WordPress Tests function and class declaration stubs for static analysis.",
"homepage": "https://github.com/php-stubs/wordpress-tests-stubs",
"keywords": [
"PHPStan",
"static analysis",
"wordpress"
],
"support": {
"issues": "https://github.com/php-stubs/wordpress-tests-stubs/issues",
"source": "https://github.com/php-stubs/wordpress-tests-stubs/tree/v6.2.0"
},
"time": "2023-05-15T07:50:52+00:00"
},
{
"name": "phpcompatibility/php-compatibility",
"version": "9.3.5",

View File

@ -1,3 +1,3 @@
---
title: Getting Started with WooCommerce
category_title: Getting Started with WooCommerce
---

View File

@ -0,0 +1,9 @@
---
post_title: Install the Plugin
---
## Install the plugin
1. Download the plugin from the [GitHub repository](https://example.com).
2. Upload the plugin to your WordPress site.
3. Activate the plugin.

View File

@ -1,5 +1,5 @@
---
title: Local Development
post_title: Local Development
---
## Local Development

View File

@ -1,3 +1,3 @@
---
title: Troubleshooting Problems
category_title: Troubleshooting Problems
---

View File

@ -1,5 +1,5 @@
---
title: What Went Wrong?
post_title: What Went Wrong?
---
## Try some troubleshooting

View File

@ -1,3 +1,3 @@
---
title: Testing WooCommerce
category_title: Testing WooCommerce
---

View File

@ -1,5 +1,5 @@
---
title: Unit Testing
post_title: Unit Testing
---
## Unit Test

View File

@ -1,39 +1,53 @@
{
"categories": [
{
"title": "Getting Started with WooCommerce",
"posts": [
{
"title": "Local Development",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/get-started/local-development.md",
"id": "c068ce54044fa44c760a69bd71ef21274f2a5a37"
}
],
"categories": [
{
"title": "Troubleshooting Problems",
"posts": [
{
"title": "What Went Wrong?",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md",
"id": "1f88c4d039e72c059c928ab475ad1ea0a02c8abb"
}
],
"categories": []
}
]
},
{
"title": "Testing WooCommerce",
"posts": [
{
"title": "Unit Testing",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/testing/unit-tests.md",
"id": "120770c899215a889246b47ac883e4dda1f97b8b"
}
],
"categories": []
}
],
"hash": "180a6ce0bebb8e84072fda451758ef6326490a99e7b3a349b9e45baa8c7c54ac"
}
"categories": [
{
"category_title": "Getting Started with WooCommerce",
"posts": [
{
"post_title": "Local Development",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/plugins/woocommerce-docs/example-docs/get-started/local-development.md",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/get-started/local-development.md",
"id": "c068ce54044fa44c760a69bd71ef21274f2a5a37"
}
],
"categories": [
{
"category_title": "Installation",
"posts": [
{
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/plugins/woocommerce-docs/example-docs/get-started/installation/install-plugin.md",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/get-started/installation/install-plugin.md",
"id": "fb59bd01dda7b090e5b0a557948e155a6b679d6a"
}
],
"categories": []
},
{
"category_title": "Troubleshooting Problems",
"posts": [
{
"post_title": "What Went Wrong?",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md",
"id": "1f88c4d039e72c059c928ab475ad1ea0a02c8abb"
}
],
"categories": []
}
]
},
{
"category_title": "Testing WooCommerce",
"posts": [
{
"post_title": "Unit Testing",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/plugins/woocommerce-docs/example-docs/testing/unit-tests.md",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce-docs/example-docs/testing/unit-tests.md",
"id": "120770c899215a889246b47ac883e4dda1f97b8b"
}
],
"categories": []
}
],
"hash": "db0047299d7dd99bd9a657d59be9e6e4f113710a253c27417427965f7a5ebc8c"
}

View File

@ -31,6 +31,39 @@ class ManifestProcessor {
return $converter;
}
/**
* Generate post args
*
* @param mixed $post The post to generate args for.
* @param mixed $post_content The post content.
* @return array
*/
private static function generate_post_args( $post, $post_content ) {
$possible_attributes = array(
'post_title',
'post_author',
'post_date',
'comment_status',
'post_status',
);
$args = array();
foreach ( $possible_attributes as $attribute ) {
if ( isset( $post[ $attribute ] ) ) {
$args[ $attribute ] = $post[ $attribute ];
}
}
$args['post_content'] = $post_content;
if ( ! isset( $args['post_status'] ) ) {
$args['post_status'] = 'publish';
}
return $args;
}
/**
* Process categories
*
@ -40,25 +73,27 @@ class ManifestProcessor {
*/
private static function process_categories( $categories, $logger_action_id, $parent_id = 0 ) {
foreach ( $categories as $category ) {
$term = term_exists( $category['title'], 'category' );
$term = term_exists( $category['category_title'], 'category' );
$category_args = array( 'parent' => $parent_id );
if ( isset( $category['category_slug'] ) ) {
$category_args['slug'] = $category['category_slug'];
}
// If the category doesn't exist, create it.
if ( 0 === $term || null === $term ) {
$term = wp_insert_term(
$category['title'],
$category['category_title'],
'category',
array(
'parent' => $parent_id,
)
$category_args
);
} else {
// If the category exists, update it.
$term = wp_update_term(
$term['term_id'],
'category',
array(
'parent' => $parent_id,
)
$category_args
);
}
@ -85,31 +120,27 @@ class ManifestProcessor {
// If the post doesn't exist, create it.
if ( ! $existing_post ) {
$post_id = \WooCommerceDocs\Data\DocsStore::insert_docs_post(
array(
'post_title' => $post['title'],
'post_content' => $blocks,
'post_status' => 'publish',
),
self::generate_post_args( $post, $blocks ),
$post['id']
);
\ActionScheduler_Logger::instance()->log( $logger_action_id, 'Created post with id: ' . $post_id );
} else {
$post_update = self::generate_post_args( $post, $blocks );
$post_update = array_merge( $post_update, array( 'ID' => $existing_post->ID ) );
// if the post exists, update it .
$post_id = \WoocommerceDocs\Data\DocsStore::update_docs_post(
array(
'ID' => $existing_post->ID,
'post_title' => $post['title'],
'post_content' => $blocks,
),
$post_update,
$post['id']
);
\ActionScheduler_Logger::instance()->log( $logger_action_id, 'Updated post with id: ' . $post_id );
}
wp_set_post_categories( $post_id, array( $term['term_id'] ), $parent_id );
wp_set_post_categories( $post_id, array( $term['term_id'] ) );
}
// Process any sub-categories.

View File

@ -11,3 +11,6 @@ $tests_dir = getenv( 'WP_TESTS_DIR' );
require_once $tests_dir . '/includes/functions.php';
require_once $tests_dir . '/includes/bootstrap.php';
// Require action-scheduler manually.
require_once __DIR__ . '/../vendor/woocommerce/action-scheduler/action-scheduler.php';

View File

@ -0,0 +1,79 @@
<?php
namespace WooCommerceDocs\Tests\Manifest;
use WooCommerceDocs\Manifest\ManifestProcessor;
use WP_UnitTestCase;
use WooCommerceDocs\Data\DocsStore;
/**
* Class ManifestProcessorTest
*
* @package WooCommerceDocs\Tests\Manifest
*/
class ManifestProcessorTest extends WP_UnitTestCase {
/**
* Test processing a manifest into WordPress posts and categories.
*/
public function test_process_manifest() {
$manifest = json_decode( file_get_contents( __DIR__ . '/fixtures/manifest.json' ), true );
$md_file = file_get_contents( __DIR__ . '/fixtures/test.md' );
// Mock the wp_remote_get function with a filter.
add_filter(
'pre_http_request',
function ( $preempt, $args, $url ) use ( $md_file ) {
return array(
'response' => array(
'code' => 200,
'message' => 'OK',
),
'body' => $md_file,
);
},
10,
3
);
ManifestProcessor::process_manifest( $manifest, 123 );
$get_started_category = get_term_by( 'name', 'Getting Started with WooCommerce', 'category' );
$this->assertNotFalse( $get_started_category );
$install_category = get_term_by( 'name', 'Installation', 'category' );
$this->assertNotFalse( $install_category );
// Check parent is correct.
$this->assertEquals( $get_started_category->term_id, $install_category->parent );
$troubleshoot_category = get_term_by( 'name', 'Troubleshooting Problems', 'category' );
$this->assertNotFalse( $troubleshoot_category );
// Check parent is correct.
$this->assertEquals( $get_started_category->term_id, $troubleshoot_category->parent );
$testing_category = get_term_by( 'name', 'Testing WooCommerce', 'category' );
$this->assertNotFalse( $testing_category );
// check posts exist using the DocStore.
$posts = DocsStore::get_posts();
$this->assertEquals( 4, count( $posts ) );
$install_plugin_post = $posts[0];
$local_dev_post = $posts[1];
$unit_testing_post = $posts[2];
$what_went_wrong_post = $posts[3];
// check doc titles (note that they are returned in alpha order).
$this->assertEquals( 'Install the Plugin', $install_plugin_post->post_title );
$this->assertEquals( 'Local Development', $local_dev_post->post_title );
$this->assertEquals( 'Unit Testing', $unit_testing_post->post_title );
$this->assertEquals( 'What Went Wrong?', $what_went_wrong_post->post_title );
// Assert that post was assigned to categories.
$this->assertTrue( has_category( $install_category->term_id, $install_plugin_post->ID ) );
$this->assertTrue( has_category( $troubleshoot_category->term_id, $what_went_wrong_post->ID ) );
$this->assertTrue( has_category( $testing_category->term_id, $unit_testing_post->ID ) );
$this->assertTrue( has_category( $troubleshoot_category->term_id, $what_went_wrong_post->ID ) );
}
}

View File

@ -0,0 +1,54 @@
{
"categories": [
{
"category_title": "Getting Started with WooCommerce",
"posts": [
{
"post_title": "Local Development",
"edit_url": "https://example.com/edit/local-development.md",
"url": "https://example.com/local-development.md",
"id": "c068ce54044fa44c760a69bd71ef21274f2a5a37"
}
],
"categories": [
{
"category_title": "Installation",
"posts": [
{
"post_title": "Install the Plugin",
"edit_url": "https://example.com/edit/install-plugin.md",
"url": "https://example.com/install-plugin.md",
"id": "fb59bd01dda7b090e5b0a557948e155a6b679d6a"
}
],
"categories": []
},
{
"category_title": "Troubleshooting Problems",
"posts": [
{
"post_title": "What Went Wrong?",
"edit_url": "https://example.com/edit/what-went-wrong.md",
"url": "https://example.com/what-went-wrong.md",
"id": "1f88c4d039e72c059c928ab475ad1ea0a02c8abb"
}
],
"categories": []
}
]
},
{
"category_title": "Testing WooCommerce",
"posts": [
{
"post_title": "Unit Testing",
"edit_url": "https://example.com/edit/unit-tests.md",
"url": "https://example.com/unit-tests.md",
"id": "120770c899215a889246b47ac883e4dda1f97b8b"
}
],
"categories": []
}
],
"hash": "db0047299d7dd99bd9a657d59be9e6e4f113710a253c27417427965f7a5ebc8c"
}

View File

@ -0,0 +1,7 @@
---
post_title: Testing Post
---
### Testing Post
This is a test post.

View File

@ -3474,6 +3474,9 @@ importers:
gray-matter:
specifier: ^4.0.3
version: 4.0.3
js-yaml:
specifier: ^4.1.0
version: 4.1.0
octokit:
specifier: ^2.0.14
version: 2.0.14
@ -9184,8 +9187,8 @@ packages:
dependencies:
'@babel/core': 7.21.3
'@babel/helper-module-imports': 7.16.0
'@babel/helper-plugin-utils': 7.14.5
babel-plugin-polyfill-corejs2: 0.3.0(@babel/core@7.21.3)
'@babel/helper-plugin-utils': 7.21.5
babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3)
babel-plugin-polyfill-corejs3: 0.4.0(@babel/core@7.21.3)
babel-plugin-polyfill-regenerator: 0.3.0(@babel/core@7.21.3)
semver: 6.3.0
@ -18453,7 +18456,7 @@ packages:
'@wordpress/style-engine': 0.15.0
'@wordpress/token-list': 2.19.0
'@wordpress/url': 3.29.0
'@wordpress/warning': 2.34.0
'@wordpress/warning': 2.19.0
'@wordpress/wordcount': 3.19.0
change-case: 4.1.2
classnames: 2.3.1
@ -19813,7 +19816,7 @@ packages:
cosmiconfig: 7.0.1
eslint: 8.32.0
eslint-config-prettier: 8.5.0(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint-import-resolver-typescript@2.5.0)(eslint-import-resolver-webpack@0.13.2)(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint@8.32.0)
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.54.0)(eslint@8.32.0)(jest@27.5.1)(typescript@4.9.5)
eslint-plugin-jsdoc: 39.9.1(eslint@8.32.0)
eslint-plugin-jsx-a11y: 6.5.1(eslint@8.32.0)
@ -19854,7 +19857,7 @@ packages:
cosmiconfig: 7.0.1
eslint: 8.32.0
eslint-config-prettier: 8.5.0(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint-import-resolver-typescript@2.5.0)(eslint-import-resolver-webpack@0.13.2)(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint@8.32.0)
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.54.0)(eslint@8.32.0)(jest@27.5.1)(typescript@4.9.5)
eslint-plugin-jsdoc: 39.9.1(eslint@8.32.0)
eslint-plugin-jsx-a11y: 6.5.1(eslint@8.32.0)
@ -19895,7 +19898,7 @@ packages:
cosmiconfig: 7.0.1
eslint: 8.32.0
eslint-config-prettier: 8.5.0(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint-import-resolver-typescript@2.5.0)(eslint-import-resolver-webpack@0.13.2)(eslint@8.32.0)
eslint-plugin-import: 2.25.4(@typescript-eslint/parser@5.54.0)(eslint@8.32.0)
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.54.0)(eslint@8.32.0)(jest@29.5.0)(typescript@4.9.5)
eslint-plugin-jsdoc: 39.9.1(eslint@8.32.0)
eslint-plugin-jsx-a11y: 6.5.1(eslint@8.32.0)
@ -27112,7 +27115,7 @@ packages:
has: 1.0.3
is-core-module: 2.8.0
is-glob: 4.0.3
minimatch: 3.0.4
minimatch: 3.1.2
object.values: 1.1.5
resolve: 1.20.0
tsconfig-paths: 3.14.0
@ -27152,6 +27155,36 @@ packages:
- eslint-import-resolver-webpack
- supports-color
/eslint-plugin-import@2.25.4(@typescript-eslint/parser@5.54.0)(eslint@8.32.0):
resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.54.0(eslint@8.32.0)(typescript@4.9.5)
array-includes: 3.1.4
array.prototype.flat: 1.2.5
debug: 2.6.9(supports-color@6.1.0)
doctrine: 2.1.0
eslint: 8.32.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.3(@typescript-eslint/parser@5.54.0)(eslint-import-resolver-node@0.3.6)(eslint-import-resolver-typescript@2.5.0)(eslint-import-resolver-webpack@0.13.2)
has: 1.0.3
is-core-module: 2.8.0
is-glob: 4.0.3
minimatch: 3.1.2
object.values: 1.1.5
resolve: 1.20.0
tsconfig-paths: 3.14.0
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
/eslint-plugin-jest@23.20.0(eslint@7.32.0)(typescript@4.9.5):
resolution: {integrity: sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==}
engines: {node: '>=8'}
@ -34072,7 +34105,7 @@ packages:
esprima: 4.0.1
/js-yaml@3.5.3:
resolution: {integrity: sha1-6e5ggrBld3DkNG368qWMWZIlH3Y=}
resolution: {integrity: sha512-rkxjJUwevxyYOdr45k3IOyZjyhRbEMqUvcMyXXeBXTf8kdFaFKUIvMkdHgIcFIjNIoctM6l/emA7OjXYVabYSw==}
hasBin: true
dependencies:
argparse: 1.0.10

View File

@ -23,6 +23,7 @@
"glob": "^10.2.4",
"graphql": "^16.6.0",
"gray-matter": "^4.0.3",
"js-yaml": "^4.1.0",
"octokit": "^2.0.14",
"ora": "^5.4.1",
"promptly": "^3.2.0",

View File

@ -38,8 +38,13 @@ export const generateManifestCommand = new Command( 'create' )
'Root directory of the markdown files, used to generate URLs.',
process.cwd()
)
.option(
'-be --baseEditUrl <baseEditUrl>',
'Base url to provide edit links to. This option will be ignored if your baseUrl is not a GitHub URL.',
'https://github.com/woocommerce/woocommerce/edit/trunk'
)
.action( async ( dir, projectName, options ) => {
const { outputFilePath, baseUrl, rootDir } = options;
const { outputFilePath, baseUrl, rootDir, baseEditUrl } = options;
// determine if the rootDir is absolute or relative
const absoluteRootDir = path.isAbsolute( rootDir )
@ -60,7 +65,8 @@ export const generateManifestCommand = new Command( 'create' )
absoluteRootDir,
absoluteSubDir,
projectName,
baseUrl
baseUrl,
baseEditUrl
);
Logger.endTask();

View File

@ -1,3 +1,4 @@
---
title: Getting Started with WooCommerce
category_title: Getting Started with WooCommerce
category_slug: get-started
---

View File

@ -0,0 +1,5 @@
### Install the plugin
1. Download the plugin from the [GitHub repository](https://example.com).
2. Upload the plugin to your WordPress site.
3. Activate the plugin.

View File

@ -1,5 +1,5 @@
---
title: Local Development
post_title: Local Development
---
## Local Development

View File

@ -1,3 +1,3 @@
---
title: Troubleshooting Problems
category_title: Troubleshooting Problems
---

View File

@ -1,5 +1,5 @@
---
title: What Went Wrong?
post_title: What Went Wrong?
---
## Try some troubleshooting

View File

@ -1,3 +1,3 @@
---
title: Testing WooCommerce
category_title: Testing WooCommerce
---

View File

@ -1,5 +1,5 @@
---
title: Unit Testing
category_title: Unit Testing
---
## Unit Test

View File

@ -6,7 +6,7 @@ import path from 'path';
/**
* Internal dependencies
*/
import { generateFileUrl } from '../generate-manifest';
import { generateFileUrl } from '../generate-urls';
describe( 'generateFileUrl', () => {
it( 'should generate a file url relative to the root directory provided', () => {

View File

@ -0,0 +1,31 @@
/**
* Internal dependencies
*/
import { generatePostFrontMatter } from '../generate-frontmatter';
describe( 'generateFrontmatter', () => {
it( 'should not allow disallowed attributes', () => {
const frontMatter = generatePostFrontMatter( `---
title: Hello World
description: This is a description
post_content: This is some content
post_title: This is a title
---
` );
expect( frontMatter ).toEqual( {
post_title: 'This is a title',
} );
} );
it( 'should not do additional date parsing', () => {
const frontMatter = generatePostFrontMatter( `---
post_date: 2023-07-12 15:30:00
---
` );
expect( frontMatter ).toEqual( {
post_date: '2023-07-12 15:30:00',
} );
} );
} );

View File

@ -18,31 +18,47 @@ describe( 'generateManifest', () => {
rootDir,
dir,
'example-docs',
'https://example.com'
'https://example.com',
'https://example.com/edit'
);
const topLevelCategories = manifest.categories;
expect( topLevelCategories[ 0 ].title ).toEqual(
expect( topLevelCategories[ 0 ].category_title ).toEqual(
'Getting Started with WooCommerce'
);
expect( topLevelCategories[ 1 ].title ).toEqual(
expect( topLevelCategories[ 1 ].category_title ).toEqual(
'Testing WooCommerce'
);
const subCategories = topLevelCategories[ 0 ].categories;
expect( subCategories[ 0 ].title ).toEqual(
expect( subCategories[ 1 ].category_title ).toEqual(
'Troubleshooting Problems'
);
} );
it( 'should create categories with titles where there is no index README', async () => {
const manifest = await generateManifestFromDirectory(
rootDir,
dir,
'example-docs',
'https://example.com',
'https://example.com/edit'
);
expect(
manifest.categories[ 0 ].categories[ 0 ].category_title
).toEqual( 'Installation' );
} );
it( 'should create post urls with the correct url', async () => {
const manifest = await generateManifestFromDirectory(
rootDir,
dir,
'example-docs',
'https://example.com'
'https://example.com',
'https://example.com/edit'
);
expect( manifest.categories[ 0 ].posts[ 0 ].url ).toEqual(
@ -52,7 +68,7 @@ describe( 'generateManifest', () => {
expect(
manifest.categories[ 0 ].categories[ 0 ].posts[ 0 ].url
).toEqual(
'https://example.com/example-docs/get-started/troubleshooting/what-went-wrong.md'
'https://example.com/example-docs/get-started/installation/install-plugin.md'
);
} );
@ -61,9 +77,24 @@ describe( 'generateManifest', () => {
rootDir,
dir,
'example-docs',
'https://example.com'
'https://example.com',
'https://example.com/edit'
);
expect( manifest.hash ).not.toBeUndefined();
} );
it( 'should generate edit_url when github is in the base url', async () => {
const manifest = await generateManifestFromDirectory(
rootDir,
dir,
'example-docs',
'https://github.com',
'https://github.com/edit'
);
expect( manifest.categories[ 0 ].posts[ 0 ].edit_url ).toEqual(
'https://github.com/edit/example-docs/get-started/local-development.md'
);
} );
} );

View File

@ -0,0 +1,41 @@
/**
* External dependencies
*/
import matter from 'gray-matter';
import yaml from 'js-yaml';
/**
* Generate front-matter for supported post attributes.
*
* @param fileContents
*/
export const generatePostFrontMatter = (
fileContents: string
): {
[ key: string ]: unknown;
} => {
const allowList = [
'post_date',
'post_title',
'page_template',
'post_author',
'post_name',
'category_title',
'category_slug',
];
const frontMatter = matter( fileContents, {
engines: {
// By passing yaml.JSON_SCHEMA we disable date parsing that changes date format.
// See https://github.com/jonschlinkert/gray-matter/issues/62#issuecomment-577628177 for more details.
yaml: ( s ) => yaml.load( s, { schema: yaml.JSON_SCHEMA } ),
},
} ).data;
return Object.keys( frontMatter )
.filter( ( key ) => allowList.includes( key ) )
.reduce( ( obj, key ) => {
obj[ key ] = frontMatter[ key ];
return obj;
}, {} );
};

View File

@ -3,10 +3,15 @@
*/
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { glob } from 'glob';
import crypto from 'crypto';
/**
* Internal dependencies
*/
import { generatePostFrontMatter } from './generate-frontmatter';
import { generateFileUrl } from './generate-urls';
interface Category {
[ key: string ]: unknown;
posts?: Post[];
@ -23,59 +28,28 @@ function generatePageId( filePath: string, prefix = '' ) {
return hash.digest( 'hex' );
}
/**
* Generates a file url relative to the root directory provided.
*
* @param baseUrl The base url to use for the file url.
* @param rootDirectory The root directory where the file resides.
* @param subDirectory The sub-directory where the file resides.
* @param absoluteFilePath The absolute path to the file.
* @return The file url.
*/
export const generateFileUrl = (
baseUrl: string,
rootDirectory: string,
subDirectory: string,
absoluteFilePath: string
) => {
// check paths are absolute
for ( const filePath of [
rootDirectory,
subDirectory,
absoluteFilePath,
] ) {
if ( ! path.isAbsolute( filePath ) ) {
throw new Error(
`File URLs cannot be generated without absolute paths. ${ filePath } is not absolute.`
);
}
}
// Generate a path from the subdirectory to the file path.
const relativeFilePath = path.resolve( subDirectory, absoluteFilePath );
// Determine the relative path from the rootDirectory to the filePath.
const relativePath = path.relative( rootDirectory, relativeFilePath );
return `${ baseUrl }/${ relativePath }`;
};
async function processDirectory(
rootDirectory: string,
subDirectory: string,
projectName: string,
baseUrl: string,
baseEditUrl: string,
checkReadme = true
): Promise< Category > {
let category: Category = {};
const category: Category = {};
// Process README.md (if exists) for the category definition.
const readmePath = path.join( subDirectory, 'README.md' );
if ( checkReadme && fs.existsSync( readmePath ) ) {
const readmeContent = fs.readFileSync( readmePath, 'utf-8' );
const readmeFrontmatter = matter( readmeContent ).data;
category = { ...readmeFrontmatter };
category.posts = [];
const frontMatter = generatePostFrontMatter( readmeContent );
Object.assign( category, frontMatter );
} else if ( checkReadme ) {
// derive the category title from the directory name, capitalize first letter
const categoryTitle = path.basename( subDirectory );
category.category_title =
categoryTitle.charAt( 0 ).toUpperCase() + categoryTitle.slice( 1 );
}
const markdownFiles = glob.sync( path.join( subDirectory, '*.md' ) );
@ -84,7 +58,18 @@ async function processDirectory(
if ( filePath !== readmePath || ! checkReadme ) {
// Skip README.md which we have already processed.
const fileContent = fs.readFileSync( filePath, 'utf-8' );
const fileFrontmatter = matter( fileContent ).data;
const fileFrontmatter = generatePostFrontMatter( fileContent );
category.posts = [];
if ( baseUrl.includes( 'github' ) ) {
fileFrontmatter.edit_url = generateFileUrl(
baseEditUrl,
rootDirectory,
subDirectory,
filePath
);
}
const post: Post = { ...fileFrontmatter };
category.posts.push( {
@ -111,7 +96,8 @@ async function processDirectory(
rootDirectory,
subdirectory,
projectName,
baseUrl
baseUrl,
baseEditUrl
);
category.categories.push( subcategory );
@ -124,13 +110,15 @@ export async function generateManifestFromDirectory(
rootDirectory: string,
subDirectory: string,
projectName: string,
baseUrl: string
baseUrl: string,
baseEditUrl: string
) {
const manifest = await processDirectory(
rootDirectory,
subDirectory,
projectName,
baseUrl,
baseEditUrl,
false
);

View File

@ -0,0 +1,40 @@
/**
* External dependencies
*/
import path from 'path';
/**
* Generates a file url relative to the root directory provided.
*
* @param baseUrl The base url to use for the file url.
* @param rootDirectory The root directory where the file resides.
* @param subDirectory The sub-directory where the file resides.
* @param absoluteFilePath The absolute path to the file.
* @return The file url.
*/
export const generateFileUrl = (
baseUrl: string,
rootDirectory: string,
subDirectory: string,
absoluteFilePath: string
) => {
// check paths are absolute
for ( const filePath of [
rootDirectory,
subDirectory,
absoluteFilePath,
] ) {
if ( ! path.isAbsolute( filePath ) ) {
throw new Error(
`File URLs cannot be generated without absolute paths. ${ filePath } is not absolute.`
);
}
}
// Generate a path from the subdirectory to the file path.
const relativeFilePath = path.resolve( subDirectory, absoluteFilePath );
// Determine the relative path from the rootDirectory to the filePath.
const relativePath = path.relative( rootDirectory, relativeFilePath );
return `${ baseUrl }/${ relativePath }`;
};