diff --git a/plugins/woocommerce-docs/composer.json b/plugins/woocommerce-docs/composer.json index df04ee26dde..a0e0e48df07 100644 --- a/plugins/woocommerce-docs/composer.json +++ b/plugins/woocommerce-docs/composer.json @@ -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": { diff --git a/plugins/woocommerce-docs/composer.lock b/plugins/woocommerce-docs/composer.lock index e749cbd3d7f..b6979a10a5e 100644 --- a/plugins/woocommerce-docs/composer.lock +++ b/plugins/woocommerce-docs/composer.lock @@ -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", diff --git a/plugins/woocommerce-docs/example-docs/get-started/README.md b/plugins/woocommerce-docs/example-docs/get-started/README.md index afc56c6d653..e4055c4a5c1 100644 --- a/plugins/woocommerce-docs/example-docs/get-started/README.md +++ b/plugins/woocommerce-docs/example-docs/get-started/README.md @@ -1,3 +1,3 @@ --- -title: Getting Started with WooCommerce +category_title: Getting Started with WooCommerce --- diff --git a/plugins/woocommerce-docs/example-docs/get-started/installation/install-plugin.md b/plugins/woocommerce-docs/example-docs/get-started/installation/install-plugin.md new file mode 100644 index 00000000000..8160d7ca984 --- /dev/null +++ b/plugins/woocommerce-docs/example-docs/get-started/installation/install-plugin.md @@ -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. diff --git a/plugins/woocommerce-docs/example-docs/get-started/local-development.md b/plugins/woocommerce-docs/example-docs/get-started/local-development.md index 8b4d7061258..1c630e8b288 100644 --- a/plugins/woocommerce-docs/example-docs/get-started/local-development.md +++ b/plugins/woocommerce-docs/example-docs/get-started/local-development.md @@ -1,5 +1,5 @@ --- -title: Local Development +post_title: Local Development --- ## Local Development diff --git a/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/README.md b/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/README.md index 20932f6038a..77ba9104ad8 100644 --- a/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/README.md +++ b/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/README.md @@ -1,3 +1,3 @@ --- -title: Troubleshooting Problems +category_title: Troubleshooting Problems --- diff --git a/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md b/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md index ff7a01a982e..d9035c4f95a 100644 --- a/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md +++ b/plugins/woocommerce-docs/example-docs/get-started/troubleshooting/what-went-wrong.md @@ -1,5 +1,5 @@ --- -title: What Went Wrong? +post_title: What Went Wrong? --- ## Try some troubleshooting diff --git a/plugins/woocommerce-docs/example-docs/testing/README.md b/plugins/woocommerce-docs/example-docs/testing/README.md index b2555483dc9..1a313d13b8f 100644 --- a/plugins/woocommerce-docs/example-docs/testing/README.md +++ b/plugins/woocommerce-docs/example-docs/testing/README.md @@ -1,3 +1,3 @@ --- -title: Testing WooCommerce +category_title: Testing WooCommerce --- diff --git a/plugins/woocommerce-docs/example-docs/testing/unit-tests.md b/plugins/woocommerce-docs/example-docs/testing/unit-tests.md index 7357bff1ea8..990fefd4c7b 100644 --- a/plugins/woocommerce-docs/example-docs/testing/unit-tests.md +++ b/plugins/woocommerce-docs/example-docs/testing/unit-tests.md @@ -1,5 +1,5 @@ --- -title: Unit Testing +post_title: Unit Testing --- ## Unit Test diff --git a/plugins/woocommerce-docs/scripts/manifest.json b/plugins/woocommerce-docs/scripts/manifest.json index 00166289cc7..8378d30a0fb 100644 --- a/plugins/woocommerce-docs/scripts/manifest.json +++ b/plugins/woocommerce-docs/scripts/manifest.json @@ -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" -} \ No newline at end of file + "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" +} diff --git a/plugins/woocommerce-docs/src/Manifest/ManifestProcessor.php b/plugins/woocommerce-docs/src/Manifest/ManifestProcessor.php index 9ade523afcf..4457d0a7787 100644 --- a/plugins/woocommerce-docs/src/Manifest/ManifestProcessor.php +++ b/plugins/woocommerce-docs/src/Manifest/ManifestProcessor.php @@ -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. diff --git a/plugins/woocommerce-docs/tests/bootstrap.php b/plugins/woocommerce-docs/tests/bootstrap.php index 05ef07128de..a3b8c4752ea 100644 --- a/plugins/woocommerce-docs/tests/bootstrap.php +++ b/plugins/woocommerce-docs/tests/bootstrap.php @@ -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'; diff --git a/plugins/woocommerce-docs/tests/src/Manifest/ManifestProcessorTest.php b/plugins/woocommerce-docs/tests/src/Manifest/ManifestProcessorTest.php new file mode 100644 index 00000000000..eb51a4d306a --- /dev/null +++ b/plugins/woocommerce-docs/tests/src/Manifest/ManifestProcessorTest.php @@ -0,0 +1,79 @@ + 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 ) ); + } +} diff --git a/plugins/woocommerce-docs/tests/src/Manifest/fixtures/manifest.json b/plugins/woocommerce-docs/tests/src/Manifest/fixtures/manifest.json new file mode 100644 index 00000000000..8b56747003e --- /dev/null +++ b/plugins/woocommerce-docs/tests/src/Manifest/fixtures/manifest.json @@ -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" +} diff --git a/plugins/woocommerce-docs/tests/src/Manifest/fixtures/test.md b/plugins/woocommerce-docs/tests/src/Manifest/fixtures/test.md new file mode 100644 index 00000000000..5f61f81e257 --- /dev/null +++ b/plugins/woocommerce-docs/tests/src/Manifest/fixtures/test.md @@ -0,0 +1,7 @@ +--- +post_title: Testing Post +--- + +### Testing Post + +This is a test post. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67706c21cc1..25f2c0a9e9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/tools/monorepo-utils/package.json b/tools/monorepo-utils/package.json index a08e5ab3bdc..bcdd6eb8494 100644 --- a/tools/monorepo-utils/package.json +++ b/tools/monorepo-utils/package.json @@ -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", diff --git a/tools/monorepo-utils/src/md-docs/commands/manifest/create/index.ts b/tools/monorepo-utils/src/md-docs/commands/manifest/create/index.ts index 4d4cf40c0c0..115c72dab9c 100644 --- a/tools/monorepo-utils/src/md-docs/commands/manifest/create/index.ts +++ b/tools/monorepo-utils/src/md-docs/commands/manifest/create/index.ts @@ -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 ', + '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(); diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/README.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/README.md index afc56c6d653..2363601bcc4 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/README.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/README.md @@ -1,3 +1,4 @@ --- -title: Getting Started with WooCommerce +category_title: Getting Started with WooCommerce +category_slug: get-started --- diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/installation/install-plugin.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/installation/install-plugin.md new file mode 100644 index 00000000000..c8b717789fd --- /dev/null +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/installation/install-plugin.md @@ -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. diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/local-development.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/local-development.md index 8b4d7061258..1c630e8b288 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/local-development.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/local-development.md @@ -1,5 +1,5 @@ --- -title: Local Development +post_title: Local Development --- ## Local Development diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/README.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/README.md index 20932f6038a..77ba9104ad8 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/README.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/README.md @@ -1,3 +1,3 @@ --- -title: Troubleshooting Problems +category_title: Troubleshooting Problems --- diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/what-went-wrong.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/what-went-wrong.md index ff7a01a982e..d9035c4f95a 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/what-went-wrong.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/get-started/troubleshooting/what-went-wrong.md @@ -1,5 +1,5 @@ --- -title: What Went Wrong? +post_title: What Went Wrong? --- ## Try some troubleshooting diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/README.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/README.md index b2555483dc9..1a313d13b8f 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/README.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/README.md @@ -1,3 +1,3 @@ --- -title: Testing WooCommerce +category_title: Testing WooCommerce --- diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/unit-tests.md b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/unit-tests.md index 7357bff1ea8..58e42ceb3c5 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/unit-tests.md +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/fixtures/example-docs/testing/unit-tests.md @@ -1,5 +1,5 @@ --- -title: Unit Testing +category_title: Unit Testing --- ## Unit Test diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-file-url.ts b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-file-url.ts index 69e2c354ab3..bfe4da9fa1a 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-file-url.ts +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-file-url.ts @@ -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', () => { diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-frontmatter.ts b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-frontmatter.ts new file mode 100644 index 00000000000..948585c0568 --- /dev/null +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-frontmatter.ts @@ -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', + } ); + } ); +} ); diff --git a/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-manifest.ts b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-manifest.ts index 089d0a4d442..479cf908b5e 100644 --- a/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-manifest.ts +++ b/tools/monorepo-utils/src/md-docs/lib/__tests__/generate-manifest.ts @@ -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' + ); + } ); } ); diff --git a/tools/monorepo-utils/src/md-docs/lib/generate-frontmatter.ts b/tools/monorepo-utils/src/md-docs/lib/generate-frontmatter.ts new file mode 100644 index 00000000000..79087036e4a --- /dev/null +++ b/tools/monorepo-utils/src/md-docs/lib/generate-frontmatter.ts @@ -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; + }, {} ); +}; diff --git a/tools/monorepo-utils/src/md-docs/lib/generate-manifest.ts b/tools/monorepo-utils/src/md-docs/lib/generate-manifest.ts index faf1f76787d..f481e1b7487 100644 --- a/tools/monorepo-utils/src/md-docs/lib/generate-manifest.ts +++ b/tools/monorepo-utils/src/md-docs/lib/generate-manifest.ts @@ -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 ); diff --git a/tools/monorepo-utils/src/md-docs/lib/generate-urls.ts b/tools/monorepo-utils/src/md-docs/lib/generate-urls.ts new file mode 100644 index 00000000000..ffd3403825e --- /dev/null +++ b/tools/monorepo-utils/src/md-docs/lib/generate-urls.ts @@ -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 }`; +};